From 7afb4200b62da14a26210c69ebd7edf5ff4e6654 Mon Sep 17 00:00:00 2001 From: Arno Moonen Date: Sat, 4 Jan 2025 22:52:32 +0100 Subject: [PATCH] Update range of numeric characteristic if an out of range value is received from Zigbee2MQTT --- CHANGELOG.md | 4 +++ src/converters/action.ts | 5 +++- src/converters/basic_sensors/basic.ts | 5 +++- src/converters/battery.ts | 2 +- src/converters/climate.ts | 2 +- src/converters/cover.ts | 2 +- src/converters/light.ts | 2 +- src/converters/lock.ts | 2 +- src/converters/monitor.ts | 40 +++++++++++++++++++++------ src/converters/switch.ts | 2 +- 10 files changed, 50 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9249e96f..88708bf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ Since version 1.0.0, we try to follow the [Semantic Versioning](https://semver.o ## [Unreleased] +### Changed + +- For numeric characteristics that have a range set, the range is automatically updated if an out of range value is received from Zigbee2MQTT. + ## [1.11.0-beta.7] - 2025-01-04 ### Changed diff --git a/src/converters/action.ts b/src/converters/action.ts index 072761da..9e6c1aeb 100644 --- a/src/converters/action.ts +++ b/src/converters/action.ts @@ -6,6 +6,7 @@ import { CharacteristicMonitor, MappingCharacteristicMonitor } from './monitor'; import { Characteristic, CharacteristicProps, CharacteristicValue } from 'homebridge'; import { getOrAddCharacteristic } from '../helpers'; import { SwitchActionHelper, SwitchActionMapping } from './action_helper'; +import { BasicLogger } from '../logger'; export class StatelessProgrammableSwitchCreator implements ServiceCreator { createServicesFromExposes(accessory: BasicAccessory, exposes: ExposesEntry[]): void { @@ -54,6 +55,7 @@ class StatelessProgrammableSwitchHandler implements ServiceHandler { public readonly identifier: string; private readonly monitor: CharacteristicMonitor; public readonly mainCharacteristics: Characteristic[] = []; + private readonly log: BasicLogger; constructor( accessory: BasicAccessory, @@ -61,6 +63,7 @@ class StatelessProgrammableSwitchHandler implements ServiceHandler { mapping: SwitchActionMapping ) { this.identifier = StatelessProgrammableSwitchHandler.generateIdentifier(actionExpose.endpoint, mapping.subType); + this.log = accessory.log; // Create service let subType = mapping.subType; @@ -113,7 +116,7 @@ class StatelessProgrammableSwitchHandler implements ServiceHandler { } updateState(state: Record): void { - this.monitor.callback(state); + this.monitor.callback(state, this.log); } static generateIdentifier(endpoint: string | undefined, mappingSubType: string | undefined) { diff --git a/src/converters/basic_sensors/basic.ts b/src/converters/basic_sensors/basic.ts index 5c37a1e8..618bb05f 100644 --- a/src/converters/basic_sensors/basic.ts +++ b/src/converters/basic_sensors/basic.ts @@ -4,12 +4,14 @@ import { CharacteristicMonitor, MappingCharacteristicMonitor } from '../monitor' import { Characteristic, CharacteristicValue, Service } from 'homebridge'; import { getOrAddCharacteristic } from '../../helpers'; import { hap } from '../../hap'; +import { BasicLogger } from '../../logger'; export type ServiceConstructor = (serviceName: string, subType: string | undefined) => Service; export type IdentifierGenerator = (endpoint: string | undefined, accessory: BasicAccessory) => string; export abstract class BasicSensorHandler implements ServiceHandler { + protected log: BasicLogger; protected monitors: CharacteristicMonitor[] = []; protected tamperExpose?: ExposesEntryWithBinaryProperty; protected lowBatteryExpose?: ExposesEntryWithBinaryProperty; @@ -25,6 +27,7 @@ export abstract class BasicSensorHandler implements ServiceHandler { service: ServiceConstructor, additionalSubType?: string | undefined ) { + this.log = accessory.log; const endpoint = sensorExpose.endpoint; let sub = endpoint; @@ -92,6 +95,6 @@ export abstract class BasicSensorHandler implements ServiceHandler { } updateState(state: Record): void { - this.monitors.forEach((m) => m.callback(state)); + this.monitors.forEach((m) => m.callback(state, this.log)); } } diff --git a/src/converters/battery.ts b/src/converters/battery.ts index 53aca692..9f0760d0 100644 --- a/src/converters/battery.ts +++ b/src/converters/battery.ts @@ -141,7 +141,7 @@ class BatteryHandler implements ServiceHandler { } updateState(state: Record): void { - this.monitors.forEach((m) => m.callback(state)); + this.monitors.forEach((m) => m.callback(state, this.accessory.log)); } static generateIdentifier(endpoint: string | undefined) { diff --git a/src/converters/climate.ts b/src/converters/climate.ts index 58cefea2..8f3cb723 100644 --- a/src/converters/climate.ts +++ b/src/converters/climate.ts @@ -268,7 +268,7 @@ class ThermostatHandler implements ServiceHandler { } updateState(state: Record): void { - this.monitors.forEach((m) => m.callback(state)); + this.monitors.forEach((m) => m.callback(state, this.accessory.log)); } static generateIdentifier(endpoint: string | undefined) { diff --git a/src/converters/cover.ts b/src/converters/cover.ts index 50f59989..c31712a8 100644 --- a/src/converters/cover.ts +++ b/src/converters/cover.ts @@ -200,7 +200,7 @@ class CoverHandler implements ServiceHandler { } updateState(state: Record): void { - this.monitors.forEach((m) => m.callback(state)); + this.monitors.forEach((m) => m.callback(state, this.accessory.log)); if (this.motorStateExpose !== undefined && this.motorStateExpose.property in state) { const latestMotorState = state[this.motorStateExpose.property] as string; diff --git a/src/converters/light.ts b/src/converters/light.ts index fa5ae6aa..1199ae6d 100644 --- a/src/converters/light.ts +++ b/src/converters/light.ts @@ -224,7 +224,7 @@ class LightHandler implements ServiceHandler { } } - this.monitors.forEach((m) => m.callback(state)); + this.monitors.forEach((m) => m.callback(state, this.accessory.log)); } private disableAdaptiveLightingBasedOnState(colorModeIsTemperature: boolean, state: Record) { diff --git a/src/converters/lock.ts b/src/converters/lock.ts index 23889343..999f2758 100644 --- a/src/converters/lock.ts +++ b/src/converters/lock.ts @@ -127,7 +127,7 @@ class LockHandler implements ServiceHandler { } updateState(state: Record): void { - this.monitors.forEach((m) => m.callback(state)); + this.monitors.forEach((m) => m.callback(state, this.accessory.log)); } private handleSetState(value: CharacteristicValue, callback: CharacteristicSetCallback): void { diff --git a/src/converters/monitor.ts b/src/converters/monitor.ts index 72d11c67..fafa70ed 100644 --- a/src/converters/monitor.ts +++ b/src/converters/monitor.ts @@ -1,9 +1,10 @@ import { Characteristic, CharacteristicValue, Service, WithUUID } from 'homebridge'; +import { BasicLogger } from '../logger'; export type MqttToHomeKitValueTransformer = (value: unknown) => CharacteristicValue | undefined; export interface CharacteristicMonitor { - callback(state: Record): void; + callback(state: Record, logger: BasicLogger): void; } abstract class BaseCharacteristicMonitor implements CharacteristicMonitor { @@ -15,17 +16,40 @@ abstract class BaseCharacteristicMonitor implements CharacteristicMonitor { abstract transformValueFromMqtt(value: unknown): CharacteristicValue | undefined; - callback(state: Record): void { + callback(state: Record, logger: BasicLogger): void { if (this.key in state) { - let value = state[this.key]; + const value = state[this.key]; if (value !== undefined) { - value = this.transformValueFromMqtt(value); - if (value !== undefined) { - this.service.updateCharacteristic(this.characteristic, value as CharacteristicValue); + const transformed_value = this.transformValueFromMqtt(value); + if (transformed_value !== undefined) { + this.updateRangeIfNeeded(transformed_value, logger); + this.service.updateCharacteristic(this.characteristic, transformed_value); } } } } + + private updateRangeIfNeeded(value: CharacteristicValue, logger: BasicLogger): void { + if (typeof value !== 'number') { + // Only numeric values have a range + return; + } + + const c = this.service.getCharacteristic(this.characteristic); + if (c === undefined) { + return; + } + + if (c.props.minValue !== undefined && value < c.props.minValue) { + // Update min value + logger.warn(`${c.displayName} minimum value updated from ${c.props.minValue} to ${value}, due to received value.`); + c.setProps({ minValue: value }); + } else if (c.props.maxValue !== undefined && value > c.props.maxValue) { + // Update max value + logger.warn(`${c.displayName} maximum value updated from ${c.props.maxValue} to ${value}, due to received value.`); + c.setProps({ maxValue: value }); + } + } } export class NestedCharacteristicMonitor implements CharacteristicMonitor { @@ -38,10 +62,10 @@ export class NestedCharacteristicMonitor implements CharacteristicMonitor { } } - callback(state: Record): void { + callback(state: Record, logger: BasicLogger): void { if (this.key in state) { const nested_state = state[this.key] as Record; - this.monitors.forEach((m) => m.callback(nested_state)); + this.monitors.forEach((m) => m.callback(nested_state, logger)); } } } diff --git a/src/converters/switch.ts b/src/converters/switch.ts index af3ec34e..796dffcb 100644 --- a/src/converters/switch.ts +++ b/src/converters/switch.ts @@ -116,7 +116,7 @@ class SwitchHandler implements ServiceHandler { } updateState(state: Record): void { - this.monitor.callback(state); + this.monitor.callback(state, this.accessory.log); } private handleSetOn(value: CharacteristicValue, callback: CharacteristicSetCallback): void {