Skip to content

Commit

Permalink
Update range of characteristic if out of range numeric value is recei…
Browse files Browse the repository at this point in the history
…ved (#985)

* Update range of numeric characteristic if an out of range value is received from Zigbee2MQTT

* Minor tweak to release-it config while I'm at it
  • Loading branch information
itavero authored Jan 4, 2025
1 parent adc5b99 commit 1726609
Show file tree
Hide file tree
Showing 11 changed files with 50 additions and 17 deletions.
1 change: 0 additions & 1 deletion .release-it.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ hooks:
plugins:
"@release-it/keep-a-changelog":
addVersionUrl: true
addUnreleased: true
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/converters/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -54,13 +55,15 @@ class StatelessProgrammableSwitchHandler implements ServiceHandler {
public readonly identifier: string;
private readonly monitor: CharacteristicMonitor;
public readonly mainCharacteristics: Characteristic[] = [];
private readonly log: BasicLogger;

constructor(
accessory: BasicAccessory,
private readonly actionExpose: ExposesEntryWithEnumProperty,
mapping: SwitchActionMapping
) {
this.identifier = StatelessProgrammableSwitchHandler.generateIdentifier(actionExpose.endpoint, mapping.subType);
this.log = accessory.log;

// Create service
let subType = mapping.subType;
Expand Down Expand Up @@ -113,7 +116,7 @@ class StatelessProgrammableSwitchHandler implements ServiceHandler {
}

updateState(state: Record<string, unknown>): void {
this.monitor.callback(state);
this.monitor.callback(state, this.log);
}

static generateIdentifier(endpoint: string | undefined, mappingSubType: string | undefined) {
Expand Down
5 changes: 4 additions & 1 deletion src/converters/basic_sensors/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -92,6 +95,6 @@ export abstract class BasicSensorHandler implements ServiceHandler {
}

updateState(state: Record<string, unknown>): void {
this.monitors.forEach((m) => m.callback(state));
this.monitors.forEach((m) => m.callback(state, this.log));
}
}
2 changes: 1 addition & 1 deletion src/converters/battery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ class BatteryHandler implements ServiceHandler {
}

updateState(state: Record<string, unknown>): void {
this.monitors.forEach((m) => m.callback(state));
this.monitors.forEach((m) => m.callback(state, this.accessory.log));
}

static generateIdentifier(endpoint: string | undefined) {
Expand Down
2 changes: 1 addition & 1 deletion src/converters/climate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ class ThermostatHandler implements ServiceHandler {
}

updateState(state: Record<string, unknown>): void {
this.monitors.forEach((m) => m.callback(state));
this.monitors.forEach((m) => m.callback(state, this.accessory.log));
}

static generateIdentifier(endpoint: string | undefined) {
Expand Down
2 changes: 1 addition & 1 deletion src/converters/cover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ class CoverHandler implements ServiceHandler {
}

updateState(state: Record<string, unknown>): 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;
Expand Down
2 changes: 1 addition & 1 deletion src/converters/light.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>) {
Expand Down
2 changes: 1 addition & 1 deletion src/converters/lock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class LockHandler implements ServiceHandler {
}

updateState(state: Record<string, unknown>): 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 {
Expand Down
40 changes: 32 additions & 8 deletions src/converters/monitor.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown>): void;
callback(state: Record<string, unknown>, logger: BasicLogger): void;
}

abstract class BaseCharacteristicMonitor implements CharacteristicMonitor {
Expand All @@ -15,17 +16,40 @@ abstract class BaseCharacteristicMonitor implements CharacteristicMonitor {

abstract transformValueFromMqtt(value: unknown): CharacteristicValue | undefined;

callback(state: Record<string, unknown>): void {
callback(state: Record<string, unknown>, 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 {
Expand All @@ -38,10 +62,10 @@ export class NestedCharacteristicMonitor implements CharacteristicMonitor {
}
}

callback(state: Record<string, unknown>): void {
callback(state: Record<string, unknown>, logger: BasicLogger): void {
if (this.key in state) {
const nested_state = state[this.key] as Record<string, unknown>;
this.monitors.forEach((m) => m.callback(nested_state));
this.monitors.forEach((m) => m.callback(nested_state, logger));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/converters/switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class SwitchHandler implements ServiceHandler {
}

updateState(state: Record<string, unknown>): void {
this.monitor.callback(state);
this.monitor.callback(state, this.accessory.log);
}

private handleSetOn(value: CharacteristicValue, callback: CharacteristicSetCallback): void {
Expand Down

0 comments on commit 1726609

Please sign in to comment.