From aebab5f692cbae4fc941fd72c4625eae85484831 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Sun, 23 Oct 2022 21:35:05 +0200 Subject: [PATCH 01/21] feat(platform): ignore devices in auto-discovery - implements #497 - ignore devices via their MAC address --- config.schema.json | 12 ++++++++++++ src/platform.ts | 9 ++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/config.schema.json b/config.schema.json index 236bade..d95813e 100644 --- a/config.schema.json +++ b/config.schema.json @@ -104,6 +104,18 @@ } } } + }, + "ignore": { + "title": "Ignored Devices", + "description": "Devices in this list will be excluded when found via auto-discovery.", + "type": "array", + "items": { + "mac": { + "title": "MAC-Address", + "type": "string", + "pattern": "^([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}$" + } + } } } } diff --git a/src/platform.ts b/src/platform.ts index 7240961..1bef398 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -751,7 +751,14 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { const mac: string = this.byteToHexString(msg.subarray(0, 6)); const deviceSuffix: string = mac.substr(6, 6); - if (!this.discovered.has(mac)) { + // Check if already discovered, and if not ignored + // Implements #497 + if ( + !this.discovered.has(mac) && + this.config.ignore && + this.config.ignore.findIndex((i: { mac: string }) => i.mac === mac) === + -1 + ) { switch (t) { case DeviceTypes.MYSTROM_BUTTON_PLUS: throw new DeviceNotImplementedError( From 6ce261e92d57a115c6362e9a45a0b4131ea1c6c3 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 14:49:10 +0200 Subject: [PATCH 02/21] style(dingz): :art: fix typo --- src/platform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform.ts b/src/platform.ts index 1bef398..da4ecef 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -269,7 +269,7 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { token?: string; existingAccessory?: PlatformAccessory; }): Promise { - // Run a diacovery of changed things every 10 seconds + // Run a discovery of changed things every 10 seconds this.log.debug( `addDingzDevice() --> Add configured device -> ${name} (${address})`, ); From 3edf342ec5cdb4daa087a21b9e6e543b6f692108 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 14:51:33 +0200 Subject: [PATCH 03/21] feat(dingz): :sparkles: implement device ignore list - implement #497 --- config.schema.json | 33 ++++--- src/platform.ts | 239 ++++++++++++++++++++++++++------------------- 2 files changed, 161 insertions(+), 111 deletions(-) diff --git a/config.schema.json b/config.schema.json index d95813e..676163a 100644 --- a/config.schema.json +++ b/config.schema.json @@ -46,6 +46,27 @@ "type": "string", "description": "Set a global authentication token. This will be used for auto-discovery on the local network and as default token for manually specified devices." }, + "ignore": { + "title": "Ignored Devices", + "description": "Devices in this list will be excluded when found via auto-discovery.", + "type": "array", + "items": { + "title": "Device", + "type": "object", + "properties": { + "mac": { + "title": "MAC-Address (without `:`)", + "type": "string", + "pattern": "^([A-Fa-f0-9]{2}){5}[A-Fa-f0-9]{2}$", + "required": true + }, + "comment": { + "title": "Comment", + "type": "string" + } + } + } + }, "callbackHostname": { "title": "Hostname / IP to use for button callbacks ", "type": "string", @@ -104,18 +125,6 @@ } } } - }, - "ignore": { - "title": "Ignored Devices", - "description": "Devices in this list will be excluded when found via auto-discovery.", - "type": "array", - "items": { - "mac": { - "title": "MAC-Address", - "type": "string", - "pattern": "^([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}$" - } - } } } } diff --git a/src/platform.ts b/src/platform.ts index da4ecef..dc098ac 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -83,6 +83,7 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { // this is used to track restored cached accessories public accessories: AccessoryTypes = {}; private discovered = new Map(); + private ignored = new Map(); private readonly app: e.Application = e(); constructor( @@ -92,6 +93,23 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { ) { axiosRetry(axios, { retries: 5, retryDelay: axiosRetry.exponentialDelay }); + // Adds ignored devices from Config + if (this.config.ignore) { + for (const device of this.config.ignore) { + // Only add to map if mac is set + if (device.mac) { + const mac = device.mac.toUpperCase(); + this.ignored.set(mac, device?.comment || ''); + this.log.info( + chalk.redBright('[Platform]'), + `Will be ignoring device ${ + device?.comment || '' + } with MAC Address ${mac}`, + ); + } + } + } + // When this event is fired it means Homebridge has restored all cached accessories from disk. // Dynamic Platform plugins should only register new accessories after this event was fired, // in order to ensure they weren't added to homebridge already. This event can also be used @@ -107,9 +125,7 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { this.addDevices(); } // Discovers devices from UDP - if (this.config.autoDiscover) { - this.setupDeviceDiscovery(); - } + this.setupDeviceDiscovery(); // set-up the callback server ... this.callbackServer(); @@ -147,6 +163,13 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { const context = accessory.context; if (context.device && context.device.accessoryClass) { + if (this.ignored.has(context.device.mac.toUpperCase())) { + this.log.warn( + chalk.redBright('[Platform]'), + 'Cached accessory is in list of ignored devices, but will continue loading it. Consider manually removing it from Homebridge.', + ); + } + this.log.debug( 'Restoring accessory of class ->', context.device.accessoryClass, @@ -739,7 +762,12 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { } } - private datagramMessageHandler(msg: Uint8Array, remoteInfo: RemoteInfo) { + /** + * Parses and interprets auto-discovery messages + * @param msg + * @param remoteInfo + */ + private autoDiscoveryMessageHandler(msg: Uint8Array, remoteInfo: RemoteInfo) { // const mac: string = dataBuffer.toString('hex', 0, 6); try { @@ -751,108 +779,121 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { const mac: string = this.byteToHexString(msg.subarray(0, 6)); const deviceSuffix: string = mac.substr(6, 6); + // If auto-discovery is disabled, we will return, however + // the MAC of the discovered device will be printed + if (!this.config.autoDiscover) { + this.log.info( + `Auto-discovery disabled: ignoring discovered device ${mac} at ${remoteInfo.address}`, + ); + return; + } + // Check if already discovered, and if not ignored // Implements #497 - if ( - !this.discovered.has(mac) && - this.config.ignore && - this.config.ignore.findIndex((i: { mac: string }) => i.mac === mac) === - -1 - ) { - switch (t) { - case DeviceTypes.MYSTROM_BUTTON_PLUS: - throw new DeviceNotImplementedError( - `Device discovered at ${remoteInfo.address} of unsupported type ${DeviceTypes[t]}`, - ); - break; - case DeviceTypes.MYSTROM_BUTTON: - retryWithBreaker - .execute(() => { - this.addMyStromButtonDevice({ - address: remoteInfo.address, - name: `Button ${deviceSuffix}`, - token: this.config.globalToken, - mac: mac, - }); - }) - .then(() => { - this.discovered.set(mac, remoteInfo); - }); - break; - case DeviceTypes.MYSTROM_LEDSTRIP: - retryWithBreaker - .execute(() => { - this.addMyStromLightbulbDevice({ - address: remoteInfo.address, - name: `LED Strip ${deviceSuffix}`, - token: this.config.globalToken, + if (this.ignored.has(mac.toUpperCase())) { + this.log.info( + 'Ignoring discovered device ', + this.ignored.get(mac.toUpperCase()).comment || '', + ' at', + mac, + ); + } else { + if (!this.discovered.has(mac)) { + switch (t) { + case DeviceTypes.MYSTROM_BUTTON_PLUS: + throw new DeviceNotImplementedError( + `Device discovered at ${remoteInfo.address} of unsupported type ${DeviceTypes[t]}`, + ); + break; + case DeviceTypes.MYSTROM_BUTTON: + retryWithBreaker + .execute(() => { + this.addMyStromButtonDevice({ + address: remoteInfo.address, + name: `Button ${deviceSuffix}`, + token: this.config.globalToken, + mac: mac, + }); + }) + .then(() => { + this.discovered.set(mac, remoteInfo); }); - }) - .then(() => { - this.discovered.set(mac, remoteInfo); - }); - break; - case DeviceTypes.MYSTROM_BULB: - retryWithBreaker - .execute(() => { - this.addMyStromLightbulbDevice({ - address: remoteInfo.address, - name: `Lightbulb ${deviceSuffix}`, - token: this.config.globalToken, + break; + case DeviceTypes.MYSTROM_LEDSTRIP: + retryWithBreaker + .execute(() => { + this.addMyStromLightbulbDevice({ + address: remoteInfo.address, + name: `LED Strip ${deviceSuffix}`, + token: this.config.globalToken, + }); + }) + .then(() => { + this.discovered.set(mac, remoteInfo); }); - }) - .then(() => { - this.discovered.set(mac, remoteInfo); - }); - break; - case DeviceTypes.MYSTROM_SWITCH_CHV1: - case DeviceTypes.MYSTROM_SWITCH_CHV2: - case DeviceTypes.MYSTROM_SWITCH_EU: - retryWithBreaker - .execute(() => { - this.addMyStromSwitchDevice({ - address: remoteInfo.address, - name: `Switch ${deviceSuffix}`, - token: this.config.globalToken, + break; + case DeviceTypes.MYSTROM_BULB: + retryWithBreaker + .execute(() => { + this.addMyStromLightbulbDevice({ + address: remoteInfo.address, + name: `Lightbulb ${deviceSuffix}`, + token: this.config.globalToken, + }); + }) + .then(() => { + this.discovered.set(mac, remoteInfo); }); - }) - .then(() => { - this.discovered.set(mac, remoteInfo); - }); - break; - case DeviceTypes.MYSTROM_PIR: - retryWithBreaker - .execute(() => { - this.addMyStromPIRDevice({ - address: remoteInfo.address, - name: `PIR ${deviceSuffix}`, - token: this.config.globalToken, + break; + case DeviceTypes.MYSTROM_SWITCH_CHV1: + case DeviceTypes.MYSTROM_SWITCH_CHV2: + case DeviceTypes.MYSTROM_SWITCH_EU: + retryWithBreaker + .execute(() => { + this.addMyStromSwitchDevice({ + address: remoteInfo.address, + name: `Switch ${deviceSuffix}`, + token: this.config.globalToken, + }); + }) + .then(() => { + this.discovered.set(mac, remoteInfo); }); - }) - .then(() => { - this.discovered.set(mac, remoteInfo); - }); - break; - case DeviceTypes.DINGZ: - retryWithBreaker - .execute(() => { - this.addDingzDevice({ - address: remoteInfo.address, - name: `DINGZ ${deviceSuffix}`, - token: this.config.globalToken, + break; + case DeviceTypes.MYSTROM_PIR: + retryWithBreaker + .execute(() => { + this.addMyStromPIRDevice({ + address: remoteInfo.address, + name: `PIR ${deviceSuffix}`, + token: this.config.globalToken, + }); + }) + .then(() => { + this.discovered.set(mac, remoteInfo); }); - }) - .then(() => { - this.discovered.set(mac, remoteInfo); - }) - .catch((e) => this.handleError.bind(this, e)); - break; - default: - this.log.warn(`Unknown device: ${t}`); - break; + break; + case DeviceTypes.DINGZ: + retryWithBreaker + .execute(() => { + this.addDingzDevice({ + address: remoteInfo.address, + name: `DINGZ ${deviceSuffix}`, + token: this.config.globalToken, + }); + }) + .then(() => { + this.discovered.set(mac, remoteInfo); + }) + .catch((e) => this.handleError.bind(this, e)); + break; + default: + this.log.warn(`Unknown device: ${t}`); + break; + } + } else { + this.log.debug('Stopping discovery of already known device:', mac); } - } else { - this.log.debug('Stopping discovery of already known device:', mac); } } catch (e) { if (e instanceof DeviceNotImplementedError) { @@ -888,7 +929,7 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { }); discoverySocket - .on('message', this.datagramMessageHandler.bind(this)) + .on('message', this.autoDiscoveryMessageHandler.bind(this)) .bind(DINGZ_DISCOVERY_PORT); setTimeout(() => { this.log.info('Stopping discovery'); From 3beaea76d242ddffdda82b83db93348405b0c8c6 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:17:33 +0200 Subject: [PATCH 04/21] style(dingz): :pencil2: remove spurious whitespace and fix device comment --- src/platform.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index dc098ac..4133e88 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -792,9 +792,9 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { // Implements #497 if (this.ignored.has(mac.toUpperCase())) { this.log.info( - 'Ignoring discovered device ', - this.ignored.get(mac.toUpperCase()).comment || '', - ' at', + 'Ignoring discovered device', + this.ignored.get(mac.toUpperCase()) || '', + 'at', mac, ); } else { From 0cca8d6d31558abc32887f1529a1d6196c3f33ff Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:18:40 +0200 Subject: [PATCH 05/21] build(dingz): :construction_worker: add launch config for vscode debugging --- .vscode/launch.json | 17 +++++++++++++++++ package.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ac27231 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Homebridge Debug", + "skipFiles": ["/**"], + "program": "/usr/local/bin/homebridge", + "args": "-I", + "outFiles": ["${workspaceFolder}/**/*.js"] + } + ] +} diff --git a/package.json b/package.json index d414c22..6ef3ecd 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "prepublishOnly": "yarn pinst --disable", "prepare": "yarn run lint && yarn run build && yarn run depcheck", "changelog": "changelog --exclude ci,chore", - "debug": "yarn dlx homebridge -I" + "debug": "yarn run lint && yarn run build && yarn dlx homebridge -I" }, "keywords": [ "homebridge-plugin", From bacd15d63f33cfcf5b56e912d941be919d9c276b Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:32:35 +0200 Subject: [PATCH 06/21] fix(dingz): :arrow_up: upgrade code for new simple-color-converter version --- src/dingzAccessory.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dingzAccessory.ts b/src/dingzAccessory.ts index b1a3071..c7b63ee 100644 --- a/src/dingzAccessory.ts +++ b/src/dingzAccessory.ts @@ -1329,9 +1329,9 @@ export class DingzAccessory extends DingzDaBaseAccessory { color: `hex #${state.rgb}`, to: 'hsv', }); - this.dingzStates.LED.hue = hsv.c; - this.dingzStates.LED.saturation = hsv.s; - this.dingzStates.LED.value = hsv.i; + this.dingzStates.LED.hue = hsv.color.h; + this.dingzStates.LED.saturation = hsv.color.s; + this.dingzStates.LED.value = hsv.color.v; } ledService From b3b04ec1df075c38e7b91f5ca4866811a7132dfc Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:37:03 +0200 Subject: [PATCH 07/21] docs(dingz): :memo: upgrade documentation of the new ignore feature #497 --- config.schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.schema.json b/config.schema.json index 676163a..9bda009 100644 --- a/config.schema.json +++ b/config.schema.json @@ -48,14 +48,14 @@ }, "ignore": { "title": "Ignored Devices", - "description": "Devices in this list will be excluded when found via auto-discovery.", + "description": "Devices in this list will be excluded when found via auto-discovery. Add an entry (MAC-Address without the colon) for each devices that shall be ignored. The MAC-Address of your myStrom and Dingz devices can be found in the respective app, or via the device's own webpage.", "type": "array", "items": { "title": "Device", "type": "object", "properties": { "mac": { - "title": "MAC-Address (without `:`)", + "title": "MAC-Address", "type": "string", "pattern": "^([A-Fa-f0-9]{2}){5}[A-Fa-f0-9]{2}$", "required": true From 9babb3cb2e26ba5fedf00c0f3d11d825e52c3aa2 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:51:04 +0200 Subject: [PATCH 08/21] feat(dingz): :children_crossing: reduce noisyness of the plugin when discovering devices - during discoverym, only log ignored devices once --- src/platform.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/platform.ts b/src/platform.ts index 4133e88..ff240da 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -99,7 +99,10 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { // Only add to map if mac is set if (device.mac) { const mac = device.mac.toUpperCase(); - this.ignored.set(mac, device?.comment || ''); + this.ignored.set(mac, { + comment: device?.comment || '', + isignored: false, + }); this.log.info( chalk.redBright('[Platform]'), `Will be ignoring device ${ @@ -166,7 +169,9 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { if (this.ignored.has(context.device.mac.toUpperCase())) { this.log.warn( chalk.redBright('[Platform]'), - 'Cached accessory is in list of ignored devices, but will continue loading it. Consider manually removing it from Homebridge.', + 'This cached accessory is also in the list of ignored devices.', + 'The plugin will continue loading it.', + 'Consider manually removing it from Homebridge.', ); } @@ -791,12 +796,15 @@ export class DingzDaHomebridgePlatform implements DynamicPlatformPlugin { // Check if already discovered, and if not ignored // Implements #497 if (this.ignored.has(mac.toUpperCase())) { - this.log.info( - 'Ignoring discovered device', - this.ignored.get(mac.toUpperCase()) || '', - 'at', - mac, - ); + if (!this.ignored.get(mac.toUpperCase()).isignored) { + this.log.info( + 'Ignoring discovered device', + this.ignored.get(mac.toUpperCase()).comment || '', + 'at', + mac, + ); + this.ignored.get(mac.toUpperCase()).isignored = true; + } } else { if (!this.discovered.has(mac)) { switch (t) { From 6d08365ecab7b6f15b0710a68b06fd942e036abb Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 16:45:55 +0200 Subject: [PATCH 09/21] fix(dingz): :art: improve code w.r.t. callbacks - remove duplicate code, fiy formatting & logic for callback endpoints --- src/dingzAccessory.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/dingzAccessory.ts b/src/dingzAccessory.ts index c7b63ee..9f1dc65 100644 --- a/src/dingzAccessory.ts +++ b/src/dingzAccessory.ts @@ -223,15 +223,21 @@ export class DingzAccessory extends DingzDaBaseAccessory { this.getButtonCallbackUrl() .then((callBackUrl) => { + // Set the callback URL + const endpoints = ['generic']; + + // Add PIR callbacks, depending on dingz Firmware version + if (this.hw.has_pir) { + if (semver.lt(this.hw.fw_version, '1.2.0')) { + endpoints.push('pir/single'); + } else { + endpoints.push('pir/generic', 'pir/rise', 'pir/fall'); + } + } + if (this.platform.config.callbackOverride) { this.log.warn('Override callback URL ->', callBackUrl); - // Set the callback URL (Override!) - const endpoints = // Only set `pir/single` for older FW - this.hw.has_pir && semver.lt(this.hw.fw_version, '1.2.0') - ? ['generic', 'pir/single'] - : this.hw.has_pir - ? ['generic', 'pir/generic', 'pir/rise', 'pir/fall'] - : ['generic']; + this.platform.setButtonCallbackUrl({ baseUrl: this.baseUrl, token: this.device.token, @@ -239,13 +245,7 @@ export class DingzAccessory extends DingzDaBaseAccessory { }); } else if (!callBackUrl?.url.includes(this.platform.getCallbackUrl())) { this.log.warn('Update existing callback URL ->', callBackUrl); - // Set the callback URL (Override!) - const endpoints = - this.hw.has_pir && semver.lt(this.hw.fw_version, '1.2.0') - ? ['generic', 'pir/single'] - : this.hw.has_pir - ? ['generic', 'pir/generic', 'pir/rise', 'pir/fall'] - : ['generic']; + this.platform.setButtonCallbackUrl({ baseUrl: this.baseUrl, token: this.device.token, From 9c5b6a2fd1330385bee821767b5479caaf34a3e5 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 17:01:29 +0200 Subject: [PATCH 10/21] fix(dingz): :bug: attempt to fix #419: tilt angle warnings - update code to fix #419, potentially at least --- src/dingzAccessory.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/dingzAccessory.ts b/src/dingzAccessory.ts index 9f1dc65..fc45447 100644 --- a/src/dingzAccessory.ts +++ b/src/dingzAccessory.ts @@ -835,12 +835,11 @@ export class DingzAccessory extends DingzDaBaseAccessory { // Set min/max Values // FIXME: Implement different lamella/blind modes #24 - const maxTiltValue = semver.lt(this.hw.fw_version, '1.2.0') ? 90 : 100; service .getCharacteristic(this.platform.Characteristic.TargetHorizontalTiltAngle) .setProps({ minValue: 0, - maxValue: maxTiltValue, + maxValue: 90, minStep: this.platform.config.minStepTiltAngle, }) // dingz Maximum values .on(CharacteristicEventTypes.SET, this.setTiltAngle.bind(this, index)); @@ -881,9 +880,6 @@ export class DingzAccessory extends DingzDaBaseAccessory { * - We're moving by setting new positions in the UI [x] * - We're moving by pressing the "up/down" buttons in the UI or Hardware [x] */ - - const maxTiltValue = semver.lt(this.hw.fw_version, '1.2.0') ? 90 : 100; - service .getCharacteristic(this.platform.Characteristic.TargetPosition) .updateValue(state.position); @@ -891,7 +887,7 @@ export class DingzAccessory extends DingzDaBaseAccessory { .getCharacteristic( this.platform.Characteristic.TargetHorizontalTiltAngle, ) - .updateValue((state.lamella / 100) * maxTiltValue); // Old FW: Set in °, Get in % (...) + .updateValue((state.lamella / 100) * 90); // Lamella position set in ° in HomeKit let positionState: number; switch (state.moving) { @@ -910,7 +906,7 @@ export class DingzAccessory extends DingzDaBaseAccessory { .getCharacteristic( this.platform.Characteristic.CurrentHorizontalTiltAngle, ) - .updateValue((state.lamella / 100) * maxTiltValue); // Set in °, Get in % (...) + .updateValue((state.lamella / 100) * 90); // Lamella position set in ° in HomeKit break; } service @@ -1012,7 +1008,9 @@ export class DingzAccessory extends DingzDaBaseAccessory { tiltAngle, ); - callback(this.reachabilityState, (tiltAngle / 100) * 90); // FIXES #371: internally, it's %, HomeKit expects ° + // FIXES #371, #419: internally, it's % (but only in newer firmware, v1.2.0 and lower has ° as well), HomeKit expects ° + const maxTiltValue = semver.lt(this.hw.fw_version, '1.2.0') ? 90 : 100; + callback(this.reachabilityState, (tiltAngle / maxTiltValue) * 90); } private getPositionState( From a6e3f0a56bf7e1a01ca0c16a647a4eb8e3d75b91 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 17:26:57 +0200 Subject: [PATCH 11/21] refactor(dingz): :arrow_up: upgrade semver --- package.json | 2 +- src/lib/libs.d.ts | 1 - yarn.lock | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6ef3ecd..397f624 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "is-valid-host": "^1.0.1", "limit-number": "^3.0.0", "qs": "^6.10.3", - "semver": "^7.3.5", + "semver": "^7.3.8", "simple-color-converter": "^2.1.13" }, "devDependencies": { diff --git a/src/lib/libs.d.ts b/src/lib/libs.d.ts index 2eedae8..69477e8 100644 --- a/src/lib/libs.d.ts +++ b/src/lib/libs.d.ts @@ -1,4 +1,3 @@ declare module 'simple-color-converter'; declare module 'is-valid-host'; -declare module 'semver'; declare module 'limit-number'; diff --git a/yarn.lock b/yarn.lock index 7ca53f1..7b35a76 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3770,7 +3770,7 @@ __metadata: qs: ^6.10.3 rimraf: ^3.0.2 semantic-release: ^19.0.2 - semver: ^7.3.5 + semver: ^7.3.8 simple-color-converter: ^2.1.13 typescript: ^4.5.5 languageName: unknown @@ -6846,7 +6846,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7": +"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8": version: 7.3.8 resolution: "semver@npm:7.3.8" dependencies: From 12c306f4487a86b75f0939e7de8b72663d3591bb Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 17:29:15 +0200 Subject: [PATCH 12/21] fix(dingz): :bug: implement different endpoints for dingz FW >= 1.4.0 - implements fix for #511 --- src/dingzAccessory.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dingzAccessory.ts b/src/dingzAccessory.ts index fc45447..90a1941 100644 --- a/src/dingzAccessory.ts +++ b/src/dingzAccessory.ts @@ -1550,7 +1550,10 @@ export class DingzAccessory extends DingzDaBaseAccessory { * Returns the callback URL for the device */ public async getButtonCallbackUrl(): Promise { - const getCallbackEndpoint = '/api/v1/action/generic/generic'; + // FIXES #511: different endpoint URLs for Callback from FW v1.4.x forward + const getCallbackEndpoint = semver.gte(this.hw.fw_version, '1.4.0') + ? '/api/v1/action/generic' + : '/api/v1/action/generic/generic'; this.log.debug('Getting the callback URL -> ', getCallbackEndpoint); return await this.request.get(getCallbackEndpoint).then((response) => { return response.data; From d218c53f12d6c43f37cb5b68e31c4189b361fb87 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 17:45:13 +0200 Subject: [PATCH 13/21] build(npm): :package: update npm ignore to remove spurious files --- .npmignore | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.npmignore b/.npmignore index c601615..b919c84 100644 --- a/.npmignore +++ b/.npmignore @@ -125,13 +125,17 @@ web_modules/ # yarn v2 +.yarn/build-state.yml .yarn/cache +.yarn/install-state.gz +.yarn/plugins +.yarn/sdks .yarn/unplugged -.yarn/build-state.yml .pnp.* # Github templates and actions, own stuff .github api .prettierrc +.prettierignore commitlint.config.js \ No newline at end of file From b7588ee9869117d04111e81d368631f531973e39 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 18:23:47 +0200 Subject: [PATCH 14/21] fix(dingz): :bug: further fixes for new callback-url scheme - fixes new callback url scheme for FW v1.4.x fixes #511 --- src/dingzAccessory.ts | 8 +++++++- src/lib/commonTypes.ts | 4 +++- src/myStromPIRAccessory.ts | 5 ++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/dingzAccessory.ts b/src/dingzAccessory.ts index 90a1941..24aeebd 100644 --- a/src/dingzAccessory.ts +++ b/src/dingzAccessory.ts @@ -243,7 +243,13 @@ export class DingzAccessory extends DingzDaBaseAccessory { token: this.device.token, endpoints: endpoints, }); - } else if (!callBackUrl?.url.includes(this.platform.getCallbackUrl())) { + } else if ( + // FIXME: because of #511 + (semver.lt(this.hw.fw_version, '1.4.0') && + !callBackUrl?.url?.includes(this.platform.getCallbackUrl())) || + (semver.gte(this.hw.fw_version, '1.4.0') && + !callBackUrl?.generic?.includes(this.platform.getCallbackUrl())) + ) { this.log.warn('Update existing callback URL ->', callBackUrl); this.platform.setButtonCallbackUrl({ diff --git a/src/lib/commonTypes.ts b/src/lib/commonTypes.ts index 8512858..4df3198 100644 --- a/src/lib/commonTypes.ts +++ b/src/lib/commonTypes.ts @@ -65,6 +65,8 @@ export interface AccessoryTypes { [key: string]: AccessoryType; } +// FIXME: Needed because of #511 export interface AccessoryActionUrl { - url: string; + url?: string; + generic?: string; } diff --git a/src/myStromPIRAccessory.ts b/src/myStromPIRAccessory.ts index 4948165..6e8cb08 100644 --- a/src/myStromPIRAccessory.ts +++ b/src/myStromPIRAccessory.ts @@ -155,7 +155,10 @@ export class MyStromPIRAccessory extends DingzDaBaseAccessory { token: this.device.token, endpoints: ['pir/generic'], }); - } else if (!callBackUrl?.url.includes(this.platform.getCallbackUrl())) { + } else if ( + // FIXME: Needed because of #511 + !callBackUrl?.url?.includes(this.platform.getCallbackUrl()) + ) { this.log.warn('Update existing callback URL ->', callBackUrl); // Set the callback URL (Override!) this.platform.setButtonCallbackUrl({ From ea3eeab8cc645fc983222dbb7ec31eadf2c3e21b Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 18:57:27 +0200 Subject: [PATCH 15/21] fix(dingz): :bug: further fixes for upcoming FW 1.4.x changes - fixes #511 --- src/dingzAccessory.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dingzAccessory.ts b/src/dingzAccessory.ts index 24aeebd..62feb8e 100644 --- a/src/dingzAccessory.ts +++ b/src/dingzAccessory.ts @@ -225,6 +225,7 @@ export class DingzAccessory extends DingzDaBaseAccessory { .then((callBackUrl) => { // Set the callback URL const endpoints = ['generic']; + const platformCallbackUrl = this.platform.getCallbackUrl(); // Add PIR callbacks, depending on dingz Firmware version if (this.hw.has_pir) { @@ -246,9 +247,9 @@ export class DingzAccessory extends DingzDaBaseAccessory { } else if ( // FIXME: because of #511 (semver.lt(this.hw.fw_version, '1.4.0') && - !callBackUrl?.url?.includes(this.platform.getCallbackUrl())) || + !callBackUrl?.url?.includes(platformCallbackUrl)) || (semver.gte(this.hw.fw_version, '1.4.0') && - !callBackUrl?.generic?.includes(this.platform.getCallbackUrl())) + !callBackUrl?.generic?.includes(platformCallbackUrl)) ) { this.log.warn('Update existing callback URL ->', callBackUrl); From c05a2898462e1aceb2bde5703bc1e8c61a45a51f Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 21:32:27 +0200 Subject: [PATCH 16/21] fix(dingz): :bug: further tweaks to lamellas - fixes #419 (again :-)) --- src/dingzAccessory.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dingzAccessory.ts b/src/dingzAccessory.ts index 62feb8e..1ee2872 100644 --- a/src/dingzAccessory.ts +++ b/src/dingzAccessory.ts @@ -980,14 +980,14 @@ export class DingzAccessory extends DingzDaBaseAccessory { 'Set Characteristic TargetHorizontalTiltAngle on ', index, '->', - angle, + `${angle}°`, ); const id = this.getWindowCoveringId(index); if (this.dingzStates.WindowCovers[id]) { await this.setWindowCovering({ id: id, blind: this.dingzStates.WindowCovers[id].position, - lamella: angle as number, + lamella: ((angle as number) / 90) * 100, // FIXES #419, we must convert ° to % callback: callback, }); } From c6422e26bfb94af89e58c71ef6d5c1d9f639eb8f Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 21:42:05 +0200 Subject: [PATCH 17/21] refactor(dingz): :loud_sound: add more logging --- src/dingzAccessory.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dingzAccessory.ts b/src/dingzAccessory.ts index 1ee2872..0eb8831 100644 --- a/src/dingzAccessory.ts +++ b/src/dingzAccessory.ts @@ -944,7 +944,7 @@ export class DingzAccessory extends DingzDaBaseAccessory { await this.setWindowCovering({ id: id, blind: position as number, - lamella: windowCovering.lamella, + lamella: (windowCovering.lamella / 90) * 100, // FIXES #419, we must convert ° to % callback: callback, }); } @@ -1495,6 +1495,9 @@ export class DingzAccessory extends DingzDaBaseAccessory { lamella: number; callback: CharacteristicSetCallback; }) { + this.log.debug( + `Setting WindowCovering ${id} to position ${blind} and angle ${lamella}°`, + ); // The API says the parameters can be omitted. This is not true // {{ip}}/api/v1/shade/0?blind=&lamella= const setWindowCoveringEndpoint = `${this.baseUrl}/api/v1/shade/${id}`; From 8df89afa0e1f11b0716d43577ea44079d17be882 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 21:50:59 +0200 Subject: [PATCH 18/21] build(dingz): :package: improve packaging for npm - remove unnecessary files from package --- .npmignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.npmignore b/.npmignore index b919c84..d1f451c 100644 --- a/.npmignore +++ b/.npmignore @@ -131,6 +131,8 @@ web_modules/ .yarn/plugins .yarn/sdks .yarn/unplugged +.yarn/releases +.yarnrc.yml .pnp.* # Github templates and actions, own stuff From 156514152bbbcd9357b66eddbb75ee51dab26a32 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 22:06:51 +0200 Subject: [PATCH 19/21] fix(dingz): :bug: also fix PIR callback endpoints - PIR endpoints on dingz are doubled, requiring changes to the plugin - fixes #511 --- src/dingzAccessory.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/dingzAccessory.ts b/src/dingzAccessory.ts index 0eb8831..bcd301f 100644 --- a/src/dingzAccessory.ts +++ b/src/dingzAccessory.ts @@ -231,8 +231,11 @@ export class DingzAccessory extends DingzDaBaseAccessory { if (this.hw.has_pir) { if (semver.lt(this.hw.fw_version, '1.2.0')) { endpoints.push('pir/single'); - } else { + } else if (semver.lt(this.hw.fw_version, '1.4.0')) { endpoints.push('pir/generic', 'pir/rise', 'pir/fall'); + } else { + // FIXES #511: Newer FW have (yet!) other endpoint for PIR callbacks + endpoints.push('pir1/rise', 'pir1/fall'); } } @@ -260,7 +263,7 @@ export class DingzAccessory extends DingzDaBaseAccessory { endpoints: endpoints, }); } else { - this.log.debug('Callback URL already set ->', callBackUrl?.url); + this.log.debug('Callback URL already set ->', callBackUrl); } }) .catch(this.handleRequestErrors.bind(this)); From 1ae53f8fc7a39662c9794f917123049b2924d1ab Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 22:18:11 +0200 Subject: [PATCH 20/21] fix(dingz): :bug: lamella API only accepts integer values - position and angle must be integer values --- src/dingzAccessory.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dingzAccessory.ts b/src/dingzAccessory.ts index bcd301f..873835e 100644 --- a/src/dingzAccessory.ts +++ b/src/dingzAccessory.ts @@ -1498,6 +1498,12 @@ export class DingzAccessory extends DingzDaBaseAccessory { lamella: number; callback: CharacteristicSetCallback; }) { + // The API only accepts integer numbers. + // As we juggle with ° vs %, we must round + // the values for blind and lamella to the nearest integer + blind = Math.round(blind); + lamella = Math.round(lamella); + this.log.debug( `Setting WindowCovering ${id} to position ${blind} and angle ${lamella}°`, ); From dfc23b0e1142d7d064736fe0324a2c17fd3eceb7 Mon Sep 17 00:00:00 2001 From: Johann Richard <189003+johannrichard@users.noreply.github.com> Date: Mon, 24 Oct 2022 22:32:16 +0200 Subject: [PATCH 21/21] ci(github): :zap: implement workflow caching - implement `yarn` workflow caching in build & semantic-release workflows --- .github/workflows/build.yml | 1 + .github/workflows/semantic-release.yml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 564862f..53163da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,7 @@ jobs: uses: actions/setup-node@main with: node-version: ${{ matrix.node-version }} + cache: 'yarn' # - name: Upgrade yarn # run: yarn set version berry diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml index ee80964..9d0a0e1 100644 --- a/.github/workflows/semantic-release.yml +++ b/.github/workflows/semantic-release.yml @@ -5,7 +5,7 @@ on: push: branches: [master, beta, alpha] -# fine-grained permissions +# fine-grained permissions # see https://github.com/semantic-release/github and https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token permissions: contents: write @@ -22,6 +22,7 @@ jobs: - uses: actions/setup-node@main with: node-version: '16' + cache: 'yarn' - name: Install dependencies run: yarn set version berry