ha-frontend/src/data/entity_registry.ts

309 lines
7.9 KiB
TypeScript

import { Connection, createCollection } from "home-assistant-js-websocket";
import { Store } from "home-assistant-js-websocket/dist/store";
import memoizeOne from "memoize-one";
import { computeStateName } from "../common/entity/compute_state_name";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
import { HomeAssistant } from "../types";
import { LightColor } from "./light";
import { computeDomain } from "../common/entity/compute_domain";
export { subscribeEntityRegistryDisplay } from "./ws-entity_registry_display";
type EntityCategory = "config" | "diagnostic";
export interface EntityRegistryDisplayEntry {
entity_id: string;
name?: string;
icon?: string;
device_id?: string;
area_id?: string;
labels: string[];
hidden?: boolean;
entity_category?: EntityCategory;
translation_key?: string;
platform?: string;
display_precision?: number;
}
export interface EntityRegistryDisplayEntryResponse {
entities: {
ei: string;
di?: string;
ai?: string;
lb: string[];
ec?: number;
en?: string;
ic?: string;
pl?: string;
tk?: string;
hb?: boolean;
dp?: number;
}[];
entity_categories: Record<number, EntityCategory>;
}
export interface EntityRegistryEntry {
id: string;
entity_id: string;
name: string | null;
icon: string | null;
platform: string;
config_entry_id: string | null;
device_id: string | null;
area_id: string | null;
labels: string[];
disabled_by: "user" | "device" | "integration" | "config_entry" | null;
hidden_by: Exclude<EntityRegistryEntry["disabled_by"], "config_entry">;
entity_category: EntityCategory | null;
has_entity_name: boolean;
original_name?: string;
unique_id: string;
translation_key?: string;
options: EntityRegistryOptions | null;
categories: { [scope: string]: string };
}
export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
capabilities: Record<string, unknown>;
original_icon?: string;
device_class?: string;
original_device_class?: string;
aliases: string[];
}
export interface UpdateEntityRegistryEntryResult {
entity_entry: ExtEntityRegistryEntry;
reload_delay?: number;
require_restart?: boolean;
}
export interface SensorEntityOptions {
display_precision?: number | null;
suggested_display_precision?: number | null;
unit_of_measurement?: string | null;
}
export interface LightEntityOptions {
favorite_colors?: LightColor[];
}
export interface NumberEntityOptions {
unit_of_measurement?: string | null;
}
export interface LockEntityOptions {
default_code?: string | null;
}
export interface WeatherEntityOptions {
precipitation_unit?: string | null;
pressure_unit?: string | null;
temperature_unit?: string | null;
visibility_unit?: string | null;
wind_speed_unit?: string | null;
}
export interface SwitchAsXEntityOptions {
entity_id: string;
invert: boolean;
}
export interface EntityRegistryOptions {
number?: NumberEntityOptions;
sensor?: SensorEntityOptions;
lock?: LockEntityOptions;
weather?: WeatherEntityOptions;
light?: LightEntityOptions;
switch_as_x?: SwitchAsXEntityOptions;
conversation?: Record<string, unknown>;
"cloud.alexa"?: Record<string, unknown>;
"cloud.google_assistant"?: Record<string, unknown>;
}
export interface EntityRegistryEntryUpdateParams {
name?: string | null;
icon?: string | null;
device_class?: string | null;
area_id?: string | null;
disabled_by?: string | null;
hidden_by: string | null;
new_entity_id?: string;
options_domain?: string;
options?:
| SensorEntityOptions
| NumberEntityOptions
| LockEntityOptions
| WeatherEntityOptions
| LightEntityOptions;
aliases?: string[];
labels?: string[];
categories?: { [scope: string]: string | null };
}
const batteryPriorities = ["sensor", "binary_sensor"];
export const findBatteryEntity = <T extends { entity_id: string }>(
hass: HomeAssistant,
entities: T[]
): T | undefined => {
const batteryEntities = entities
.filter(
(entity) =>
hass.states[entity.entity_id] &&
hass.states[entity.entity_id].attributes.device_class === "battery" &&
batteryPriorities.includes(computeDomain(entity.entity_id))
)
.sort(
(a, b) =>
batteryPriorities.indexOf(computeDomain(a.entity_id)) -
batteryPriorities.indexOf(computeDomain(b.entity_id))
);
if (batteryEntities.length > 0) {
return batteryEntities[0];
}
return undefined;
};
export const findBatteryChargingEntity = <T extends { entity_id: string }>(
hass: HomeAssistant,
entities: T[]
): T | undefined =>
entities.find(
(entity) =>
hass.states[entity.entity_id] &&
hass.states[entity.entity_id].attributes.device_class ===
"battery_charging"
);
export const computeEntityRegistryName = (
hass: HomeAssistant,
entry: EntityRegistryEntry
): string | null => {
if (entry.name) {
return entry.name;
}
const state = hass.states[entry.entity_id];
if (state) {
return computeStateName(state);
}
return entry.original_name ? entry.original_name : entry.entity_id;
};
export const getExtendedEntityRegistryEntry = (
hass: HomeAssistant,
entityId: string
): Promise<ExtEntityRegistryEntry> =>
hass.callWS({
type: "config/entity_registry/get",
entity_id: entityId,
});
export const getExtendedEntityRegistryEntries = (
hass: HomeAssistant,
entityIds: string[]
): Promise<Record<string, ExtEntityRegistryEntry>> =>
hass.callWS({
type: "config/entity_registry/get_entries",
entity_ids: entityIds,
});
export const updateEntityRegistryEntry = (
hass: HomeAssistant,
entityId: string,
updates: Partial<EntityRegistryEntryUpdateParams>
): Promise<UpdateEntityRegistryEntryResult> =>
hass.callWS({
type: "config/entity_registry/update",
entity_id: entityId,
...updates,
});
export const removeEntityRegistryEntry = (
hass: HomeAssistant,
entityId: string
): Promise<void> =>
hass.callWS({
type: "config/entity_registry/remove",
entity_id: entityId,
});
export const fetchEntityRegistry = (conn: Connection) =>
conn.sendMessagePromise<EntityRegistryEntry[]>({
type: "config/entity_registry/list",
});
export const fetchEntityRegistryDisplay = (conn: Connection) =>
conn.sendMessagePromise<EntityRegistryDisplayEntryResponse>({
type: "config/entity_registry/list_for_display",
});
const subscribeEntityRegistryUpdates = (
conn: Connection,
store: Store<EntityRegistryEntry[]>
) =>
conn.subscribeEvents(
debounce(
() =>
fetchEntityRegistry(conn).then((entities) =>
store.setState(entities, true)
),
500,
true
),
"entity_registry_updated"
);
export const subscribeEntityRegistry = (
conn: Connection,
onChange: (entities: EntityRegistryEntry[]) => void
) =>
createCollection<EntityRegistryEntry[]>(
"_entityRegistry",
fetchEntityRegistry,
subscribeEntityRegistryUpdates,
conn,
onChange
);
export const sortEntityRegistryByName = (
entries: EntityRegistryEntry[],
language: string
) =>
entries.sort((entry1, entry2) =>
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "", language)
);
export const entityRegistryByEntityId = memoizeOne(
(entries: EntityRegistryEntry[]) => {
const entities: Record<string, EntityRegistryEntry> = {};
for (const entity of entries) {
entities[entity.entity_id] = entity;
}
return entities;
}
);
export const entityRegistryById = memoizeOne(
(entries: EntityRegistryEntry[]) => {
const entities: Record<string, EntityRegistryEntry> = {};
for (const entity of entries) {
entities[entity.id] = entity;
}
return entities;
}
);
export const getEntityPlatformLookup = (
entities: EntityRegistryEntry[]
): Record<string, string> => {
const entityLookup = {};
for (const confEnt of entities) {
if (!confEnt.platform) {
continue;
}
entityLookup[confEnt.entity_id] = confEnt.platform;
}
return entityLookup;
};