-
-
Notifications
You must be signed in to change notification settings - Fork 634
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
feat(config): support Intermatic PE653 v3.1 with water temperature reading #7545
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import { | ||
CommandClasses, | ||
type GetValueDB, | ||
type MessageOrCCLogEntry, | ||
type MessageRecord, | ||
type ValueID, | ||
ValueMetadata, | ||
type WithAddress, | ||
Check failure on line 8 in packages/cc/src/cc/manufacturerProprietary/IntermaticPE653CC.ts GitHub Actions / lint (18)
|
||
ZWaveError, | ||
ZWaveErrorCodes, | ||
validatePayload, | ||
} from "@zwave-js/core/safe"; | ||
import { type CCEncodingContext, type CCParsingContext } from "@zwave-js/cc"; | ||
import { Bytes } from "@zwave-js/shared"; | ||
import { validateArgs } from "@zwave-js/transformers"; | ||
Check failure on line 15 in packages/cc/src/cc/manufacturerProprietary/IntermaticPE653CC.ts GitHub Actions / lint (18)
|
||
import { | ||
POLL_VALUE, | ||
type PollValueImplementation, | ||
SET_VALUE, | ||
type SetValueImplementation, | ||
throwUnsupportedProperty, | ||
throwWrongValueType, | ||
} from "../../lib/API.js"; | ||
import { | ||
type CCRaw, | ||
type CommandClassOptions, | ||
type InterviewContext, | ||
type PersistValuesContext, | ||
type RefreshValuesContext, | ||
} from "../../lib/CommandClass.js"; | ||
import { | ||
ManufacturerProprietaryCC, | ||
ManufacturerProprietaryCCAPI, | ||
} from "../ManufacturerProprietaryCC.js"; | ||
import { | ||
manufacturerId, | ||
manufacturerProprietaryAPI, | ||
} from "./Decorators.js"; | ||
|
||
export const MANUFACTURERID_INTERMATIC_LEGACY = 0x0005; | ||
export const MANUFACTURERID_INTERMATIC_NEW = 0x0072; | ||
|
||
/** Returns the ValueID used to store the current water temperature */ | ||
export function getIntermaticWaterTempValueId(): ValueID { | ||
return { | ||
commandClass: CommandClasses["Manufacturer Proprietary"], | ||
property: "intermatic", | ||
propertyKey: "waterTemperature", | ||
}; | ||
} | ||
|
||
/** Returns the value metadata for water temperature */ | ||
export function getIntermaticWaterTempMetadata(): ValueMetadata { | ||
return { | ||
...ValueMetadata.Number, | ||
label: "Water Temperature", | ||
unit: "°F", | ||
min: 0, | ||
max: 100, | ||
}; | ||
} | ||
|
||
@manufacturerProprietaryAPI(MANUFACTURERID_INTERMATIC_LEGACY) | ||
export class IntermaticPE653CCAPI extends ManufacturerProprietaryCCAPI { | ||
public async getWaterTemperature(): Promise<number | undefined> { | ||
const valueId = getIntermaticWaterTempValueId(); | ||
return this.getValueDB()?.getValue(valueId); | ||
} | ||
|
||
protected get [SET_VALUE](): SetValueImplementation { | ||
return async function( | ||
this: IntermaticPE653CCAPI, | ||
{ property }, | ||
value, | ||
) { | ||
if (property !== "intermatic") { | ||
throwUnsupportedProperty(this.ccId, property); | ||
} | ||
|
||
// This is a read-only CC | ||
throwWrongValueType(this.ccId, property, "readonly", typeof value); | ||
}; | ||
} | ||
|
||
protected get [POLL_VALUE](): PollValueImplementation { | ||
return async function(this: IntermaticPE653CCAPI, { property }) { | ||
if (property !== "intermatic") { | ||
throwUnsupportedProperty(this.ccId, property); | ||
} | ||
|
||
return this.getWaterTemperature(); | ||
}; | ||
} | ||
} | ||
|
||
@manufacturerId(MANUFACTURERID_INTERMATIC_LEGACY) | ||
export class IntermaticPE653CC extends ManufacturerProprietaryCC { | ||
public constructor(options: CommandClassOptions) { | ||
super(options); | ||
this.manufacturerId = MANUFACTURERID_INTERMATIC_LEGACY; | ||
} | ||
|
||
public static from(raw: CCRaw, ctx: CCParsingContext): IntermaticPE653CC { | ||
// Check if the manufacturer ID matches either the legacy or new ID | ||
const ccCommand = raw.ccCommand ?? 0; | ||
const manufacturerId = (ccCommand << 8) | raw.payload[0]; | ||
if (manufacturerId !== MANUFACTURERID_INTERMATIC_LEGACY && | ||
manufacturerId !== MANUFACTURERID_INTERMATIC_NEW) { | ||
throw new ZWaveError( | ||
`Invalid manufacturer ID for Intermatic PE653: ${manufacturerId}`, | ||
ZWaveErrorCodes.Argument_Invalid | ||
); | ||
} | ||
|
||
const cc = new IntermaticPE653CC({ | ||
nodeId: ctx.sourceNodeId, | ||
}); | ||
cc.manufacturerId = manufacturerId; | ||
|
||
// Validate payload length for PE653 v3.1 water temperature frame | ||
validatePayload(raw.payload.length >= 13, "PE653 frame too short"); | ||
|
||
// Parse the payload according to the Intermatic PE653 protocol | ||
const waterTemp = raw.payload[12]; | ||
cc.waterTemperature = waterTemp; | ||
|
||
return cc; | ||
} | ||
|
||
public waterTemperature?: number; | ||
|
||
public async interview(ctx: InterviewContext): Promise<void> { | ||
// No interview needed for this CC | ||
this.setInterviewComplete(ctx, true); | ||
} | ||
|
||
public async refreshValues(ctx: RefreshValuesContext): Promise<void> { | ||
// This CC is read-only and updates are received automatically | ||
// Skip refresh since this is a read-only CC | ||
} | ||
|
||
public persistValues(ctx: PersistValuesContext): boolean { | ||
if (this.waterTemperature != undefined) { | ||
const valueId = getIntermaticWaterTempValueId(); | ||
const metadata = getIntermaticWaterTempMetadata(); | ||
|
||
this.getValueDB(ctx)?.setMetadata(valueId, metadata); | ||
this.getValueDB(ctx)?.setValue(valueId, this.waterTemperature); | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
public serialize(ctx: CCEncodingContext): Bytes { | ||
// The manufacturer ID is encoded in the first two bytes | ||
const manufacturerId = this.manufacturerId ?? MANUFACTURERID_INTERMATIC_LEGACY; | ||
(this.ccCommand as unknown as number) = (manufacturerId >>> 8) & 0xff; | ||
Check failure on line 157 in packages/cc/src/cc/manufacturerProprietary/IntermaticPE653CC.ts GitHub Actions / lint (18)
|
||
this.payload = Bytes.from([manufacturerId & 0xff]); | ||
return super.serialize(ctx); | ||
Check failure on line 159 in packages/cc/src/cc/manufacturerProprietary/IntermaticPE653CC.ts GitHub Actions / lint (18)
|
||
} | ||
|
||
public toLogEntry(ctx?: GetValueDB): MessageOrCCLogEntry { | ||
const message: MessageRecord = { | ||
waterTemperature: this.waterTemperature ?? "unknown", | ||
}; | ||
return { | ||
...super.toLogEntry(ctx), | ||
message, | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from "./Decorators.js"; | ||
export * from "./FibaroCC.js"; | ||
export * from "./IntermaticPE653CC.js"; |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this file should have been copied, not moved. After all there are still users with the old version. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,39 @@ | ||
{ | ||
"manufacturer": "Intermatic", | ||
"manufacturerId": "0x0005", | ||
"label": "PE653", | ||
"description": "Pool Control", | ||
"manufacturer": "Intermatic 0x0072 WaterTemp", | ||
"manufacturerId": "0x0072", | ||
"label": "PE653 0x0072 WaterTemp", | ||
"description": "Pool Control 0x0072 WaterTemp", | ||
Comment on lines
+2
to
+5
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just edit the manufacturer ID, keep the rest as-is. Unless the device is marketed under a new name? |
||
"devices": [ | ||
{ | ||
"productType": "0x5045", | ||
"productId": "0x0653" | ||
"productType": "0x0500", | ||
"productId": "0x7205" | ||
} | ||
], | ||
"firmwareVersion": { | ||
"min": "0.0", | ||
"max": "255.255" | ||
}, | ||
"proprietary": { | ||
"manufacturerProprietaryConfig": { | ||
"manufacturerId": 114, | ||
"commandClasses": { | ||
"waterTemperature": { | ||
"type": "number", | ||
"unit": "°F", | ||
"min": 0, | ||
"max": 100, | ||
"readOnly": true, | ||
"valueChangeOptions": ["always"], | ||
"stateless": false, | ||
"isFromConfig": true, | ||
"ccSpecific": { | ||
"propertyName": "waterTemperature", | ||
"propertyKey": "waterTemperature" | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
Comment on lines
+16
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems unnecessary |
||
"paramInformation": [ | ||
{ | ||
"#": "1[0x02]", | ||
|
@@ -481,72 +502,64 @@ | |
}, | ||
"compat": [ | ||
{ | ||
// Fixes #4588: Firmware v3.4 has numerous bugs related to multi-endpoint support. | ||
// Firmware v3.3 and v3.1 do not appear to have the same issues. | ||
"$if": "firmwareVersion === 3.4", | ||
"commandClasses": { | ||
// Force use of Multi Channel CC V1 despite the device reporting V2 | ||
"add": { | ||
"Multi Channel": { | ||
"isSupported": true, | ||
"version": 1 | ||
} | ||
}, | ||
// The firmware handles requests on some endpoints incorrectly, often reporting garbage | ||
// that confuses discovery or inhibits operation. Remove all of these broken CCs. | ||
"remove": { | ||
// BasicCC: All endpoints control the state of Switch 1 so only keep the root endpoint | ||
// to reduce clutter and to handle received BASIC_SET events. | ||
"Basic": { | ||
"endpoints": [1, 2, 3, 4, 5] | ||
}, | ||
// ManufacturerSpecificCC: Endpoint 1 erroneously reports an incorrect manufacturer | ||
// and product ID, unlike on the root endpoint. | ||
"Manufacturer Specific": { | ||
"endpoints": [1] | ||
}, | ||
// ClockCC: Endpoint 1 erroneously reports a time with an invalid minute field, | ||
// unlike on the root endpoint. | ||
"Clock": { | ||
"endpoints": [1] | ||
}, | ||
// AssociationCC: Endpoint 1 erroneously reports that it supports 133 associated nodes | ||
// but association commands don't work at all, unlike on the root endpoint. | ||
"Association": { | ||
"endpoints": [1] | ||
}, | ||
// VersionCC: Endpoint 1 reports an unknown version, unlike on the root endpoint. | ||
"Version": { | ||
"endpoints": [1] | ||
} | ||
} | ||
}, | ||
// The device sometimes sends BASIC_SET to the lifeline association when the state of Switch 1 | ||
// changes but the value is always 0 so treat it as an event. | ||
"mapBasicSet": "event" | ||
}, | ||
{ | ||
"commandClasses": { | ||
// Force use of Multi Channel CC V1 despite the device reporting V2 | ||
"add": { | ||
"Multi Channel": { | ||
"isSupported": true, | ||
"version": 1 | ||
Comment on lines
-484
to
538
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep the comments. They explain why each compat flag exists. |
||
}, | ||
"Manufacturer Proprietary": { | ||
"isSupported": true, | ||
"version": 1, | ||
"optional": false | ||
} | ||
Comment on lines
+540
to
544
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you share an interview log? I'd expect this to be advertised as supported. |
||
} | ||
}, | ||
"overrideQueries": { | ||
// The response to the setpoint query is off by one bit: https://github.com/zwave-js/node-zwave-js/issues/5335 | ||
"Thermostat Setpoint": [ | ||
{ | ||
"method": "getSupportedSetpointTypes", | ||
"result": [ | ||
1, // Heating | ||
7 // Furnace | ||
] | ||
} | ||
] | ||
Comment on lines
-537
to
-548
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this issue fixed in this device version? |
||
} | ||
} | ||
] | ||
], | ||
"endpoints": { | ||
"0": { | ||
"commandClasses": { | ||
"0x72": { | ||
"isSupported": true, | ||
"version": 1 | ||
}, | ||
"0x91": { | ||
"isSupported": true, | ||
"version": 1, | ||
"optional": false | ||
} | ||
} | ||
} | ||
} | ||
Comment on lines
+549
to
+563
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likewise, I'd like to see the interview log to know if this is necessary. |
||
} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These implementations cannot be here. If they are needed, they must be in the |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { | ||
CommandClass, | ||
ManufacturerProprietaryCC, | ||
CCAPI, | ||
ValueMetadata, | ||
ValueID, | ||
} from "zwave-js"; | ||
Comment on lines
+1
to
+7
Check notice Code scanning / CodeQL Unused variable, import, function or class Note
Unused import CommandClass.
|
||
|
||
const INTERMATIC_MANUFACTURER_ID = 0x0005; | ||
|
||
export class IntermaticPE653CC extends ManufacturerProprietaryCC { | ||
public static readonly manufacturerId = INTERMATIC_MANUFACTURER_ID; | ||
public static readonly version = 1; | ||
|
||
public deserialize(data: Buffer): void { | ||
super.deserialize(data); | ||
|
||
if (data.length >= 13) { | ||
const waterTemp = data.readUInt8(12); | ||
this.persistWaterTemp(waterTemp); | ||
} | ||
} | ||
|
||
private persistWaterTemp(waterTemp: number): void { | ||
const valueId: ValueID = { | ||
commandClass: this.ccId, | ||
property: "waterTemperature", | ||
}; | ||
|
||
const metadata: ValueMetadata = { | ||
label: "Water Temperature", | ||
unit: "°F", | ||
valueType: "number", | ||
min: 0, | ||
max: 100, | ||
}; | ||
|
||
this.getValueDB()?.setMetadata(valueId, metadata); | ||
this.getValueDB()?.setValue(valueId, waterTemp); | ||
} | ||
} | ||
|
||
export class IntermaticPE653CCAPI extends CCAPI { | ||
public async getWaterTemperature(): Promise<number | undefined> { | ||
const valueId: ValueID = { | ||
commandClass: this.ccId, | ||
property: "waterTemperature", | ||
}; | ||
|
||
return this.getValueDB()?.getValue(valueId); | ||
} | ||
} | ||
|
||
// Register this CC with Z-Wave JS | ||
ManufacturerProprietaryCC.addImplementation(INTERMATIC_MANUFACTURER_ID, IntermaticPE653CC); |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note