inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
249 lines (217 loc) • 6.92 kB
text/typescript
import "@zwave-js/cc";
import {
BasicCC,
BasicCCGet,
BasicCCSet,
CentralSceneCCNotification,
CentralSceneCommand,
CentralSceneKeys,
CommandClass,
commandClass,
expectedCCResponse,
getExpectedCCResponse,
getImplementedVersion,
getImplementedVersionStatic,
implementedVersion,
} from "@zwave-js/cc";
import {
assertZWaveError,
CommandClasses,
ZWaveErrorCodes,
} from "@zwave-js/core";
import { createTestingHost } from "@zwave-js/host";
import type { ThrowingMap } from "@zwave-js/shared";
import { MockController } from "@zwave-js/testing";
import { createDefaultMockControllerBehaviors } from "../../../Utils";
import type { Driver } from "../../driver/Driver";
import { createAndStartTestingDriver } from "../../driver/DriverMock";
import { ZWaveNode } from "../../node/Node";
import { SendDataRequest } from "../../serialapi/transport/SendDataMessages";
(7)
(0xffff as any)
class DummyCC extends CommandClass {}
class DummyCCSubClass1 extends DummyCC {
private x: any;
}
(DummyCCSubClass1)
class DummyCCSubClass2 extends DummyCC {
private y: any;
}
describe("lib/commandclass/CommandClass => ", () => {
describe("creating and serializing()", () => {
const host = createTestingHost();
it(`should work for unspecified commands`, () => {
// Repro for #1219
const cc = new CommandClass(host, {
nodeId: 2,
ccId: 0x5d,
ccCommand: 0x02,
payload: Buffer.from([1, 2, 3]),
});
const msg = new SendDataRequest(host, {
command: cc,
callbackId: 0xfe,
});
expect(msg.serialize()).toEqual(
Buffer.from("010c001302055d0201020325fe63", "hex"),
);
});
});
describe("from()", () => {
const host = createTestingHost();
it("throws CC_NotImplemented when receiving a non-implemented CC", () => {
// This is a Node Provisioning CC. Change it when that CC is implemented
assertZWaveError(
() =>
CommandClass.from(host, {
data: Buffer.from("78030100", "hex"),
nodeId: 5,
}),
{
errorCode: ZWaveErrorCodes.CC_NotImplemented,
},
);
});
it("does not throw when the CC is implemented", () => {
expect(() =>
CommandClass.from(host, {
// CRC-16 with BasicCC
data: Buffer.from("560120024d26", "hex"),
nodeId: 5,
}),
).not.toThrow();
});
});
describe("getImplementedVersion()", () => {
const host = createTestingHost();
it("should return the implemented version for a CommandClass instance", () => {
const cc = new BasicCC(host, { nodeId: 1 });
expect(getImplementedVersion(cc)).toBe(2);
});
it("should return the implemented version for a numeric CommandClass key", () => {
const cc = CommandClasses.Basic;
expect(getImplementedVersion(cc)).toBe(2);
});
it.skip("should return 0 for a non-implemented CommandClass instance", () => {
// const cc = new CommandClass(undefined);
// expect(getImplementedVersion(cc)).toBe(0);
});
it("should return the implemented version for a numeric CommandClass key", () => {
const cc = -1;
expect(getImplementedVersion(cc)).toBe(0);
});
it("should work with inheritance", () => {
const cc = new BasicCCGet(host, { nodeId: 1 });
expect(getImplementedVersion(cc)).toBe(2);
});
});
describe("getImplementedVersionStatic()", () => {
it("should return the implemented version for a CommandClass constructor", () => {
expect(getImplementedVersionStatic(BasicCC)).toBe(2);
});
it("should work on inherited classes", () => {
expect(getImplementedVersionStatic(DummyCCSubClass1)).toBe(7);
});
});
it.skip("serializing with an undefined or null payload should behave like an empty payload", () => {
// const cc1 = new CommandClass(undefined, 1, 1, Buffer.from([]));
// const cc2 = new CommandClass(undefined, 1, 1, undefined);
// const cc3 = new CommandClass(undefined, 1, 1, null);
// const serialized1 = cc1.serialize();
// const serialized2 = cc2.serialize();
// const serialized3 = cc3.serialize();
// expect(serialized1).toEqual(serialized2);
// expect(serialized2).toEqual(serialized3);
});
describe("expectMoreMessages()", () => {
const host = createTestingHost();
it("returns false by default", () => {
const cc = new DummyCC(host, { nodeId: 1 });
expect(cc.expectMoreMessages([])).toBeFalse();
});
});
describe("getExpectedCCResponse()", () => {
const host = createTestingHost();
it("returns the expected CC response like it was defined", () => {
const cc = new DummyCCSubClass2(host, { nodeId: 1 });
const actual = getExpectedCCResponse(cc);
expect(actual).toBe(DummyCCSubClass1);
});
});
describe("persistValues()", () => {
let driver: Driver;
let controller: MockController;
let node2: ZWaveNode;
beforeAll(async () => {
({ driver } = await createAndStartTestingDriver({
loadConfiguration: false,
skipNodeInterview: true,
beforeStartup(mockPort) {
controller = new MockController({ serial: mockPort });
controller.defineBehavior(
...createDefaultMockControllerBehaviors(),
);
},
}));
}, 30000);
afterAll(async () => {
await driver.destroy();
});
beforeEach(() => {
node2 = new ZWaveNode(2, driver);
(driver.controller.nodes as ThrowingMap<number, ZWaveNode>).set(
node2.id,
node2,
);
});
afterEach(() => {
node2.destroy();
(driver.controller.nodes as ThrowingMap<number, ZWaveNode>).delete(
node2.id,
);
});
it(`should not update "interviewComplete" in the value DB`, () => {
// Repro for #383
const cc = new BasicCCSet(driver, {
nodeId: node2.id,
targetValue: 55,
});
cc.setInterviewComplete(driver, true);
const mockSetValue = jest.fn();
node2.valueDB.setValue = mockSetValue;
cc.persistValues(driver);
const properties = mockSetValue.mock.calls
.map(([arg0]) => arg0)
.map(({ property }) => property);
expect(properties).not.toContainValue("interviewComplete");
});
it(`should not store values marked as "events" (non-stateful)`, async () => {
const cc = new CentralSceneCCNotification(driver, {
nodeId: node2.id,
data: Buffer.from([
CommandClasses["Central Scene"],
CentralSceneCommand.Notification,
1, // seq number
CentralSceneKeys.KeyPressed,
1, // scene number
]),
});
// Central Scene should use the value notification event instead of added/updated
const spyN = jest.fn();
const spyA = jest.fn();
node2.on("value notification", spyN);
node2.on("value added", spyA);
node2.on("value updated", spyA);
await node2.handleCommand(cc);
expect(spyA).not.toBeCalled();
expect(spyN).toBeCalled();
expect(spyN.mock.calls[0][1].value).toBe(
CentralSceneKeys.KeyPressed,
);
// and not persist the value in the DB
expect(
node2.valueDB.getValues(CommandClasses["Central Scene"]),
).toHaveLength(0);
});
});
});