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

Update range of characteristic if out of range numeric value is received #985

Merged
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: 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
Loading