526 lines
23 KiB
TypeScript
526 lines
23 KiB
TypeScript
import * as plugins from '../../plugins.js';
|
|
import type { IIntegrationEntity, IIntegrationEvent } from '../../core/types.js';
|
|
import type { IDeconzGroup, IDeconzLight, IDeconzSensor, IDeconzSnapshot, IDeconzWebsocketEvent } from './deconz.types.js';
|
|
|
|
interface IDeconzSensorEntityDescription {
|
|
key: string;
|
|
platform: 'binary_sensor' | 'sensor';
|
|
stateKey?: string;
|
|
configKey?: string;
|
|
nameSuffix?: string;
|
|
unit?: string;
|
|
deviceClass?: string;
|
|
value: (sensorArg: IDeconzSensor) => unknown;
|
|
}
|
|
|
|
const POWER_PLUG_TYPES = new Set([
|
|
'on/off light',
|
|
'on/off output',
|
|
'on/off plug-in unit',
|
|
'smart plug',
|
|
]);
|
|
|
|
const COVER_TYPES = new Set([
|
|
'level controllable output',
|
|
'window covering controller',
|
|
'window covering device',
|
|
]);
|
|
|
|
const BINARY_SENSOR_DESCRIPTIONS: IDeconzSensorEntityDescription[] = [
|
|
{ key: 'alarm', platform: 'binary_sensor', stateKey: 'alarm', deviceClass: 'safety', value: (sensorArg) => sensorArg.state?.alarm },
|
|
{ key: 'carbon_monoxide', platform: 'binary_sensor', stateKey: 'carbonmonoxide', deviceClass: 'carbon_monoxide', value: (sensorArg) => sensorArg.state?.carbonmonoxide },
|
|
{ key: 'fire', platform: 'binary_sensor', stateKey: 'fire', deviceClass: 'smoke', value: (sensorArg) => sensorArg.state?.fire },
|
|
{ key: 'flag', platform: 'binary_sensor', stateKey: 'flag', value: (sensorArg) => sensorArg.state?.flag },
|
|
{ key: 'open', platform: 'binary_sensor', stateKey: 'open', deviceClass: 'opening', value: (sensorArg) => sensorArg.state?.open },
|
|
{ key: 'presence', platform: 'binary_sensor', stateKey: 'presence', deviceClass: 'motion', value: (sensorArg) => sensorArg.state?.presence },
|
|
{ key: 'vibration', platform: 'binary_sensor', stateKey: 'vibration', deviceClass: 'vibration', value: (sensorArg) => sensorArg.state?.vibration },
|
|
{ key: 'water', platform: 'binary_sensor', stateKey: 'water', deviceClass: 'moisture', value: (sensorArg) => sensorArg.state?.water },
|
|
{ key: 'tampered', platform: 'binary_sensor', configKey: 'tampered', nameSuffix: 'Tampered', deviceClass: 'tamper', value: (sensorArg) => sensorArg.config?.tampered },
|
|
{ key: 'low_battery', platform: 'binary_sensor', configKey: 'lowbattery', nameSuffix: 'Low Battery', deviceClass: 'battery', value: (sensorArg) => sensorArg.config?.lowbattery },
|
|
];
|
|
|
|
const SENSOR_DESCRIPTIONS: IDeconzSensorEntityDescription[] = [
|
|
{ key: 'air_quality', platform: 'sensor', stateKey: 'airquality', value: (sensorArg) => sensorArg.state?.airquality },
|
|
{ key: 'air_quality_ppb', platform: 'sensor', stateKey: 'airqualityppb', nameSuffix: 'PPB', unit: 'ppb', value: (sensorArg) => sensorArg.state?.airqualityppb },
|
|
{ key: 'battery', platform: 'sensor', configKey: 'battery', nameSuffix: 'Battery', unit: '%', deviceClass: 'battery', value: (sensorArg) => sensorArg.config?.battery ?? sensorArg.state?.battery },
|
|
{ key: 'button_event', platform: 'sensor', stateKey: 'buttonevent', nameSuffix: 'Button Event', value: (sensorArg) => sensorArg.state?.buttonevent },
|
|
{ key: 'consumption', platform: 'sensor', stateKey: 'consumption', unit: 'kWh', deviceClass: 'energy', value: (sensorArg) => DeconzMapper.scaleConsumption(sensorArg.state?.consumption) },
|
|
{ key: 'current', platform: 'sensor', stateKey: 'current', unit: 'A', value: (sensorArg) => sensorArg.state?.current },
|
|
{ key: 'humidity', platform: 'sensor', stateKey: 'humidity', unit: '%', deviceClass: 'humidity', value: (sensorArg) => DeconzMapper.scaleHundred(sensorArg.state?.humidity) },
|
|
{ key: 'light_level', platform: 'sensor', stateKey: 'lightlevel', unit: 'lx', deviceClass: 'illuminance', value: (sensorArg) => DeconzMapper.lightLevel(sensorArg) },
|
|
{ key: 'power', platform: 'sensor', stateKey: 'power', unit: 'W', deviceClass: 'power', value: (sensorArg) => sensorArg.state?.power },
|
|
{ key: 'pressure', platform: 'sensor', stateKey: 'pressure', unit: 'hPa', deviceClass: 'pressure', value: (sensorArg) => sensorArg.state?.pressure },
|
|
{ key: 'status', platform: 'sensor', stateKey: 'status', value: (sensorArg) => sensorArg.state?.status },
|
|
{ key: 'temperature', platform: 'sensor', stateKey: 'temperature', unit: 'C', deviceClass: 'temperature', value: (sensorArg) => DeconzMapper.scaleHundred(sensorArg.state?.temperature) },
|
|
{ key: 'voltage', platform: 'sensor', stateKey: 'voltage', unit: 'V', deviceClass: 'voltage', value: (sensorArg) => sensorArg.state?.voltage },
|
|
];
|
|
|
|
export class DeconzMapper {
|
|
public static toDevices(snapshotArg: IDeconzSnapshot): plugins.shxInterfaces.data.IDeviceDefinition[] {
|
|
const updatedAt = new Date().toISOString();
|
|
const devices: plugins.shxInterfaces.data.IDeviceDefinition[] = [];
|
|
const bridgeId = this.bridgeId(snapshotArg);
|
|
|
|
devices.push({
|
|
id: this.gatewayDeviceId(snapshotArg),
|
|
integrationDomain: 'deconz',
|
|
name: snapshotArg.config?.name || snapshotArg.config?.devicename || 'deCONZ Gateway',
|
|
protocol: 'zigbee',
|
|
manufacturer: 'dresden elektronik',
|
|
model: snapshotArg.config?.devicename || snapshotArg.config?.modelid || 'deCONZ',
|
|
online: snapshotArg.config?.rfconnected !== false,
|
|
features: [
|
|
{ id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
|
|
],
|
|
state: [
|
|
{ featureId: 'connectivity', value: snapshotArg.config?.rfconnected === false ? 'offline' : 'online', updatedAt },
|
|
],
|
|
metadata: {
|
|
bridgeId,
|
|
apiVersion: snapshotArg.config?.apiversion,
|
|
softwareVersion: snapshotArg.config?.swversion,
|
|
websocketPort: snapshotArg.config?.websocketport,
|
|
},
|
|
});
|
|
|
|
for (const [lightId, light] of Object.entries(snapshotArg.lights)) {
|
|
devices.push(this.lightToDevice(lightId, light, updatedAt));
|
|
}
|
|
|
|
for (const [groupId, group] of Object.entries(snapshotArg.groups)) {
|
|
if (!group.lights?.length) {
|
|
continue;
|
|
}
|
|
devices.push(this.groupToDevice(bridgeId, groupId, group, updatedAt));
|
|
}
|
|
|
|
for (const [sensorId, sensor] of Object.entries(snapshotArg.sensors)) {
|
|
devices.push(this.sensorToDevice(sensorId, sensor, updatedAt));
|
|
}
|
|
|
|
return devices;
|
|
}
|
|
|
|
public static toEntities(snapshotArg: IDeconzSnapshot): IIntegrationEntity[] {
|
|
const entities: IIntegrationEntity[] = [];
|
|
const usedIds = new Map<string, number>();
|
|
const bridgeId = this.bridgeId(snapshotArg);
|
|
|
|
for (const [lightId, light] of Object.entries(snapshotArg.lights)) {
|
|
entities.push(this.lightToEntity(lightId, light, usedIds));
|
|
}
|
|
|
|
for (const [groupId, group] of Object.entries(snapshotArg.groups)) {
|
|
if (!group.lights?.length) {
|
|
continue;
|
|
}
|
|
entities.push(this.groupToEntity(bridgeId, groupId, group, usedIds));
|
|
}
|
|
|
|
for (const [sensorId, sensor] of Object.entries(snapshotArg.sensors)) {
|
|
if (this.isThermostat(sensor)) {
|
|
entities.push(this.climateToEntity(sensorId, sensor, usedIds));
|
|
}
|
|
|
|
for (const description of [...BINARY_SENSOR_DESCRIPTIONS, ...SENSOR_DESCRIPTIONS]) {
|
|
if (this.isThermostat(sensor) && description.key === 'temperature') {
|
|
continue;
|
|
}
|
|
const value = description.value(sensor);
|
|
if (value === undefined || value === null) {
|
|
continue;
|
|
}
|
|
entities.push(this.sensorToEntity(sensorId, sensor, description, value, usedIds));
|
|
}
|
|
}
|
|
|
|
return entities;
|
|
}
|
|
|
|
public static toIntegrationEvent(eventArg: IDeconzWebsocketEvent): IIntegrationEvent {
|
|
return {
|
|
type: eventArg.e === 'added' ? 'device_added' : eventArg.e === 'deleted' ? 'device_removed' : 'state_changed',
|
|
integrationDomain: 'deconz',
|
|
data: eventArg,
|
|
timestamp: Date.now(),
|
|
};
|
|
}
|
|
|
|
public static scaleHundred(valueArg: unknown): number | undefined {
|
|
if (typeof valueArg !== 'number') {
|
|
return undefined;
|
|
}
|
|
return Math.abs(valueArg) > 200 ? valueArg / 100 : valueArg;
|
|
}
|
|
|
|
public static scaleConsumption(valueArg: unknown): number | undefined {
|
|
if (typeof valueArg !== 'number') {
|
|
return undefined;
|
|
}
|
|
return Math.abs(valueArg) >= 1000 ? valueArg / 1000 : valueArg;
|
|
}
|
|
|
|
public static lightLevel(sensorArg: IDeconzSensor): number | undefined {
|
|
if (typeof sensorArg.state?.lux === 'number') {
|
|
return sensorArg.state.lux;
|
|
}
|
|
if (typeof sensorArg.state?.lightlevel !== 'number') {
|
|
return undefined;
|
|
}
|
|
return Math.round(Math.pow(10, (sensorArg.state.lightlevel - 1) / 10000));
|
|
}
|
|
|
|
private static lightToDevice(lightIdArg: string, lightArg: IDeconzLight, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
|
|
const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
|
|
{ id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
|
|
];
|
|
const state: plugins.shxInterfaces.data.IDeviceState[] = [
|
|
{ featureId: 'connectivity', value: this.isLightAvailable(lightArg) ? 'online' : 'offline', updatedAt: updatedAtArg },
|
|
];
|
|
|
|
if (this.isCoverLight(lightArg)) {
|
|
features.push({ id: 'position', capability: 'cover', name: 'Position', readable: true, writable: true, unit: '%' });
|
|
this.pushDeviceState(state, 'position', this.coverPosition(lightArg), updatedAtArg);
|
|
} else if (this.isSwitchLight(lightArg)) {
|
|
features.push({ id: 'on', capability: 'switch', name: 'Power', readable: true, writable: true });
|
|
this.pushDeviceState(state, 'on', lightArg.state?.on ?? false, updatedAtArg);
|
|
} else {
|
|
features.push({ id: 'on', capability: 'light', name: 'Power', readable: true, writable: true });
|
|
this.pushDeviceState(state, 'on', lightArg.state?.on ?? false, updatedAtArg);
|
|
if (typeof lightArg.state?.bri === 'number') {
|
|
features.push({ id: 'brightness', capability: 'light', name: 'Brightness', readable: true, writable: true, unit: '%' });
|
|
this.pushDeviceState(state, 'brightness', Math.round(lightArg.state.bri / 255 * 100), updatedAtArg);
|
|
}
|
|
}
|
|
|
|
return {
|
|
id: this.lightDeviceId(lightIdArg, lightArg),
|
|
integrationDomain: 'deconz',
|
|
name: lightArg.name || `deCONZ Light ${lightIdArg}`,
|
|
protocol: 'zigbee',
|
|
manufacturer: lightArg.manufacturername || 'Unknown',
|
|
model: lightArg.modelid || lightArg.type,
|
|
online: this.isLightAvailable(lightArg),
|
|
features,
|
|
state,
|
|
metadata: {
|
|
deconzId: lightIdArg,
|
|
uniqueId: lightArg.uniqueid,
|
|
type: lightArg.type,
|
|
softwareVersion: lightArg.swversion,
|
|
},
|
|
};
|
|
}
|
|
|
|
private static groupToDevice(bridgeIdArg: string, groupIdArg: string, groupArg: IDeconzGroup, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
|
|
const state: plugins.shxInterfaces.data.IDeviceState[] = [
|
|
{ featureId: 'connectivity', value: 'online', updatedAt: updatedAtArg },
|
|
{ featureId: 'on', value: groupArg.state?.any_on ?? groupArg.action?.on ?? false, updatedAt: updatedAtArg },
|
|
];
|
|
if (typeof groupArg.action?.bri === 'number') {
|
|
state.push({ featureId: 'brightness', value: Math.round(groupArg.action.bri / 255 * 100), updatedAt: updatedAtArg });
|
|
}
|
|
|
|
return {
|
|
id: this.groupDeviceId(bridgeIdArg, groupIdArg),
|
|
integrationDomain: 'deconz',
|
|
name: groupArg.name || `deCONZ Group ${groupIdArg}`,
|
|
protocol: 'zigbee',
|
|
manufacturer: 'dresden elektronik',
|
|
model: 'deCONZ group',
|
|
online: true,
|
|
features: [
|
|
{ id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
|
|
{ id: 'on', capability: 'light', name: 'Power', readable: true, writable: true },
|
|
{ id: 'brightness', capability: 'light', name: 'Brightness', readable: true, writable: true, unit: '%' },
|
|
],
|
|
state,
|
|
metadata: {
|
|
bridgeId: bridgeIdArg,
|
|
deconzId: groupIdArg,
|
|
lights: groupArg.lights,
|
|
type: groupArg.type,
|
|
},
|
|
};
|
|
}
|
|
|
|
private static sensorToDevice(sensorIdArg: string, sensorArg: IDeconzSensor, updatedAtArg: string): plugins.shxInterfaces.data.IDeviceDefinition {
|
|
const features: plugins.shxInterfaces.data.IDeviceFeature[] = [
|
|
{ id: 'connectivity', capability: 'sensor', name: 'Connectivity', readable: true, writable: false },
|
|
];
|
|
const state: plugins.shxInterfaces.data.IDeviceState[] = [
|
|
{ featureId: 'connectivity', value: this.isSensorAvailable(sensorArg) ? 'online' : 'offline', updatedAt: updatedAtArg },
|
|
];
|
|
|
|
if (this.isThermostat(sensorArg)) {
|
|
features.push({ id: 'target_temperature', capability: 'climate', name: 'Target Temperature', readable: true, writable: true, unit: 'C' });
|
|
features.push({ id: 'current_temperature', capability: 'climate', name: 'Current Temperature', readable: true, writable: false, unit: 'C' });
|
|
this.pushDeviceState(state, 'target_temperature', this.targetTemperature(sensorArg), updatedAtArg);
|
|
this.pushDeviceState(state, 'current_temperature', this.scaleHundred(sensorArg.state?.temperature), updatedAtArg);
|
|
}
|
|
|
|
for (const description of [...BINARY_SENSOR_DESCRIPTIONS, ...SENSOR_DESCRIPTIONS]) {
|
|
if (this.isThermostat(sensorArg) && description.key === 'temperature') {
|
|
continue;
|
|
}
|
|
const value = description.value(sensorArg);
|
|
if (value === undefined || value === null) {
|
|
continue;
|
|
}
|
|
features.push({
|
|
id: description.key,
|
|
capability: 'sensor',
|
|
name: this.entityName(sensorArg, description),
|
|
readable: true,
|
|
writable: Boolean(sensorArg.type?.startsWith('CLIP') && description.stateKey),
|
|
unit: description.unit,
|
|
});
|
|
this.pushDeviceState(state, description.key, description.platform === 'binary_sensor' ? Boolean(value) : value, updatedAtArg);
|
|
}
|
|
|
|
return {
|
|
id: this.sensorDeviceId(sensorIdArg, sensorArg),
|
|
integrationDomain: 'deconz',
|
|
name: sensorArg.name || `deCONZ Sensor ${sensorIdArg}`,
|
|
protocol: 'zigbee',
|
|
manufacturer: sensorArg.manufacturername || 'Unknown',
|
|
model: sensorArg.modelid || sensorArg.type,
|
|
online: this.isSensorAvailable(sensorArg),
|
|
features,
|
|
state,
|
|
metadata: {
|
|
deconzId: sensorIdArg,
|
|
uniqueId: sensorArg.uniqueid,
|
|
type: sensorArg.type,
|
|
softwareVersion: sensorArg.swversion,
|
|
},
|
|
};
|
|
}
|
|
|
|
private static lightToEntity(lightIdArg: string, lightArg: IDeconzLight, usedIdsArg: Map<string, number>): IIntegrationEntity {
|
|
const platform = this.isCoverLight(lightArg) ? 'cover' : this.isSwitchLight(lightArg) ? 'switch' : 'light';
|
|
const resourcePath = `/lights/${lightIdArg}/state`;
|
|
return {
|
|
id: this.entityId(platform, lightArg.name || `deCONZ Light ${lightIdArg}`, usedIdsArg),
|
|
uniqueId: `deconz_light_${this.slug(lightArg.uniqueid || lightIdArg)}`,
|
|
integrationDomain: 'deconz',
|
|
deviceId: this.lightDeviceId(lightIdArg, lightArg),
|
|
platform,
|
|
name: lightArg.name || `deCONZ Light ${lightIdArg}`,
|
|
state: platform === 'cover' ? this.coverState(lightArg) : lightArg.state?.on ? 'on' : 'off',
|
|
attributes: {
|
|
deconzResource: 'lights',
|
|
deconzId: lightIdArg,
|
|
deconzPath: resourcePath,
|
|
uniqueId: lightArg.uniqueid,
|
|
type: lightArg.type,
|
|
brightness: lightArg.state?.bri,
|
|
colorMode: lightArg.state?.colormode,
|
|
colorTemperature: lightArg.state?.ct,
|
|
position: platform === 'cover' ? this.coverPosition(lightArg) : undefined,
|
|
reachable: lightArg.state?.reachable,
|
|
},
|
|
available: this.isLightAvailable(lightArg),
|
|
};
|
|
}
|
|
|
|
private static groupToEntity(bridgeIdArg: string, groupIdArg: string, groupArg: IDeconzGroup, usedIdsArg: Map<string, number>): IIntegrationEntity {
|
|
return {
|
|
id: this.entityId('light', groupArg.name || `deCONZ Group ${groupIdArg}`, usedIdsArg),
|
|
uniqueId: `deconz_group_${this.slug(bridgeIdArg)}_${this.slug(groupIdArg)}`,
|
|
integrationDomain: 'deconz',
|
|
deviceId: this.groupDeviceId(bridgeIdArg, groupIdArg),
|
|
platform: 'light',
|
|
name: groupArg.name || `deCONZ Group ${groupIdArg}`,
|
|
state: groupArg.state?.any_on || groupArg.action?.on ? 'on' : 'off',
|
|
attributes: {
|
|
deconzResource: 'groups',
|
|
deconzId: groupIdArg,
|
|
deconzPath: `/groups/${groupIdArg}/action`,
|
|
isDeconzGroup: true,
|
|
allOn: groupArg.state?.all_on,
|
|
anyOn: groupArg.state?.any_on,
|
|
brightness: groupArg.action?.bri,
|
|
lights: groupArg.lights,
|
|
},
|
|
available: true,
|
|
};
|
|
}
|
|
|
|
private static sensorToEntity(
|
|
sensorIdArg: string,
|
|
sensorArg: IDeconzSensor,
|
|
descriptionArg: IDeconzSensorEntityDescription,
|
|
valueArg: unknown,
|
|
usedIdsArg: Map<string, number>
|
|
): IIntegrationEntity {
|
|
const name = this.entityName(sensorArg, descriptionArg);
|
|
return {
|
|
id: this.entityId(descriptionArg.platform, name, usedIdsArg),
|
|
uniqueId: `deconz_sensor_${this.slug(sensorArg.uniqueid || sensorIdArg)}_${descriptionArg.key}`,
|
|
integrationDomain: 'deconz',
|
|
deviceId: this.sensorDeviceId(sensorIdArg, sensorArg),
|
|
platform: descriptionArg.platform,
|
|
name,
|
|
state: descriptionArg.platform === 'binary_sensor' ? (valueArg ? 'on' : 'off') : valueArg,
|
|
attributes: {
|
|
deconzResource: 'sensors',
|
|
deconzId: sensorIdArg,
|
|
deconzPath: descriptionArg.configKey ? `/sensors/${sensorIdArg}/config` : `/sensors/${sensorIdArg}/state`,
|
|
deconzStateKey: descriptionArg.stateKey,
|
|
deconzConfigKey: descriptionArg.configKey,
|
|
deviceClass: descriptionArg.deviceClass,
|
|
unit: descriptionArg.unit,
|
|
type: sensorArg.type,
|
|
uniqueId: sensorArg.uniqueid,
|
|
lastUpdated: sensorArg.state?.lastupdated,
|
|
},
|
|
available: this.isSensorAvailable(sensorArg),
|
|
};
|
|
}
|
|
|
|
private static climateToEntity(sensorIdArg: string, sensorArg: IDeconzSensor, usedIdsArg: Map<string, number>): IIntegrationEntity {
|
|
return {
|
|
id: this.entityId('climate', sensorArg.name || `deCONZ Thermostat ${sensorIdArg}`, usedIdsArg),
|
|
uniqueId: `deconz_climate_${this.slug(sensorArg.uniqueid || sensorIdArg)}`,
|
|
integrationDomain: 'deconz',
|
|
deviceId: this.sensorDeviceId(sensorIdArg, sensorArg),
|
|
platform: 'climate',
|
|
name: sensorArg.name || `deCONZ Thermostat ${sensorIdArg}`,
|
|
state: this.climateMode(sensorArg),
|
|
attributes: {
|
|
deconzResource: 'sensors',
|
|
deconzId: sensorIdArg,
|
|
deconzPath: `/sensors/${sensorIdArg}/config`,
|
|
currentTemperature: this.scaleHundred(sensorArg.state?.temperature),
|
|
targetTemperature: this.targetTemperature(sensorArg),
|
|
fanMode: sensorArg.config?.fanmode,
|
|
preset: sensorArg.config?.preset,
|
|
locked: sensorArg.config?.locked,
|
|
valve: sensorArg.config?.valve,
|
|
mode: sensorArg.config?.mode,
|
|
},
|
|
available: this.isSensorAvailable(sensorArg),
|
|
};
|
|
}
|
|
|
|
private static pushDeviceState(
|
|
stateArg: plugins.shxInterfaces.data.IDeviceState[],
|
|
featureIdArg: string,
|
|
valueArg: unknown,
|
|
updatedAtArg: string
|
|
): void {
|
|
if (valueArg === undefined) {
|
|
return;
|
|
}
|
|
stateArg.push({ featureId: featureIdArg, value: valueArg as plugins.shxInterfaces.data.TDeviceStateValue, updatedAt: updatedAtArg });
|
|
}
|
|
|
|
private static entityName(sensorArg: IDeconzSensor, descriptionArg: IDeconzSensorEntityDescription): string {
|
|
const baseName = sensorArg.name || 'deCONZ Sensor';
|
|
return descriptionArg.nameSuffix ? `${baseName} ${descriptionArg.nameSuffix}` : baseName;
|
|
}
|
|
|
|
private static isLightAvailable(lightArg: IDeconzLight): boolean {
|
|
return lightArg.state?.reachable !== false;
|
|
}
|
|
|
|
private static isSensorAvailable(sensorArg: IDeconzSensor): boolean {
|
|
return sensorArg.config?.reachable !== false && sensorArg.config?.on !== false;
|
|
}
|
|
|
|
private static isSwitchLight(lightArg: IDeconzLight): boolean {
|
|
const type = lightArg.type?.toLowerCase() || '';
|
|
return POWER_PLUG_TYPES.has(type) || type.includes('plug') || type.includes('outlet');
|
|
}
|
|
|
|
private static isCoverLight(lightArg: IDeconzLight): boolean {
|
|
const type = lightArg.type?.toLowerCase() || '';
|
|
return COVER_TYPES.has(type) || lightArg.state?.lift !== undefined || lightArg.state?.tilt !== undefined;
|
|
}
|
|
|
|
private static isThermostat(sensorArg: IDeconzSensor): boolean {
|
|
const type = sensorArg.type?.toLowerCase() || '';
|
|
return type.includes('thermostat')
|
|
|| sensorArg.config?.heatsetpoint !== undefined
|
|
|| sensorArg.config?.coolsetpoint !== undefined
|
|
|| sensorArg.config?.mode !== undefined && sensorArg.state?.temperature !== undefined;
|
|
}
|
|
|
|
private static coverState(lightArg: IDeconzLight): string {
|
|
if (lightArg.state?.open === false || lightArg.state?.lift === 100) {
|
|
return 'closed';
|
|
}
|
|
return 'open';
|
|
}
|
|
|
|
private static coverPosition(lightArg: IDeconzLight): number | undefined {
|
|
if (typeof lightArg.state?.lift === 'number') {
|
|
return this.clamp(100 - lightArg.state.lift, 0, 100);
|
|
}
|
|
if (typeof lightArg.state?.open === 'boolean') {
|
|
return lightArg.state.open ? 100 : 0;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
private static climateMode(sensorArg: IDeconzSensor): string {
|
|
if (sensorArg.config?.mode) {
|
|
return sensorArg.config.mode;
|
|
}
|
|
return sensorArg.config?.on === false ? 'off' : 'heat';
|
|
}
|
|
|
|
private static targetTemperature(sensorArg: IDeconzSensor): number | undefined {
|
|
if (sensorArg.config?.mode === 'cool' && typeof sensorArg.config.coolsetpoint === 'number') {
|
|
return this.scaleHundred(sensorArg.config.coolsetpoint);
|
|
}
|
|
if (typeof sensorArg.config?.heatsetpoint === 'number') {
|
|
return this.scaleHundred(sensorArg.config.heatsetpoint);
|
|
}
|
|
if (typeof sensorArg.config?.coolsetpoint === 'number') {
|
|
return this.scaleHundred(sensorArg.config.coolsetpoint);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
private static gatewayDeviceId(snapshotArg: IDeconzSnapshot): string {
|
|
return `deconz.gateway.${this.slug(this.bridgeId(snapshotArg))}`;
|
|
}
|
|
|
|
private static lightDeviceId(lightIdArg: string, lightArg: IDeconzLight): string {
|
|
return `deconz.light.${this.slug(this.serialFromUniqueId(lightArg.uniqueid) || lightArg.uniqueid || lightIdArg)}`;
|
|
}
|
|
|
|
private static groupDeviceId(bridgeIdArg: string, groupIdArg: string): string {
|
|
return `deconz.group.${this.slug(bridgeIdArg)}_${this.slug(groupIdArg)}`;
|
|
}
|
|
|
|
private static sensorDeviceId(sensorIdArg: string, sensorArg: IDeconzSensor): string {
|
|
return `deconz.sensor.${this.slug(this.serialFromUniqueId(sensorArg.uniqueid) || sensorArg.uniqueid || sensorIdArg)}`;
|
|
}
|
|
|
|
private static bridgeId(snapshotArg: IDeconzSnapshot): string {
|
|
return snapshotArg.config?.bridgeid || snapshotArg.config?.uuid || 'unknown';
|
|
}
|
|
|
|
private static serialFromUniqueId(uniqueIdArg?: string): string | undefined {
|
|
return uniqueIdArg?.split('-')[0];
|
|
}
|
|
|
|
private static entityId(platformArg: string, nameArg: string, usedIdsArg: Map<string, number>): string {
|
|
const base = `${platformArg}.${this.slug(nameArg)}`;
|
|
const count = usedIdsArg.get(base) || 0;
|
|
usedIdsArg.set(base, count + 1);
|
|
return count === 0 ? base : `${base}_${count + 1}`;
|
|
}
|
|
|
|
private static slug(valueArg: string): string {
|
|
return valueArg.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'deconz';
|
|
}
|
|
|
|
private static clamp(valueArg: number, minArg: number, maxArg: number): number {
|
|
return Math.min(maxArg, Math.max(minArg, valueArg));
|
|
}
|
|
}
|