From 6e2e94da557a3acb760b0b4e1fdbf15f13195e97 Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Sun, 29 Dec 2024 17:01:07 +0100 Subject: [PATCH 01/19] Update devices.js --- lib/devices.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/devices.js b/lib/devices.js index f0b0cd6a..0bf84130 100644 --- a/lib/devices.js +++ b/lib/devices.js @@ -656,9 +656,12 @@ const devices = [ models: ['WXKG02LM', 'WXKG02LM_rev2'], icon: 'img/86sw2.png', states: [ - states.left_click, states.right_click, states.both_click, - states.left_click_long, states.left_click_double, states.right_click_long, states.right_click_double, - states.both_click_long, states.both_click_double, states.voltage, states.battery +// states.left_click, states.right_click, states.both_click, +// states.left_click_long, states.left_click_double, states.right_click_long, states.right_click_double, +// states.both_click_long, states.both_click_double, states.voltage, states.battery + states.lumi_left_click, states.lumi_right_click, states.lumi_both_click, + states.lumi_left_click_long, states.lumi_right_click_long, states.lumi_left_click_double, states.lumi_right_click_double, + states.lumi_both_click_long, states.lumi_both_click_double, states.voltage, states.battery ], }, { From 9d2260ce15ce1fdc098bdc486d41585bac32cc92 Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Tue, 31 Dec 2024 18:43:41 +0100 Subject: [PATCH 02/19] Bump version to 1.11.0 --- package-lock.json | 8 ++++---- package.json | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d37b2a4..f6e38ee4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "iobroker.zigbee", - "version": "1.10.13", + "version": "1.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "iobroker.zigbee", - "version": "1.10.13", + "version": "1.11.0", "license": "MIT", "dependencies": { "@iobroker/adapter-core": "^3.2.2", @@ -16,8 +16,8 @@ "tar": "^7.4.3", "typescript": "^5.6.3", "uri-js": "^4.4.1", - "zigbee-herdsman": "2.1.4", - "zigbee-herdsman-converters": "21.9.2" + "zigbee-herdsman": "3.2.0", + "zigbee-herdsman-converters": "21.11.0" }, "devDependencies": { "@alcalzone/release-script": "^3.8.0", diff --git a/package.json b/package.json index 5ba78191..6ccccd7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iobroker.zigbee", - "version": "1.10.14", + "version": "1.11.0", "author": { "name": "Kirov Ilya", "email": "kirovilya@gmail.com" @@ -28,8 +28,8 @@ "ajv": "^8.17.1", "uri-js": "^4.4.1", "typescript": "^5.6.3", - "zigbee-herdsman": "2.1.9", - "zigbee-herdsman-converters": "21.9.2" + "zigbee-herdsman": "3.2.0", + "zigbee-herdsman-converters": "21.11.0" }, "description": "Zigbee devices", "devDependencies": { From b72b302dcf28521ceb9c7661492004325564956d Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:05:01 +0100 Subject: [PATCH 03/19] Test fix _release --- lib/statescontroller.js | 94 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 11 deletions(-) diff --git a/lib/statescontroller.js b/lib/statescontroller.js index e16c75db..4c73344c 100644 --- a/lib/statescontroller.js +++ b/lib/statescontroller.js @@ -598,13 +598,12 @@ class StatesController extends EventEmitter { async publishToState(devId, model, payload) { const devStates = await this.getDevStates(`0x${devId}`, model); - let has_debug = false; - if (this.checkDebugDevice(devId)) + + const has_elevated_debug = (this.checkDebugDevice(devId) && !payload.hasOwnProperty('msg_from_zigbee')); + + if (has_elevated_debug) { - if (!payload.hasOwnProperty('msg_from_zigbee')) { - this.warn(`ELEVATED I1: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`); - has_debug = true; - } + this.warn(`ELEVATED I1: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`); } if (!devStates) { if (has_debug) this.error(`ELEVATED IE2: no device states for device ${devId} type '${model}'`) @@ -613,31 +612,49 @@ class StatesController extends EventEmitter { // find states for payload let has_published = false; + let extra_payload = undefined; + if (payload.hasOwnProperty('action') && typeof payload.action == 'string' && payload.action.endsWith('_release') { + extra_payload = {'action': payload.action.replace('_release')}; + } + if (devStates.states !== undefined) { try { const states = statesMapping.commonStates.concat( devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id)) ); - + + let extra_states = {}; for (const stateInd in states) { const statedesc = states[stateInd]; let value; + let extra_value = undefined; if (statedesc.getter) { value = statedesc.getter(payload); } else { value = payload[statedesc.prop || statedesc.id]; } + if (extra_payload) { + if (statedesc.getter) { + extra_value = statedesc.getter(extra_payload); + } + } // checking value if (value === undefined || value === null) { + if (extra_value != undefined && extra_value != null) { + extra_states[stateID] = {'statedesc': statedesc, 'value':extra_value}; + } continue; } let stateID = statedesc.id; - if (has_debug && statedesc.id !== 'msg_from_zigbee') { + if (has_elevated_debug && statedesc.id !== 'msg_from_zigbee') { this.warn(`ELEVATED I2: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`); } + this.TriggerStateUpdate(devId, statedesc, value); + +/* const common = { name: statedesc.name, type: statedesc.type, @@ -673,23 +690,78 @@ class StatesController extends EventEmitter { this.updateState(devId, stateID, value, common); } } +*/ + has_published = true; + } + if (!has_published && extra_states != {}) { + foreach (candidateID in extra_states) { + const candidate = extra_states[candidateID]; + if (candidate) { + if (has_elevated_debug) { + this.warn(`ELEVATED I2a: value generated '${JSON.stringify(candidate.value)}' from device ${devId} for candidate '${candidate.statedesc.name}'`); + } + TriggerStateUpdatea(devId, candidate.statedesc, candidate.value); + } + } has_published = true; } } catch (e) { this.debug(`No states in device ${devId} : payload ${JSON.stringify(payload)}`); - if (has_debug) + if (has_elevated_debug) this.error(`ELEVATED IE3: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`); } - if (!has_published && has_debug) { + if (!has_published && has_elevated_debug) { this.error(`ELEVATED IE4: No value published for device ${devId}`); } } else { - if (has_debug) + if (has_elevated_debug) this.error(`ELEVATED IE5: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`); } } + + async TriggerStateUpdate(devId, statedesc, value) + { + const common = { + name: statedesc.name, + type: statedesc.type, + unit: statedesc.unit, + read: statedesc.read, + write: statedesc.write, + icon: statedesc.icon, + role: statedesc.role, + min: statedesc.min, + max: statedesc.max, + }; + + let stateID = statedesc.id; + + if (typeof value === 'object' && value.hasOwnProperty('stateid')) { + stateID = `${stateID}.${value.stateid}`; + if (value.hasOwnProperty('unit')) { + common.unit = value.unit; + } + common.name = value.name ? value.name : value.stateid; + common.role = value.role ? `value.${value.role}` : 'number'; + value = value.value; + } + + // if needs to return value to back after timeout + if (statedesc.isEvent) { + this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, !value); + } else { + if (statedesc.prepublish) { + this.collectOptions(devId, model, options => + statedesc.prepublish(devId, value, newvalue => + this.updateState(devId, stateID, newvalue, common), options) + ); + } else { + this.updateState(devId, stateID, value, common); + } + } + + } } module.exports = StatesController; From 3b0264eca0a98fcdc5de95e29a4f5af59e6a5a2a Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:20:19 +0100 Subject: [PATCH 04/19] Update statescontroller.js bugfix --- lib/statescontroller.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/statescontroller.js b/lib/statescontroller.js index 4c73344c..145cc19d 100644 --- a/lib/statescontroller.js +++ b/lib/statescontroller.js @@ -613,7 +613,7 @@ class StatesController extends EventEmitter { let has_published = false; let extra_payload = undefined; - if (payload.hasOwnProperty('action') && typeof payload.action == 'string' && payload.action.endsWith('_release') { + if (payload.hasOwnProperty('action') && typeof payload.action == 'string' && payload.action.endsWith('_release')) { extra_payload = {'action': payload.action.replace('_release')}; } @@ -622,7 +622,7 @@ class StatesController extends EventEmitter { const states = statesMapping.commonStates.concat( devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id)) ); - + let extra_states = {}; for (const stateInd in states) { const statedesc = states[stateInd]; @@ -636,13 +636,13 @@ class StatesController extends EventEmitter { if (extra_payload) { if (statedesc.getter) { extra_value = statedesc.getter(extra_payload); - } + } } // checking value if (value === undefined || value === null) { if (extra_value != undefined && extra_value != null) { extra_states[stateID] = {'statedesc': statedesc, 'value':extra_value}; - } + } continue; } @@ -694,7 +694,7 @@ class StatesController extends EventEmitter { has_published = true; } if (!has_published && extra_states != {}) { - foreach (candidateID in extra_states) { + for (candidateID in extra_states) { const candidate = extra_states[candidateID]; if (candidate) { if (has_elevated_debug) { From 55125d5820472e54d05d143e041fd5173af367da Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:52:51 +0100 Subject: [PATCH 05/19] Test Deactivate map and ping --- lib/zbDeviceAvailability.js | 4 +++- lib/zigbeecontroller.js | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/zbDeviceAvailability.js b/lib/zbDeviceAvailability.js index 70389589..8a3b3e69 100644 --- a/lib/zbDeviceAvailability.js +++ b/lib/zbDeviceAvailability.js @@ -49,7 +49,7 @@ class DeviceAvailability extends BaseExtension { }); this.startDevicePingQueue = []; // simple fifo array for starting device pings this.startDevicePingTimeout = null; // handle for the timeout which empties the queue - this.startDevicePingDelay = 200; // 200 ms delay between starting the ping timeout + this.startDevicePingDelay = 2000; // 200 ms delay between starting the ping timeout this.name = 'DeviceAvailability'; this.elevate_debug = false; } @@ -74,6 +74,8 @@ class DeviceAvailability extends BaseExtension { } isPingable(device) { + return false; + if (this.active_ping) { if (this.forced_ping && forcedPingable.find(d => d && d.hasOwnProperty('zigbeeModel') && d.zigbeeModel.includes(device.modelID))) { return true; diff --git a/lib/zigbeecontroller.js b/lib/zigbeecontroller.js index c5c8271a..fb3d5635 100644 --- a/lib/zigbeecontroller.js +++ b/lib/zigbeecontroller.js @@ -715,6 +715,7 @@ class ZigbeeController extends EventEmitter { } async getMap(callback) { +/* try { const devices = this.herdsman.getDevices(true); const lqis = []; @@ -790,6 +791,8 @@ class ZigbeeController extends EventEmitter { this.sendError(error); this.debug(`Failed to get map: ${safeJsonStringify(error.stack)}`); } + */ + this.error('Map currently not supported'); } async publish(deviceID, cid, cmd, zclData, cfg, ep, type, callback, zclSeqNum) { From ad35f53f4a117558591afb9b7386d6c9ee3b4d03 Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Fri, 3 Jan 2025 10:58:08 +0100 Subject: [PATCH 06/19] Revert "Update statescontroller.js" This reverts commit 3b0264eca0a98fcdc5de95e29a4f5af59e6a5a2a. --- lib/statescontroller.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/statescontroller.js b/lib/statescontroller.js index 145cc19d..4c73344c 100644 --- a/lib/statescontroller.js +++ b/lib/statescontroller.js @@ -613,7 +613,7 @@ class StatesController extends EventEmitter { let has_published = false; let extra_payload = undefined; - if (payload.hasOwnProperty('action') && typeof payload.action == 'string' && payload.action.endsWith('_release')) { + if (payload.hasOwnProperty('action') && typeof payload.action == 'string' && payload.action.endsWith('_release') { extra_payload = {'action': payload.action.replace('_release')}; } @@ -622,7 +622,7 @@ class StatesController extends EventEmitter { const states = statesMapping.commonStates.concat( devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id)) ); - + let extra_states = {}; for (const stateInd in states) { const statedesc = states[stateInd]; @@ -636,13 +636,13 @@ class StatesController extends EventEmitter { if (extra_payload) { if (statedesc.getter) { extra_value = statedesc.getter(extra_payload); - } + } } // checking value if (value === undefined || value === null) { if (extra_value != undefined && extra_value != null) { extra_states[stateID] = {'statedesc': statedesc, 'value':extra_value}; - } + } continue; } @@ -694,7 +694,7 @@ class StatesController extends EventEmitter { has_published = true; } if (!has_published && extra_states != {}) { - for (candidateID in extra_states) { + foreach (candidateID in extra_states) { const candidate = extra_states[candidateID]; if (candidate) { if (has_elevated_debug) { From 8c4f43139121de649a0b7f565847e5792d225112 Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:10:10 +0100 Subject: [PATCH 07/19] revert and fix --- lib/statescontroller.js | 83 +++-------------------------------------- main.js | 16 +++++--- 2 files changed, 16 insertions(+), 83 deletions(-) diff --git a/lib/statescontroller.js b/lib/statescontroller.js index 4c73344c..0bdc8e30 100644 --- a/lib/statescontroller.js +++ b/lib/statescontroller.js @@ -603,27 +603,21 @@ class StatesController extends EventEmitter { if (has_elevated_debug) { - this.warn(`ELEVATED I1: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`); + this.warn(`ELEVATED I01: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`); } if (!devStates) { - if (has_debug) this.error(`ELEVATED IE2: no device states for device ${devId} type '${model}'`) + if (has_debug) this.error(`ELEVATED IE02: no device states for device ${devId} type '${model}'`) return; } // find states for payload let has_published = false; - let extra_payload = undefined; - if (payload.hasOwnProperty('action') && typeof payload.action == 'string' && payload.action.endsWith('_release') { - extra_payload = {'action': payload.action.replace('_release')}; - } - if (devStates.states !== undefined) { try { const states = statesMapping.commonStates.concat( devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id)) ); - let extra_states = {}; for (const stateInd in states) { const statedesc = states[stateInd]; let value; @@ -633,28 +627,17 @@ class StatesController extends EventEmitter { } else { value = payload[statedesc.prop || statedesc.id]; } - if (extra_payload) { - if (statedesc.getter) { - extra_value = statedesc.getter(extra_payload); - } - } // checking value if (value === undefined || value === null) { - if (extra_value != undefined && extra_value != null) { - extra_states[stateID] = {'statedesc': statedesc, 'value':extra_value}; - } continue; } let stateID = statedesc.id; if (has_elevated_debug && statedesc.id !== 'msg_from_zigbee') { - this.warn(`ELEVATED I2: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`); + this.warn(`ELEVATED I02: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`); } - this.TriggerStateUpdate(devId, statedesc, value); - -/* const common = { name: statedesc.name, type: statedesc.type, @@ -690,78 +673,24 @@ class StatesController extends EventEmitter { this.updateState(devId, stateID, value, common); } } -*/ - has_published = true; - } - if (!has_published && extra_states != {}) { - foreach (candidateID in extra_states) { - const candidate = extra_states[candidateID]; - if (candidate) { - if (has_elevated_debug) { - this.warn(`ELEVATED I2a: value generated '${JSON.stringify(candidate.value)}' from device ${devId} for candidate '${candidate.statedesc.name}'`); - } - TriggerStateUpdatea(devId, candidate.statedesc, candidate.value); - } - } has_published = true; } } catch (e) { this.debug(`No states in device ${devId} : payload ${JSON.stringify(payload)}`); if (has_elevated_debug) - this.error(`ELEVATED IE3: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`); + this.error(`ELEVATED IE03: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`); } if (!has_published && has_elevated_debug) { - this.error(`ELEVATED IE4: No value published for device ${devId}`); + this.error(`ELEVATED IE04: No value published for device ${devId}`); } } else { if (has_elevated_debug) - this.error(`ELEVATED IE5: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`); + this.error(`ELEVATED IE05: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`); } } - async TriggerStateUpdate(devId, statedesc, value) - { - const common = { - name: statedesc.name, - type: statedesc.type, - unit: statedesc.unit, - read: statedesc.read, - write: statedesc.write, - icon: statedesc.icon, - role: statedesc.role, - min: statedesc.min, - max: statedesc.max, - }; - - let stateID = statedesc.id; - - if (typeof value === 'object' && value.hasOwnProperty('stateid')) { - stateID = `${stateID}.${value.stateid}`; - if (value.hasOwnProperty('unit')) { - common.unit = value.unit; - } - common.name = value.name ? value.name : value.stateid; - common.role = value.role ? `value.${value.role}` : 'number'; - value = value.value; - } - - // if needs to return value to back after timeout - if (statedesc.isEvent) { - this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, !value); - } else { - if (statedesc.prepublish) { - this.collectOptions(devId, model, options => - statedesc.prepublish(devId, value, newvalue => - this.updateState(devId, stateID, newvalue, common), options) - ); - } else { - this.updateState(devId, stateID, value, common); - } - } - - } } module.exports = StatesController; diff --git a/main.js b/main.js index b95d9b35..3ad09659 100644 --- a/main.js +++ b/main.js @@ -477,7 +477,7 @@ class Zigbee extends utils.Adapter { shortMessage.device = device.ieeeAddr; shortMessage.meta = undefined; shortMessage.endpoint = (message.endpoint.ID ? message.endpoint.ID: -1); - this.log.warn(`ELEVATED I0: Zigbee Event of Type ${type} from device ${safeJsonStringify(device.ieeeAddr)}, incoming event: ${safeJsonStringify(shortMessage)}`); + this.log.warn(`ELEVATED I00: Zigbee Event of Type ${type} from device ${safeJsonStringify(device.ieeeAddr)}, incoming event: ${safeJsonStringify(shortMessage)}`); } // this assigment give possibility to use iobroker logger in code of the converters, via meta.logger meta.logger = this.log; @@ -561,7 +561,7 @@ class Zigbee extends utils.Adapter { if (type !== 'readResponse') { this.log.debug(`No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`); if (has_elevated_debug) - this.log.warn(`ELEVATED IE0: No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`); + this.log.warn(`ELEVATED IE00: No converter available for '${mappedModel.model}' '${devId}' with cluster '${cluster}' and type '${type}'`); } return; } @@ -643,7 +643,7 @@ class Zigbee extends utils.Adapter { if (!mappedModel) { this.log.debug(`No mapped model for ${model}`); - if (has_elevated_debug) this.log.warn(`ELEVATED O2: No mapped model for ${model}`) + if (has_elevated_debug) this.log.warn(`ELEVATED O02: No mapped model for ${model}`) return; } @@ -715,6 +715,7 @@ class Zigbee extends utils.Adapter { } let converter = undefined; + let msg_counter = 0; for (const c of mappedModel.toZigbee) { if (!c.hasOwnProperty('convertSet')) continue; @@ -725,21 +726,24 @@ class Zigbee extends utils.Adapter { { converter = c; if (has_elevated_debug) - this.log.warn(`ELEVATED O3A: Setting converter to keyless converter for ${deviceId} of type ${model}`) + this.log.warn(`ELEVATED O3.${msg_counter}: Setting converter to keyless converter for ${deviceId} of type ${model}`) this.log.debug('setting converter to keyless converter') + msg_counter++; } else { - if (has_elevated_debug) this.log.warn(`ELEVATED O3B: ignoring keyless converter for ${deviceId} of type ${model}`) + if (has_elevated_debug) this.log.warn(`ELEVATED O3.${msg_counter}: ignoring keyless converter for ${deviceId} of type ${model}`) this.log.debug('ignoring keyless converter') + msg_counter++; } continue; } if (c.key.includes(stateDesc.prop) || c.key.includes(stateDesc.setattr) || c.key.includes(stateDesc.id)) { this.log.debug(`${(converter===undefined?'Setting':'Overriding')}' converter to converter with key(s)'${JSON.stringify(c.key)}}`) - if (has_elevated_debug) this.log.warn(`ELEVATED O3C: ${(converter===undefined?'Setting':'Overriding')}' converter to converter with key(s)'${JSON.stringify(c.key)}}`) + if (has_elevated_debug) this.log.warn(`ELEVATED O3.${msg_counter}: ${(converter===undefined?'Setting':'Overriding')}' converter to converter with key(s)'${JSON.stringify(c.key)}}`) converter = c; + msg_counter++; } } if (converter === undefined) { From 13fde33de6ef4a5a91cbf897030707154089ee3d Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Sat, 4 Jan 2025 16:43:19 +0100 Subject: [PATCH 08/19] Fix Pairing --- lib/exclude.js | 26 ++++++++++++++++++++++++++ lib/statescontroller.js | 2 +- lib/zigbeecontroller.js | 29 +++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/lib/exclude.js b/lib/exclude.js index c2714f1d..cc655bd1 100644 --- a/lib/exclude.js +++ b/lib/exclude.js @@ -117,8 +117,34 @@ class Exclude { } } +/* + async moveExcludeStorage() + { + try { + const states = await this.adapter.getStatesOf('exclude') + for (const state of states) { + this.adapter.setObjectNotExists( "info.exclude.".concat(state.id), + { + type: 'state', + common: {name: exclude_mod}, + }, + () => this.adapter.setState(stateId, exclude_mod, true, () => + callback()), + ); + + } + + } + catch (error) { + this.error(`Failed to getExclude ${error.stack}`) + + } + } +*/ getExclude(callback) { try { + + const exclude = []; this.adapter.getStatesOf('exclude', (err, states) => { if (!err && states) { diff --git a/lib/statescontroller.js b/lib/statescontroller.js index 0bdc8e30..9e5bde34 100644 --- a/lib/statescontroller.js +++ b/lib/statescontroller.js @@ -334,7 +334,7 @@ class StatesController extends EventEmitter { this.info(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `); } else { this.info(`deleting disconnected state ${JSON.stringify(statename)} of ${devId} `); - this.adapter.deleteState(devId, null, state._id); + this.deleteObj(devId.concat('.',statename)); } } else { this.debug(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `); diff --git a/lib/zigbeecontroller.js b/lib/zigbeecontroller.js index fb3d5635..d856a419 100644 --- a/lib/zigbeecontroller.js +++ b/lib/zigbeecontroller.js @@ -129,6 +129,7 @@ class ZigbeeController extends EventEmitter { this.herdsman.on('deviceJoined', this.handleDeviceJoined.bind(this)); this.herdsman.on('deviceLeave', this.handleDeviceLeave.bind(this)); this.herdsman.on('message', this.handleMessage.bind(this)); + this.herdsman.on('permitJoinChanged', this.handlePermitJoinChanged.bind(this)); await this.herdsman.start(); @@ -503,6 +504,13 @@ class ZigbeeController extends EventEmitter { // Permit join async permitJoin(permitTime, devid, failure) { + try { + await this.herdsman.permitJoin(permitTime); + } catch (e) { + this.sendError(e); + this.error(`Failed to open the network: ${e.stack}`); + } + /* let permitDev; if (isFunction(devid) && !isFunction(failure)) { failure = devid; @@ -524,13 +532,18 @@ class ZigbeeController extends EventEmitter { if (permitTime && !this.herdsman.getPermitJoin()) { clearInterval(this._permitJoinInterval); this._permitJoinTime = permitTime; - await this.herdsman.permitJoin(true, permitDev, this._permitJoinTime); + await this.herdsman.adapter.permitJoin(this._permitJoinTime); + await this.herdsman.greenPower.permitJoin(this._permitJoinTime); + + //await this.herdsman.permitJoin(true, permitDev, this._permitJoinTime); this._permitJoinInterval = setInterval(async () => { this.emit('pairing', 'Pairing time left', this._permitJoinTime); if (this._permitJoinTime === 0) { this.info('Zigbee: stop joining'); clearInterval(this._permitJoinInterval); - await this.herdsman.permitJoin(false); + //await this.herdsman.permitJoin(false); + await this.herdsman.adapter.permitJoin(0); + await this.herdsman.greenPower.permitJoin(0); } this._permitJoinTime -= 1; }, 1000); @@ -546,6 +559,18 @@ class ZigbeeController extends EventEmitter { this.sendError(e); this.error(`Failed to open the network: ${e.stack}`); } + */ + } + + async handlePermitJoinChanged(data) + { + try { + this.warn(`Error in PermitJoinChanged: ${JSON.Stringify(error.message)}`); + } + catch (error) { + this.error(`Error in PermitJoinChanged: ${error.message}`); + } + } // Remove device From 8bb5a2edc0c7c0c9b7b33693a209bce8c7e2ce76 Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:36:17 +0100 Subject: [PATCH 09/19] 1.11.1 * (asgothian) Fix Pairing * (asgothian) change ping * (asgothian) delay map generation until refresh is activated * (asgothian) remove bindings tab from zigbee tab * (asgothian) reorder tabs in configuration * (asgothian) remove binding tab from configuration * (asgothian) remove map from configuration * (asgothian) add debug to zigbee tab (work in progress) --- README.md | 11 ++++++ admin/admin.js | 2 +- admin/index_m.html | 10 ++--- admin/tab_m.html | 5 ++- lib/utils.js | 2 +- lib/zbDeviceAvailability.js | 11 ++++-- lib/zigbeecontroller.js | 77 +++++++++++-------------------------- package-lock.json | 4 +- package.json | 2 +- 9 files changed, 52 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index f981fdbe..0fde76ae 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,17 @@ You can thank the authors by these links: ----------------------------------------------------------------------------------------------------- ## Changelog +### 1.11.1 (2025-01-02) +* (asgothian) Fix Pairing +* (asgothian) change ping +* (asgothian) delay map generation until refresh is activated +* (asgothian) remove bindings tab from zigbee tab +* (asgothian) reorder tabs in configuration +* (asgothian) remove binding tab from configuration +* (asgothian) remove map from configuration +* (asgothian) add debug to zigbee tab (work in progress) +* (asgothian) Herdsman 3.2.0, Converters 21.11.0 + ### 1.10.14 (2025-01-01) * (arteck) Herdsman 2.1.9, Converters 20.58.0 * (asgothian) Fix: Aqara T1M (CL-L02D) diff --git a/admin/admin.js b/admin/admin.js index 0ff0c4bd..a3b7698e 100644 --- a/admin/admin.js +++ b/admin/admin.js @@ -848,7 +848,7 @@ function load(settings, onChange) { //dialog = new MatDialog({EndingTop: '50%'}); getDevices(); - getMap(); + //getMap(); //addCard(); // Signal to admin, that no changes yet diff --git a/admin/index_m.html b/admin/index_m.html index 82fa0c52..7492c9ac 100644 --- a/admin/index_m.html +++ b/admin/index_m.html @@ -690,13 +690,11 @@
Zigbee adapter
diff --git a/lib/utils.js b/lib/utils.js index a685fa0f..57a584ae 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -157,7 +157,7 @@ exports.decimalToHex = decimalToHex; exports.getZbId = getZbId; exports.getAdId = getAdId; exports.getModelRegEx = getModelRegEx; -exports.isRouter = device => device.type === 'Router' && !forceEndDevice.includes(device.modelID); +exports.isRouter = device => (device.type === 'Router' || device.powerSource.startsWith('Mains')) && !forceEndDevice.includes(device.modelID); exports.isBatteryPowered = device => device.powerSource && device.powerSource === 'Battery'; exports.isXiaomiDevice = device => device.modelID !== 'lumi.router' && diff --git a/lib/zbDeviceAvailability.js b/lib/zbDeviceAvailability.js index 8a3b3e69..2b78c8a2 100644 --- a/lib/zbDeviceAvailability.js +++ b/lib/zbDeviceAvailability.js @@ -49,7 +49,7 @@ class DeviceAvailability extends BaseExtension { }); this.startDevicePingQueue = []; // simple fifo array for starting device pings this.startDevicePingTimeout = null; // handle for the timeout which empties the queue - this.startDevicePingDelay = 2000; // 200 ms delay between starting the ping timeout + this.startDevicePingDelay = 500; // 200 ms delay between starting the ping timeout this.name = 'DeviceAvailability'; this.elevate_debug = false; } @@ -74,7 +74,6 @@ class DeviceAvailability extends BaseExtension { } isPingable(device) { - return false; if (this.active_ping) { if (this.forced_ping && forcedPingable.find(d => d && d.hasOwnProperty('zigbeeModel') && d.zigbeeModel.includes(device.modelID))) { @@ -106,6 +105,7 @@ class DeviceAvailability extends BaseExtension { } }); this.number_of_registered_devices++; + this.info(`registering device Ping (${this.number_of_registered_devices}) for ${device.ieeeAddr} (${device.modelID})`); this.availability_timeout = Math.max(Math.min(this.number_of_registered_devices * AverageTimeBetweenPings, MaxAvailabilityTimeout), MinAvailabilityTimeout); this.startDevicePingQueue.push({device, entity}); if (this.startDevicePingTimeout == null) { @@ -159,7 +159,7 @@ class DeviceAvailability extends BaseExtension { const ieeeAddr = device.ieeeAddr; const resolvedEntity = entity ? entity : await this.zigbee.resolveEntity(ieeeAddr); if (!resolvedEntity) { - this.debug(`Stop pinging '${ieeeAddr}' ${device.modelID}, device is not known anymore`); + this.warn(`Stop pinging '${ieeeAddr}' ${device.modelID}, device is not known anymore`); return; } if (this.isPingable(device)) { @@ -169,6 +169,7 @@ class DeviceAvailability extends BaseExtension { pingCount = {failed: 0, reported: 0}; } + /* // first see if we can "ping" the device by reading a Status try { for (const key of toZigbeeCandidates) { @@ -187,10 +188,12 @@ class DeviceAvailability extends BaseExtension { // intentionally empty: Just present to ensure we cause no harm // when reading the state fails. => fall back on standard Ping function } +*/ try { + this.warn(`Pinging '${ieeeAddr}' (${device.modelID})`) await device.ping(); this.publishAvailability(device, true); - this.debug(`Successfully pinged ${ieeeAddr} ${device.modelID}`); + this.warn(`Successfully pinged ${ieeeAddr} (${device.modelID})`); this.setTimerPingable(device, 1); this.ping_counters[device.ieeeAddr].failed = 0; } catch (error) { diff --git a/lib/zigbeecontroller.js b/lib/zigbeecontroller.js index d856a419..3ca76db3 100644 --- a/lib/zigbeecontroller.js +++ b/lib/zigbeecontroller.js @@ -505,70 +505,39 @@ class ZigbeeController extends EventEmitter { // Permit join async permitJoin(permitTime, devid, failure) { try { + this._permitJoinTime = permitTime; await this.herdsman.permitJoin(permitTime); } catch (e) { this.sendError(e); this.error(`Failed to open the network: ${e.stack}`); } - /* - let permitDev; - if (isFunction(devid) && !isFunction(failure)) { - failure = devid; - } else { - if (devid != '') { - permitDev = this.getDevice(devid); - } else { - permitDev = ''; - } - } - - if (permitTime) { - this.info('Zigbee: allowing new devices to join.'); - } else { - this.info('Zigbee: disabling joining new devices.'); - } - - try { - if (permitTime && !this.herdsman.getPermitJoin()) { - clearInterval(this._permitJoinInterval); - this._permitJoinTime = permitTime; - await this.herdsman.adapter.permitJoin(this._permitJoinTime); - await this.herdsman.greenPower.permitJoin(this._permitJoinTime); - - //await this.herdsman.permitJoin(true, permitDev, this._permitJoinTime); - this._permitJoinInterval = setInterval(async () => { - this.emit('pairing', 'Pairing time left', this._permitJoinTime); - if (this._permitJoinTime === 0) { - this.info('Zigbee: stop joining'); - clearInterval(this._permitJoinInterval); - //await this.herdsman.permitJoin(false); - await this.herdsman.adapter.permitJoin(0); - await this.herdsman.greenPower.permitJoin(0); - } - this._permitJoinTime -= 1; - }, 1000); - } else if (this.herdsman.getPermitJoin()) { - if (permitTime) { - this.info('Joining already permitted'); - } else { - clearInterval(this._permitJoinInterval); - await this.herdsman.permitJoin(false, permitDev); - } - } - } catch (e) { - this.sendError(e); - this.error(`Failed to open the network: ${e.stack}`); - } - */ } async handlePermitJoinChanged(data) { try { - this.warn(`Error in PermitJoinChanged: ${JSON.Stringify(error.message)}`); + this.debug(`Event handlePermitJoinChanged received with ${JSON.stringify(data)}`); + if (data.permitted) { + if (!this._permitJoinInterval) { + this.info(`Opening zigbee Network for ${this._permitJoinTime} seconds`) + this._permitJoinInterval = setInterval(async () => { + this.emit('pairing', 'Pairing time left', this._permitJoinTime); + this._permitJoinTime -= 1; + }, 1000); + + } + } + else { + this.info(`Closing Zigbee network, ${this._permitJoinTime} seconds remaining`) + clearInterval(this._permitJoinInterval); + this._permitJoinInterval = null; + this.emit('pairing', 'Pairing time left', 0); + this.emit('pairing', 'Closing network.'); + } + } catch (error) { - this.error(`Error in PermitJoinChanged: ${error.message}`); + this.error(`Error in handlePermitJoinChanged: ${error.message}`); } } @@ -740,8 +709,8 @@ class ZigbeeController extends EventEmitter { } async getMap(callback) { -/* try { + this.warn('get Map called'); const devices = this.herdsman.getDevices(true); const lqis = []; const routing = []; @@ -816,8 +785,6 @@ class ZigbeeController extends EventEmitter { this.sendError(error); this.debug(`Failed to get map: ${safeJsonStringify(error.stack)}`); } - */ - this.error('Map currently not supported'); } async publish(deviceID, cid, cmd, zclData, cfg, ep, type, callback, zclSeqNum) { diff --git a/package-lock.json b/package-lock.json index 592c2f0a..b464bb25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "iobroker.zigbee", - "version": "1.11.0", + "version": "1.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "iobroker.zigbee", - "version": "1.11.0", + "version": "1.11.1", "license": "MIT", "dependencies": { "@iobroker/adapter-core": "^3.2.2", diff --git a/package.json b/package.json index e6720877..409ead3a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iobroker.zigbee", - "version": "1.11.0", + "version": "1.11.1", "author": { "name": "Kirov Ilya", "email": "kirovilya@gmail.com" From 266a9441c6939d740f9b5e4593f64bcd9a0f39b0 Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:13:17 +0100 Subject: [PATCH 10/19] 1.11.2 Debug options --- admin/admin.js | 76 ++++++++++++++++++++++++++++++++--------- io-package.json | 18 ++++++++-- lib/commands.js | 26 ++++++++++++++ lib/devices.js | 10 ------ lib/exposes.js | 26 ++++++++++++-- lib/statescontroller.js | 46 ++++++++++++++++++++----- lib/utils.js | 2 +- package.json | 6 ++-- 8 files changed, 166 insertions(+), 44 deletions(-) diff --git a/admin/admin.js b/admin/admin.js index a3b7698e..d17f7a14 100644 --- a/admin/admin.js +++ b/admin/admin.js @@ -8,6 +8,7 @@ const Materialize = (typeof M !== 'undefined') ? M : Materialize, namespace = 'zigbee.' + instance, namespaceLen = namespace.length; let devices = [], + debugDevices = [], messages = [], map = {}, mapEdges = null, @@ -94,7 +95,7 @@ function getLQICls(value) { if (value < 20) return 'icon-red'; if (value < 50) return 'icon-orange'; } - return ''; + return 'icon-green'; } @@ -173,7 +174,7 @@ function getGroupCard(dev) { info = info.concat(` `); const image = ``; - const dashCard = getDashCard(dev, `img/group_${memberCount}.png`); + const dashCard = getDashCard(dev, `img/group_${memberCount}.png`, memberCount > 0); const card = `
${dashCard}
@@ -220,7 +221,9 @@ function getCard(dev) { img_src = dev.icon || dev.common.icon, rooms = [], isActive = (dev.common.deactivated ? false : true), - lang = systemLang || 'en'; + lang = systemLang || 'en', + ieee = id.replace(namespace + '.', ''), + isDebug = checkDebugDevice(ieee); for (const r in dev.rooms) { if (dev.rooms[r].hasOwnProperty(lang)) { rooms.push(dev.rooms[r][lang]); @@ -228,6 +231,7 @@ function getCard(dev) { rooms.push(dev.rooms[r]); } } + console.warn('debug for ' + ieee + ' is ' + isDebug); const room = rooms.join(',') || ' '; const paired = (dev.paired) ? '' : 'leak_remove'; const rid = id.split('.').join('_'); @@ -237,18 +241,19 @@ function getCard(dev) { battery_cls = (isActive ? getBatteryCls(dev.battery) : ''), lqi_cls = getLQICls(dev.link_quality), battery = (dev.battery && isActive) ? `
battery_std
${dev.battery}
` : '', - lq = (dev.link_quality > 0 && isActive) ? `
network_check
` : '', - status = (dev.link_quality > 0 && isActive) ? `
check_circle
` : (isActive ? `
leak_remove
` : ''), + lq = (dev.link_quality > 0) ? `
network_check
` + : `
leak_remove
`, + status = (isActive ? lq : `
cancel
`), info = `
    -
  • ieee:0x${id.replace(namespace + '.', '')}
  • +
  • ieee:0x${ieee}
  • nwk:${(nwk) ? nwk.toString() + ' (0x' + nwk.toString(16) + ')' : ''}
  • model:${modelUrl}
  • groups:${dev.groupNames || ''}
`, - permitJoinBtn = (dev.info && dev.info.device._type == 'Router') ? '' : '', deactBtn = ``, + debugBtn = ``, infoBtn = (nwk) ? `` : ''; const dashCard = getDashCard(dev); const card = `
@@ -259,7 +264,7 @@ function getCard(dev) {
${battery} - ${lq} + ${status} @@ -274,17 +279,17 @@ function getCard(dev) { ${infoBtn} ${room} - - ${deactBtn} - ${permitJoinBtn} + ${debugBtn}
@@ -616,6 +621,12 @@ function showDevices() { const name = getDevName(dev_block); editName(id, name); }); + $('.card-reveal-buttons button[name=\'swapdebug\']').click(function () { + const dev_block = $(this).parents('div.device'); + const id = getDevId(dev_block); + const name = getDevName(dev_block); + toggleDebugDevice(id, name); + }); $('.card-reveal-buttons button[name=\'editgrp\']').click(function () { const dev_block = $(this).parents('div.device'); const id = dev_block.attr('id').replace(namespace + '.group_', ''); @@ -751,9 +762,39 @@ function getCoordinatorInfo() { } }); } +function checkDebugDevice(id) { + if (debugDevices.indexOf(id) > -1) return 0 + for (const addressPart of debugDevices) { + if (typeof id === 'string' && id.includes(addressPart)) { + return debugDevices.indexOf(addressPart)+1; + } + } + return -1; +} +async function toggleDebugDevice(id) { + console.warn('toggleDebugDevices with id ' + id); + sendTo(namespace, 'setDeviceDebug', {id:id}, function (msg) { + sendTo(namespace, 'getDebugDevices', {}, function(msg) { + if (msg && typeof (msg.debugDevices == 'array')) { + debugDevices = msg.debugDevices; + } + else + debugDevices = []; + }); + console.warn('toggleDebugDevices.result ' + JSON.stringify(debugDevices)); + showDevices(); + }); +} function getDevices() { getCoordinatorInfo(); + sendTo(namespace, 'getDebugDevices', {}, function(msg) { + if (msg && typeof (msg.debugDevices == 'array')) { + debugDevices = msg.debugDevices; + } + else + debugDevices = []; + }); sendTo(namespace, 'getDevices', {}, function (msg) { if (msg) { if (msg.error) { @@ -2827,7 +2868,7 @@ function sortByTitle(element) { return element.querySelector('.card-title').textContent.toLowerCase().trim(); } -function getDashCard(dev, groupImage) { +function getDashCard(dev, groupImage, groupstatus) { const title = dev.common.name, id = dev._id, type = dev.common.type, @@ -2842,11 +2883,13 @@ function getDashCard(dev, groupImage) { nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined, battery_cls = getBatteryCls(dev.battery), lqi_cls = getLQICls(dev.link_quality), + unconnected_icon = (groupImage ? (groupstatus ? '
check_circle
' : '
cancel
') :'
leak_remove
') battery = (dev.battery && isActive) ? `
battery_std
${dev.battery}
` : '', - lq = (dev.link_quality > 0 && isActive) ? `
network_check
` : (isActive ? '
check_circle
' : ''), - status = (dev.link_quality > 0 && isActive) ? `
check_circle
` : (groupImage || !isActive ? '' : `
leak_remove
`), - permitJoinBtn = (isActive && dev.info && dev.info.device._type === 'Router') ? '' : '', - infoBtn = (nwk) ? `` : '', + lq = (dev.link_quality > 0 && isActive) ? `
network_check
` + : (isActive ? unconnected_icon : ''), + //status = (dev.link_quality > 0 && isActive) ? `
check_circle
` : (groupImage || !isActive ? '' : `
leak_remove
`), + //permitJoinBtn = (isActive && dev.info && dev.info.device._type === 'Router') ? '' : '', + //infoBtn = (nwk) ? `` : '', idleTime = (dev.link_quality_lc > 0 && isActive) ? `
access_time
` : ''; const info = (dev.statesDef) ? dev.statesDef.map((stateDef) => { const id = stateDef.id; @@ -2888,7 +2931,6 @@ function getDashCard(dev, groupImage) { ${idleTime} ${battery} ${lq} - ${status} ${title}
diff --git a/io-package.json b/io-package.json index 0fd45509..ffceefdb 100644 --- a/io-package.json +++ b/io-package.json @@ -1,8 +1,21 @@ { "common": { "name": "zigbee", - "version": "1.10.14", + "version": "1.11.2", "news": { + "1.11.2": { + "en": "debug for states", + "de": "debug for states", + "ru": "debug for states", + "pt": "debug for states", + "nl": "debug for states", + "fr": "debug for states", + "it": "debug for states", + "es": "debug for states", + "pl": "debug for states", + "uk": "debug for states", + "zh-cn": "debug for states" + }, "1.10.14": { "en": "Herdsman 2.1.9, Converters 20.58.0\nFix: Aqara T1M (CL-L02D) \ndeleteDeviceStates change to deleteObj", "de": "Herdsman 2.1.9, Konverter 20.58.0\nFix: Aqara T1M (CL-L02D)\nlöschen DeviceState Änderung zum Löschen Ob", @@ -248,7 +261,8 @@ "cancel" ] } - ] + ], + "installedFrom": "asgothian/ioBroker.zigbee#1.11" }, "native": { "port": "", diff --git a/lib/commands.js b/lib/commands.js index a10915fb..df1fb2b9 100644 --- a/lib/commands.js +++ b/lib/commands.js @@ -114,6 +114,16 @@ class Commands { this.setDeviceActivated(obj.from, obj.command, obj.message, obj.callback); } break; + case 'setDeviceDebug': + if (obj.message && typeof obj.message === 'object') { + this.toggleDeviceDebug(obj.from, obj.command, obj.message, obj.callback); + } + break; + case 'getDebugDevices': + if (obj.message && typeof obj.message === 'object') { + this.getDebugDevices(obj.from, obj.command, obj.message, obj.callback); + } + break; } } } @@ -209,6 +219,7 @@ class Commands { async getDevices(from, command, id, callback) { if (this.zbController) { + this.warn(`getDevices called from ${from} with command ${JSON.stringify(command)} and id ${JSON.stringify(id)}`); const pairedDevices = await this.zbController.getClients(true); const groups = {}; let rooms; @@ -532,6 +543,21 @@ class Commands { } } + async toggleDeviceDebug(from, command, msg, callback) { + if (this.stController) { + const id = msg.id; + const result = await this.stController.toggleDeviceDebug(id); + this.adapter.sendTo(from, command, {debugDevices:result}, callback) + } + } + + async getDebugDevices(from, command, msg, callback) { + if (this.stController) { + this.warn("get Debug Devices Called"); + this.stController.getDebugDevices((debugDevices) => this.adapter.sendTo(from, command, {debugDevices:debugDevices}, callback)); + } + } + async reconfigure(from, command, msg, callback) { if (this.zbController) { const devid = getZbId(msg.id); diff --git a/lib/devices.js b/lib/devices.js index 652c96b2..8c3ce0ac 100644 --- a/lib/devices.js +++ b/lib/devices.js @@ -600,16 +600,6 @@ function states_with_epname(entity, states) { } const devices = [ - /* { - models: ['WXKG01LM'], - icon: 'img/xiaomi_wireless_switch.png', - states: [ - states.click, states.double_click, states.triple_click, states.quad_click, - states.many_click, states.long_click, states.voltage, states.battery, - states.long_press, - ], - }, - */ { models: ['WXKG11LM'], icon: 'img/aqara_switch.png', diff --git a/lib/exposes.js b/lib/exposes.js index 3b2b150f..aaf8ced3 100644 --- a/lib/exposes.js +++ b/lib/exposes.js @@ -16,6 +16,7 @@ function genState(expose, role, name, desc) { const stateId = stname.replace(/\*/g, ''); const stateName = (desc || expose.description || expose.name); const propName = expose.property; + // 'switch' | 'lock' | 'binary' | 'list' | 'numeric' | 'enum' | 'text' | 'composite' | 'light' | 'cover' | 'fan' | 'climate'; switch (expose.type) { case 'binary': state = { @@ -671,6 +672,7 @@ function createFromExposes(model, def) { case 'enum': switch (expose.name) { + case 'action': { // Ansatz: @@ -681,12 +683,15 @@ function createFromExposes(model, def) { if (!Array.isArray(expose.values)) break; const hasHold = expose.values.find((actionName) => actionName.includes('hold')); const hasRelease = expose.values.find((actionName) => actionName.includes('release')); + const hasPress = expose.values.find((actionName) => actionName.includes('press')); + const hasPressRelease = expose.values.find((actionName) => actionName.includes('press_release')); for (const actionName of expose.values) { // is release state ? - skip if (hasHold && hasRelease && actionName.includes('release')) continue; // is hold state ? if (hasHold && hasRelease && actionName.includes('hold')) { const releaseActionName = actionName.replace('hold', 'release'); + const releaseActionName2 = actionName.concat('_release'); state = { id: actionName.replace(/\*/g, ''), prop: 'action', @@ -696,9 +701,11 @@ function createFromExposes(model, def) { write: false, read: true, type: 'boolean', - getter: payload => payload.action === actionName ? true : (payload.action === releaseActionName ? false : undefined), + getter: payload => payload.action === actionName ? true : (payload.action === releaseActionName || payload.action === releaseActionName2 ? false : undefined), }; - } else { + } else if (hasPress && hasPressRelease && actionName.includes('press')) { + let getterKey = actionName.concat('_release'); + if (expose.values.indexOf(getterKey) < 0) getterKey = actionName; state = { id: actionName.replace(/\*/g, ''), prop: 'action', @@ -708,10 +715,23 @@ function createFromExposes(model, def) { write: false, read: true, type: 'boolean', + getter: payload => payload.action === getterKey ? true : undefined, + isEvent: true, + }; + } else { + state = { + id: actionName.replace(/\*/g, ''), + prop: 'action', + name: actionName, + icon: undefined, + role: 'button', + write: false, + read: true, + type: 'boolean', getter: payload => payload.action === actionName ? true : undefined, isEvent: true, }; - } + }; pushToStates(state, expose.access); } state = null; diff --git a/lib/statescontroller.js b/lib/statescontroller.js index 9e5bde34..a0b10d8a 100644 --- a/lib/statescontroller.js +++ b/lib/statescontroller.js @@ -64,7 +64,7 @@ class StatesController extends EventEmitter { }, 5000); } - getDebugDevices() { + getDebugDevices(callback) { this.debugDevices = []; this.adapter.getState(`${this.adapter.namespace}.info.debugmessages`, (err, state) => { if (state) { @@ -72,11 +72,29 @@ class StatesController extends EventEmitter { this.debugDevices = state.val.split(';'); } this.info(`debug devices set to ${JSON.stringify(this.debugDevices)}`); + if (callback) callback(this.debugDevices); } }); } + async toggleDeviceDebug(id) { + const arr = /zigbee.[0-9].([^.]+)/gm.exec(id); + if (arr[1] === undefined) { + this.warn(`unable to extract id from state ${id}`); + return []; + } + const stateKey = arr[1]; + this.warn('statekey is ' + stateKey + ', arr is ' + JSON.stringify(arr) + ' id was ' + id); + if (typeof (this.debugDevices) != 'array') this.getDebugDevices(() => { + const idx = this.debugDevices.indexOf(stateKey); + if (idx < 0) this.debugDevices.push(stateKey); + else this.debugDevices.splice(idx, 1); + this.adapter.setState(`${this.adapter.namespace}.info.debugmessages`, this.debugDevices.join(';')); + return this.debugDevices; + }); + } + checkDebugDevice(dev) { if (typeof dev != 'string' || dev == '') return false; if (this.debugDevices === undefined) { @@ -596,6 +614,17 @@ class StatesController extends EventEmitter { statesMapping.fillStatesWithExposes(allExcludesObj); } + async elevatedMessage(device, message, isError) { + if (isError) this.error(message); else this.warn(message); + // emit data here for debug tab later + + } + + async elevatedDebugMessage(id, message, isError) { + if (isError) this.error(message); else this.warn(message); + this.emit('debugmessage', {id: id, message:message}); + } + async publishToState(devId, model, payload) { const devStates = await this.getDevStates(`0x${devId}`, model); @@ -603,10 +632,11 @@ class StatesController extends EventEmitter { if (has_elevated_debug) { - this.warn(`ELEVATED I01: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`); + this.elevatedMessage(devId, `ELEVATED I01: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`, false); } if (!devStates) { - if (has_debug) this.error(`ELEVATED IE02: no device states for device ${devId} type '${model}'`) + if (has_elevated_debug) + this.elevatedMessage(devId, `ELEVATED IE02: no device states for device ${devId} type '${model}'`, true) return; } // find states for payload @@ -634,8 +664,8 @@ class StatesController extends EventEmitter { let stateID = statedesc.id; - if (has_elevated_debug && statedesc.id !== 'msg_from_zigbee') { - this.warn(`ELEVATED I02: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`); + if (has_elevated_debug) { + this.elevatedMessage(devId, `ELEVATED I02: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`, false); } const common = { @@ -678,16 +708,16 @@ class StatesController extends EventEmitter { } catch (e) { this.debug(`No states in device ${devId} : payload ${JSON.stringify(payload)}`); if (has_elevated_debug) - this.error(`ELEVATED IE03: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`); + this.elevatedMessage(devId, `ELEVATED IE03: error when enumerating states of ${devId} for payload ${JSON.stringify(payload)}, ${(e ? e.name : 'undefined')} (${(e ? e.message : '')}).`, true); } if (!has_published && has_elevated_debug) { - this.error(`ELEVATED IE04: No value published for device ${devId}`); + this.elevatedMessage(devId, `ELEVATED IE04: No value published for device ${devId}`, true); } } else { if (has_elevated_debug) - this.error(`ELEVATED IE05: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`); + this.elevatedMessage(devId, `ELEVATED IE05: No states matching the payload ${JSON.stringify(payload)} for device ${devId}`, true); } } diff --git a/lib/utils.js b/lib/utils.js index 57a584ae..e860e746 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -157,7 +157,7 @@ exports.decimalToHex = decimalToHex; exports.getZbId = getZbId; exports.getAdId = getAdId; exports.getModelRegEx = getModelRegEx; -exports.isRouter = device => (device.type === 'Router' || device.powerSource.startsWith('Mains')) && !forceEndDevice.includes(device.modelID); +exports.isRouter = device => (device.type === 'Router' || (typeof device.powerSource == 'string' && device.powerSource.startsWith('Mains'))) && !forceEndDevice.includes(device.modelID); exports.isBatteryPowered = device => device.powerSource && device.powerSource === 'Battery'; exports.isXiaomiDevice = device => device.modelID !== 'lumi.router' && diff --git a/package.json b/package.json index 409ead3a..5b20f7de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iobroker.zigbee", - "version": "1.11.1", + "version": "1.11.2", "author": { "name": "Kirov Ilya", "email": "kirovilya@gmail.com" @@ -28,8 +28,8 @@ "ajv": "^8.17.1", "uri-js": "^4.4.1", "typescript": "^5.6.3", - "zigbee-herdsman": "3.2.0", - "zigbee-herdsman-converters": "21.11.0" + "zigbee-herdsman": "^3.2.0", + "zigbee-herdsman-converters": "^21.11.0" }, "description": "Zigbee devices", "devDependencies": { From 2308bc4133ecd1dbae28be78302aa41abb611214 Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Sun, 12 Jan 2025 19:24:01 +0100 Subject: [PATCH 11/19] Update package-lock --- admin/img/PTM 215Z.png | Bin 0 -> 20796 bytes admin/img/ZY-M100-24GV3.png | Bin 0 -> 58978 bytes package-lock.json | 119 +++++++++++------------------------- 3 files changed, 37 insertions(+), 82 deletions(-) create mode 100644 admin/img/PTM 215Z.png create mode 100644 admin/img/ZY-M100-24GV3.png diff --git a/admin/img/PTM 215Z.png b/admin/img/PTM 215Z.png new file mode 100644 index 0000000000000000000000000000000000000000..4b57d5a0327a0e49668ebde74ce61d4efe7e4aee GIT binary patch literal 20796 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

(EZW(Sm}pb8nT(bV^B?9}KCW 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 V4KMdhz}h1K+`Mk8U#9CA@jnjjexv{Z literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index b464bb25..749e5938 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "iobroker.zigbee", - "version": "1.11.1", + "version": "1.11.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "iobroker.zigbee", - "version": "1.11.1", + "version": "1.11.2", "license": "MIT", "dependencies": { "@iobroker/adapter-core": "^3.2.2", @@ -16,8 +16,8 @@ "tar": "^7.4.3", "typescript": "^5.6.3", "uri-js": "^4.4.1", - "zigbee-herdsman": "3.2.0", - "zigbee-herdsman-converters": "21.11.0" + "zigbee-herdsman": "^3.2.0", + "zigbee-herdsman-converters": "^21.11.0" }, "devDependencies": { "@alcalzone/release-script": "^3.8.0", @@ -685,9 +685,10 @@ } }, "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "license": "MIT" }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", @@ -1033,17 +1034,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -1364,7 +1354,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/atob": { "version": "2.1.2", @@ -1382,17 +1373,13 @@ "version": "1.7.7", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dev": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, - "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" - }, "node_modules/bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -1525,9 +1512,10 @@ "dev": true }, "node_modules/bonjour-service": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", - "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" @@ -1935,6 +1923,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2158,6 +2147,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -2184,6 +2174,7 @@ "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "license": "MIT", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, @@ -2859,11 +2850,6 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3018,6 +3004,7 @@ "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "dev": true, "funding": [ { "type": "individual", @@ -3084,6 +3071,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3763,18 +3751,6 @@ "entities": "^2.0.0" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -4703,6 +4679,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -4711,6 +4688,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -5084,6 +5062,7 @@ "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "license": "MIT", "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" @@ -5833,7 +5812,8 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "node_modules/pump": { "version": "2.0.1", @@ -5864,11 +5844,6 @@ "node": ">=6" } }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -6774,15 +6749,6 @@ "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", "dev": true }, - "node_modules/streamx": { - "version": "2.15.6", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz", - "integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==", - "dependencies": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" - } - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -6947,16 +6913,6 @@ "node": ">=18" } }, - "node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, "node_modules/tar/node_modules/mkdirp": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", @@ -7006,7 +6962,8 @@ "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "license": "MIT" }, "node_modules/time-stamp": { "version": "1.1.0", @@ -7669,14 +7626,15 @@ } }, "node_modules/zigbee-herdsman": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/zigbee-herdsman/-/zigbee-herdsman-2.1.9.tgz", - "integrity": "sha512-MXutgNp83F84sjhNfGPangv0uJD8zRtpRMjd7Xrr6hmKUgBwvdN/xQ/SfqpOarIqJaUNSO5T0Q2N13XPw2ukzQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/zigbee-herdsman/-/zigbee-herdsman-3.2.2.tgz", + "integrity": "sha512-BMhgUExzZBmh3gbfmsGAnRX2WfbNeNIj9hJT/Z3FixOSszFY0seBclzFweTxEVBS/oV9O+TsKemKsUww102KzQ==", + "license": "MIT", "dependencies": { "@serialport/bindings-cpp": "^12.0.1", "@serialport/parser-delimiter": "^12.0.0", "@serialport/stream": "^12.0.0", - "bonjour-service": "^1.2.1", + "bonjour-service": "^1.3.0", "debounce": "^2.2.0", "fast-deep-equal": "^3.1.3", "mixin-deep": "^2.0.1", @@ -7684,18 +7642,15 @@ } }, "node_modules/zigbee-herdsman-converters": { - "version": "20.58.0", - "resolved": "https://registry.npmjs.org/zigbee-herdsman-converters/-/zigbee-herdsman-converters-20.58.0.tgz", - "integrity": "sha512-TqBdR0W7PRO4M+s1IjcmakuIMdG3I+9daK/10kLw1oAwSW/JZEav094HgPn+a4AdMk2FW9m8LhCRtyKVJC4y8w==", + "version": "21.16.0", + "resolved": "https://registry.npmjs.org/zigbee-herdsman-converters/-/zigbee-herdsman-converters-21.16.0.tgz", + "integrity": "sha512-F0HCgla8OTd7ZVlDJRmBcxTrgre8XMiMl/BO0byrPlFmmbvZwZ2aklCeGQjLXnj5hv3tMIQIbt1c6uEbP5ruJQ==", + "license": "MIT", "dependencies": { - "axios": "^1.7.7", "buffer-crc32": "^1.0.0", - "https-proxy-agent": "^7.0.5", "iconv-lite": "^0.6.3", "semver": "^7.6.3", - "tar-stream": "^3.1.7", - "uri-js": "^4.4.1", - "zigbee-herdsman": "^2.1.9" + "zigbee-herdsman": "^3.2.1" } } } From 0aa1636c7fbc2c17ce0e615591dfbf2b8087da6a Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:07:07 +0100 Subject: [PATCH 12/19] Update main.js --- main.js | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/main.js b/main.js index a1c9700c..f7fe0406 100644 --- a/main.js +++ b/main.js @@ -625,7 +625,8 @@ class Zigbee extends utils.Adapter { }); } - async publishFromState(deviceId, model, stateModel, stateList, options) { + + async publishFromState(deviceId, model, stateModel, stateList, options) { let isGroup = false; const has_elevated_debug = this.stController.checkDebugDevice(deviceId) @@ -654,7 +655,10 @@ class Zigbee extends utils.Adapter { if (!mappedModel.toZigbee) { - this.log.error(`No toZigbee in mapped model for ${model}`); + if (has_elevated_debug) + this.log.error(`ELEVATED OE01: No toZigbee in mapped model ${model}`) + else + this.log.error(`No toZigbee in mapped model for ${model}`); return; } @@ -733,7 +737,10 @@ class Zigbee extends utils.Adapter { for (const c of mappedModel.toZigbee) { if (!c.hasOwnProperty('convertSet')) continue; - this.log.debug(`Type of toZigbee is '${typeof c}', Contains key ${(c.hasOwnProperty('key')?JSON.stringify(c.key):'false ')}`) + if (has_elevated_debug) + this.log.warn(`EX Type of toZigbee is '${typeof c}', Contains key ${(c.hasOwnProperty('key')?JSON.stringify(c.key):'false ')}`) + else + this.log.debug(`Type of toZigbee is '${typeof c}', Contains key ${(c.hasOwnProperty('key')?JSON.stringify(c.key):'false ')}`) if (!c.hasOwnProperty('key')) { if (c.hasOwnProperty('convertSet') && converter === undefined) @@ -763,6 +770,7 @@ class Zigbee extends utils.Adapter { this.sendError(`No converter available for '${model}' with key '${stateDesc.id}' `); return; } + if (has_elevated_debug) this.log.warn('EX: converter found for ' + JSON.stringify(converter.key)); const preparedValue = (stateDesc.setter) ? stateDesc.setter(value, options) : value; const preparedOptions = (stateDesc.setterOpt) ? stateDesc.setterOpt(value, options) : {}; @@ -779,9 +787,10 @@ class Zigbee extends utils.Adapter { const epName = stateDesc.epname !== undefined ? stateDesc.epname : (stateDesc.prop || stateDesc.id); const key = stateDesc.setattr || stateDesc.prop || stateDesc.id; - this.log.debug(`convert ${key}, ${safeJsonStringify(preparedValue)}, ${safeJsonStringify(preparedOptions)}`); - if (has_elevated_debug) this.log.warn(`ELEVATED O4: convert ${key}, ${safeJsonStringify(preparedValue)}, ${safeJsonStringify(preparedOptions)} for device ${deviceId} with Endpoint ${epName}`); - + if (has_elevated_debug) + this.log.warn(`ELEVATED O4: convert ${key}, ${safeJsonStringify(preparedValue)}, ${safeJsonStringify(preparedOptions)} for device ${deviceId} with Endpoint ${epName}`); + else + this.log.debug(`convert ${key}, ${safeJsonStringify(preparedValue)}, ${safeJsonStringify(preparedOptions)}`); let target; if (model === 'group') { target = entity.mapped; @@ -790,7 +799,10 @@ class Zigbee extends utils.Adapter { target = target.endpoint; } - this.log.debug(`target: ${safeJsonStringify(target)}`); + if (has_elevated_debug) + this.log.warn(`EX: target is ${safeJsonStringify(target)}`); + else + this.log.debug(`target: ${safeJsonStringify(target)}`); const meta = { endpoint_name: epName, @@ -818,9 +830,12 @@ class Zigbee extends utils.Adapter { } try { + if (has_elevated_debug) this.log.warn(`ELEVATED OX: calling convertSet with Parameters ${safeJsonStringify(target)},${safeJsonStringify(key)},${safeJsonStringify(preparedValue)},${safeJsonStringify(meta)}`) const result = await converter.convertSet(target, key, preparedValue, meta); - this.log.debug(`convert result ${safeJsonStringify(result)}`); - if (has_elevated_debug) this.log.warn(`ELEVATED O05: convert result ${safeJsonStringify(result)} sent to device ${deviceId}`); + if (has_elevated_debug) + this.log.warn(`ELEVATED O05: convert result ${safeJsonStringify(result)} sent to device ${deviceId}`); + else + this.log.debug(`convert result ${safeJsonStringify(result)}`); if (result !== undefined) { if (stateModel && !isGroup) { this.acknowledgeState(deviceId, model, stateDesc, value); From 0571f4859ce772f1340f6d41552528b7d82a218a Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:36:49 +0100 Subject: [PATCH 13/19] Update main.js --- main.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/main.js b/main.js index f7fe0406..5df16e90 100644 --- a/main.js +++ b/main.js @@ -786,9 +786,12 @@ class Zigbee extends utils.Adapter { } const epName = stateDesc.epname !== undefined ? stateDesc.epname : (stateDesc.prop || stateDesc.id); + const key = stateDesc.setattr || stateDesc.prop || stateDesc.id; - if (has_elevated_debug) + if (has_elevated_debug) { + const epmsg = (stateDesc.epname !== undefined ? ' Endpoint '+epName : ' unnamed Endpoint'); this.log.warn(`ELEVATED O4: convert ${key}, ${safeJsonStringify(preparedValue)}, ${safeJsonStringify(preparedOptions)} for device ${deviceId} with Endpoint ${epName}`); + } else this.log.debug(`convert ${key}, ${safeJsonStringify(preparedValue)}, ${safeJsonStringify(preparedOptions)}`); let target; @@ -797,6 +800,7 @@ class Zigbee extends utils.Adapter { } else { target = await this.zbController.resolveEntity(deviceId, epName); target = target.endpoint; + EPID=target.ID } if (has_elevated_debug) @@ -830,7 +834,17 @@ class Zigbee extends utils.Adapter { } try { - if (has_elevated_debug) this.log.warn(`ELEVATED OX: calling convertSet with Parameters ${safeJsonStringify(target)},${safeJsonStringify(key)},${safeJsonStringify(preparedValue)},${safeJsonStringify(meta)}`) + if (has_elevated_debug) { + let metastring = ['{']; + for (const prop in meta) { + if (prop != 'device') + metastring.push(`${prop}: ${JSON.stringify(meta.prop)}`); + else + metastring.push(`${prop}: "zigbee device"`); + } + metastring.push('}'); + this.log.warn(`ELEVATED OX: calling convertSet with Parameters ${safeJsonStringify(target)},${safeJsonStringify(key)},${safeJsonStringify(preparedValue)},${safeJsonStringify(metastring.join(','))}`); + } const result = await converter.convertSet(target, key, preparedValue, meta); if (has_elevated_debug) this.log.warn(`ELEVATED O05: convert result ${safeJsonStringify(result)} sent to device ${deviceId}`); From ccf6062bddbaa2867bd1dd15af5be2e6f562db63 Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:40:34 +0100 Subject: [PATCH 14/19] Update main.js --- main.js | 1 - 1 file changed, 1 deletion(-) diff --git a/main.js b/main.js index 5df16e90..1fcfabcb 100644 --- a/main.js +++ b/main.js @@ -800,7 +800,6 @@ class Zigbee extends utils.Adapter { } else { target = await this.zbController.resolveEntity(deviceId, epName); target = target.endpoint; - EPID=target.ID } if (has_elevated_debug) From c4b5bacafeaa018162716798118d9e10d6139fd1 Mon Sep 17 00:00:00 2001 From: asgothian <45667167+asgothian@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:38:49 +0100 Subject: [PATCH 15/19] 1.11.2 - overrides 1 --- admin/admin.js | 172 +++++++++++++--- admin/index_m.html | 35 +++- admin/tab_m.html | 33 ++- lib/binding.js | 2 +- lib/commands.js | 61 +++++- lib/devices.js | 46 +++-- lib/exclude.js | 84 +++----- lib/exposes.js | 2 + lib/groups.js | 18 +- lib/localConfig.js | 374 ++++++++++++++++++++++++++++++++++ lib/statescontroller.js | 164 +++++++++++---- lib/utils.js | 2 +- lib/zbDeviceAvailability.js | 21 +- lib/zbDeviceEvent.js | 15 +- lib/zigbeecontroller.js | 393 +++++++++++++++--------------------- main.js | 104 +++------- 16 files changed, 1057 insertions(+), 469 deletions(-) create mode 100644 lib/localConfig.js diff --git a/admin/admin.js b/admin/admin.js index 0ff0c4bd..85cea7dc 100644 --- a/admin/admin.js +++ b/admin/admin.js @@ -8,6 +8,7 @@ const Materialize = (typeof M !== 'undefined') ? M : Materialize, namespace = 'zigbee.' + instance, namespaceLen = namespace.length; let devices = [], + debugDevices = [], messages = [], map = {}, mapEdges = null, @@ -94,7 +95,7 @@ function getLQICls(value) { if (value < 20) return 'icon-red'; if (value < 50) return 'icon-orange'; } - return ''; + return 'icon-green'; } @@ -173,7 +174,7 @@ function getGroupCard(dev) { info = info.concat(` `); const image = ``; - const dashCard = getDashCard(dev, `img/group_${memberCount}.png`); + const dashCard = getDashCard(dev, `img/group_${memberCount}.png`, memberCount > 0); const card = `

${dashCard}
@@ -199,6 +200,9 @@ function getGroupCard(dev) { +
@@ -217,10 +221,12 @@ function getCard(dev) { id = dev._id, type = (dev.common.type ? dev.common.type : 'unknown'), type_url = (dev.common.type ? sanitizeModelParameter(dev.common.type) : 'unknown'), - img_src = dev.icon || dev.common.icon, + img_src = dev.common.icon || dev.icon, rooms = [], isActive = (dev.common.deactivated ? false : true), - lang = systemLang || 'en'; + lang = systemLang || 'en', + ieee = id.replace(namespace + '.', ''), + isDebug = checkDebugDevice(ieee); for (const r in dev.rooms) { if (dev.rooms[r].hasOwnProperty(lang)) { rooms.push(dev.rooms[r][lang]); @@ -228,6 +234,7 @@ function getCard(dev) { rooms.push(dev.rooms[r]); } } + console.warn('debug for ' + ieee + ' is ' + isDebug); const room = rooms.join(',') || ' '; const paired = (dev.paired) ? '' : 'leak_remove'; const rid = id.split('.').join('_'); @@ -237,18 +244,19 @@ function getCard(dev) { battery_cls = (isActive ? getBatteryCls(dev.battery) : ''), lqi_cls = getLQICls(dev.link_quality), battery = (dev.battery && isActive) ? `
battery_std
${dev.battery}
` : '', - lq = (dev.link_quality > 0 && isActive) ? `
network_check
` : '', - status = (dev.link_quality > 0 && isActive) ? `
check_circle
` : (isActive ? `
leak_remove
` : ''), + lq = (dev.link_quality > 0) ? `
network_check
` + : `
leak_remove
`, + status = (isActive ? lq : `
cancel
`), info = `
    -
  • ieee:0x${id.replace(namespace + '.', '')}
  • +
  • ieee:0x${ieee}
  • nwk:${(nwk) ? nwk.toString() + ' (0x' + nwk.toString(16) + ')' : ''}
  • model:${modelUrl}
  • groups:${dev.groupNames || ''}
`, - permitJoinBtn = (dev.info && dev.info.device._type == 'Router') ? '' : '', deactBtn = ``, + debugBtn = ``, infoBtn = (nwk) ? `` : ''; const dashCard = getDashCard(dev); const card = `
@@ -259,7 +267,7 @@ function getCard(dev) {
${battery} - ${lq} + ${status} @@ -274,17 +282,20 @@ function getCard(dev) { ${infoBtn} ${room} - - + ${deactBtn} - ${permitJoinBtn} + ${debugBtn}
@@ -616,6 +627,17 @@ function showDevices() { const name = getDevName(dev_block); editName(id, name); }); + $('.card-reveal-buttons button[name=\'swapdebug\']').click(function () { + const dev_block = $(this).parents('div.device'); + const id = getDevId(dev_block); + const name = getDevName(dev_block); + toggleDebugDevice(id, name); + }); + $('.card-reveal-buttons button[name=\'swapimage\']').click(function () { + const dev_block = $(this).parents('div.device'); + const id = getDevId(dev_block); + selectImageOverride(id); + }); $('.card-reveal-buttons button[name=\'editgrp\']').click(function () { const dev_block = $(this).parents('div.device'); const id = dev_block.attr('id').replace(namespace + '.group_', ''); @@ -751,9 +773,97 @@ function getCoordinatorInfo() { } }); } +function checkDebugDevice(id) { + if (debugDevices.indexOf(id) > -1) return 0 + for (const addressPart of debugDevices) { + if (typeof id === 'string' && id.includes(addressPart)) { + return debugDevices.indexOf(addressPart)+1; + } + } + return -1; +} +async function toggleDebugDevice(id) { + sendTo(namespace, 'setDeviceDebug', {id:id}, function (msg) { + sendTo(namespace, 'getDebugDevices', {}, function(msg) { + if (msg && typeof (msg.debugDevices == 'array')) { + debugDevices = msg.debugDevices; + } + else + debugDevices = []; + }); + console.warn('toggleDebugDevices.result ' + JSON.stringify(debugDevices)); + showDevices(); + }); +} + +/* +sendTo(namespace, 'getLocalImages', {}, function(msg) { + if (msg && msg.imageData) { +// const element = $('#localimages'); + console.warn('imageData length is ' + msg.imageData.length); + localImages = msg.imageData; + } +}); +*/ + +function updateDeviceImage(device, image, global) { + sendTo(namespace, 'updateDeviceImage', {target: device, image: image, global:global}, function(msg) { + if (msg && msg.hasOwnProperty.error) { + showMessage(msg.error, _('Error')); + } + getDevices((global ? '':device)); + }); +} + +async function selectImageOverride(id) { + const dev = devices.find((d) => d._id == id); + let localImages = undefined; + const selectItems= ['']; + sendTo(namespace, 'getLocalImages', {}, function(msg) { + if (msg && msg.imageData) { + // const element = $('#localimages'); + const imagedata = msg.imageData; + imagedata.push( { file:'none', name:'default', data:dev.common.icon || dev.icon}) + + list2select('#images', imagedata, selectItems, + function (key, image) { + return image.name + }, + function (key, image) { + return image.file; + }, + function (key, image) { + if (image.file == 'none') { + return `data-icon="${image.data}"`; + } else { + return `data-icon="data:image/png; base64, ${image.data}"`; + } + }, + ); + + $('#modaledit a.btn[name=\'save\']').unbind('click'); + $('#modaledit a.btn[name=\'save\']').click(() => { + const image = $('#chooseimage').find('#images option:selected').val(); + const global = $('#chooseimage').find('#globaloverride').prop('checked'); + updateDeviceImage(id, image, global); + // console.warn(`selected image file name is ${newName}`); + // updateDev(id, newName, groupsbyid); + }); + $('#chooseimage').modal('open'); + Materialize.updateTextFields(); + } + }); +} function getDevices() { getCoordinatorInfo(); + sendTo(namespace, 'getDebugDevices', {}, function(msg) { + if (msg && typeof (msg.debugDevices == 'array')) { + debugDevices = msg.debugDevices; + } + else + debugDevices = []; + }); sendTo(namespace, 'getDevices', {}, function (msg) { if (msg) { if (msg.error) { @@ -794,14 +904,26 @@ function getMap() { }); } +function getRandomExtPanID() +{ + const bytes = []; + for (var i = 0;i<16;i++) { + bytes.push(Math.floor(Math.random() * 16).toString(16)); + } + return bytes.join(''); +} + + // the function loadSettings has to exist ... function load(settings, onChange) { if (settings.panID === undefined) { - settings.panID = 6754; +// settings.panID = 6754; + settings.panID = Math.floor(Math.random() * 10000); } if (settings.extPanID === undefined) { - settings.extPanID = 'DDDDDDDDDDDDDDDD'; +// settings.extPanID = 'DDDDDDDDDDDDDDDD'; + settings.extPanID = getRandomExtPanID(); } // fix for previous wrong value if (settings.extPanID === 'DDDDDDDDDDDDDDD') { @@ -844,11 +966,12 @@ function load(settings, onChange) { } } + getComPorts(onChange); //dialog = new MatDialog({EndingTop: '50%'}); getDevices(); - getMap(); + //getMap(); //addCard(); // Signal to admin, that no changes yet @@ -1065,7 +1188,7 @@ socket.on('stateChange', function (id, state) { socket.on('objectChange', function (id, obj) { if (id.substring(0, namespaceLen) !== namespace) return; //console.log('objectChange', id, obj); - if (obj && obj.type == 'device' && obj.common.type !== 'group') { + if (obj && obj.type == 'device') { // && obj.common.type !== 'group') { updateDevice(id); } if (!obj) { @@ -1128,7 +1251,7 @@ function showNetworkMap(devices, map) { label: (dev.link_quality > 0 ? dev.common.name : `${dev.common.name}\n(disconnected)`), title: dev._id.replace(namespace + '.', '') + extInfo, shape: 'circularImage', - image: dev.icon, + image: dev.common.icon || dev.icon, imagePadding: {top: 5, bottom: 5, left: 5, right: 5}, color: {background: 'white', highlight: {background: 'white'}}, font: {color: '#007700'}, @@ -2827,11 +2950,11 @@ function sortByTitle(element) { return element.querySelector('.card-title').textContent.toLowerCase().trim(); } -function getDashCard(dev, groupImage) { +function getDashCard(dev, groupImage, groupstatus) { const title = dev.common.name, id = dev._id, type = dev.common.type, - img_src = (groupImage ? groupImage : dev.icon || dev.common.icon), + img_src = (groupImage ? groupImage : dev.common.icon || dev.icon), isActive = !dev.common.deactivated, rooms = [], lang = systemLang || 'en'; @@ -2842,11 +2965,13 @@ function getDashCard(dev, groupImage) { nwk = (dev.info && dev.info.device) ? dev.info.device._networkAddress : undefined, battery_cls = getBatteryCls(dev.battery), lqi_cls = getLQICls(dev.link_quality), + unconnected_icon = (groupImage ? (groupstatus ? '
check_circle
' : '
cancel
') :'
leak_remove
') battery = (dev.battery && isActive) ? `
battery_std
${dev.battery}
` : '', - lq = (dev.link_quality > 0 && isActive) ? `
network_check
` : (isActive ? '
check_circle
' : ''), - status = (dev.link_quality > 0 && isActive) ? `
check_circle
` : (groupImage || !isActive ? '' : `
leak_remove
`), - permitJoinBtn = (isActive && dev.info && dev.info.device._type === 'Router') ? '' : '', - infoBtn = (nwk) ? `` : '', + lq = (dev.link_quality > 0 && isActive) ? `
network_check
` + : (isActive ? unconnected_icon : ''), + //status = (dev.link_quality > 0 && isActive) ? `
check_circle
` : (groupImage || !isActive ? '' : `
leak_remove
`), + //permitJoinBtn = (isActive && dev.info && dev.info.device._type === 'Router') ? '' : '', + //infoBtn = (nwk) ? `` : '', idleTime = (dev.link_quality_lc > 0 && isActive) ? `
access_time
` : ''; const info = (dev.statesDef) ? dev.statesDef.map((stateDef) => { const id = stateDef.id; @@ -2888,7 +3013,6 @@ function getDashCard(dev, groupImage) { ${idleTime} ${battery} ${lq} - ${status} ${title} diff --git a/admin/index_m.html b/admin/index_m.html index 82fa0c52..f3a05942 100644 --- a/admin/index_m.html +++ b/admin/index_m.html @@ -690,13 +690,11 @@
Zigbee adapter
+ + - + -