zigbee-herdsman
Version:
An open source ZigBee gateway solution with node.js.
281 lines (256 loc) • 12.4 kB
text/typescript
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.`);
});
});