UNPKG

zigbee-herdsman

Version:

An open source ZigBee gateway solution with node.js.

281 lines (256 loc) 12.4 kB
import * as Zcl from "../../../src/zspec/zcl"; import type {Command, CustomClusters} from "../../../src/zspec/zcl/definition/tstype"; const CUSTOM_CLUSTERS: CustomClusters = { genBasic: {ID: Zcl.Clusters.genBasic.ID, commands: {}, commandsResponse: {}, attributes: {myCustomAttr: {ID: 65533, type: Zcl.DataType.UINT8}}}, myCustomCluster: {ID: 65534, commands: {}, commandsResponse: {}, attributes: {myCustomAttr: {ID: 65533, type: Zcl.DataType.UINT8}}}, myCustomClusterManuf: { ID: 65533, manufacturerCode: 65534, commands: {}, commandsResponse: {}, attributes: {myCustomAttr: {ID: 65533, type: Zcl.DataType.UINT8}}, }, manuSpecificProfalux1NoManuf: { ID: Zcl.Clusters.manuSpecificProfalux1.ID, commands: {}, commandsResponse: {}, attributes: {myCustomAttr: {ID: 65533, type: Zcl.DataType.UINT8}}, }, }; describe("ZCL Utils", () => { it("Creates status error", () => { const zclError = new Zcl.StatusError(Zcl.Status.ABORT); expect(zclError).toBeInstanceOf(Zcl.StatusError); expect(zclError.code).toStrictEqual(Zcl.Status.ABORT); expect(zclError.message).toStrictEqual(`Status '${Zcl.Status[Zcl.Status.ABORT]}'`); }); it("Gets data type class", () => { expect(Zcl.Utils.getDataTypeClass(Zcl.DataType.UINT16)).toStrictEqual(Zcl.DataTypeClass.ANALOG); expect(Zcl.Utils.getDataTypeClass(Zcl.DataType.DATA16)).toStrictEqual(Zcl.DataTypeClass.DISCRETE); expect(() => { Zcl.Utils.getDataTypeClass(Zcl.DataType.NO_DATA); }).toThrow(); }); it.each([ [ "by ID", {key: Zcl.Clusters.genBasic.ID, manufacturerCode: undefined, customClusters: {}}, {cluster: Zcl.Clusters.genBasic, name: "genBasic"}, ], ["by name", {key: "genAlarms", manufacturerCode: undefined, customClusters: {}}, {cluster: Zcl.Clusters.genAlarms, name: "genAlarms"}], [ "by ID with no manufacturer code", {key: Zcl.Clusters.genAlarms.ID, manufacturerCode: 123, customClusters: {}}, {cluster: Zcl.Clusters.genAlarms, name: "genAlarms"}, ], [ "by ID with non-matching manufacturer code", {key: Zcl.Clusters.sprutDevice.ID, manufacturerCode: 123, customClusters: {}}, {cluster: Zcl.Clusters.sprutDevice, name: "sprutDevice"}, ], [ "by ID with matching manufacturer code", {key: Zcl.Clusters.sprutDevice.ID, manufacturerCode: Zcl.Clusters.sprutDevice.manufacturerCode!, customClusters: {}}, {cluster: Zcl.Clusters.sprutDevice, name: "sprutDevice"}, ], [ "custom by ID", {key: CUSTOM_CLUSTERS.myCustomCluster.ID, manufacturerCode: undefined, customClusters: CUSTOM_CLUSTERS}, {cluster: CUSTOM_CLUSTERS.myCustomCluster, name: "myCustomCluster"}, ], [ "custom by name", {key: "myCustomCluster", manufacturerCode: undefined, customClusters: CUSTOM_CLUSTERS}, {cluster: CUSTOM_CLUSTERS.myCustomCluster, name: "myCustomCluster"}, ], [ "custom by ID with no manufacturer code", {key: CUSTOM_CLUSTERS.myCustomCluster.ID, manufacturerCode: 123, customClusters: CUSTOM_CLUSTERS}, {cluster: CUSTOM_CLUSTERS.myCustomCluster, name: "myCustomCluster"}, ], [ "custom by ID with non-matching manufacturer code", {key: CUSTOM_CLUSTERS.myCustomClusterManuf.ID, manufacturerCode: 123, customClusters: CUSTOM_CLUSTERS}, {cluster: CUSTOM_CLUSTERS.myCustomClusterManuf, name: "myCustomClusterManuf"}, ], [ "custom by ID with matching manufacturer code", { key: CUSTOM_CLUSTERS.myCustomClusterManuf.ID, manufacturerCode: CUSTOM_CLUSTERS.myCustomClusterManuf.manufacturerCode!, customClusters: CUSTOM_CLUSTERS, }, {cluster: CUSTOM_CLUSTERS.myCustomClusterManuf, name: "myCustomClusterManuf"}, ], [ "custom by ID overriding same Zcl ID entirely", {key: CUSTOM_CLUSTERS.genBasic.ID, manufacturerCode: undefined, customClusters: CUSTOM_CLUSTERS}, {cluster: CUSTOM_CLUSTERS.genBasic, name: "genBasic"}, ], [ "by ID ignoring same custom ID if Zcl is better match with manufacturer code", { key: CUSTOM_CLUSTERS.manuSpecificProfalux1NoManuf.ID, manufacturerCode: Zcl.ManufacturerCode.PROFALUX, customClusters: CUSTOM_CLUSTERS, }, {cluster: Zcl.Clusters.manuSpecificProfalux1, name: "manuSpecificProfalux1"}, ], ])("Gets cluster %s", (_name, payload, expected) => { const cluster = Zcl.Utils.getCluster(payload.key, payload.manufacturerCode, payload.customClusters); expect(cluster.ID).toStrictEqual(expected.cluster.ID); expect(cluster.name).toStrictEqual(expected.name); for (const k in expected.cluster.attributes) { expect(cluster.attributes[k]).toBeDefined(); expect(cluster.attributes[k].name).toStrictEqual(k); } expect(cluster.manufacturerCode).toStrictEqual(expected.cluster.manufacturerCode); expect(cluster.getAttribute).toBeInstanceOf(Function); expect(cluster.getCommand).toBeInstanceOf(Function); expect(cluster.getCommandResponse).toBeInstanceOf(Function); expect(cluster.hasAttribute).toBeInstanceOf(Function); }); it("Creates empty cluster when getting by invalid ID", () => { const cluster = Zcl.Utils.getCluster(99999, undefined, {}); expect(cluster.ID).toStrictEqual(99999); expect(cluster.name).toStrictEqual("99999"); expect(cluster.manufacturerCode).toStrictEqual(undefined); expect(cluster.attributes).toStrictEqual({}); expect(cluster.commands).toStrictEqual({}); expect(cluster.commandsResponse).toStrictEqual({}); expect(cluster.getAttribute).toBeInstanceOf(Function); expect(cluster.getCommand).toBeInstanceOf(Function); expect(cluster.getCommandResponse).toBeInstanceOf(Function); expect(cluster.hasAttribute).toBeInstanceOf(Function); }); it("Throws when getting invalid cluster name", () => { expect(() => { Zcl.Utils.getCluster("invalid", undefined, {}); }).toThrow(); }); it.each([ [ "by ID", {key: Zcl.Clusters.genBasic.attributes.zclVersion.ID, manufacturerCode: undefined, customClusters: {}}, {cluster: Zcl.Clusters.genBasic, name: "zclVersion"}, ], ["by name", {key: "alarmCount", manufacturerCode: undefined, customClusters: {}}, {cluster: Zcl.Clusters.genAlarms, name: "alarmCount"}], [ "by ID with no manufacturer code", {key: Zcl.Clusters.genAlarms.attributes.alarmCount.ID, manufacturerCode: 123, customClusters: {}}, {cluster: Zcl.Clusters.genAlarms, name: "alarmCount"}, ], [ "by ID with matching manufacturer code", { key: Zcl.Clusters.haDiagnostic.attributes.danfossSystemStatusCode.ID, manufacturerCode: Zcl.ManufacturerCode.DANFOSS_A_S, customClusters: {}, }, {cluster: Zcl.Clusters.haDiagnostic, name: "danfossSystemStatusCode"}, ], [ "custom by ID", {key: CUSTOM_CLUSTERS.genBasic.attributes.myCustomAttr.ID, manufacturerCode: undefined, customClusters: CUSTOM_CLUSTERS}, {cluster: Zcl.Clusters.genBasic, name: "myCustomAttr"}, ], [ "custom by name", {key: "myCustomAttr", manufacturerCode: undefined, customClusters: CUSTOM_CLUSTERS}, {cluster: Zcl.Clusters.genBasic, name: "myCustomAttr"}, ], ])("Gets and checks cluster attribute %s", (_name, payload, expected) => { const cluster = Zcl.Utils.getCluster(expected.cluster.ID, payload.manufacturerCode, payload.customClusters); const attribute = cluster.getAttribute(payload.key); expect(cluster.hasAttribute(payload.key)).toBeTruthy(); expect(attribute).toStrictEqual(cluster.attributes[expected.name]); }); it("Throws when getting invalid attribute", () => { const cluster = Zcl.Utils.getCluster(Zcl.Clusters.genAlarms.ID, undefined, {}); expect(() => { cluster.getAttribute("abcd"); }).toThrow(); expect(() => { cluster.getAttribute(99999); }).toThrow(); }); it("Throws when getting attribute with invalid manufacturer code", () => { const cluster = Zcl.Utils.getCluster(Zcl.Clusters.haDiagnostic.ID, 123, {}); expect(() => { cluster.getAttribute(Zcl.Clusters.haDiagnostic.attributes.danfossSystemStatusCode.ID); }).toThrow(); }); it.each([ ["by ID", {key: Zcl.Clusters.genBasic.commands.resetFactDefault.ID}, {cluster: Zcl.Clusters.genBasic, name: "resetFactDefault"}], ["by name", {key: "resetAll"}, {cluster: Zcl.Clusters.genAlarms, name: "resetAll"}], ])("Gets cluster command %s", (_name, payload, expected) => { const cluster = Zcl.Utils.getCluster(expected.cluster.ID, undefined, {}); const command = cluster.getCommand(payload.key); expect(command).toStrictEqual(cluster.commands[expected.name]); }); it("Throws when getting invalid command", () => { const cluster = Zcl.Utils.getCluster(Zcl.Clusters.genAlarms.ID, undefined, {}); expect(() => { cluster.getCommand("abcd"); }).toThrow(); expect(() => { cluster.getCommand(99999); }).toThrow(); }); it.each([ [ "by ID", {key: Zcl.Clusters.genIdentify.commandsResponse.identifyQueryRsp.ID}, {cluster: Zcl.Clusters.genIdentify, name: "identifyQueryRsp"}, ], ["by name", {key: "getEventLog"}, {cluster: Zcl.Clusters.genAlarms, name: "getEventLog"}], ])("Gets cluster command response %s", (_name, payload, expected) => { const cluster = Zcl.Utils.getCluster(expected.cluster.ID, undefined, {}); const commandResponse = cluster.getCommandResponse(payload.key); expect(commandResponse).toStrictEqual(cluster.commandsResponse[expected.name]); }); it("Throws when getting invalid command response", () => { const cluster = Zcl.Utils.getCluster(Zcl.Clusters.genAlarms.ID, undefined, {}); expect(() => { cluster.getCommandResponse("abcd"); }).toThrow(); expect(() => { cluster.getCommandResponse(99999); }).toThrow(); }); it.each([ ["by ID", {key: Zcl.Foundation.writeUndiv.ID}, {cluster: Zcl.Foundation.writeUndiv, name: "writeUndiv"}], ["by name", {key: "read"}, {cluster: Zcl.Foundation.read, name: "read"}], ])("Gets global command %s", (_name, payload, expected) => { const command: Command = { ID: expected.cluster.ID, name: expected.name, parameters: expected.cluster.parameters, }; if (expected.cluster.response) { command.response = expected.cluster.response; } expect(Zcl.Utils.getGlobalCommand(payload.key)).toStrictEqual(command); }); it("Throws when getting invalid global command", () => { expect(() => { Zcl.Utils.getGlobalCommand(99999); }).toThrow(); expect(() => { Zcl.Utils.getGlobalCommand("abcd"); }).toThrow(); }); it("Checks cluster name", () => { expect(Zcl.Utils.isClusterName("genBasic")).toBeTruthy(); expect(Zcl.Utils.isClusterName("genAlarms")).toBeTruthy(); expect(Zcl.Utils.isClusterName("invalid")).toBeFalsy(); }); it("Gets Foundation command", () => { expect(Zcl.Utils.getFoundationCommand(0)).toStrictEqual(Zcl.Foundation.read); }); it("Throws when getting invalid Foundation command ID", () => { expect(() => { Zcl.Utils.getFoundationCommand(9999); }).toThrow(`Foundation command '9999' does not exist.`); }); });