Skip to content

Commit

Permalink
Reimplemented FanAccessory; Add Ceiling Fan Light support (fsd). (#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
0x5e authored Nov 1, 2022
1 parent 8ee0c3a commit fdb4f1e
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 3 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
- Fix access_token undefined error. (https://github.com/tuya/tuya-homebridge/issues/298#issuecomment-1278238870 by @Azukovskij )

### Accessory category specific
- Rewrite `BaseAccessory`, `SwitchAccessory`, `OutletAccessory`, `LightAccessory`, `ContactSensorAccessory`, `LeakSensorAccessory`, `SmokeSensorAccessory`, `GarageDoorAccessory`, `WindowCoveringAccessory` reduce about 50% code size. Now writing a accessory class is much more simple.
- Rewrite `BaseAccessory`, `SwitchAccessory`, `OutletAccessory`, `LightAccessory`, `ContactSensorAccessory`, `LeakSensorAccessory`, `SmokeSensorAccessory`, `GarageDoorAccessory`, `WindowCoveringAccessory`, `FanAccessory` reduce about 50% code size. Now writing a accessory class is much more simple.
- Legacy accessory codes moved to `src/accessory/legacy/` folder.
- [Light] Add `debounce` and command send queue, more stable during frequent operations on different characteristics.
- [Light] Fix wrong color temperature map. (https://github.com/tuya/tuya-homebridge/issues/136 by @XiaoTonyLuo and https://github.com/tuya/tuya-homebridge/pull/135 by @levidhuyvetter)
Expand All @@ -43,6 +43,7 @@
- [Thermostat] Add Thermostat support (`wk`).
- [Light] Add Spotlight support (`sxd`).
- [Valve] Add Irrigator support (`ggq`).
- [Fanv2] Add Ceiling Fan Light support (`fsd`).

### Known issue
- Sometimes mqtt not respond quickly, the older message received later than newer one. This will influence the accessory status update.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ If beta version works fine for a while, it will be merged into the upstream repo
- Switch
- Outlet
- Lightbulb
- Fan
- Garage Door Opener
- Window Covering
- Smoke Sensor
Expand Down
2 changes: 1 addition & 1 deletion SUPPORTED_DEVICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Most category code is pinyin abbreviation of Chinese name.
| String Lights | 灯串 | dc | Lightbulb ||
| Strip Lights | 灯带 | dd | Lightbulb ||
| Motion Sensor Light | 感应灯 | gyd | | |
| Ceiling Fan Light | 风扇灯 | fsd | | |
| Ceiling Fan Light | 风扇灯 | fsd | Fanv2 | |
| Solar Light | 太阳能灯 | tyndj | | |
| Dimmer | 调光器 | tgq | Lightbulb ||
| Remote Control | 遥控器 | ykq | | |
Expand Down
4 changes: 3 additions & 1 deletion src/accessory/AccessoryFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import BaseAccessory from './BaseAccessory';
import LightAccessory from './LightAccessory';
import OutletAccessory from './OutletAccessory';
import SwitchAccessory from './SwitchAccessory';
import FanAccessory from './FanAccessory';
import GarageDoorAccessory from './GarageDoorAccessory';
import WindowAccessory from './WindowAccessory';
import WindowCoveringAccessory from './WindowCoveringAccessory';
Expand Down Expand Up @@ -56,8 +57,9 @@ export default class AccessoryFactory {
handler = new SwitchAccessory(platform, accessory);
break;
case 'fs':
case 'fsd':
case 'fskg':
// TODO Fanv2Accessory
handler = new FanAccessory(platform, accessory);
break;
case 'ckmkzq':
handler = new GarageDoorAccessory(platform, accessory);
Expand Down
172 changes: 172 additions & 0 deletions src/accessory/FanAccessory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { PlatformAccessory } from 'homebridge';
import { TuyaDeviceStatus, TuyaDeviceFunctionEnumProperty, TuyaDeviceFunctionIntegerProperty } from '../device/TuyaDevice';
import { TuyaPlatform } from '../platform';
import BaseAccessory from './BaseAccessory';

export default class FanAccessory extends BaseAccessory {

constructor(platform: TuyaPlatform, accessory: PlatformAccessory) {
super(platform, accessory);

this.configureActive();
this.configureRotationSpeed();

this.configureLightOn();
this.configureLightBrightness();
}

fanService() {
return this.accessory.getService(this.Service.Fanv2)
|| this.accessory.addService(this.Service.Fanv2);
}

lightService() {
return this.accessory.getService(this.Service.Lightbulb)
|| this.accessory.addService(this.Service.Lightbulb);
}

getFanActiveStatus() {
return this.device.getDeviceStatus('switch_fan')
|| this.device.getDeviceStatus('fan_switch')
|| this.device.getDeviceStatus('switch');
}

getFanSpeedFunction() {
return this.device.getDeviceFunction('fan_speed');
}

getFanSpeedLevelFunction() {
return this.device.getDeviceFunction('fan_speed_enum');
}

getFanSpeedLevelProperty() {
return this.device.getDeviceFunctionProperty('fan_speed_enum') as TuyaDeviceFunctionEnumProperty | undefined;
}

getFanSwingStatus() {
return this.device.getDeviceStatus('fan_horizontal');
}

getLightOnStatus() {
return this.device.getDeviceStatus('light')
|| this.device.getDeviceStatus('switch_led');
}

getLightBrightnessStatus() {
return this.device.getDeviceStatus('bright_value');
}

getLightBrightnessProperty() {
return this.device.getDeviceFunctionProperty('bright_value') as TuyaDeviceFunctionIntegerProperty | undefined;
}


configureActive() {
this.fanService().getCharacteristic(this.Characteristic.Active)
.onGet(() => {
const status = this.getFanActiveStatus()!;
return status.value as boolean;
})
.onSet(value => {
const status = this.getFanActiveStatus()!;
this.deviceManager.sendCommands(this.device.id, [{
code: status.code,
value: (value === this.Characteristic.Active.ACTIVE) ? true : false,
}]);
});
}

configureRotationSpeed() {
const speedFunction = this.getFanSpeedFunction();
const speedLevelFunction = this.getFanSpeedLevelFunction();
const speedLevelProperty = this.getFanSpeedLevelProperty();
const props = { minValue: 0, maxValue: 100, minStep: 1};
if (!speedFunction) {
if (speedLevelProperty) {
props.minStep = Math.floor(100 / (speedLevelProperty.range.length - 1));
props.maxValue = props.minStep * (speedLevelProperty.range.length - 1);
} else {
props.minStep = 100;
}
}
this.log.debug(`Set props for RotationSpeed: ${JSON.stringify(props)}`);

this.fanService().getCharacteristic(this.Characteristic.RotationSpeed)
.onGet(() => {
if (speedFunction) {
const status = this.device.getDeviceStatus(speedFunction.code);
let value = Math.max(0, status?.value as number);
value = Math.min(100, value);
return value;
} else {
if (speedLevelProperty) {
const status = this.device.getDeviceStatus(speedLevelFunction!.code)!;
const index = speedLevelProperty.range.indexOf(status.value as string);
return props.minStep * index;
}

const status = this.getFanActiveStatus()!;
return (status.value as boolean) ? 100 : 0;
}
})
.onSet(value => {
const commands: TuyaDeviceStatus[] = [];
if (speedFunction) {
commands.push({ code: speedFunction.code, value: value as number });
} else {
if (speedLevelProperty) {
const index = value as number / props.minStep;
commands.push({ code: speedLevelFunction!.code, value: speedLevelProperty.range[index] });
} else {
const on = this.getFanActiveStatus()!;
commands.push({ code: on.code, value: (value > 50) ? true : false });
}
}
this.deviceManager.sendCommands(this.device.id, commands);
})
.setProps(props);
}

configureLightOn() {
const status = this.getLightOnStatus();
if (!status) {
return;
}

this.lightService().getCharacteristic(this.Characteristic.On)
.onGet(() => {
const status = this.getLightOnStatus();
return status?.value as boolean;
})
.onSet(value => {
this.deviceManager.sendCommands(this.device.id, [{
code: status.code,
value: value as boolean,
}]);
});
}

configureLightBrightness() {

const property = this.getLightBrightnessProperty();
if (!property) {
return;
}

this.lightService().getCharacteristic(this.Characteristic.Brightness)
.onGet(() => {
const status = this.getLightBrightnessStatus();
let value = Math.floor(100 * (status?.value as number) / property.max);
value = Math.max(0, value);
value = Math.min(100, value);
return value;
})
.onSet(value => {
const status = this.getLightBrightnessStatus()!;
this.deviceManager.sendCommands(this.device.id, [{
code: status.code,
value: Math.floor((value as number) * property.max / 100),
}]);
});
}
}

0 comments on commit fdb4f1e

Please sign in to comment.