Skip to content

Commit

Permalink
fix: parse negative setback state consistently (#7366)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone authored Nov 5, 2024
1 parent f850354 commit 6510aa8
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 32 deletions.
9 changes: 5 additions & 4 deletions packages/cc/src/cc/ClimateControlScheduleCC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,9 @@ export class ClimateControlScheduleCCOverrideReport
): ClimateControlScheduleCCOverrideReport {
validatePayload(raw.payload.length >= 2);
const overrideType: ScheduleOverrideType = raw.payload[0] & 0b11;
const overrideState: SetbackState = decodeSetbackState(raw.payload[1])
|| raw.payload[1];
const overrideState: SetbackState = decodeSetbackState(raw.payload, 1)
// If we receive an unknown setback state, return the raw value
|| raw.payload.readInt8(1);

return new this({
nodeId: ctx.sourceNodeId,
Expand Down Expand Up @@ -554,8 +555,8 @@ export class ClimateControlScheduleCCOverrideSet
public overrideState: SetbackState;

public serialize(ctx: CCEncodingContext): Bytes {
this.payload = Bytes.from([
this.overrideType & 0b11,
this.payload = Bytes.concat([
[this.overrideType & 0b11],
encodeSetbackState(this.overrideState),
]);
return super.serialize(ctx);
Expand Down
22 changes: 10 additions & 12 deletions packages/cc/src/cc/ThermostatSetbackCC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,9 @@ export class ThermostatSetbackCCSet extends ThermostatSetbackCC {
validatePayload(raw.payload.length >= 2);
const setbackType: SetbackType = raw.payload[0] & 0b11;

// If we receive an unknown setback state, return the raw value
const rawSetbackState = raw.payload.readInt8(1);
const setbackState: SetbackState = decodeSetbackState(rawSetbackState)
|| rawSetbackState;
const setbackState: SetbackState = decodeSetbackState(raw.payload, 1)
// If we receive an unknown setback state, return the raw value
|| raw.payload.readInt8(1);

return new this({
nodeId: ctx.sourceNodeId,
Expand All @@ -210,8 +209,8 @@ export class ThermostatSetbackCCSet extends ThermostatSetbackCC {
public setbackState: SetbackState;

public serialize(ctx: CCEncodingContext): Bytes {
this.payload = Bytes.from([
this.setbackType & 0b11,
this.payload = Bytes.concat([
[this.setbackType & 0b11],
encodeSetbackState(this.setbackState),
]);
return super.serialize(ctx);
Expand Down Expand Up @@ -257,10 +256,9 @@ export class ThermostatSetbackCCReport extends ThermostatSetbackCC {
validatePayload(raw.payload.length >= 2);
const setbackType: SetbackType = raw.payload[0] & 0b11;

// If we receive an unknown setback state, return the raw value
const rawSetbackState = raw.payload.readInt8(1);
const setbackState: SetbackState = decodeSetbackState(rawSetbackState)
|| rawSetbackState;
const setbackState: SetbackState = decodeSetbackState(raw.payload, 1)
// If we receive an unknown setback state, return the raw value
|| raw.payload.readInt8(1);

return new this({
nodeId: ctx.sourceNodeId,
Expand All @@ -274,8 +272,8 @@ export class ThermostatSetbackCCReport extends ThermostatSetbackCC {
public readonly setbackState: SetbackState;

public serialize(ctx: CCEncodingContext): Bytes {
this.payload = Bytes.from([
this.setbackType & 0b11,
this.payload = Bytes.concat([
[this.setbackType & 0b11],
encodeSetbackState(this.setbackState),
]);
return super.serialize(ctx);
Expand Down
18 changes: 11 additions & 7 deletions packages/cc/src/lib/serializers.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ZWaveErrorCodes, assertZWaveError } from "@zwave-js/core";
import { Bytes } from "@zwave-js/shared/safe";
import { test } from "vitest";
import {
decodeSetbackState,
Expand All @@ -15,14 +16,16 @@ test("encodeSetbackState() should return the defined values for the special stat
) as (keyof typeof setbackSpecialStateValues)[]
) {
t.expect(
encodeSetbackState(state as any),
encodeSetbackState(state as any)[0],
).toBe(setbackSpecialStateValues[state]);
}
});

test("encodeSetbackState() should return the value times 10 otherwise", (t) => {
for (const val of [0.1, 12, -12.7, 0, 5.5]) {
t.expect(encodeSetbackState(val)).toBe(val * 10);
const result = encodeSetbackState(val);
const raw = result.readInt8(0);
t.expect(raw).toBe(val * 10);
}
});

Expand All @@ -32,19 +35,20 @@ test("decodeSetbackState() should return the defined values for the special stat
setbackSpecialStateValues,
) as (keyof typeof setbackSpecialStateValues)[]
) {
t.expect(decodeSetbackState(setbackSpecialStateValues[state])).toBe(
state,
);
const raw = Uint8Array.from([setbackSpecialStateValues[state]]);
t.expect(decodeSetbackState(raw)).toBe(state);
}
});

test("decodeSetbackState() should return undefined if an unknown special state is passed", (t) => {
t.expect(decodeSetbackState(0x7e)).toBeUndefined();
t.expect(decodeSetbackState(Uint8Array.from([0x7e]))).toBeUndefined();
});

test("decodeSetbackState() should return the value divided by 10 otherwise", (t) => {
for (const val of [1, 120, -127, 0, 55]) {
t.expect(decodeSetbackState(val)).toBe(val / 10);
const raw = new Bytes(1);
raw.writeInt8(val);
t.expect(decodeSetbackState(raw)).toBe(val / 10);
}
});

Expand Down
32 changes: 23 additions & 9 deletions packages/cc/src/lib/serializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,29 @@ export const setbackSpecialStateValues: Record<SetbackSpecialState, number> = {
* @publicAPI
* Encodes a setback state to use in a ThermostatSetbackCC
*/
export function encodeSetbackState(state: SetbackState): number {
if (typeof state === "string") return setbackSpecialStateValues[state];
state = clamp(state, -12.8, 12);
return Math.round(state * 10);
export function encodeSetbackState(state: SetbackState): Bytes {
let rawValue: number;
if (typeof state === "string") {
rawValue = setbackSpecialStateValues[state];
} else {
state = clamp(state, -12.8, 12);
rawValue = Math.round(state * 10);
}

const ret = new Bytes(1);
ret.writeInt8(rawValue);
return ret;
}

/**
* @publicAPI
* Decodes a setback state used in a ThermostatSetbackCC
*/
export function decodeSetbackState(val: number): SetbackState | undefined {
export function decodeSetbackState(
data: Uint8Array,
offset: number = 0,
): SetbackState | undefined {
const val = Bytes.view(data).readInt8(offset);
if (val > 120) {
// Special state, try to look it up
const foundEntry = Object.entries(setbackSpecialStateValues).find(
Expand All @@ -49,7 +61,7 @@ export function decodeSwitchpoint(data: Uint8Array): Switchpoint {
return {
hour: data[0] & 0b000_11111,
minute: data[1] & 0b00_111111,
state: decodeSetbackState(data[2]),
state: decodeSetbackState(data, 2),
};
}

Expand All @@ -64,9 +76,11 @@ export function encodeSwitchpoint(point: Switchpoint): Bytes {
ZWaveErrorCodes.CC_Invalid,
);
}
return Bytes.from([
point.hour & 0b000_11111,
point.minute & 0b00_111111,
return Bytes.concat([
[
point.hour & 0b000_11111,
point.minute & 0b00_111111,
],
encodeSetbackState(point.state),
]);
}
Expand Down

0 comments on commit 6510aa8

Please sign in to comment.