Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Multiple Dimmer and Dimmer Switch support (tgq, tgkg). #82

Merged
merged 4 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add Ceiling Fan Light support (`fsd`). (#37)
- Add Thermostat Valve support (`wkf`). (#50)
- Add Motion Sensor Light support (`gyd`). (#65)
- Add Multiple Dimmer and Dimmer Switch support (`tgq`, `tgkg`).


### Fixed
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ If beta version works fine for a while, it will be merged into the upstream repo
- More supported devices.
- [Light] Spotlight (`sxd`)
- [Light] Motion Sensor Light (`gyd`)
- [Dimmer] Dual Dimmer (`tgq`)
- [Dimmer] Dual Dimmer Switch (`tgkg`)
- [Switch] Scene Light Socket (`qjdcz`)
- [Fanv2] Ceiling Fan Light (`fsd`)
- [Window] Door and Window Controller (`mc`)
Expand Down
7 changes: 5 additions & 2 deletions src/accessory/AccessoryFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TuyaPlatform } from '../platform';

import BaseAccessory from './BaseAccessory';
import LightAccessory from './LightAccessory';
import DimmerAccessory from './DimmerAccessory';
import OutletAccessory from './OutletAccessory';
import SwitchAccessory from './SwitchAccessory';
import FanAccessory from './FanAccessory';
Expand Down Expand Up @@ -44,11 +45,13 @@ export default class AccessoryFactory {
case 'dc':
case 'dd':
case 'gyd':
case 'tgq':
case 'sxd':
case 'tgkg':
handler = new LightAccessory(platform, accessory);
break;
case 'tgq':
case 'tgkg':
handler = new DimmerAccessory(platform, accessory);
break;
case 'cz':
case 'pc':
handler = new OutletAccessory(platform, accessory);
Expand Down
136 changes: 136 additions & 0 deletions src/accessory/DimmerAccessory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { PlatformAccessory, Service } from 'homebridge';
import { TuyaDeviceSchemaIntegerProperty, TuyaDeviceStatus } from '../device/TuyaDevice';
import { TuyaPlatform } from '../platform';
import BaseAccessory from './BaseAccessory';
import { remap, limit } from '../util/util';

export default class DimmerAccessory extends BaseAccessory {

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

this.configure();
}

getOnSchema(index: number) {
if (index === 0) {
return this.getSchema('switch')
|| this.getSchema('switch_led');
}
return this.getSchema(`switch_${index}`)
|| this.getSchema(`switch_led_${index}`);
}

getBrightnessSchema(index: number) {
if (index === 0) {
return this.getSchema('bright_value');
}
return this.getSchema(`bright_value_${index}`);
}


configure() {

for (let index = 0; index <= 3; index++) {
const schema = this.getBrightnessSchema(index);
if (!schema) {
continue;
}

const oldService = this.accessory.getService(this.Service.Lightbulb);
if (oldService && oldService?.subtype === undefined) {
this.platform.log.warn('Remove old service:', oldService.UUID);
this.accessory.removeService(oldService);
}

const service = this.accessory.getService(schema.code)
|| this.accessory.addService(this.Service.Lightbulb, schema.code, schema.code);
this.configureOn(service, index);
this.configureBrightness(service, index);
}
}

configureOn(service: Service, index: number) {
const schema = this.getOnSchema(index);
if (!schema) {
return;
}

service.getCharacteristic(this.Characteristic.On)
.onGet(() => {
const status = this.getStatus(schema.code)!;
return status.value as boolean;
})
.onSet((value) => {
this.log.debug(`Characteristic.On set to: ${value}`);
this.sendCommands([{ code: schema.code, value: value as boolean }], true);
});
}

configureBrightness(service: Service, index: number) {
const schema = this.getBrightnessSchema(index);
if (!schema) {
return;
}

const { max } = schema.property as TuyaDeviceSchemaIntegerProperty;
const range = max; // not max - min
const props = {
minValue: 0,
maxValue: 100,
minStep: 1,
};

const minStatus = this.getStatus(`brightness_min_${index}`);
const maxStatus = this.getStatus(`brightness_max_${index}`);
if (minStatus && maxStatus && maxStatus.value > minStatus.value) {
const minValue = Math.ceil(remap(minStatus.value as number, 0, range, 0, 100));
const maxValue = Math.floor(remap(maxStatus.value as number, 0, range, 0, 100));
props.minValue = Math.max(props.minValue, minValue);
props.maxValue = Math.min(props.maxValue, maxValue);
}
this.log.debug('Set props for Brightness:', props);

service.getCharacteristic(this.Characteristic.Brightness)
.onGet(() => {
const status = this.getStatus(schema.code)!;
let value = status.value as number;
value = remap(value, 0, range, 0, 100);
value = limit(value, props.minValue, props.maxValue);
return value;
})
.onSet((value) => {
this.log.debug(`Characteristic.Brightness set to: ${value}`);
let brightValue = value as number;
brightValue = remap(brightValue, 0, 100, 0, range);
brightValue = Math.floor(brightValue);
this.sendCommands([{ code: schema.code, value: brightValue }], true);
}).setProps(props);

}

async onDeviceStatusUpdate(status: TuyaDeviceStatus[]) {

// brightness range updated
if (status.length !== this.device.status.length) {
for (const _status of status) {
if (!_status.code.startsWith('brightness_min_')
&& !_status.code.startsWith('brightness_max_')) {
continue;
}

this.platform.log.warn('Brightness range updated, please restart homebridge to take effect.');
// TODO updating props
// this.platform.log.debug('Brightness range updated, resetting props...');
// this.configure();
break;
}
}

super.onDeviceStatusUpdate(status);
}

}
6 changes: 2 additions & 4 deletions src/accessory/LightAccessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,12 @@ export default class LightAccessory extends BaseAccessory {
}

getOnSchema() {
return this.getSchema('switch_led')
|| this.getSchema('switch_led_1');
return this.getSchema('switch_led');
}

getBrightnessSchema() {
return this.getSchema('bright_value')
|| this.getSchema('bright_value_v2')
|| this.getSchema('bright_value_1');
|| this.getSchema('bright_value_v2');
}

getColorTemperatureSchema() {
Expand Down
22 changes: 22 additions & 0 deletions src/util/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export function remap(
value: number,
srcStart: number,
srcEnd: number,
dstStart: number,
dstEnd: number,
) {
const percent = (value - srcStart) / (srcEnd - srcStart);
const result = percent * (dstEnd - dstStart) + dstStart;
return result;
}

export function limit(
value: number,
start: number,
end: number,
) {
let result = value;
result = Math.min(end, result);
result = Math.max(start, result);
return result;
}