Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 87 additions & 53 deletions src/data/logbook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import {
BINARY_STATE_OFF,
BINARY_STATE_ON,
DOMAINS_WITH_DYNAMIC_PICTURE,
} from "../common/const";
import { computeDomain } from "../common/entity/compute_domain";
import { BINARY_STATE_OFF, BINARY_STATE_ON } from "../common/const";
import { computeStateDisplay } from "../common/entity/compute_state_display";
import { LocalizeFunc } from "../common/translations/localize";
import { HomeAssistant } from "../types";
Expand All @@ -10,26 +14,43 @@ const LOGBOOK_LOCALIZE_PATH = "ui.components.logbook.messages";
export const CONTINUOUS_DOMAINS = ["proximity", "sensor"];

export interface LogbookEntry {
// Python timestamp. Do *1000 to get JS timestamp.
when: number;
// Base data
when: number; // Python timestamp. Do *1000 to get JS timestamp.
name: string;
message?: string;
entity_id?: string;
icon?: string;
source?: string;
source?: string; // The trigger source
domain?: string;
state?: string; // The state of the entity
// Context data
context_id?: string;
context_user_id?: string;
context_event_type?: string;
context_domain?: string;
context_service?: string;
context_service?: string; // Service calls only
context_entity_id?: string;
context_entity_id_name?: string;
context_entity_id_name?: string; // Legacy, not longer sent
context_name?: string;
context_state?: string; // The state of the entity
context_source?: string; // The trigger source
context_message?: string;
state?: string;
}

//
// Localization mapping for all the triggers in core
// in homeassistant.components.homeassistant.triggers
//
const triggerPhrases = {
"numeric state of": "triggered_by_numeric_state_of", // number state trigger
"state of": "triggered_by_state_of", // state trigger
event: "triggered_by_event", // event trigger
time: "triggered_by_time", // time trigger
"time pattern": "triggered_by_time_pattern", // time trigger
"Home Assistant stopping": "triggered_by_homeassistant_stopping", // stop event
"Home Assistant starting": "triggered_by_homeassistant_starting", // start event
};

const DATA_CACHE: {
[cacheKey: string]: { [entityId: string]: Promise<LogbookEntry[]> };
} = {};
Expand All @@ -39,17 +60,13 @@ export const getLogbookDataForContext = async (
startDate: string,
contextId?: string
): Promise<LogbookEntry[]> => {
const localize = await hass.loadBackendTranslation("device_class");
return addLogbookMessage(
await hass.loadBackendTranslation("device_class");
return getLogbookDataFromServer(
hass,
localize,
await getLogbookDataFromServer(
hass,
startDate,
undefined,
undefined,
contextId
)
startDate,
undefined,
undefined,
contextId
);
};

Expand All @@ -60,42 +77,17 @@ export const getLogbookData = async (
entityIds?: string[],
deviceIds?: string[]
): Promise<LogbookEntry[]> => {
const localize = await hass.loadBackendTranslation("device_class");
return addLogbookMessage(
hass,
localize,
// bypass cache if we have a device ID
deviceIds?.length
? await getLogbookDataFromServer(
hass,
startDate,
endDate,
entityIds,
undefined,
deviceIds
)
: await getLogbookDataCache(hass, startDate, endDate, entityIds)
);
};

const addLogbookMessage = (
hass: HomeAssistant,
localize: LocalizeFunc,
logbookData: LogbookEntry[]
): LogbookEntry[] => {
for (const entry of logbookData) {
const stateObj = hass!.states[entry.entity_id!];
if (entry.state && stateObj) {
entry.message = getLogbookMessage(
await hass.loadBackendTranslation("device_class");
return deviceIds?.length
? getLogbookDataFromServer(
hass,
localize,
entry.state,
stateObj,
computeDomain(entry.entity_id!)
);
}
}
return logbookData;
startDate,
endDate,
entityIds,
undefined,
deviceIds
)
: getLogbookDataCache(hass, startDate, endDate, entityIds);
};

const getLogbookDataCache = async (
Expand Down Expand Up @@ -204,7 +196,49 @@ export const clearLogbookCache = (startDate: string, endDate: string) => {
DATA_CACHE[`${startDate}${endDate}`] = {};
};

const getLogbookMessage = (
export const createHistoricState = (
currentStateObj: HassEntity,
state?: string
): HassEntity => <HassEntity>(<unknown>{
entity_id: currentStateObj.entity_id,
state: state,
attributes: {
// Rebuild the historical state by copying static attributes only
device_class: currentStateObj?.attributes.device_class,
source_type: currentStateObj?.attributes.source_type,
has_date: currentStateObj?.attributes.has_date,
has_time: currentStateObj?.attributes.has_time,
// We do not want to use dynamic entity pictures (e.g., from media player) for the log book rendering,
// as they would present a false state in the log (played media right now vs actual historic data).
entity_picture_local: DOMAINS_WITH_DYNAMIC_PICTURE.has(
computeDomain(currentStateObj.entity_id)
)
? undefined
: currentStateObj?.attributes.entity_picture_local,
entity_picture: DOMAINS_WITH_DYNAMIC_PICTURE.has(
computeDomain(currentStateObj.entity_id)
)
? undefined
: currentStateObj?.attributes.entity_picture,
},
});

export const localizeTriggerSource = (
localize: LocalizeFunc,
source: string
) => {
for (const triggerPhrase in triggerPhrases) {
if (source.startsWith(triggerPhrase)) {
return source.replace(
triggerPhrase,
`${localize(`ui.components.logbook.${triggerPhrases[triggerPhrase]}`)}`
);
}
}
return source;
};

export const localizeStateMessage = (
hass: HomeAssistant,
localize: LocalizeFunc,
state: string,
Expand Down
Loading