From 18397d9aede9cea51c79dc36f5779eeb76171ad2 Mon Sep 17 00:00:00 2001 From: Reed Taylor Date: Fri, 31 May 2024 15:21:44 -0700 Subject: [PATCH 1/6] Add supported fan to readme list --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f2e368b..f397c2b 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,10 @@ Homebridge plugin for Dreo brand smart devices. [Dreo Fans on Amazon](https://ww * DR-HTF004S * DR-HTF005S * DR-HTF007S +#### Pedestal Fans +* DR-HPF001S +* DR-HAF003S #### Other Fans -* DR-HAF003S (Pedestal Fan) * DR-HAF004S (Table Fan) Please open an issue if you have another model that works or doesn't work. The plugin *should* also be compatible with multiple devices on the same account but I haven't tested this. Non-fan smart devices are not supported at this time, but if you have another device and can help me test some code out I would definitely be open to adding support. From c4071ab6d4932b4c1941a6ceadd483272992c230 Mon Sep 17 00:00:00 2001 From: Reed Taylor Date: Fri, 31 May 2024 15:22:22 -0700 Subject: [PATCH 2/6] Add support for changing oscillation mode for DR-HPF001S pedestal fan --- src/FanAccessory.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/FanAccessory.ts b/src/FanAccessory.ts index c7ada27..0f18218 100644 --- a/src/FanAccessory.ts +++ b/src/FanAccessory.ts @@ -66,10 +66,12 @@ export class FanAccessory { .onGet(this.getRotationSpeed.bind(this)); // check whether fan supports oscillation - if (state.shakehorizon !== undefined || state.hoscon !== undefined) { + if (state.shakehorizon !== undefined || state.hoscon !== undefined || state.oscmode !== undefined) { // some fans use different commands to toggle oscillation, determine which one should be used if (state.hoscon !== undefined) { this.fanState.SwingMethod = 'hoscon'; + } else if (state.oscmode !== undefined) { + this.fanState.SwingMethod = 'oscmode'; } // register handlers for Swing Mode (oscillation) this.service.getCharacteristic(this.platform.Characteristic.SwingMode) @@ -133,6 +135,11 @@ export class FanAccessory { this.service.getCharacteristic(this.platform.Characteristic.SwingMode).updateValue(this.fanState.Swing); this.platform.log.debug('Oscillation mode:', data.reported.hoscon); break; + case 'oscmode': + this.fanState.Swing = Boolean(data.reported.oscmode); + this.service.getCharacteristic(this.platform.Characteristic.SwingMode).updateValue(this.fanState.Swing); + this.platform.log.debug('Oscillation mode:', data.reported.oscmode); + break; case 'temperature': if (this.temperatureService !== undefined && !shouldHideTemperatureSensor) { this.fanState.Temperature = this.correctedTemperature(data.reported.temperature); @@ -198,7 +205,7 @@ export class FanAccessory { this.ws.send(JSON.stringify({ 'devicesn': this.accessory.context.device.sn, 'method': 'control', - 'params': {[this.fanState.SwingMethod]: Boolean(value)}, + 'params': {[this.fanState.SwingMethod]: this.fanState.SwingMethod === 'oscmode' ? Number(value) : Boolean(value)}, 'timestamp': Date.now(), })); } From 7fd42240bf1a2552640aa15d4819df62f1938a4d Mon Sep 17 00:00:00 2001 From: Reed Taylor Date: Fri, 31 May 2024 16:23:58 -0700 Subject: [PATCH 3/6] Add child lock for supported fans --- src/FanAccessory.ts | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/FanAccessory.ts b/src/FanAccessory.ts index 0f18218..60cf4ab 100644 --- a/src/FanAccessory.ts +++ b/src/FanAccessory.ts @@ -15,7 +15,8 @@ export class FanAccessory { On: false, Speed: 1, Swing: false, - SwingMethod: 'shakehorizon', // some fans use hoscon instead of shakehorizon to control swing mode + SwingMethod: 'shakehorizon', // some fans use hoscon or oscmode instead of shakehorizon to control swing mode + LockPhysicalControls: this.platform.Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED, MaxSpeed: 1, Temperature: 0, }; @@ -80,6 +81,15 @@ export class FanAccessory { this.fanState.Swing = state[this.fanState.SwingMethod].state; } + // check if child lock is supported + if (state.childlockon !== undefined) { + // register handlers for Lock Physical Controls + this.service.getCharacteristic(this.platform.Characteristic.LockPhysicalControls) + .onSet(this.setLockPhysicalControls.bind(this)) + .onGet(this.getLockPhysicalControls.bind(this)); + this.fanState.LockPhysicalControls = this.getChildLockCharacteristic(state.childlockon.state); + } + const shouldHideTemperatureSensor = this.platform.config.hideTemperatureSensor || false; // default to false if not defined // If temperature is defined and we are not hiding the sensor @@ -140,6 +150,12 @@ export class FanAccessory { this.service.getCharacteristic(this.platform.Characteristic.SwingMode).updateValue(this.fanState.Swing); this.platform.log.debug('Oscillation mode:', data.reported.oscmode); break; + case 'childlockon': + this.fanState.LockPhysicalControls = this.getChildLockCharacteristic(data.reported.childlockon) + this.service.getCharacteristic(this.platform.Characteristic.LockPhysicalControls) + .updateValue(this.fanState.LockPhysicalControls); + this.platform.log.debug('Child lock:', data.reported.childlockon); + break; case 'temperature': if (this.temperatureService !== undefined && !shouldHideTemperatureSensor) { this.fanState.Temperature = this.correctedTemperature(data.reported.temperature); @@ -156,6 +172,12 @@ export class FanAccessory { }); } + getChildLockCharacteristic(value: boolean) { + return value + ? this.platform.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED + : this.platform.Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED; + } + // Handle requests to set the "Active" characteristic handleActiveSet(value) { this.platform.log.debug('Triggered SET Active:', value); @@ -218,6 +240,20 @@ export class FanAccessory { return this.fanState.Temperature; } + // turn child lock on/off + async setLockPhysicalControls(value) { + this.ws.send(JSON.stringify({ + 'devicesn': this.accessory.context.device.sn, + 'method': 'control', + 'params': {'childlockon': value === this.platform.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED}, + 'timestamp': Date.now(), + })); + } + + getLockPhysicalControls() { + return this.fanState.LockPhysicalControls; + } + correctedTemperature(temperatureFromDreo) { const offset = this.platform.config.temperatureOffset || 0; // default to 0 if not defined // Dreo response is always Fahrenheit - convert to Celsius which is what HomeKit expects From a11335ab4181988c5a86c6850ba3dbbcccd7a58e Mon Sep 17 00:00:00 2001 From: Reed Taylor Date: Fri, 31 May 2024 16:39:09 -0700 Subject: [PATCH 4/6] Add auto mode control when supported --- src/FanAccessory.ts | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/FanAccessory.ts b/src/FanAccessory.ts index 60cf4ab..89774c6 100644 --- a/src/FanAccessory.ts +++ b/src/FanAccessory.ts @@ -16,6 +16,7 @@ export class FanAccessory { Speed: 1, Swing: false, SwingMethod: 'shakehorizon', // some fans use hoscon or oscmode instead of shakehorizon to control swing mode + Mode: this.platform.Characteristic.TargetFanState.MANUAL, LockPhysicalControls: this.platform.Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED, MaxSpeed: 1, Temperature: 0, @@ -81,6 +82,15 @@ export class FanAccessory { this.fanState.Swing = state[this.fanState.SwingMethod].state; } + // check if mode control is supported + if (state.mode !== undefined) { + // register handlers for Target Fan State + this.service.getCharacteristic(this.platform.Characteristic.TargetFanState) + .onSet(this.setMode.bind(this)) + .onGet(this.getMode.bind(this)); + this.fanState.Mode = this.getTargetFanStateCharacteristic(state.mode.state); + } + // check if child lock is supported if (state.childlockon !== undefined) { // register handlers for Lock Physical Controls @@ -150,6 +160,11 @@ export class FanAccessory { this.service.getCharacteristic(this.platform.Characteristic.SwingMode).updateValue(this.fanState.Swing); this.platform.log.debug('Oscillation mode:', data.reported.oscmode); break; + case 'mode': + this.fanState.Mode = this.getTargetFanStateCharacteristic(data.reported.mode); + this.service.getCharacteristic(this.platform.Characteristic.TargetFanState).updateValue(this.fanState.Mode); + this.platform.log.debug('Fan mode:', data.reported.mode); + break; case 'childlockon': this.fanState.LockPhysicalControls = this.getChildLockCharacteristic(data.reported.childlockon) this.service.getCharacteristic(this.platform.Characteristic.LockPhysicalControls) @@ -172,6 +187,13 @@ export class FanAccessory { }); } + getTargetFanStateCharacteristic(value: number) { + // Show all non-automatic modes as MANUAL + return value === 4 + ? this.platform.Characteristic.TargetFanState.AUTO + : this.platform.Characteristic.TargetFanState.MANUAL; + } + getChildLockCharacteristic(value: boolean) { return value ? this.platform.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED @@ -236,8 +258,18 @@ export class FanAccessory { return this.fanState.Swing; } - async getTemperature() { - return this.fanState.Temperature; + // set fan mode + async setMode(value) { + this.ws.send(JSON.stringify({ + 'devicesn': this.accessory.context.device.sn, + 'method': 'control', + 'params': {'mode': value === this.platform.Characteristic.TargetFanState.AUTO ? 4 : 1}, + 'timestamp': Date.now(), + })); + } + + async getMode() { + return this.fanState.Mode; } // turn child lock on/off @@ -254,6 +286,10 @@ export class FanAccessory { return this.fanState.LockPhysicalControls; } + async getTemperature() { + return this.fanState.Temperature; + } + correctedTemperature(temperatureFromDreo) { const offset = this.platform.config.temperatureOffset || 0; // default to 0 if not defined // Dreo response is always Fahrenheit - convert to Celsius which is what HomeKit expects From 865a7cbb2e783ca742c1605801acb0c81c3d66bf Mon Sep 17 00:00:00 2001 From: Reed Taylor Date: Fri, 31 May 2024 16:51:52 -0700 Subject: [PATCH 5/6] simplify code to match styling --- src/FanAccessory.ts | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/FanAccessory.ts b/src/FanAccessory.ts index 89774c6..c7581cb 100644 --- a/src/FanAccessory.ts +++ b/src/FanAccessory.ts @@ -16,8 +16,8 @@ export class FanAccessory { Speed: 1, Swing: false, SwingMethod: 'shakehorizon', // some fans use hoscon or oscmode instead of shakehorizon to control swing mode - Mode: this.platform.Characteristic.TargetFanState.MANUAL, - LockPhysicalControls: this.platform.Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED, + AutoMode: false, + LockPhysicalControls: false, MaxSpeed: 1, Temperature: 0, }; @@ -88,7 +88,7 @@ export class FanAccessory { this.service.getCharacteristic(this.platform.Characteristic.TargetFanState) .onSet(this.setMode.bind(this)) .onGet(this.getMode.bind(this)); - this.fanState.Mode = this.getTargetFanStateCharacteristic(state.mode.state); + this.fanState.AutoMode = this.convertModeToBoolean(state.mode.state); } // check if child lock is supported @@ -97,7 +97,7 @@ export class FanAccessory { this.service.getCharacteristic(this.platform.Characteristic.LockPhysicalControls) .onSet(this.setLockPhysicalControls.bind(this)) .onGet(this.getLockPhysicalControls.bind(this)); - this.fanState.LockPhysicalControls = this.getChildLockCharacteristic(state.childlockon.state); + this.fanState.LockPhysicalControls = Boolean(state.childlockon.state); } const shouldHideTemperatureSensor = this.platform.config.hideTemperatureSensor || false; // default to false if not defined @@ -161,12 +161,13 @@ export class FanAccessory { this.platform.log.debug('Oscillation mode:', data.reported.oscmode); break; case 'mode': - this.fanState.Mode = this.getTargetFanStateCharacteristic(data.reported.mode); - this.service.getCharacteristic(this.platform.Characteristic.TargetFanState).updateValue(this.fanState.Mode); + this.fanState.AutoMode = this.convertModeToBoolean(data.reported.mode); + this.service.getCharacteristic(this.platform.Characteristic.TargetFanState) + .updateValue(this.fanState.AutoMode); this.platform.log.debug('Fan mode:', data.reported.mode); break; case 'childlockon': - this.fanState.LockPhysicalControls = this.getChildLockCharacteristic(data.reported.childlockon) + this.fanState.LockPhysicalControls = Boolean(data.reported.childlockon); this.service.getCharacteristic(this.platform.Characteristic.LockPhysicalControls) .updateValue(this.fanState.LockPhysicalControls); this.platform.log.debug('Child lock:', data.reported.childlockon); @@ -187,19 +188,6 @@ export class FanAccessory { }); } - getTargetFanStateCharacteristic(value: number) { - // Show all non-automatic modes as MANUAL - return value === 4 - ? this.platform.Characteristic.TargetFanState.AUTO - : this.platform.Characteristic.TargetFanState.MANUAL; - } - - getChildLockCharacteristic(value: boolean) { - return value - ? this.platform.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED - : this.platform.Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED; - } - // Handle requests to set the "Active" characteristic handleActiveSet(value) { this.platform.log.debug('Triggered SET Active:', value); @@ -269,7 +257,7 @@ export class FanAccessory { } async getMode() { - return this.fanState.Mode; + return this.fanState.AutoMode; } // turn child lock on/off @@ -277,7 +265,7 @@ export class FanAccessory { this.ws.send(JSON.stringify({ 'devicesn': this.accessory.context.device.sn, 'method': 'control', - 'params': {'childlockon': value === this.platform.Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED}, + 'params': {'childlockon': Number(value)}, 'timestamp': Date.now(), })); } @@ -295,4 +283,9 @@ export class FanAccessory { // Dreo response is always Fahrenheit - convert to Celsius which is what HomeKit expects return ((temperatureFromDreo + offset) - 32) * 5 / 9; } + + convertModeToBoolean(value: number) { + // Show all non-automatic modes as "Manual" + return value === 4; + } } From fb92b15181d2571e8ddf61d4ec3d0dd1f4208ac8 Mon Sep 17 00:00:00 2001 From: Gavin Zyonse Date: Tue, 11 Jun 2024 20:06:48 -0400 Subject: [PATCH 6/6] Simplify oscillation mode selection and match new comment style --- src/FanAccessory.ts | 50 ++++++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/FanAccessory.ts b/src/FanAccessory.ts index c7581cb..81a3fab 100644 --- a/src/FanAccessory.ts +++ b/src/FanAccessory.ts @@ -15,7 +15,7 @@ export class FanAccessory { On: false, Speed: 1, Swing: false, - SwingMethod: 'shakehorizon', // some fans use hoscon or oscmode instead of shakehorizon to control swing mode + SwingMethod: 'none', // Variable used to control oscillation (shakehorizon, hoscon, oscmode) AutoMode: false, LockPhysicalControls: false, MaxSpeed: 1, @@ -67,14 +67,17 @@ export class FanAccessory { .onSet(this.setRotationSpeed.bind(this)) .onGet(this.getRotationSpeed.bind(this)); - // check whether fan supports oscillation - if (state.shakehorizon !== undefined || state.hoscon !== undefined || state.oscmode !== undefined) { - // some fans use different commands to toggle oscillation, determine which one should be used - if (state.hoscon !== undefined) { - this.fanState.SwingMethod = 'hoscon'; - } else if (state.oscmode !== undefined) { - this.fanState.SwingMethod = 'oscmode'; - } + // Check whether fan supports oscillation + // Some fans use different commands to toggle oscillation, determine which one should be used + if (state.shakehorizon !== undefined) { + this.fanState.SwingMethod = 'shakehorizon'; + } else if (state.hoscon !== undefined) { + this.fanState.SwingMethod = 'hoscon'; + } else if (state.oscmode !== undefined) { + this.fanState.SwingMethod = 'oscmode'; + } + + if (this.fanState.SwingMethod !== 'none') { // register handlers for Swing Mode (oscillation) this.service.getCharacteristic(this.platform.Characteristic.SwingMode) .onSet(this.setSwingMode.bind(this)) @@ -82,18 +85,18 @@ export class FanAccessory { this.fanState.Swing = state[this.fanState.SwingMethod].state; } - // check if mode control is supported + // Check if mode control is supported if (state.mode !== undefined) { - // register handlers for Target Fan State + // Register handlers for Target Fan State this.service.getCharacteristic(this.platform.Characteristic.TargetFanState) .onSet(this.setMode.bind(this)) .onGet(this.getMode.bind(this)); this.fanState.AutoMode = this.convertModeToBoolean(state.mode.state); } - // check if child lock is supported + // Check if child lock is supported if (state.childlockon !== undefined) { - // register handlers for Lock Physical Controls + // Register handlers for Lock Physical Controls this.service.getCharacteristic(this.platform.Characteristic.LockPhysicalControls) .onSet(this.setLockPhysicalControls.bind(this)) .onGet(this.getLockPhysicalControls.bind(this)); @@ -137,27 +140,32 @@ export class FanAccessory { switch(Object.keys(data.reported)[0]) { case 'poweron': this.fanState.On = data.reported.poweron; - this.service.getCharacteristic(this.platform.Characteristic.Active).updateValue(this.fanState.On); + this.service.getCharacteristic(this.platform.Characteristic.Active) + .updateValue(this.fanState.On); this.platform.log.debug('Fan power:', data.reported.poweron); break; case 'windlevel': this.fanState.Speed = data.reported.windlevel * 100 / this.fanState.MaxSpeed; - this.service.getCharacteristic(this.platform.Characteristic.RotationSpeed).updateValue(this.fanState.Speed); + this.service.getCharacteristic(this.platform.Characteristic.RotationSpeed) + .updateValue(this.fanState.Speed); this.platform.log.debug('Fan speed:', data.reported.windlevel); break; case 'shakehorizon': this.fanState.Swing = data.reported.shakehorizon; - this.service.getCharacteristic(this.platform.Characteristic.SwingMode).updateValue(this.fanState.Swing); + this.service.getCharacteristic(this.platform.Characteristic.SwingMode) + .updateValue(this.fanState.Swing); this.platform.log.debug('Oscillation mode:', data.reported.shakehorizon); break; case 'hoscon': this.fanState.Swing = data.reported.hoscon; - this.service.getCharacteristic(this.platform.Characteristic.SwingMode).updateValue(this.fanState.Swing); + this.service.getCharacteristic(this.platform.Characteristic.SwingMode) + .updateValue(this.fanState.Swing); this.platform.log.debug('Oscillation mode:', data.reported.hoscon); break; case 'oscmode': this.fanState.Swing = Boolean(data.reported.oscmode); - this.service.getCharacteristic(this.platform.Characteristic.SwingMode).updateValue(this.fanState.Swing); + this.service.getCharacteristic(this.platform.Characteristic.SwingMode) + .updateValue(this.fanState.Swing); this.platform.log.debug('Oscillation mode:', data.reported.oscmode); break; case 'mode': @@ -246,7 +254,7 @@ export class FanAccessory { return this.fanState.Swing; } - // set fan mode + // Set fan mode async setMode(value) { this.ws.send(JSON.stringify({ 'devicesn': this.accessory.context.device.sn, @@ -260,7 +268,7 @@ export class FanAccessory { return this.fanState.AutoMode; } - // turn child lock on/off + // Turn child lock on/off async setLockPhysicalControls(value) { this.ws.send(JSON.stringify({ 'devicesn': this.accessory.context.device.sn, @@ -286,6 +294,6 @@ export class FanAccessory { convertModeToBoolean(value: number) { // Show all non-automatic modes as "Manual" - return value === 4; + return (value === 4); } }