inventoresed
Version:
Z-Wave driver written entirely in JavaScript/TypeScript
226 lines (197 loc) • 7.01 kB
text/typescript
import type { BinarySensorCCAPI } from "@zwave-js/cc/BinarySensorCC";
import { BinarySwitchCCAPI } from "@zwave-js/cc/BinarySwitchCC";
import {
assertZWaveError,
CommandClasses,
ZWaveErrorCodes,
} from "@zwave-js/core";
import type { MockSerialPort } from "@zwave-js/serial";
import { FunctionType } from "@zwave-js/serial";
import type { ThrowingMap } from "@zwave-js/shared";
import { wait } from "alcalzone-shared/async";
import { ZWaveController } from "../controller/Controller";
import type { Driver } from "../driver/Driver";
import { createAndStartDriver } from "../test/utils";
import { ZWaveNode } from "./Node";
// Test mock for isFunctionSupported to control which commands are getting used
function isFunctionSupported(fn: FunctionType): boolean {
switch (fn) {
case FunctionType.SendDataBridge:
case FunctionType.SendDataMulticastBridge:
return false;
}
return true;
}
describe("lib/node/VirtualEndpoint", () => {
let driver: Driver;
let serialport: MockSerialPort;
beforeEach(async () => {
({ driver, serialport } = await createAndStartDriver());
driver["_controller"] = new ZWaveController(driver);
driver["_controller"].isFunctionSupported = isFunctionSupported;
});
function makePhysicalNode(nodeId: number): ZWaveNode {
const node = new ZWaveNode(nodeId, driver);
(driver.controller.nodes as ThrowingMap<number, ZWaveNode>).set(
nodeId,
node,
);
return node;
}
// function setNumEndpoints(node: ZWaveNode, numEndpoints: number) {
// node.valueDB.setValue(
// {
// commandClass: CommandClasses["Multi Channel"],
// property: "individualCount",
// },
// numEndpoints,
// );
// }
afterEach(async () => {
await driver.destroy();
driver.removeAllListeners();
});
describe("createAPI", () => {
it("throws if a non-implemented API should be created", () => {
const broadcast = driver.controller.getBroadcastNode();
assertZWaveError(() => broadcast.createAPI(0xbada55), {
errorCode: ZWaveErrorCodes.CC_NoAPI,
messageMatches: "no associated API",
});
});
it("the broadcast API throws when trying to access a non-supported CC", async () => {
makePhysicalNode(2);
makePhysicalNode(3);
const broadcast = driver.controller.getBroadcastNode();
// We must not use Basic CC here, because that is assumed to be always supported
const api = broadcast.createAPI(
CommandClasses["Binary Switch"],
) as BinarySensorCCAPI;
// this does not throw
api.isSupported();
// this does
await assertZWaveError(() => api.get(), {
errorCode: ZWaveErrorCodes.CC_NotSupported,
});
});
it("the broadcast API should know it is a broadcast API", async () => {
makePhysicalNode(2);
makePhysicalNode(3);
const broadcast = driver.controller.getBroadcastNode();
expect(
broadcast.createAPI(CommandClasses.Basic)["isBroadcast"](),
).toBeTrue();
});
it("the multicast API should know it is a multicast API", async () => {
makePhysicalNode(2);
makePhysicalNode(3);
const multicast = driver.controller.getMulticastGroup([2, 3]);
expect(
multicast.createAPI(CommandClasses.Basic)["isMulticast"](),
).toBeTrue();
});
});
describe("commandClasses dictionary", () => {
let node2: ZWaveNode;
let node3: ZWaveNode;
beforeEach(() => {
node2 = makePhysicalNode(2);
node3 = makePhysicalNode(3);
});
it("throws when trying to access a non-implemented CC", () => {
const broadcast = driver.controller.getBroadcastNode();
assertZWaveError(() => (broadcast.commandClasses as any).FOOBAR, {
errorCode: ZWaveErrorCodes.CC_NotImplemented,
messageMatches: "FOOBAR is not implemented",
});
});
it("throws when trying to use a command of an unsupported CC", () => {
const broadcast = driver.controller.getBroadcastNode();
assertZWaveError(
() => broadcast.commandClasses["Binary Switch"].set(true),
{
errorCode: ZWaveErrorCodes.CC_NotSupported,
messageMatches:
"does not support the Command Class Binary Switch",
},
);
});
it("does not throw when checking support of a CC", () => {
const broadcast = driver.controller.getBroadcastNode();
expect(
broadcast.commandClasses["Binary Switch"].isSupported(),
).toBeFalse();
});
it("does not throw when accessing the ID of a CC", () => {
const broadcast = driver.controller.getBroadcastNode();
expect(broadcast.commandClasses["Binary Switch"].ccId).toBe(
CommandClasses["Binary Switch"],
);
});
it("does not throw when scoping the API options", () => {
const broadcast = driver.controller.getBroadcastNode();
broadcast.commandClasses["Binary Switch"].withOptions({});
});
it("returns all supported CCs when being enumerated", () => {
// No supported CCs, empty array
let broadcast = driver.controller.getBroadcastNode();
let actual = [...broadcast.commandClasses];
expect(actual).toEqual([]);
// Supported and controlled CCs
node2.addCC(CommandClasses["Binary Switch"], { isSupported: true });
node2.addCC(CommandClasses["Wake Up"], { isControlled: true });
node3.addCC(CommandClasses["Binary Switch"], { isSupported: true });
node3.addCC(CommandClasses.Version, { isSupported: true });
broadcast = driver.controller.getBroadcastNode();
actual = [...broadcast.commandClasses];
expect(actual).toHaveLength(1);
expect(actual.map((api) => api.constructor)).toIncludeAllMembers([
BinarySwitchCCAPI,
// VersionCCAPI cannot be used in broadcast
// WakeUpCCAPI is not supported (only controlled), so no API!
]);
});
it("returns [object Object] when turned into a string", () => {
const broadcast = driver.controller.getBroadcastNode();
expect((broadcast.commandClasses as any)[Symbol.toStringTag]).toBe(
"[object Object]",
);
});
it("returns undefined for other symbol properties", () => {
const broadcast = driver.controller.getBroadcastNode();
expect(
(broadcast.commandClasses as any)[Symbol.unscopables],
).toBeUndefined();
});
});
describe("uses the correct commands behind the scenes", () => {
it("broadcast", async () => {
makePhysicalNode(2);
makePhysicalNode(3);
const broadcast = driver.controller.getBroadcastNode();
broadcast.commandClasses.Basic.set(99);
await wait(1);
// » [Node 255] [REQ] [SendData]
// │ transmit options: 0x25
// │ callback id: 1
// └─[BasicCCSet]
expect(serialport.lastWrite).toEqual(
Buffer.from("010a0013ff0320016325017c", "hex"),
);
});
it("multicast", async () => {
makePhysicalNode(2);
makePhysicalNode(3);
const multicast = driver.controller.getMulticastGroup([2, 3]);
multicast.commandClasses.Basic.set(99);
await wait(1);
// » [Node 2, 3] [REQ] [SendData]
// │ transmit options: 0x25
// │ callback id: 1
// └─[BasicCCSet]
expect(serialport.lastWrite).toEqual(
Buffer.from("010c001402020303200163250181", "hex"),
);
});
});
});