mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-29 07:08:56 +00:00
fix(ignore): Use zhc types (#20006)
* fix(ignore): Use zhc TS types * updates
This commit is contained in:
@@ -4,6 +4,7 @@ import utils from '../util/utils';
|
||||
import * as settings from '../util/settings';
|
||||
import debounce from 'debounce';
|
||||
import bind from 'bind-decorator';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
const retrieveOnReconnect = [
|
||||
{keys: ['state']},
|
||||
@@ -199,8 +200,13 @@ export default class Availability extends Extension {
|
||||
for (const item of retrieveOnReconnect) {
|
||||
if (item.condition && this.state.get(device) && !item.condition(this.state.get(device))) continue;
|
||||
const converter = device.definition.toZigbee.find((c) => c.key.find((k) => item.keys.includes(k)));
|
||||
await converter?.convertGet?.(device.endpoint(), item.keys[0],
|
||||
{message: this.state.get(device), mapped: device.definition})
|
||||
const options: KeyValue = device.options;
|
||||
const state = this.state.get(device);
|
||||
const meta: zhc.Tz.Meta = {
|
||||
message: this.state.get(device), mapped: device.definition, logger, endpoint_name: null,
|
||||
options, state, device: device.zh,
|
||||
};
|
||||
await converter?.convertGet?.(device.endpoint(), item.keys[0], meta)
|
||||
.catch((e) => {
|
||||
logger.error(`Failed to read state of '${device.name}' after reconnect (${e.message})`);
|
||||
});
|
||||
|
||||
@@ -12,12 +12,13 @@ import Group from '../model/group';
|
||||
import data from '../util/data';
|
||||
import JSZip from 'jszip';
|
||||
import fs from 'fs';
|
||||
import type * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
const requestRegex = new RegExp(`${settings.get().mqtt.base_topic}/bridge/request/(.*)`);
|
||||
|
||||
type DefinitionPayload = {
|
||||
model: string, vendor: string, description: string, exposes: zhc.DefinitionExpose[], supports_ota:
|
||||
boolean, icon: string, options: zhc.DefinitionExpose[],
|
||||
model: string, vendor: string, description: string, exposes: zhc.Expose[], supports_ota:
|
||||
boolean, icon: string, options: zhc.Expose[],
|
||||
};
|
||||
|
||||
export default class Bridge extends Extension {
|
||||
@@ -687,7 +688,9 @@ export default class Bridge extends Extension {
|
||||
|
||||
getDefinitionPayload(device: Device): DefinitionPayload {
|
||||
if (!device.definition) return null;
|
||||
let icon = device.options.icon ? device.options.icon : device.definition.icon;
|
||||
// @ts-expect-error icon is valid for external definitions
|
||||
const definitionIcon = device.definition.icon;
|
||||
let icon = device.options.icon ?? definitionIcon;
|
||||
if (icon) {
|
||||
icon = icon.replace('${zigbeeModel}', utils.sanitizeImageParameter(device.zh.modelID));
|
||||
icon = icon.replace('${model}', utils.sanitizeImageParameter(device.definition.model));
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as settings from '../util/settings';
|
||||
import utils from '../util/utils';
|
||||
import logger from '../util/logger';
|
||||
import stringify from 'json-stable-stringify-without-jsonify';
|
||||
import zhc from 'zigbee-herdsman-converters';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
import Extension from './extension';
|
||||
import bind from 'bind-decorator';
|
||||
import Device from '../model/device';
|
||||
@@ -114,8 +114,7 @@ export default class Configure extends Extension {
|
||||
|
||||
logger.info(`Configuring '${device.name}'`);
|
||||
try {
|
||||
await device.definition.configure(device.zh, this.zigbee.firstCoordinatorEndpoint(), logger,
|
||||
device.options);
|
||||
await device.definition.configure(device.zh, this.zigbee.firstCoordinatorEndpoint(), logger);
|
||||
logger.info(`Successfully configured '${device.name}'`);
|
||||
device.zh.meta.configured = zhc.getConfigureKey(device.definition);
|
||||
device.zh.save();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import zhc from 'zigbee-herdsman-converters';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
import * as settings from '../util/settings';
|
||||
import utils from '../util/utils';
|
||||
import Extension from './extension';
|
||||
@@ -12,7 +12,7 @@ export default class ExternalConverters extends Extension {
|
||||
for (const definition of utils.getExternalConvertersDefinitions(settings.get())) {
|
||||
const toAdd = {...definition};
|
||||
delete toAdd['homeassistant'];
|
||||
zhc.addDeviceDefinition(toAdd);
|
||||
zhc.addDefinition(toAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ import bind from 'bind-decorator';
|
||||
import Extension from './extension';
|
||||
import Device from '../model/device';
|
||||
import Group from '../model/group';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
const topicRegex =
|
||||
new RegExp(`^${settings.get().mqtt.base_topic}/bridge/request/group/members/(remove|add|remove_all)$`);
|
||||
const legacyTopicRegex = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/group/(.+)/(remove|add|remove_all)$`);
|
||||
const legacyTopicRegexRemoveAll = new RegExp(`^${settings.get().mqtt.base_topic}/bridge/group/remove_all$`);
|
||||
|
||||
const stateProperties: {[s: string]: (value: string, exposes: zhc.DefinitionExpose[]) => boolean} = {
|
||||
const stateProperties: {[s: string]: (value: string, exposes: zhc.Expose[]) => boolean} = {
|
||||
'state': () => true,
|
||||
'brightness': (value, exposes) =>
|
||||
!!exposes.find((e) => e.type === 'light' && e.features.find((f) => f.name === 'brightness')),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as settings from '../util/settings';
|
||||
import logger from '../util/logger';
|
||||
import utils from '../util/utils';
|
||||
import utils, {isNumericExposeFeature, isBinaryExposeFeature, isEnumExposeFeature} from '../util/utils';
|
||||
import stringify from 'json-stable-stringify-without-jsonify';
|
||||
import assert from 'assert';
|
||||
import Extension from './extension';
|
||||
import bind from 'bind-decorator';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
interface MockProperty {property: string, value: KeyValue | string}
|
||||
|
||||
@@ -52,7 +53,7 @@ const legacyMapping = [
|
||||
},
|
||||
];
|
||||
|
||||
const featurePropertyWithoutEndpoint = (feature: zhc.DefinitionExposeFeature): string => {
|
||||
const featurePropertyWithoutEndpoint = (feature: zhc.Feature): string => {
|
||||
if (feature.endpoint) {
|
||||
return feature.property.slice(0, -1 + -1 * feature.endpoint.length);
|
||||
} else {
|
||||
@@ -115,8 +116,8 @@ export default class HomeAssistant extends Extension {
|
||||
this.eventBus.emitPublishAvailability();
|
||||
}
|
||||
|
||||
private exposeToConfig(exposes: zhc.DefinitionExpose[], entityType: 'device' | 'group',
|
||||
allExposes: zhc.DefinitionExpose[], definition?: zhc.Definition): DiscoveryEntry[] {
|
||||
private exposeToConfig(exposes: zhc.Expose[], entityType: 'device' | 'group',
|
||||
allExposes: zhc.Expose[], definition?: zhc.Definition): DiscoveryEntry[] {
|
||||
// For groups an array of exposes (of the same type) is passed, this is to determine e.g. what features
|
||||
// to use for a bulb (e.g. color_xy/color_temp)
|
||||
assert(entityType === 'group' || exposes.length === 1, 'Multiple exposes for device not allowed');
|
||||
@@ -126,7 +127,7 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
const discoveryEntries: DiscoveryEntry[] = [];
|
||||
const endpoint = entityType === 'device' ? exposes[0].endpoint : undefined;
|
||||
const getProperty = (feature: zhc.DefinitionExposeFeature): string => entityType === 'group' ?
|
||||
const getProperty = (feature: zhc.Feature): string => entityType === 'group' ?
|
||||
featurePropertyWithoutEndpoint(feature) : feature.property;
|
||||
|
||||
/* istanbul ignore else */
|
||||
@@ -169,8 +170,8 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
|
||||
if (hasColorTemp) {
|
||||
const colorTemps = exposes.map((expose) => expose.features.find((e) => e.name === 'color_temp'))
|
||||
.filter((e) => e);
|
||||
const colorTemps = exposes.map((expose) => expose.features
|
||||
.filter(isNumericExposeFeature).find((e) => e.name === 'color_temp'));
|
||||
const max = Math.min(...colorTemps.map((e) => e.value_max));
|
||||
const min = Math.max(...colorTemps.map((e) => e.value_min));
|
||||
discoveryEntry.discovery_payload.max_mireds = max;
|
||||
@@ -178,7 +179,7 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
|
||||
const effects = utils.arrayUnique(utils.flatten(
|
||||
allExposes.filter((e) => e.type === 'enum' && e.name === 'effect').map((e) => e.values)));
|
||||
allExposes.filter(isEnumExposeFeature).filter((e) => e.name === 'effect').map((e) => e.values)));
|
||||
if (effects.length) {
|
||||
discoveryEntry.discovery_payload.effect = true;
|
||||
discoveryEntry.discovery_payload.effect_list = effects;
|
||||
@@ -186,7 +187,7 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else if (firstExpose.type === 'switch') {
|
||||
const state = firstExpose.features.find((f) => f.name === 'state');
|
||||
const state = firstExpose.features.filter(isBinaryExposeFeature).find((f) => f.name === 'state');
|
||||
const property = getProperty(state);
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'switch',
|
||||
@@ -218,7 +219,8 @@ export default class HomeAssistant extends Extension {
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else if (firstExpose.type === 'climate') {
|
||||
const setpointProperties = ['occupied_heating_setpoint', 'current_heating_setpoint'];
|
||||
const setpoint = firstExpose.features.find((f) => setpointProperties.includes(f.name));
|
||||
const setpoint = firstExpose.features.filter(isNumericExposeFeature)
|
||||
.find((f) => setpointProperties.includes(f.name));
|
||||
assert(setpoint, 'No setpoint found');
|
||||
const temperature = firstExpose.features.find((f) => f.name === 'local_temperature');
|
||||
assert(temperature, 'No temperature found');
|
||||
@@ -243,7 +245,7 @@ export default class HomeAssistant extends Extension {
|
||||
},
|
||||
};
|
||||
|
||||
const mode = firstExpose.features.find((f) => f.name === 'system_mode');
|
||||
const mode = firstExpose.features.filter(isEnumExposeFeature).find((f) => f.name === 'system_mode');
|
||||
if (mode) {
|
||||
if (mode.values.includes('sleep')) {
|
||||
// 'sleep' is not supported by Home Assistant, but is valid according to ZCL
|
||||
@@ -283,7 +285,8 @@ export default class HomeAssistant extends Extension {
|
||||
discoveryEntry.discovery_payload.temperature_state_topic = true;
|
||||
}
|
||||
|
||||
const fanMode = firstExpose.features.find((f) => f.name === 'fan_mode');
|
||||
const fanMode = firstExpose.features.filter(isEnumExposeFeature)
|
||||
.find((f) => f.name === 'fan_mode');
|
||||
if (fanMode) {
|
||||
discoveryEntry.discovery_payload.fan_modes = fanMode.values;
|
||||
discoveryEntry.discovery_payload.fan_mode_command_topic = true;
|
||||
@@ -292,7 +295,8 @@ export default class HomeAssistant extends Extension {
|
||||
discoveryEntry.discovery_payload.fan_mode_state_topic = true;
|
||||
}
|
||||
|
||||
const swingMode = firstExpose.features.find((f) => f.name === 'swing_mode');
|
||||
const swingMode = firstExpose.features.filter(isEnumExposeFeature)
|
||||
.find((f) => f.name === 'swing_mode');
|
||||
if (swingMode) {
|
||||
discoveryEntry.discovery_payload.swing_modes = swingMode.values;
|
||||
discoveryEntry.discovery_payload.swing_mode_command_topic = true;
|
||||
@@ -301,7 +305,7 @@ export default class HomeAssistant extends Extension {
|
||||
discoveryEntry.discovery_payload.swing_mode_state_topic = true;
|
||||
}
|
||||
|
||||
const preset = firstExpose.features.find((f) => f.name === 'preset');
|
||||
const preset = firstExpose.features.filter(isEnumExposeFeature).find((f) => f.name === 'preset');
|
||||
if (preset) {
|
||||
discoveryEntry.discovery_payload.preset_modes = preset.values;
|
||||
discoveryEntry.discovery_payload.preset_mode_command_topic = 'preset';
|
||||
@@ -310,7 +314,8 @@ export default class HomeAssistant extends Extension {
|
||||
discoveryEntry.discovery_payload.preset_mode_state_topic = true;
|
||||
}
|
||||
|
||||
const tempCalibration = firstExpose.features.find((f) => f.name === 'local_temperature_calibration');
|
||||
const tempCalibration = firstExpose.features.filter(isNumericExposeFeature)
|
||||
.find((f) => f.name === 'local_temperature_calibration');
|
||||
if (tempCalibration) {
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'number',
|
||||
@@ -337,7 +342,8 @@ export default class HomeAssistant extends Extension {
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
}
|
||||
|
||||
const piHeatingDemand = firstExpose.features.find((f) => f.name === 'pi_heating_demand');
|
||||
const piHeatingDemand = firstExpose.features.filter(isNumericExposeFeature)
|
||||
.find((f) => f.name === 'pi_heating_demand');
|
||||
if (piHeatingDemand) {
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'sensor',
|
||||
@@ -358,7 +364,7 @@ export default class HomeAssistant extends Extension {
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else if (firstExpose.type === 'lock') {
|
||||
assert(!endpoint, `Endpoint not supported for lock type`);
|
||||
const state = firstExpose.features.find((f) => f.name === 'state');
|
||||
const state = firstExpose.features.filter(isBinaryExposeFeature).find((f) => f.name === 'state');
|
||||
assert(state, 'No state found');
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'lock',
|
||||
@@ -404,8 +410,8 @@ export default class HomeAssistant extends Extension {
|
||||
?.features.find((f) => f.name === 'position');
|
||||
const tilt = exposes.find((expose) => expose.features.find((e) => e.name === 'tilt'))
|
||||
?.features.find((f) => f.name === 'tilt');
|
||||
const motorState = allExposes?.find((e) => e.type === 'enum' &&
|
||||
['motor_state', 'moving'].includes(e.name) && e.access === ACCESS_STATE);
|
||||
const motorState = allExposes?.filter(isEnumExposeFeature)
|
||||
.find((e) => ['motor_state', 'moving'].includes(e.name) && e.access === ACCESS_STATE);
|
||||
const running = allExposes?.find((e) => e.type === 'binary' && e.name === 'running');
|
||||
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
@@ -436,9 +442,9 @@ export default class HomeAssistant extends Extension {
|
||||
const closingLookup = ['closing', 'close', 'backward', 'back', 'reverse', 'down', 'declining'];
|
||||
const stoppedLookup = ['stopped', 'stop', 'pause', 'paused'];
|
||||
|
||||
const openingState = motorState.values.find((s) => openingLookup.includes(s.toLowerCase()));
|
||||
const closingState = motorState.values.find((s) => closingLookup.includes(s.toLowerCase()));
|
||||
const stoppedState = motorState.values.find((s) => stoppedLookup.includes(s.toLowerCase()));
|
||||
const openingState = motorState.values.find((s) => openingLookup.includes(s.toString().toLowerCase()));
|
||||
const closingState = motorState.values.find((s) => closingLookup.includes(s.toString().toLowerCase()));
|
||||
const stoppedState = motorState.values.find((s) => stoppedLookup.includes(s.toString().toLowerCase()));
|
||||
|
||||
if (openingState && closingState && stoppedState) {
|
||||
discoveryEntry.discovery_payload.state_opening = openingState;
|
||||
@@ -496,7 +502,7 @@ export default class HomeAssistant extends Extension {
|
||||
},
|
||||
};
|
||||
|
||||
const speed = firstExpose.features.find((e) => e.name === 'mode');
|
||||
const speed = firstExpose.features.filter(isEnumExposeFeature).find((e) => e.name === 'mode');
|
||||
if (speed) {
|
||||
// A fan entity in Home Assistant 2021.3 and above may have a speed,
|
||||
// controlled by a percentage from 1 to 100, and/or non-speed presets.
|
||||
@@ -524,7 +530,7 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
|
||||
const allowed = [...speeds, ...presets];
|
||||
speed.values.forEach((s) => assert(allowed.includes(s)));
|
||||
speed.values.forEach((s) => assert(allowed.includes(s.toString())));
|
||||
const percentValues = speeds.map((s, i) => `'${s}':${i}`).join(', ');
|
||||
const percentCommands = speeds.map((s, i) => `${i}:'${s}'`).join(', ');
|
||||
const presetList = presets.map((s) => `'${s}'`).join(', ');
|
||||
@@ -547,7 +553,7 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else if (firstExpose.type === 'binary') {
|
||||
} else if (isBinaryExposeFeature(firstExpose)) {
|
||||
const lookup: {[s: string]: KeyValue}= {
|
||||
battery_low: {entity_category: 'diagnostic', device_class: 'battery'},
|
||||
button_lock: {entity_category: 'config', icon: 'mdi:lock'},
|
||||
@@ -636,7 +642,7 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
}
|
||||
} else if (firstExpose.type === 'numeric') {
|
||||
} else if (isNumericExposeFeature(firstExpose)) {
|
||||
const lookup: {[s: string]: KeyValue} = {
|
||||
ac_frequency: {device_class: 'frequency', enabled_by_default: false, entity_category: 'diagnostic',
|
||||
state_class: 'measurement'},
|
||||
@@ -847,7 +853,7 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
}
|
||||
} else if (firstExpose.type === 'enum') {
|
||||
} else if (isEnumExposeFeature(firstExpose)) {
|
||||
const lookup: {[s: string]: KeyValue} = {
|
||||
action: {icon: 'mdi:gesture-double-tap'},
|
||||
alarm_humidity: {entity_category: 'config', icon: 'mdi:water-percent-alert'},
|
||||
@@ -1107,8 +1113,8 @@ export default class HomeAssistant extends Extension {
|
||||
configs.push(entity.definition.homeassistant);
|
||||
}
|
||||
} else { // group
|
||||
const exposesByType: {[s: string]: zhc.DefinitionExpose[]} = {};
|
||||
const allExposes: zhc.DefinitionExpose[] = [];
|
||||
const exposesByType: {[s: string]: zhc.Expose[]} = {};
|
||||
const allExposes: zhc.Expose[] = [];
|
||||
|
||||
entity.zh.members.map((e) => this.zigbee.resolveEntity(e.getDevice()) as Device)
|
||||
.filter((d) => d.definition).forEach((device) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as settings from '../../util/settings';
|
||||
import logger from '../../util/logger';
|
||||
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
import utils from '../../util/utils';
|
||||
import assert from 'assert';
|
||||
import Extension from '../extension';
|
||||
@@ -140,7 +140,7 @@ export default class BridgeLegacy extends Extension {
|
||||
};
|
||||
|
||||
if (device.zh.type !== 'Coordinator') {
|
||||
const definition = zigbeeHerdsmanConverters.findByDevice(device.zh);
|
||||
const definition = zhc.findByDevice(device.zh);
|
||||
payload.model = definition ? definition.model : device.zh.modelID;
|
||||
payload.vendor = definition ? definition.vendor : '-';
|
||||
payload.description = definition ? definition.description : '-';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
import logger from '../../util/logger';
|
||||
import * as settings from '../../util/settings';
|
||||
import Extension from '../extension';
|
||||
@@ -7,14 +7,14 @@ const defaultConfiguration = {
|
||||
minimumReportInterval: 3, maximumReportInterval: 300, reportableChange: 1,
|
||||
};
|
||||
|
||||
const ZNLDP12LM = zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'ZNLDP12LM');
|
||||
const ZNLDP12LM = zhc.definitions.find((d) => d.model === 'ZNLDP12LM');
|
||||
|
||||
const devicesNotSupportingReporting = [
|
||||
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'CC2530.ROUTER'),
|
||||
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'BASICZBR3'),
|
||||
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'ZM-CSW032-D'),
|
||||
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'TS0001'),
|
||||
zigbeeHerdsmanConverters.definitions.find((d) => d.model === 'TS0115'),
|
||||
zhc.definitions.find((d) => d.model === 'CC2530.ROUTER'),
|
||||
zhc.definitions.find((d) => d.model === 'BASICZBR3'),
|
||||
zhc.definitions.find((d) => d.model === 'ZM-CSW032-D'),
|
||||
zhc.definitions.find((d) => d.model === 'TS0001'),
|
||||
zhc.definitions.find((d) => d.model === 'TS0115'),
|
||||
];
|
||||
|
||||
const reportKey = 1;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import zhc from 'zigbee-herdsman-converters';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
import Extension from './extension';
|
||||
|
||||
/**
|
||||
@@ -39,12 +39,13 @@ export default class OnEvent extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
private async callOnEvent(device: Device, type: string, data: KeyValue): Promise<void> {
|
||||
private async callOnEvent(device: Device, type: zhc.OnEventType, data: KeyValue): Promise<void> {
|
||||
const state = this.state.get(device);
|
||||
zhc.onEvent(type, data, device.zh, device.options, state);
|
||||
zhc.onEvent(type, data, device.zh);
|
||||
|
||||
if (device.definition?.onEvent) {
|
||||
await device.definition.onEvent(type, data, device.zh, device.options, state);
|
||||
const options: KeyValue = device.options;
|
||||
await device.definition.onEvent(type, data, device.zh, options, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,13 @@ import * as settings from '../util/settings';
|
||||
import logger from '../util/logger';
|
||||
import stringify from 'json-stable-stringify-without-jsonify';
|
||||
import utils from '../util/utils';
|
||||
import * as tradfriOTA from 'zigbee-herdsman-converters/lib/ota/tradfri';
|
||||
import * as zigbeeOTA from 'zigbee-herdsman-converters/lib/ota/zigbeeOTA';
|
||||
import Extension from './extension';
|
||||
import bind from 'bind-decorator';
|
||||
import Device from '../model/device';
|
||||
import dataDir from '../util/data';
|
||||
import * as URI from 'uri-js';
|
||||
import path from 'path';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
function isValidUrl(url: string): boolean {
|
||||
let parsed;
|
||||
@@ -45,7 +44,7 @@ export default class OTAUpdate extends Extension {
|
||||
this.eventBus.onMQTTMessage(this, this.onMQTTMessage);
|
||||
this.eventBus.onDeviceMessage(this, this.onZigbeeEvent);
|
||||
if (settings.get().ota.ikea_ota_use_test_url) {
|
||||
tradfriOTA.useTestURL();
|
||||
zhc.ota.tradfri.useTestURL();
|
||||
}
|
||||
|
||||
// Let zigbeeOTA module know if the override index file is provided
|
||||
@@ -56,11 +55,11 @@ export default class OTAUpdate extends Extension {
|
||||
overrideOTAIndex = dataDir.joinPath(overrideOTAIndex);
|
||||
}
|
||||
|
||||
zigbeeOTA.useIndexOverride(overrideOTAIndex);
|
||||
zhc.ota.zigbeeOTA.useIndexOverride(overrideOTAIndex);
|
||||
}
|
||||
|
||||
// In order to support local firmware files we need to let zigbeeOTA know where the data directory is
|
||||
zigbeeOTA.setDataDir(dataDir.getPath());
|
||||
zhc.ota.zigbeeOTA.setDataDir(dataDir.getPath());
|
||||
|
||||
// In case Zigbee2MQTT is restared during an update, progress and remaining values are still in state.
|
||||
// remove them.
|
||||
@@ -93,7 +92,9 @@ export default class OTAUpdate extends Extension {
|
||||
this.lastChecked[data.device.ieeeAddr] = Date.now();
|
||||
let availableResult: zhc.OtaUpdateAvailableResult = null;
|
||||
try {
|
||||
availableResult = await data.device.definition.ota.isUpdateAvailable(data.device.zh, logger, data.data);
|
||||
// @ts-expect-error typing guaranteed by data.type
|
||||
const dataData: zhc.ota.ImageInfo = data.data;
|
||||
availableResult = await data.device.definition.ota.isUpdateAvailable(data.device.zh, logger, dataData);
|
||||
} catch (e) {
|
||||
supportsOTA = false;
|
||||
logger.debug(`Failed to check if update available for '${data.device.name}' (${e.message})`);
|
||||
@@ -201,7 +202,7 @@ export default class OTAUpdate extends Extension {
|
||||
}
|
||||
|
||||
try {
|
||||
const availableResult = await device.definition.ota.isUpdateAvailable(device.zh, logger);
|
||||
const availableResult = await device.definition.ota.isUpdateAvailable(device.zh, logger, null);
|
||||
const msg = `${availableResult.available ? 'Update' : 'No update'} available for '${device.name}'`;
|
||||
logger.info(msg);
|
||||
|
||||
|
||||
+23
-22
@@ -1,6 +1,6 @@
|
||||
|
||||
import * as settings from '../util/settings';
|
||||
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
import * as philips from 'zigbee-herdsman-converters/lib/philips';
|
||||
import logger from '../util/logger';
|
||||
import utils from '../util/utils';
|
||||
@@ -17,19 +17,19 @@ const sceneConverterKeys = ['scene_store', 'scene_add', 'scene_remove', 'scene_r
|
||||
|
||||
// Legacy: don't provide default converters anymore, this is required by older z2m installs not saving group members
|
||||
const defaultGroupConverters = [
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.light_onoff_brightness,
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.light_color_colortemp,
|
||||
zhc.toZigbee.light_onoff_brightness,
|
||||
zhc.toZigbee.light_color_colortemp,
|
||||
philips.tz.effect, // Support Hue effects for groups
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.ignore_transition,
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.cover_position_tilt,
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.thermostat_occupied_heating_setpoint,
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.tint_scene,
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_move,
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.light_brightness_step,
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_step,
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.light_colortemp_move,
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.light_hue_saturation_move,
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.light_hue_saturation_step,
|
||||
zhc.toZigbee.ignore_transition,
|
||||
zhc.toZigbee.cover_position_tilt,
|
||||
zhc.toZigbee.thermostat_occupied_heating_setpoint,
|
||||
zhc.toZigbee.tint_scene,
|
||||
zhc.toZigbee.light_brightness_move,
|
||||
zhc.toZigbee.light_brightness_step,
|
||||
zhc.toZigbee.light_colortemp_step,
|
||||
zhc.toZigbee.light_colortemp_move,
|
||||
zhc.toZigbee.light_hue_saturation_move,
|
||||
zhc.toZigbee.light_hue_saturation_step,
|
||||
];
|
||||
|
||||
interface ParsedTopic {ID: string, endpoint: string, attribute: string, type: 'get' | 'set'}
|
||||
@@ -81,8 +81,8 @@ export default class Publish extends Extension {
|
||||
}
|
||||
}
|
||||
|
||||
legacyRetrieveState(re: Device | Group, converter: zhc.ToZigbeeConverter, result: zhc.ToZigbeeConverterResult,
|
||||
target: zh.Endpoint | zh.Group, key: string, meta: zhc.ToZigbeeConverterGetMeta): void {
|
||||
legacyRetrieveState(re: Device | Group, converter: zhc.Tz.Converter, result: zhc.Tz.ConvertSetResult,
|
||||
target: zh.Endpoint | zh.Group, key: string, meta: zhc.Tz.Meta): void {
|
||||
// It's possible for devices to get out of sync when writing an attribute that's not reportable.
|
||||
// So here we re-read the value after a specified timeout, this timeout could for example be the
|
||||
// transition time of a color change or for forcing a state read for devices that don't
|
||||
@@ -140,18 +140,16 @@ export default class Publish extends Extension {
|
||||
const membersState = re instanceof Group ?
|
||||
Object.fromEntries(re.zh.members.map((e) => [e.getDevice().ieeeAddr,
|
||||
this.state.get(this.zigbee.resolveEntity(e.getDevice().ieeeAddr))])) : null;
|
||||
let converters: zhc.ToZigbeeConverter[];
|
||||
let converters: zhc.Tz.Converter[];
|
||||
{
|
||||
if (Array.isArray(definition)) {
|
||||
const c = new Set(definition.map((d) => d.toZigbee).flat());
|
||||
// @ts-expect-error
|
||||
if (c.size == 0) converters = defaultGroupConverters;
|
||||
else converters = Array.from(c);
|
||||
} else if (definition) {
|
||||
converters = definition.toZigbee;
|
||||
} else {
|
||||
converters = [zigbeeHerdsmanConverters.toZigbeeConverters.read,
|
||||
zigbeeHerdsmanConverters.toZigbeeConverters.write];
|
||||
converters = [zhc.toZigbee.read, zhc.toZigbee.write];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +175,7 @@ export default class Publish extends Extension {
|
||||
entries.sort((a) => (['state', 'brightness', 'brightness_percent'].includes(a[0]) ? sorter : sorter * -1));
|
||||
|
||||
// For each attribute call the corresponding converter
|
||||
const usedConverters: {[s: number]: zhc.ToZigbeeConverter[]} = {};
|
||||
const usedConverters: {[s: number]: zhc.Tz.Converter[]} = {};
|
||||
const toPublish: {[s: number | string]: KeyValue} = {};
|
||||
const toPublishEntity: {[s: number | string]: Device | Group} = {};
|
||||
const addToToPublish = (entity: Device | Group, payload: KeyValue): void => {
|
||||
@@ -228,8 +226,11 @@ export default class Publish extends Extension {
|
||||
}
|
||||
|
||||
// Converter didn't return a result, skip
|
||||
const meta = {endpoint_name: endpointName, options: entitySettings, message: {...message}, logger, device,
|
||||
state: entityState, membersState, mapped: definition};
|
||||
const entitySettingsKeyValue: KeyValue = entitySettings;
|
||||
const meta = {
|
||||
endpoint_name: endpointName, options: entitySettingsKeyValue,
|
||||
message: {...message}, logger, device, state: entityState, membersState, mapped: definition,
|
||||
};
|
||||
|
||||
// Strip endpoint name from meta.message properties.
|
||||
if (endpointName) {
|
||||
|
||||
@@ -147,8 +147,10 @@ export default class Receive extends Extension {
|
||||
let payload: KeyValue = {};
|
||||
for (const converter of converters) {
|
||||
try {
|
||||
const convertData = {...data, device: data.device.zh};
|
||||
const options: KeyValue = data.device.options;
|
||||
const converted = await converter.convert(
|
||||
data.device.definition, data, publish, data.device.options, meta);
|
||||
data.device.definition, convertData, publish, options, meta);
|
||||
if (converted) {
|
||||
payload = {...payload, ...converted};
|
||||
}
|
||||
|
||||
+5
-4
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable brace-style */
|
||||
import * as settings from '../util/settings';
|
||||
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
export default class Device {
|
||||
public zh: zh.Device;
|
||||
@@ -17,7 +17,7 @@ export default class Device {
|
||||
// Some devices can change modelID, reconsider the definition in that case.
|
||||
// https://github.com/Koenkk/zigbee-herdsman-converters/issues/3016
|
||||
if (!this.zh.interviewing && (!this._definition || this._definitionModelID !== this.zh.modelID)) {
|
||||
this._definition = zigbeeHerdsmanConverters.findByDevice(this.zh);
|
||||
this._definition = zhc.findByDevice(this.zh);
|
||||
this._definitionModelID = this.zh.modelID;
|
||||
}
|
||||
return this._definition;
|
||||
@@ -27,10 +27,11 @@ export default class Device {
|
||||
this.zh = device;
|
||||
}
|
||||
|
||||
exposes(): zhc.DefinitionExpose[] {
|
||||
exposes(): zhc.Expose[] {
|
||||
/* istanbul ignore if */
|
||||
if (typeof this.definition.exposes == 'function') {
|
||||
return this.definition.exposes(this.zh, this.options);
|
||||
const options: KeyValue = this.options;
|
||||
return this.definition.exposes(this.zh, options);
|
||||
} else {
|
||||
return this.definition.exposes;
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable brace-style */
|
||||
import * as settings from '../util/settings';
|
||||
import zigbeeHerdsmanConverters from 'zigbee-herdsman-converters';
|
||||
import * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
export default class Group {
|
||||
public zh: zh.Group;
|
||||
@@ -25,7 +25,7 @@ export default class Group {
|
||||
|
||||
membersDefinitions(): zhc.Definition[] {
|
||||
return this.zh.members.map((m) =>
|
||||
zigbeeHerdsmanConverters.findByDevice(m.getDevice())).filter((d) => d) as zhc.Definition[];
|
||||
zhc.findByDevice(m.getDevice())).filter((d) => d) as zhc.Definition[];
|
||||
}
|
||||
|
||||
isDevice(): this is Device {return false;}
|
||||
|
||||
Vendored
+4
-67
@@ -21,6 +21,8 @@ import type {
|
||||
FrameControl as ZHFrameControl,
|
||||
} from 'zigbee-herdsman/dist/zcl';
|
||||
|
||||
import type * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
import type * as ZHEvents from 'zigbee-herdsman/dist/controller/events';
|
||||
|
||||
import type TypeEventBus from 'lib/eventBus';
|
||||
@@ -44,6 +46,8 @@ declare global {
|
||||
type Extension = TypeExtension;
|
||||
|
||||
// Types
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type ExternalDefinition = zhc.Definition & {homeassistant: any};
|
||||
interface MQTTResponse {data: KeyValue, status: 'error' | 'ok', error?: string, transaction?: string}
|
||||
interface MQTTOptions {qos?: QoS, retain?: boolean, properties?: {messageExpiryInterval: number}}
|
||||
type Scene = {id: number, name: string};
|
||||
@@ -71,73 +75,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
// zigbee-herdsman-converters
|
||||
namespace zhc {
|
||||
interface Logger {
|
||||
info: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string) => void;
|
||||
debug: (message: string) => void;
|
||||
}
|
||||
|
||||
interface ToZigbeeConverterGetMeta {message?: KeyValue, mapped?: Definition | Definition[]}
|
||||
|
||||
interface ToZigbeeConverterResult {state: KeyValue,
|
||||
membersState: {[s: string]: KeyValue}, readAfterWriteTime?: number}
|
||||
|
||||
interface ToZigbeeConverter {
|
||||
key: string[],
|
||||
convertGet?: (entity: zh.Endpoint | zh.Group, key: string, meta: ToZigbeeConverterGetMeta) => Promise<void>
|
||||
convertSet?: (entity: zh.Endpoint | zh.Group, key: string, value: KeyValue | string | number,
|
||||
meta: {state: KeyValue}) => Promise<ToZigbeeConverterResult>
|
||||
}
|
||||
|
||||
interface FromZigbeeConverter {
|
||||
cluster: string,
|
||||
type: string[] | string,
|
||||
convert: (model: Definition, msg: KeyValue, publish: (payload: KeyValue) => void, options: KeyValue,
|
||||
meta: {state: KeyValue, logger: Logger, device: zh.Device}) => Promise<KeyValue>,
|
||||
}
|
||||
|
||||
interface DefinitionExposeFeature {name: string, label: string, endpoint?: string,
|
||||
property: string, value_max?: number, value_min?: number, unit?: string,
|
||||
value_off?: string, value_on?: string, value_step?: number, values: string[], access: number}
|
||||
|
||||
interface DefinitionExpose {
|
||||
type: string, name?: string, label?: string, features?: DefinitionExposeFeature[],
|
||||
endpoint?: string, values?: string[], value_off?: string, value_on?: string, value_step?: number,
|
||||
access: number, property: string, unit?: string,
|
||||
value_min?: number, value_max?: number}
|
||||
|
||||
interface OtaUpdateAvailableResult {available: boolean, currentFileVersion: number, otaFileVersion: number}
|
||||
|
||||
interface Definition {
|
||||
model: string,
|
||||
zigbeeModel: string[],
|
||||
endpoint?: (device: zh.Device) => {[s: string]: number}
|
||||
toZigbee: ToZigbeeConverter[]
|
||||
fromZigbee: FromZigbeeConverter[]
|
||||
icon?: string
|
||||
description: string
|
||||
options: zhc.DefinitionExpose[],
|
||||
vendor: string
|
||||
exposes: DefinitionExpose[] | ((device: zh.Device, options: KeyValue) => DefinitionExpose[])
|
||||
configure?: (device: zh.Device, coordinatorEndpoint: zh.Endpoint, logger: Logger,
|
||||
options?: DeviceOptions) => Promise<void>;
|
||||
onEvent?: (type: string, data: KeyValue, device: zh.Device,
|
||||
settings: KeyValue, state: KeyValue) => Promise<void>;
|
||||
ota?: {
|
||||
isUpdateAvailable: (device: zh.Device, logger: Logger, data?: KeyValue)
|
||||
=> Promise<OtaUpdateAvailableResult>;
|
||||
updateToLatest: (device: zh.Device, logger: Logger,
|
||||
onProgress: (progress: number, remaining: number) => void) => Promise<number>;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type ExternalDefinition = Definition & {homeassistant: any};
|
||||
}
|
||||
|
||||
namespace eventdata {
|
||||
type EntityRenamed = { entity: Device | Group, homeAssisantRename: boolean, from: string, to: string };
|
||||
type DeviceRemoved = { ieeeAddr: string, name: string };
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
declare module 'zigbee-herdsman-converters' {
|
||||
export function findByDevice(device: zh.Device): zhc.Definition;
|
||||
export function getConfigureKey(definition: zhc.Definition): string | number;
|
||||
export const toZigbeeConverters: {[s: string]: zhc.ToZigbeeConverter};
|
||||
export const definitions: zhc.Definition[];
|
||||
export function addDeviceDefinition(definition: zhc.Definition): Promise<void>;
|
||||
export function onEvent(type: string, data: KeyValue, device: zh.Device, settings: KeyValue,
|
||||
state: KeyValue): Promise<void>;
|
||||
}
|
||||
+14
-1
@@ -6,6 +6,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import {detailedDiff} from 'deep-object-diff';
|
||||
import objectAssignDeep from 'object-assign-deep';
|
||||
import type * as zhc from 'zigbee-herdsman-converters';
|
||||
|
||||
// construct a local ISO8601 string (instead of UTC-based)
|
||||
// Example:
|
||||
@@ -161,7 +162,7 @@ function loadModuleFromFile(modulePath: string): unknown {
|
||||
return loadModuleFromText(moduleCode);
|
||||
}
|
||||
|
||||
function* getExternalConvertersDefinitions(settings: Settings): Generator<zhc.ExternalDefinition> {
|
||||
function* getExternalConvertersDefinitions(settings: Settings): Generator<ExternalDefinition> {
|
||||
const externalConverters = settings.external_converters;
|
||||
|
||||
for (const moduleName of externalConverters) {
|
||||
@@ -371,6 +372,18 @@ function clone(obj: KeyValue): KeyValue {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
export function isNumericExposeFeature(feature: zhc.Feature): feature is zhc.Numeric {
|
||||
return feature?.type === 'numeric';
|
||||
}
|
||||
|
||||
export function isEnumExposeFeature(feature: zhc.Feature): feature is zhc.Enum {
|
||||
return feature?.type === 'enum';
|
||||
}
|
||||
|
||||
export function isBinaryExposeFeature(feature: zhc.Feature): feature is zhc.Binary {
|
||||
return feature?.type === 'binary';
|
||||
}
|
||||
|
||||
function computeSettingsToChange(current: KeyValue, new_: KeyValue): KeyValue {
|
||||
const diff: KeyValue = detailedDiff(current, new_);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user