Skip to content

Commit

Permalink
Merge pull request #187 from athombv/sensor-updates
Browse files Browse the repository at this point in the history
Sensor device update - battery percentage & tamper alarm
  • Loading branch information
bobvandevijver authored Aug 29, 2024
2 parents 527dce9 + 49ab693 commit a702f04
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 29 deletions.
14 changes: 4 additions & 10 deletions drivers/dimmer/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ export default class TuyaOAuth2DeviceDimmer extends TuyaOAuth2Device {
}
}

async safeSetCapabilityValue(capabilityId: string, value: unknown): Promise<void> {
if (this.hasCapability(capabilityId)) {
await this.setCapabilityValue(capabilityId, value);
}
}

async onTuyaStatus(status: TuyaStatus, changed: string[]): Promise<void> {
await super.onTuyaStatus(status, changed);

Expand Down Expand Up @@ -63,7 +57,7 @@ export default class TuyaOAuth2DeviceDimmer extends TuyaOAuth2Device {
triggerCard.trigger(this, {}, {}).catch(this.error);
}

await this.safeSetCapabilityValue(`onoff.${switch_i}`, switchStatus).catch(this.error);
await this.safeSetCapabilityValue(`onoff.${switch_i}`, switchStatus);
}

if (typeof brightnessMin === 'number') {
Expand Down Expand Up @@ -98,12 +92,12 @@ export default class TuyaOAuth2DeviceDimmer extends TuyaOAuth2Device {
.catch(this.error);
}

await this.safeSetCapabilityValue(`dim.${switch_i}`, scaledValue).catch(this.error);
await this.safeSetCapabilityValue(`dim`, scaledValue).catch(this.error);
await this.safeSetCapabilityValue(`dim.${switch_i}`, scaledValue);
await this.safeSetCapabilityValue(`dim`, scaledValue);
}
}

await this.safeSetCapabilityValue('onoff', anySwitchOn).catch(this.error);
await this.safeSetCapabilityValue('onoff', anySwitchOn);
}

// TODO migrate to util onSettings
Expand Down
16 changes: 5 additions & 11 deletions drivers/socket/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ export default class TuyaOAuth2DeviceSocket extends TuyaOAuth2Device {
}
}

async safeSetCapabilityValue(capabilityId: string, value: unknown): Promise<void> {
if (this.hasCapability(capabilityId)) {
await this.setCapabilityValue(capabilityId, value);
}
}

async onTuyaStatus(status: TuyaStatus, changedStatusCodes: string[]): Promise<void> {
await super.onTuyaStatus(status, changedStatusCodes);

Expand Down Expand Up @@ -66,30 +60,30 @@ export default class TuyaOAuth2DeviceSocket extends TuyaOAuth2Device {
.catch(this.error);
}

this.safeSetCapabilityValue(switchCapability, switchStatus).catch(this.error);
await this.safeSetCapabilityValue(switchCapability, switchStatus);
}
}

if (typeof status['switch'] === 'boolean') {
anySwitchOn = anySwitchOn || status['switch'];
}

this.safeSetCapabilityValue('onoff', anySwitchOn).catch(this.error);
await this.safeSetCapabilityValue('onoff', anySwitchOn);

if (typeof status['cur_power'] === 'number') {
const scaling = 10.0 ** parseInt(this.getSetting('power_scaling') ?? '0');
this.setCapabilityValue('measure_power', status['cur_power'] / scaling).catch(this.error);
await this.safeSetCapabilityValue('measure_power', status['cur_power'] / scaling);
}

if (typeof status['cur_voltage'] === 'number') {
const scaling = 10.0 ** parseInt(this.getSetting('cur_voltage_scaling') ?? '0');
this.setCapabilityValue('measure_voltage', status['cur_voltage'] / scaling).catch(this.error);
await this.safeSetCapabilityValue('measure_voltage', status['cur_voltage'] / scaling);
}

if (typeof status['cur_current'] === 'number') {
// Additionally convert mA
const scaling = 1000.0 * 10.0 ** parseInt(this.getSetting('cur_current_scaling') ?? '0');
this.setCapabilityValue('measure_current', status['cur_current'] / scaling).catch(this.error);
await this.safeSetCapabilityValue('measure_current', status['cur_current'] / scaling);
}

if (status['child_lock'] !== undefined) {
Expand Down
10 changes: 9 additions & 1 deletion lib/TuyaOAuth2Device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export default class TuyaOAuth2Device extends OAuth2Device<TuyaOAuth2Client> {
return this.oAuth2Client.queryDataPoints(deviceId);
}

setDataPoint(dataPointId: string, value: unknown): Promise<void> {
async setDataPoint(dataPointId: string, value: unknown): Promise<void> {
const { deviceId } = this.data;
return this.oAuth2Client.setDataPoint(deviceId, dataPointId, value);
}
Expand All @@ -232,6 +232,14 @@ export default class TuyaOAuth2Device extends OAuth2Device<TuyaOAuth2Client> {
return this.oAuth2Client.getStreamingLink(deviceId, type);
}

async safeSetCapabilityValue(capabilityId: string, value: unknown): Promise<void> {
if (!this.hasCapability(capabilityId)) {
return;
}

await this.setCapabilityValue(capabilityId, value).catch(this.error);
}

log(...args: unknown[]): void {
super.log(`[tc:${this.getStoreValue('tuya_category')}]`, ...args);
}
Expand Down
18 changes: 17 additions & 1 deletion lib/TuyaOAuth2DeviceSensor.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import { TuyaStatus } from '../types/TuyaTypes';
import TuyaOAuth2Device from './TuyaOAuth2Device';
import * as TuyaSensorMigrations from '../lib/migrations/TuyaSensorMigrations';

export default class TuyaOAuth2DeviceSensor extends TuyaOAuth2Device {
async performMigrations(): Promise<void> {
await super.performMigrations();
await TuyaSensorMigrations.performMigrations(this);
}

async onTuyaStatus(status: TuyaStatus, changedStatusCodes: string[]): Promise<void> {
await super.onTuyaStatus(status, changedStatusCodes);

// alarm_battery
if (typeof status['battery_state'] === 'string') {
super.setCapabilityValue('alarm_battery', status['battery_state'] === 'low').catch(this.error);
await this.safeSetCapabilityValue('alarm_battery', status['battery_state'] === 'low');
}

// measure_battery
if (typeof status['battery_percentage'] === 'number') {
await this.safeSetCapabilityValue('measure_battery', status['battery_percentage']);
}

// alarm_tamper
if (typeof status['temper_alarm'] === 'boolean') {
await this.safeSetCapabilityValue('alarm_tamper', status['temper_alarm']);
}
}
}
Expand Down
35 changes: 29 additions & 6 deletions lib/TuyaOAuth2DriverSensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,35 @@ export default class TuyaOAuth2DriverSensor extends TuyaOAuth2Driver {
): ListDeviceProperties {
const props = super.onTuyaPairListDeviceProperties(device, specifications, dataPoints);

// alarm_battery
const hasBatteryState = device.status.some(({ code }) => code === 'battery_state');
if (hasBatteryState) {
props.store?.tuya_capabilities.push('battery_state');
props.capabilities?.push('alarm_battery');
}
const tuyaCodes = device.status.map(s => s.code);
const hasBatteryPercentage = tuyaCodes.includes('battery_percentage');

tuyaCodes.map(tuyaCode => {
switch (tuyaCode) {
case 'battery_state':
if (hasBatteryPercentage) {
// Do not add battery alarm if percentage is available
return;
}

props.capabilities.push('alarm_battery');
break;

case 'battery_percentage':
props.capabilities.push('measure_battery');
break;

case 'temper_alarm':
props.capabilities.push('alarm_tamper');
break;

default:
// Default return to not add the capability
return;
}

props.store.tuya_capabilities.push(tuyaCode);
});

return props;
}
Expand Down
24 changes: 24 additions & 0 deletions lib/migrations/MigrationStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type TuyaOAuth2Device from '../TuyaOAuth2Device';

const storeKey = '_migrations';

/** Execute a migration when not already done, based on the supplied migration id. */
export async function executeMigration(
device: TuyaOAuth2Device,
migrationId: string,
migration: () => Promise<void>,
): Promise<void> {
let migrations: string[] | undefined = device.getStoreValue(storeKey);
if (!Array.isArray(migrations)) {
migrations = [];
}

if (migrations.includes(migrationId)) {
return;
}

await migration();

migrations.push(migrationId);
await device.setStoreValue(storeKey, migrations).catch(device.error);
}
54 changes: 54 additions & 0 deletions lib/migrations/TuyaSensorMigrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import TuyaOAuth2DeviceSensor from '../TuyaOAuth2DeviceSensor';
import { executeMigration } from './MigrationStore';

export async function performMigrations(device: TuyaOAuth2DeviceSensor): Promise<void> {
await addBatteryPercentageMigration(device).catch(device.error);
await addTemperAlarmMigration(device).catch(device.error);
}

async function addBatteryPercentageMigration(device: TuyaOAuth2DeviceSensor): Promise<void> {
await executeMigration(device, 'sensor_battery_percentage', async () => {
if (device.hasCapability('measure_battery') || device.hasCapability('alarm_battery')) {
// Don't touch existing devices that already have a battery related capability
device.log('Battery percentage migration skipped');

return;
}

device.log('Migrating battery percentage...');

const status = await device.getStatus();
const batteryPercentage = status.find(s => s.code === 'battery_percentage');
if (!batteryPercentage) {
device.log('Battery percentage not supported');
return;
}

await device.addCapability('measure_battery');
await device.safeSetCapabilityValue('measure_battery', batteryPercentage.value);

device.log('Battery percentage added');
});
}

async function addTemperAlarmMigration(device: TuyaOAuth2DeviceSensor): Promise<void> {
await executeMigration(device, 'sensor_temper_alarm', async () => {
if (device.hasCapability('alarm_tamper')) {
return;
}

device.log('Migrating tamper alarm...');

const status = await device.getStatus();
const temperAlarm = status.find(s => s.code === 'temper_alarm');
if (!temperAlarm) {
device.log('Tamper alarm not supported');
return;
}

await device.addCapability('alarm_tamper');
await device.safeSetCapabilityValue('alarm_tamper', temperAlarm.value);

device.log('Tamper alarm added');
});
}

0 comments on commit a702f04

Please sign in to comment.