Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zigate: Various fixes #1203

Merged
merged 3 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions src/adapter/zigate/adapter/patchZdoBuffaloBE.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {EUI64} from '../../../zspec/tstypes';
import {BuffaloZdo} from '../../../zspec/zdo/buffaloZdo';

class ZiGateZdoBuffalo extends BuffaloZdo {
Expand All @@ -7,41 +6,22 @@ class ZiGateZdoBuffalo extends BuffaloZdo {
this.position += 2;
}

public readUInt16(): number {
const value = this.buffer.readUInt16BE(this.position);
this.position += 2;
return value;
}

public writeUInt32(value: number): void {
this.buffer.writeUInt32BE(value, this.position);
this.position += 4;
}

public readUInt32(): number {
const value = this.buffer.readUInt32BE(this.position);
this.position += 4;
return value;
}

public writeIeeeAddr(value: string /*TODO: EUI64*/): void {
this.writeUInt32(parseInt(value.slice(2, 10), 16));
this.writeUInt32(parseInt(value.slice(10), 16));
}

public readIeeeAddr(): EUI64 {
return `0x${this.readBuffer(8).toString('hex')}`;
}
}

/**
* Patch BuffaloZdo to use Big Endian variants.
*/
export const patchZdoBuffaloBE = (): void => {
BuffaloZdo.prototype.writeUInt16 = ZiGateZdoBuffalo.prototype.writeUInt16;
BuffaloZdo.prototype.readUInt16 = ZiGateZdoBuffalo.prototype.readUInt16;
BuffaloZdo.prototype.writeUInt32 = ZiGateZdoBuffalo.prototype.writeUInt32;
BuffaloZdo.prototype.readUInt32 = ZiGateZdoBuffalo.prototype.readUInt32;
BuffaloZdo.prototype.writeIeeeAddr = ZiGateZdoBuffalo.prototype.writeIeeeAddr;
BuffaloZdo.prototype.readIeeeAddr = ZiGateZdoBuffalo.prototype.readIeeeAddr;
};
13 changes: 9 additions & 4 deletions src/adapter/zigate/adapter/zigateAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,19 +224,21 @@ class ZiGateAdapter extends Adapter {
// https://zigate.fr/documentation/commandes-zigate/
switch (clusterId) {
case Zdo.ClusterId.LEAVE_REQUEST: {
const prefixedPayload = Buffer.alloc(payload.length + 3); // extra zero for `removeChildren`
prefixedPayload.writeUInt16BE(networkAddress, 0);
prefixedPayload.set(payload, 2);
// extra zero for `removeChildren`
const prefixedPayload = Buffer.alloc(payload.length + 1);
prefixedPayload.set(payload, 0);

payload = prefixedPayload;
// XXX: zigate is missing ZDO LEAVE_RESPONSE, force disable to avoid waitress timeout (LeaveIndication will do the rest)
disableResponse = true;
break;
}

case Zdo.ClusterId.BIND_REQUEST:
case Zdo.ClusterId.UNBIND_REQUEST: {
// only need adjusting when Zdo.MULTICAST_BINDING
if (payload.length === 14) {
// extra zero for endpoint
// extra zero for `endpoint`
const prefixedPayload = Buffer.alloc(payload.length + 1);
prefixedPayload.set(payload, 0);

Expand Down Expand Up @@ -280,6 +282,9 @@ class ZiGateAdapter extends Adapter {
const result = await waiter.start().promise;

return result.zdo as ZdoTypes.RequestToResponseMap[K];
} else if (clusterId === Zdo.ClusterId.LEAVE_REQUEST) {
// mock missing response (see above)
return [Zdo.Status.SUCCESS, undefined];
}
}, networkAddress);
}
Expand Down
24 changes: 9 additions & 15 deletions test/adapter/zigate/patchZdoBuffaloBE.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Zdo from '../../../src/zspec/zdo';

describe('ZiGate Patch BuffaloZdo to use BE variants', () => {
describe('ZiGate Patch BuffaloZdo to use BE variants when writing', () => {
let BuffaloZdo: typeof Zdo.Buffalo;

beforeAll(async () => {
Expand Down Expand Up @@ -35,23 +35,20 @@ describe('ZiGate Patch BuffaloZdo to use BE variants', () => {
);
});

it('readUInt16 + readUInt32', async () => {
it('readUInt16 + readUInt32 - LE', async () => {
expect(
BuffaloZdo.readResponse(
true,
Zdo.ClusterId.NWK_UPDATE_RESPONSE,
Buffer.from([0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x12, 0x34, 0x00, 0x01, 0x01, 0x12]),
),
).toStrictEqual([Zdo.Status.SUCCESS, {scannedChannels: 32768, totalTransmissions: 0x1234, totalFailures: 0x01, entryList: [0x12]}]);

// ensure regular parsing OK
expect(
).toStrictEqual(
Zdo.Buffalo.readResponse(
true,
Zdo.ClusterId.NWK_UPDATE_RESPONSE,
Buffer.from([0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x34, 0x12, 0x01, 0x00, 0x01, 0x12]),
Buffer.from([0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x12, 0x34, 0x00, 0x01, 0x01, 0x12]),
),
).toStrictEqual([Zdo.Status.SUCCESS, {scannedChannels: 32768, totalTransmissions: 0x1234, totalFailures: 0x01, entryList: [0x12]}]);
);
});

it('writeIeeeAddr', async () => {
Expand All @@ -65,22 +62,19 @@ describe('ZiGate Patch BuffaloZdo to use BE variants', () => {
);
});

it('readIeeeAddr', async () => {
it('readIeeeAddr - LE', async () => {
expect(
BuffaloZdo.readResponse(
true,
Zdo.ClusterId.IEEE_ADDRESS_RESPONSE,
Buffer.from([0x01, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x12, 0x34]),
),
).toStrictEqual([Zdo.Status.SUCCESS, {eui64: '0x1122334455667788', nwkAddress: 0x1234, startIndex: 0, assocDevList: []}]);

// ensure regular parsing OK
expect(
).toStrictEqual(
Zdo.Buffalo.readResponse(
true,
Zdo.ClusterId.IEEE_ADDRESS_RESPONSE,
Buffer.from([0x01, 0x00, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x34, 0x12]),
Buffer.from([0x01, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x12, 0x34]),
),
).toStrictEqual([Zdo.Status.SUCCESS, {eui64: '0x1122334455667788', nwkAddress: 0x1234, startIndex: 0, assocDevList: []}]);
);
});
});
185 changes: 185 additions & 0 deletions test/adapter/zigate/zdo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import {ZiGateAdapter} from '../../../src/adapter/zigate/adapter';
import {BLANK_EUI64} from '../../../src/zspec';
import * as Zdo from '../../../src/zspec/zdo';

describe('ZiGate ZDO payloads', () => {
let adapter: ZiGateAdapter;
let requestZdoSpy: jest.SpyInstance;

beforeEach(() => {
adapter = new ZiGateAdapter({panID: 0, channelList: [11]}, {}, 'tmp.db.backup', {disableLED: false});
requestZdoSpy = jest
.spyOn(
// @ts-expect-error private
adapter.driver,
'requestZdo',
)
.mockResolvedValue(true);
});

it('ZiGateCommandCode.ManagementLQI', () => {
// const ziPayload = ZiGateObject.createRequest(ZiGateCommandCode.ManagementLQI, {
// targetAddress: 0x1122,
// startIndex: 0,
// });
// console.log(ziPayload.toZiGateFrame().msgPayloadBytes.toString('hex'));
// 112200
const clusterId = Zdo.ClusterId.LQI_TABLE_REQUEST;
const zdoPayload = Zdo.Buffalo.buildRequest(false, clusterId, 0);
adapter.sendZdo('0x1122334455667788', 0x1122, clusterId, zdoPayload, true);
expect(requestZdoSpy).toHaveBeenCalledWith(clusterId, Buffer.from('112200', 'hex'));
});

it('ZiGateCommandCode.LeaveRequest', () => {
// const ziPayload = ZiGateObject.createRequest(ZiGateCommandCode.LeaveRequest, {
// extendedAddress: '0x1122334455667788',
// rejoin: 0,
// removeChildren: 0,
// });
// console.log(ziPayload.toZiGateFrame().msgPayloadBytes.toString('hex'));
// 11223344556677880000
const clusterId = Zdo.ClusterId.LEAVE_REQUEST;
const zdoPayload = Zdo.Buffalo.buildRequest(false, clusterId, '0x1122334455667788', Zdo.LeaveRequestFlags.WITHOUT_REJOIN);
adapter.sendZdo('0x1122334455667788', 0x1122, clusterId, zdoPayload, true);
expect(requestZdoSpy).toHaveBeenCalledWith(clusterId, Buffer.from('11223344556677880000', 'hex'));
});

it('ZiGateCommandCode.PermitJoin', () => {
// const ziPayload = ZiGateObject.createRequest(ZiGateCommandCode.PermitJoin, {
// targetShortAddress: 0x1122,
// interval: 254,
// TCsignificance: 1,
// });
// console.log(ziPayload.toZiGateFrame().msgPayloadBytes.toString('hex'));
// 1122fe01
const clusterId = Zdo.ClusterId.PERMIT_JOINING_REQUEST;
const zdoPayload = Zdo.Buffalo.buildRequest(false, clusterId, 254, 1, []);
adapter.sendZdo('0x1122334455667788', 0x1122, clusterId, zdoPayload, true);
expect(requestZdoSpy).toHaveBeenCalledWith(clusterId, Buffer.from('1122fe01', 'hex'));
});

it('ZiGateCommandCode.NodeDescriptor', () => {
// const ziPayload = ZiGateObject.createRequest(ZiGateCommandCode.NodeDescriptor, {
// targetShortAddress: 0x1122,
// });
// console.log(ziPayload.toZiGateFrame().msgPayloadBytes.toString('hex'));
// 1122
const clusterId = Zdo.ClusterId.NODE_DESCRIPTOR_REQUEST;
const zdoPayload = Zdo.Buffalo.buildRequest(false, clusterId, 0x1122);
adapter.sendZdo('0x1122334455667788', 0x1122, clusterId, zdoPayload, true);
expect(requestZdoSpy).toHaveBeenCalledWith(clusterId, Buffer.from('1122', 'hex'));
});

it('ZiGateCommandCode.ActiveEndpoint', () => {
// const ziPayload = ZiGateObject.createRequest(ZiGateCommandCode.ActiveEndpoint, {
// targetShortAddress: 0x1122,
// });
// console.log(ziPayload.toZiGateFrame().msgPayloadBytes.toString('hex'));
// 1122
const clusterId = Zdo.ClusterId.ACTIVE_ENDPOINTS_REQUEST;
const zdoPayload = Zdo.Buffalo.buildRequest(false, clusterId, 0x1122);
adapter.sendZdo('0x1122334455667788', 0x1122, clusterId, zdoPayload, true);
expect(requestZdoSpy).toHaveBeenCalledWith(clusterId, Buffer.from('1122', 'hex'));
});

it('ZiGateCommandCode.SimpleDescriptor', () => {
// const ziPayload = ZiGateObject.createRequest(ZiGateCommandCode.SimpleDescriptor, {
// targetShortAddress: 0x1122,
// endpoint: 3,
// });
// console.log(ziPayload.toZiGateFrame().msgPayloadBytes.toString('hex'));
// 112203
const clusterId = Zdo.ClusterId.SIMPLE_DESCRIPTOR_REQUEST;
const zdoPayload = Zdo.Buffalo.buildRequest(false, clusterId, 0x1122, 3);
adapter.sendZdo('0x1122334455667788', 0x1122, clusterId, zdoPayload, true);
expect(requestZdoSpy).toHaveBeenCalledWith(clusterId, Buffer.from('112203', 'hex'));
});

it('ZiGateCommandCode.Bind - UNICAST', () => {
// const ziPayload = ZiGateObject.createRequest(ZiGateCommandCode.Bind, {
// targetExtendedAddress: '0x1122334455667788',
// targetEndpoint: 5,
// clusterID: 0x4567,
// destinationAddressMode: Zdo.UNICAST_BINDING,
// destinationAddress: '0x9911882277336644',
// destinationEndpoint: 3,
// });
// console.log(ziPayload.toZiGateFrame().msgPayloadBytes.toString('hex'));
// 112233445566778805456703991188227733664403
const clusterId = Zdo.ClusterId.BIND_REQUEST;
const zdoPayload = Zdo.Buffalo.buildRequest(
false,
clusterId,
'0x1122334455667788',
5,
0x4567,
Zdo.UNICAST_BINDING,
'0x9911882277336644',
0,
3,
);
adapter.sendZdo('0x1122334455667788', 0x1122, clusterId, zdoPayload, true);
expect(requestZdoSpy).toHaveBeenCalledWith(clusterId, Buffer.from('112233445566778805456703991188227733664403', 'hex'));
});

it('ZiGateCommandCode.Bind - MULTICAST', () => {
// const ziPayload = ZiGateObject.createRequest(ZiGateCommandCode.Bind, {
// targetExtendedAddress: '0x1122334455667788',
// targetEndpoint: 5,
// clusterID: 0x4567,
// destinationAddressMode: Zdo.MULTICAST_BINDING,
// destinationAddress: 0x3456,
// destinationEndpoint: 0,
// });
// console.log(ziPayload.toZiGateFrame().msgPayloadBytes.toString('hex'));
// 112233445566778805456701345600
const clusterId = Zdo.ClusterId.BIND_REQUEST;
const zdoPayload = Zdo.Buffalo.buildRequest(false, clusterId, '0x1122334455667788', 5, 0x4567, Zdo.MULTICAST_BINDING, BLANK_EUI64, 0x3456, 0);
adapter.sendZdo('0x1122334455667788', 0x1122, clusterId, zdoPayload, true);
expect(requestZdoSpy).toHaveBeenCalledWith(clusterId, Buffer.from('112233445566778805456701345600', 'hex'));
});

it('ZiGateCommandCode.UnBind - UNICAST', () => {
// const ziPayload = ZiGateObject.createRequest(ZiGateCommandCode.UnBind, {
// targetExtendedAddress: '0x1122334455667788',
// targetEndpoint: 5,
// clusterID: 0x4567,
// destinationAddressMode: Zdo.UNICAST_BINDING,
// destinationAddress: '0x9911882277336644',
// destinationEndpoint: 3,
// });
// console.log(ziPayload.toZiGateFrame().msgPayloadBytes.toString('hex'));
// 112233445566778805456703991188227733664403
const clusterId = Zdo.ClusterId.UNBIND_REQUEST;
const zdoPayload = Zdo.Buffalo.buildRequest(
false,
clusterId,
'0x1122334455667788',
5,
0x4567,
Zdo.UNICAST_BINDING,
'0x9911882277336644',
0,
3,
);
adapter.sendZdo('0x1122334455667788', 0x1122, clusterId, zdoPayload, true);
expect(requestZdoSpy).toHaveBeenCalledWith(clusterId, Buffer.from('112233445566778805456703991188227733664403', 'hex'));
});

it('ZiGateCommandCode.UnBind - MULTICAST', () => {
// const ziPayload = ZiGateObject.createRequest(ZiGateCommandCode.UnBind, {
// targetExtendedAddress: '0x1122334455667788',
// targetEndpoint: 5,
// clusterID: 0x4567,
// destinationAddressMode: Zdo.MULTICAST_BINDING,
// destinationAddress: 0x3456,
// destinationEndpoint: 0,
// });
// console.log(ziPayload.toZiGateFrame().msgPayloadBytes.toString('hex'));
// 112233445566778805456701345600
const clusterId = Zdo.ClusterId.UNBIND_REQUEST;
const zdoPayload = Zdo.Buffalo.buildRequest(false, clusterId, '0x1122334455667788', 5, 0x4567, Zdo.MULTICAST_BINDING, BLANK_EUI64, 0x3456, 0);
adapter.sendZdo('0x1122334455667788', 0x1122, clusterId, zdoPayload, true);
expect(requestZdoSpy).toHaveBeenCalledWith(clusterId, Buffer.from('112233445566778805456701345600', 'hex'));
});
});