Skip to content

Commit

Permalink
feat: add notification variable for tilt (#6958)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone authored Jun 24, 2024
1 parent ba71df5 commit 59bda8c
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 1 deletion.
23 changes: 23 additions & 0 deletions packages/cc/src/cc/NotificationCC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,29 @@ export const NotificationCCValues = Object.freeze({
autoCreate: shouldAutoCreateSimpleDoorSensorValue,
} as const,
),

// Binary tilt value extracted from the Door state variable.
...V.staticPropertyAndKeyWithName(
"doorTiltState",
"Access Control",
"Door tilt state",
{
// Must be a number for compatibility reasons
...ValueMetadata.ReadOnlyUInt8,
label: "Door tilt state",
states: {
[0x00]: "Window/door is not tilted",
[0x01]: "Window/door is tilted",
},
ccSpecific: {
notificationType: 0x06,
},
} as const,
{
// This is created when the tilt state is first received.
autoCreate: false,
} as const,
),
}),

...V.defineDynamicCCValues(CommandClasses.Notification, {
Expand Down
20 changes: 19 additions & 1 deletion packages/zwave-js/src/lib/node/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5252,15 +5252,33 @@ protocol version: ${this.protocolVersion}`;
// actually support them, which makes working with the Door state variable
// very cumbersome. Also, this is currently the only notification where the enum values
// extend the state value.

// To work around this, we hard-code a notification value for the door status
// which only includes the "legacy" states for open/closed.

this.valueDB.setValue(
NotificationCCValues.doorStateSimple.endpoint(
command.endpointIndex,
),
command.notificationEvent === 0x17 ? 0x17 : 0x16,
);

// In addition to that, we also hard-code a notification value for only the tilt status.
// This will only be created after receiving a notification for the tilted state.
// Only after it exists, it will be updated. Otherwise, we'd get phantom
// values, since some devices send the enum value, even when they don't support tilt.
const tiltValue = NotificationCCValues.doorTiltState;
const tiltValueId = tiltValue.endpoint(command.endpointIndex);
let tiltValueWasCreated = this.valueDB.hasMetadata(tiltValueId);
if (command.eventParameters === 0x01 && !tiltValueWasCreated) {
this.valueDB.setMetadata(tiltValueId, tiltValue.meta);
tiltValueWasCreated = true;
}
if (tiltValueWasCreated) {
this.valueDB.setValue(
tiltValueId,
command.eventParameters === 0x01 ? 0x01 : 0x00,
);
}
}
}

Expand Down
165 changes: 165 additions & 0 deletions packages/zwave-js/src/lib/test/cc-specific/notificationEnums.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,171 @@ integrationTest("The 'simple' Door state value works correctly", {
},
});

integrationTest("The synthetic 'Door tilt state' value works correctly", {
// debug: true,

nodeCapabilities: {
commandClasses: [
CommandClasses.Version,
{
ccId: CommandClasses.Notification,
isSupported: true,
version: 8,
supportsV1Alarm: false,
notificationTypesAndEvents: {
// Access Control - Window open and Window closed
[0x06]: [0x16, 0x17],
},
},
],
},

testBody: async (t, driver, node, mockController, mockNode) => {
await node.commandClasses.Notification.getSupportedEvents(0x06);

const tiltVID = NotificationCCValues.doorTiltState.id;

const hasTiltVID = () =>
node.getDefinedValueIDs().some(
(vid) => NotificationCCValues.doorTiltState.is(vid),
);
// Before receiving any notifications with the tilt enum, the synthetic value should not exist
t.false(hasTiltVID());

// Send a notification to the node where the window is not tilted
let cc = new NotificationCCReport(mockNode.host, {
nodeId: mockController.host.ownNodeId,
notificationType: 0x06,
notificationEvent: 0x16, // Window/door is open
eventParameters: Buffer.from([0x00]), // ... in regular position
});
await mockNode.sendToController(
createMockZWaveRequestFrame(cc, {
ackRequested: false,
}),
);
// wait a bit for the value to be updated
await wait(100);

// The value should still not exist
t.false(hasTiltVID());

// ===

// Again with tilt
cc = new NotificationCCReport(mockNode.host, {
nodeId: mockController.host.ownNodeId,
notificationType: 0x06,
notificationEvent: 0x16, // Window/door is open
eventParameters: Buffer.from([0x01]), // ... in tilt position
});
await mockNode.sendToController(
createMockZWaveRequestFrame(cc, {
ackRequested: false,
}),
);
// wait a bit for the value to be updated
await wait(100);

// The value should now exist
t.true(hasTiltVID());
t.is(node.getValue(tiltVID), 0x01);

// ===

// Again without tilt
cc = new NotificationCCReport(mockNode.host, {
nodeId: mockController.host.ownNodeId,
notificationType: 0x06,
notificationEvent: 0x16, // Window/door is open
eventParameters: Buffer.from([0x00]), // ... in regular position
});
await mockNode.sendToController(
createMockZWaveRequestFrame(cc, {
ackRequested: false,
}),
);
// wait a bit for the value to be updated
await wait(100);

t.is(node.getValue(tiltVID), 0x00);

// ===

// Again with tilt to be able to detect changes
cc = new NotificationCCReport(mockNode.host, {
nodeId: mockController.host.ownNodeId,
notificationType: 0x06,
notificationEvent: 0x16, // Window/door is open
eventParameters: Buffer.from([0x01]), // ... in tilt position
});
await mockNode.sendToController(
createMockZWaveRequestFrame(cc, {
ackRequested: false,
}),
);
// wait a bit for the value to be updated
await wait(100);

t.is(node.getValue(tiltVID), 0x01);

// ===

// And now without the enum
cc = new NotificationCCReport(mockNode.host, {
nodeId: mockController.host.ownNodeId,
notificationType: 0x06,
notificationEvent: 0x17, // Window/door is closed
});
await mockNode.sendToController(
createMockZWaveRequestFrame(cc, {
ackRequested: false,
}),
);
// wait a bit for the value to be updated
await wait(100);

t.is(node.getValue(tiltVID), 0x00);

// ===

// Again with tilt to be able to detect changes
cc = new NotificationCCReport(mockNode.host, {
nodeId: mockController.host.ownNodeId,
notificationType: 0x06,
notificationEvent: 0x16, // Window/door is open
eventParameters: Buffer.from([0x01]), // ... in tilt position
});
await mockNode.sendToController(
createMockZWaveRequestFrame(cc, {
ackRequested: false,
}),
);
// wait a bit for the value to be updated
await wait(100);

t.is(node.getValue(tiltVID), 0x01);

// ===

// And again without the enum
cc = new NotificationCCReport(mockNode.host, {
nodeId: mockController.host.ownNodeId,
notificationType: 0x06,
notificationEvent: 0x16, // Window/door is open
});
await mockNode.sendToController(
createMockZWaveRequestFrame(cc, {
ackRequested: false,
}),
);
// wait a bit for the value to be updated
await wait(100);

t.is(node.getValue(tiltVID), 0x00);
},
});

integrationTest(
"Notification types with 'replace'-type enums fall back to the default value if the event parameter is not contained in the CC",
{
Expand Down

0 comments on commit 59bda8c

Please sign in to comment.