Skip to content

Commit

Permalink
fix(ignore): zigate: Various fixes (#1203)
Browse files Browse the repository at this point in the history
* zigate: Fix ZDO payloads with tests.

* zigate uses BE only when writing requests, not reading responses.

* Bypass missing `LEAVE_RESPONSE` in stack.
  • Loading branch information
Nerivec authored Sep 24, 2024
1 parent d6eef4d commit 45c0f26
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 39 deletions.
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'));
});
});

0 comments on commit 45c0f26

Please sign in to comment.