diff --git a/packages/cc/src/cc/NotificationCC.ts b/packages/cc/src/cc/NotificationCC.ts index 38d4b2094add..dff96136aff0 100644 --- a/packages/cc/src/cc/NotificationCC.ts +++ b/packages/cc/src/cc/NotificationCC.ts @@ -739,6 +739,7 @@ export class NotificationCC extends CommandClass { ?.compat?.alarmMapping; if (mappings) { // Find all mappings to a valid notification variable + const supportedNotifications = new Map>(); for (const { to } of mappings) { const notificationConfig = applHost.configManager .lookupNotification( @@ -748,6 +749,18 @@ export class NotificationCC extends CommandClass { const valueConfig = notificationConfig.lookupValue( to.notificationEvent, ); + + // Remember supported notification types and events to create the internal values later + if (!supportedNotifications.has(to.notificationType)) { + supportedNotifications.set( + to.notificationType, + new Set(), + ); + } + const supportedNotificationTypesSet = supportedNotifications + .get(to.notificationType)!; + supportedNotificationTypesSet.add(to.notificationEvent); + if (valueConfig?.type !== "state") continue; const notificationValue = NotificationCCValues @@ -780,6 +793,20 @@ export class NotificationCC extends CommandClass { } } } + + // Remember supported notification types and events in the cache + this.setValue( + applHost, + NotificationCCValues.supportedNotificationTypes, + [...supportedNotifications.keys()], + ); + for (const [type, events] of supportedNotifications) { + this.setValue( + applHost, + NotificationCCValues.supportedNotificationEvents(type), + [...events], + ); + } } // Remember that the interview is complete diff --git a/packages/zwave-js/src/lib/test/compat/notificationAlarmMapping.test.ts b/packages/zwave-js/src/lib/test/compat/notificationAlarmMapping.test.ts index faf4ef7a4a96..f6efbff44482 100644 --- a/packages/zwave-js/src/lib/test/compat/notificationAlarmMapping.test.ts +++ b/packages/zwave-js/src/lib/test/compat/notificationAlarmMapping.test.ts @@ -1,88 +1,72 @@ -import { CommandClass } from "@zwave-js/cc"; -import { ManufacturerSpecificCCValues } from "@zwave-js/cc/ManufacturerSpecificCC"; -import { NotificationCCReport } from "@zwave-js/cc/NotificationCC"; +import { + NotificationCCReport, + NotificationCCValues, +} from "@zwave-js/cc/NotificationCC"; import { CommandClasses } from "@zwave-js/core"; -import type { ThrowingMap } from "@zwave-js/shared"; -import { MockController } from "@zwave-js/testing"; -import ava, { type TestFn } from "ava"; -import { createDefaultMockControllerBehaviors } from "../../../Utils"; -import type { Driver } from "../../driver/Driver"; -import { createAndStartTestingDriver } from "../../driver/DriverMock"; -import { ZWaveNode } from "../../node/Node"; +import { createMockZWaveRequestFrame } from "@zwave-js/testing"; +import { wait } from "alcalzone-shared/async"; +import sinon from "sinon"; +import { integrationTest } from "../integrationTestSuite"; -interface TestContext { - driver: Driver; - node2: ZWaveNode; - controller: MockController; -} +integrationTest( + "the alarmMapping compat flag works correctly (using the example Kwikset 910)", + { + // debug: true, -const test = ava as TestFn; + nodeCapabilities: { + manufacturerId: 0x90, + productType: 0x01, + productId: 0x01, -test.beforeEach(async (t) => { - t.timeout(30000); - - const { driver } = await createAndStartTestingDriver({ - skipNodeInterview: true, - beforeStartup(mockPort) { - const controller = new MockController({ serial: mockPort }); - controller.defineBehavior( - ...createDefaultMockControllerBehaviors(), - ); - t.context.controller = controller; + commandClasses: [ + { + ccId: CommandClasses.Notification, + version: 1, // To make sure we rely on the compat flag + isSupported: true, + }, + CommandClasses["Manufacturer Specific"], + CommandClasses.Version, + ], }, - }); - - const node2 = new ZWaveNode(2, driver); - (driver.controller.nodes as ThrowingMap).set( - node2.id, - node2, - ); - - node2.addCC(CommandClasses.Notification, { - isSupported: true, - version: 1, - }); - t.context.driver = driver; - t.context.node2 = node2; -}); + async testBody(t, driver, node, mockController, mockNode) { + // Send a report that should be mapped to notifications + const cc = new NotificationCCReport(mockNode.host, { + nodeId: 2, + alarmType: 18, + alarmLevel: 2, + }); -test.afterEach.always(async (t) => { - const { driver } = t.context; - await driver.destroy(); - driver.removeAllListeners(); -}); + const nodeNotification = sinon.spy(); + node.on("notification", nodeNotification); -test("the alarmMapping compat flag works correctly (using the example Kwikset 910)", async (t) => { - const { driver, node2 } = t.context; - - node2.valueDB.setValue( - ManufacturerSpecificCCValues.manufacturerId.id, - 0x90, - ); - node2.valueDB.setValue(ManufacturerSpecificCCValues.productType.id, 0x01); - node2.valueDB.setValue(ManufacturerSpecificCCValues.productId.id, 0x01); - await node2["loadDeviceConfig"](); + await mockNode.sendToController( + createMockZWaveRequestFrame(cc, { + ackRequested: false, + }), + ); + await wait(100); - const rawNotification = new NotificationCCReport(driver, { - nodeId: 2, - alarmType: 18, - alarmLevel: 2, - }); - const serialized = rawNotification.serialize(); + // The correct events should be emitted + sinon.assert.calledOnce(nodeNotification); + const event = nodeNotification.getCall(0).args[2]; - const deserialized = CommandClass.from(driver, { - data: serialized, - nodeId: 2, - }) as NotificationCCReport; + t.is(event.type, 0x06); + t.is(event.event, 0x05); + t.deepEqual(event.parameters, { + userId: 2, + }); - // Call persistValues to trigger the mapping - deserialized.persistValues(driver); + // And they should be known to be supported + const supportedNotificationTypes: number[] | undefined = node + .getValue(NotificationCCValues.supportedNotificationTypes.id); + t.true(supportedNotificationTypes?.includes(0x06)); - // Keypad lock - t.is(deserialized.notificationType, 0x06); - t.is(deserialized.notificationEvent, 0x05); - t.deepEqual(deserialized.eventParameters, { - userId: 2, - }); -}); + const supportedAccessControlEvents: number[] | undefined = node + .getValue( + NotificationCCValues.supportedNotificationEvents(0x06).id, + ); + t.true(supportedAccessControlEvents?.includes(0x05)); + }, + }, +);