UNPKG

matterbridge-hass

Version:
266 lines (265 loc) 9.92 kB
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; }