matterbridge-hass
Version:
Matterbridge hass plugin
266 lines (265 loc) • 9.92 kB
JavaScript
import { randomBytes } from 'node:crypto';
import { isValidArray, isValidObject, isValidString } from 'matterbridge/utils';
const generatedEntityIds = new Set();
const generatedAreaIds = new Set();
const generatedLabelIds = new Set();
function createGeneratedId(baseId, generatedIds) {
let generatedId = baseId;
let duplicateIndex = 1;
while (generatedIds.has(generatedId)) {
duplicateIndex += 1;
generatedId = `${baseId}_${duplicateIndex}`;
}
generatedIds.add(generatedId);
return generatedId;
}
function createEntityId(name, domain) {
const normalizedName = isValidString(name) ? name.toLowerCase().replace(/ /g, '_') : 'unnamed_entity';
const baseEntityId = `${domain}.${normalizedName}`;
return createGeneratedId(baseEntityId, generatedEntityIds);
}
function createAreaId(name) {
const normalizedName = isValidString(name) ? name.toLowerCase().replace(/ /g, '_') : 'unnamed_area';
return createGeneratedId(normalizedName, generatedAreaIds);
}
function createLabelId(name) {
const normalizedName = isValidString(name) ? name.toLowerCase().replace(/ /g, '_') : 'unnamed_label';
return createGeneratedId(normalizedName, generatedLabelIds);
}
export function entityHasLabel(platform, entity, label) {
if (!isValidObject(platform) ||
!isValidObject(platform.ha) ||
!isValidObject(platform.ha.hassLabels) ||
!isValidObject(entity) ||
!isValidArray(entity.labels) ||
!label ||
!isValidString(label)) {
return false;
}
const labels = Array.from(platform.ha.hassLabels.values());
const entry = labels.find((entry) => entry.name === label);
if (!entry) {
return false;
}
return entity.labels.includes(entry.label_id);
}
export function isSplitEntity(platform, entity) {
if (!isValidObject(platform) ||
!isValidObject(platform.config) ||
!isValidObject(platform.ha) ||
!isValidObject(platform.ha.hassLabels) ||
!isValidObject(entity) ||
!isValidArray(platform.config.splitEntities) ||
!isValidString(platform.config.splitByLabel)) {
return false;
}
return platform.config.splitEntities.includes(entity.entity_id) || entityHasLabel(platform, entity, platform.config.splitByLabel);
}
export function isDeviceEntity(entity) {
if (!isValidObject(entity)) {
return false;
}
return entity.device_id != null;
}
export function isIndividualEntity(entity) {
if (!isValidObject(entity)) {
return false;
}
return entity.device_id === null;
}
export function isDisabled(entityOrDevice) {
if (!isValidObject(entityOrDevice) || (typeof entityOrDevice.disabled_by !== 'string' && entityOrDevice.disabled_by !== null)) {
return false;
}
return entityOrDevice.disabled_by !== null;
}
export function isHidden(entity) {
if (!isValidObject(entity) || (typeof entity.hidden_by !== 'string' && entity.hidden_by !== null)) {
return false;
}
return entity.hidden_by !== null;
}
export function satisfiesAreaFilter(platform, deviceOrEntity) {
if (!isValidObject(platform) || !isValidObject(platform.config) || !isValidObject(platform.ha) || !isValidObject(deviceOrEntity)) {
return false;
}
if (!isValidString(platform.config.filterByArea, 1)) {
return true;
}
if (!isValidObject(platform.ha.hassAreas) || typeof deviceOrEntity.area_id !== 'string') {
return false;
}
const area = platform.ha.hassAreas.get(deviceOrEntity.area_id);
return area?.name === platform.config.filterByArea;
}
export function satisfiesLabelFilter(platform, deviceOrEntity) {
if (!isValidObject(platform) || !isValidObject(platform.config) || !isValidObject(platform.ha) || !isValidObject(deviceOrEntity)) {
return false;
}
if (!isValidString(platform.config.filterByLabel, 1)) {
return true;
}
if (!isValidObject(platform.ha.hassLabels) || !isValidArray(deviceOrEntity.labels, 1)) {
return false;
}
const label = Array.from(platform.ha.hassLabels.values()).find((entry) => entry.name === platform.config.filterByLabel);
return label !== undefined && deviceOrEntity.labels.includes(label.label_id);
}
export function getDomain(entity) {
const entityId = typeof entity === 'string' ? entity : entity?.entity_id;
if (!isValidString(entityId)) {
throw new Error('The entity_id does not contain a domain');
}
const separatorIndex = entityId.indexOf('.');
if (separatorIndex <= 0) {
throw new Error(`The entity_id "${entityId}" does not contain a domain`);
}
return entityId.slice(0, separatorIndex);
}
export function getName(entity) {
const entityId = typeof entity === 'string' ? entity : entity?.entity_id;
if (!isValidString(entityId)) {
throw new Error('The entity_id does not contain a name');
}
const separatorIndex = entityId.indexOf('.');
if (separatorIndex === -1 || separatorIndex === entityId.length - 1) {
throw new Error(`The entity_id "${entityId}" does not contain a name`);
}
return entityId.slice(separatorIndex + 1);
}
export function getEntityName(platform, entity) {
if (!isValidObject(platform) ||
!isValidObject(platform.config) ||
!isValidObject(platform.ha) ||
!isValidObject(platform.ha.hassStates) ||
!isValidObject(entity) ||
(platform.config.splitNameStrategy !== 'Entity name' && platform.config.splitNameStrategy !== 'Friendly name')) {
return null;
}
const state = platform.ha.hassStates.get(entity.entity_id);
if (!isValidObject(state)) {
return null;
}
return platform.config.splitNameStrategy === 'Friendly name'
? (state.attributes?.friendly_name ?? entity.name ?? entity.original_name ?? null)
: (entity.name ?? entity.original_name ?? state.attributes?.friendly_name ?? null);
}
export function createUniqueId() {
return randomBytes(16).toString('hex');
}
export function generateDevice(ha, name, area_id = null, labels = []) {
const timestamp = Date.now() / 1000;
const serialNumber = '0x' + randomBytes(8).toString('hex');
const device = {
id: createUniqueId(),
area_id,
configuration_url: null,
config_entries: [],
config_entries_subentries: {},
connections: [],
created_at: timestamp,
disabled_by: null,
entry_type: null,
hw_version: null,
identifiers: [],
labels: [...labels],
manufacturer: null,
model: null,
model_id: null,
modified_at: timestamp,
name: isValidString(name) ? name : null,
name_by_user: null,
primary_config_entry: '',
serial_number: serialNumber,
sw_version: null,
via_device_id: null,
};
ha.hassDevices.set(device.id, device);
return device;
}
export function generateEntity(ha, name, domain, device = null, area_id = null, labels = [], state = 'unknown', attributes = {}) {
const timestamp = Date.now() / 1000;
const entity_id = createEntityId(name, domain);
const entity = {
id: createUniqueId(),
entity_id,
area_id: device !== null ? null : area_id,
categories: {},
config_entry_id: null,
config_subentry_id: null,
created_at: timestamp,
device_id: device?.id ?? null,
disabled_by: null,
entity_category: null,
has_entity_name: true,
hidden_by: null,
icon: null,
labels: [...labels],
modified_at: timestamp,
name: null,
options: null,
original_name: isValidString(name) ? name : null,
platform: 'jest',
translation_key: null,
unique_id: createUniqueId(),
};
ha.hassEntities.set(entity.entity_id, entity);
generateState(ha, entity, state, attributes);
return entity;
}
export function generateState(ha, entity, state = 'unknown', attributes = {}) {
const timestamp = new Date().toISOString();
const entityFriendlyName = entity.original_name ?? entity.name ?? undefined;
const deviceName = entity.device_id ? (ha.hassDevices.get(entity.device_id)?.name_by_user ?? ha.hassDevices.get(entity.device_id)?.name ?? undefined) : undefined;
const friendlyName = isValidString(deviceName) && isValidString(entityFriendlyName) ? `${deviceName} ${entityFriendlyName}` : entityFriendlyName;
const hassState = {
entity_id: entity.entity_id,
state,
last_changed: timestamp,
last_reported: timestamp,
last_updated: timestamp,
attributes: {
friendly_name: friendlyName,
...attributes,
},
context: {
id: createUniqueId(),
parent_id: null,
user_id: null,
},
};
ha.hassStates.set(hassState.entity_id, hassState);
return hassState;
}
export function generateArea(ha, name, labels = []) {
const timestamp = Date.now() / 1000;
const area = {
aliases: [],
area_id: createAreaId(name),
created_at: timestamp,
floor_id: null,
humidity_entity_id: null,
icon: null,
labels: [...labels],
modified_at: timestamp,
name: isValidString(name) ? name : 'Unnamed Area',
picture: null,
temperature_entity_id: null,
};
ha.hassAreas.set(area.area_id, area);
return area;
}
export function generateLabel(ha, name) {
const timestamp = Date.now() / 1000;
const label = {
label_id: createLabelId(name),
color: null,
created_at: timestamp,
description: null,
icon: null,
modified_at: timestamp,
name: isValidString(name) ? name : 'Unnamed Label',
};
ha.hassLabels.set(label.label_id, label);
return label;
}