Skip to content

Commit c8cb557

Browse files
authored
fix: ZDO spec: improve build/read logic and typing (#1186)
1 parent 4fa371d commit c8cb557

File tree

23 files changed

+1694
-1254
lines changed

23 files changed

+1694
-1254
lines changed

src/adapter/adapter.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ interface AdapterEventMap {
2121
}
2222

2323
abstract class Adapter extends events.EventEmitter<AdapterEventMap> {
24-
public readonly greenPowerGroup = 0x0b84;
24+
public hasZdoMessageOverhead: boolean;
2525
protected networkOptions: TsType.NetworkOptions;
2626
protected adapterOptions: TsType.AdapterOptions;
2727
protected serialPortOptions: TsType.SerialPortOptions;
@@ -34,6 +34,7 @@ abstract class Adapter extends events.EventEmitter<AdapterEventMap> {
3434
adapterOptions: TsType.AdapterOptions,
3535
) {
3636
super();
37+
this.hasZdoMessageOverhead = true;
3738
this.networkOptions = networkOptions;
3839
this.adapterOptions = adapterOptions;
3940
this.serialPortOptions = serialPortOptions;

src/adapter/deconz/adapter/deconzAdapter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class DeconzAdapter extends Adapter {
5757

5858
public constructor(networkOptions: NetworkOptions, serialPortOptions: SerialPortOptions, backupPath: string, adapterOptions: AdapterOptions) {
5959
super(networkOptions, serialPortOptions, backupPath, adapterOptions);
60+
this.hasZdoMessageOverhead = true;
6061

6162
const concurrent = this.adapterOptions && this.adapterOptions.concurrent ? this.adapterOptions.concurrent : 2;
6263

src/adapter/ember/adapter/emberAdapter.ts

+61-76
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import * as ZSpec from '../../../zspec';
1212
import {EUI64, ExtendedPanId, NodeId, PanId} from '../../../zspec/tstypes';
1313
import * as Zcl from '../../../zspec/zcl';
1414
import * as Zdo from '../../../zspec/zdo';
15-
import {BuffaloZdo} from '../../../zspec/zdo/buffaloZdo';
1615
import * as ZdoTypes from '../../../zspec/zdo/definition/tstypes';
1716
import {DeviceAnnouncePayload, DeviceJoinedPayload, DeviceLeavePayload, NetworkAddressPayload, ZclPayload} from '../../events';
1817
import SerialPortUtils from '../../serialPortUtils';
@@ -523,9 +522,9 @@ export class EmberAdapter extends Adapter {
523522
* @param messageContents The content of the response.
524523
*/
525524
private async onZDOResponse(apsFrame: EmberApsFrame, sender: NodeId, messageContents: Buffer): Promise<void> {
526-
try {
527-
const payload = BuffaloZdo.readResponse(apsFrame.clusterId, messageContents, true);
525+
const [status, payload] = Zdo.Buffalo.readResponse(this.hasZdoMessageOverhead, apsFrame.clusterId, messageContents);
528526

527+
if (status === Zdo.Status.SUCCESS) {
529528
logger.debug(() => `<~~~ [ZDO ${Zdo.ClusterId[apsFrame.clusterId]} from=${sender} ${payload ? JSON.stringify(payload) : 'OK'}]`, NS);
530529
this.oneWaitress.resolveZDO(sender, apsFrame, payload);
531530

@@ -540,8 +539,8 @@ export class EmberAdapter extends Adapter {
540539
ieeeAddr: (payload as ZdoTypes.EndDeviceAnnounce).eui64,
541540
} as DeviceAnnouncePayload);
542541
}
543-
} catch (error) {
544-
this.oneWaitress.resolveZDO(sender, apsFrame, error);
542+
} else {
543+
this.oneWaitress.resolveZDO(sender, apsFrame, new Zdo.StatusError(status));
545544
}
546545
}
547546

@@ -1513,43 +1512,6 @@ export class EmberAdapter extends Adapter {
15131512
return [status, reContext?.result];
15141513
}
15151514

1516-
/**
1517-
* Enable local permit join and optionally broadcast the ZDO Mgmt_Permit_Join_req message.
1518-
* This API can be called from any device type and still return EMBER_SUCCESS.
1519-
* If the API is called from an end device, the permit association bit will just be left off.
1520-
*
1521-
* @param duration uint8_t The duration that the permit join bit will remain on
1522-
* and other devices will be able to join the current network.
1523-
* @param broadcastMgmtPermitJoin whether or not to broadcast the ZDO Mgmt_Permit_Join_req message.
1524-
*
1525-
* @returns status of whether or not permit join was enabled.
1526-
* @returns apsFrame Will be null if not broadcasting.
1527-
* @returns messageTag The tag passed to ezspSend${x} function.
1528-
*/
1529-
private async emberPermitJoining(
1530-
duration: number,
1531-
broadcastMgmtPermitJoin: boolean,
1532-
): Promise<[SLStatus, apsFrame: EmberApsFrame | undefined, messageTag: number | undefined]> {
1533-
let status = await this.ezsp.ezspPermitJoining(duration);
1534-
let apsFrame: EmberApsFrame | undefined;
1535-
let messageTag: number | undefined;
1536-
1537-
logger.debug(`Permit joining for ${duration} sec. status=${[status]}`, NS);
1538-
1539-
if (broadcastMgmtPermitJoin) {
1540-
// `authentication`: TC significance always 1 (zb specs)
1541-
const zdoPayload = BuffaloZdo.buildPermitJoining(duration, 1, []);
1542-
[status, apsFrame, messageTag] = await this.sendZDORequest(
1543-
ZSpec.BroadcastAddress.DEFAULT,
1544-
Zdo.ClusterId.PERMIT_JOINING_REQUEST,
1545-
zdoPayload,
1546-
DEFAULT_APS_OPTIONS,
1547-
);
1548-
}
1549-
1550-
return [status, apsFrame, messageTag];
1551-
}
1552-
15531515
/**
15541516
* Set the trust center policy bitmask using decision.
15551517
* @param decision
@@ -1854,7 +1816,15 @@ export class EmberAdapter extends Adapter {
18541816
return await this.queue.execute<void>(async () => {
18551817
this.checkInterpanLock();
18561818

1857-
const zdoPayload = BuffaloZdo.buildChannelChangeRequest(newChannel, null);
1819+
const zdoPayload = Zdo.Buffalo.buildRequest(
1820+
this.hasZdoMessageOverhead,
1821+
Zdo.ClusterId.NWK_UPDATE_REQUEST,
1822+
[newChannel],
1823+
0xfe,
1824+
undefined,
1825+
undefined,
1826+
undefined,
1827+
);
18581828
const [status] = await this.sendZDORequest(
18591829
ZSpec.BroadcastAddress.SLEEPY,
18601830
Zdo.ClusterId.NWK_UPDATE_REQUEST,
@@ -2012,7 +1982,7 @@ export class EmberAdapter extends Adapter {
20121982
await preJoining();
20131983

20141984
// `authentication`: TC significance always 1 (zb specs)
2015-
const zdoPayload = BuffaloZdo.buildPermitJoining(seconds, 1, []);
1985+
const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.PERMIT_JOINING_REQUEST, seconds, 1, []);
20161986
const [status, apsFrame] = await this.sendZDORequest(
20171987
networkAddress,
20181988
Zdo.ClusterId.PERMIT_JOINING_REQUEST,
@@ -2034,35 +2004,36 @@ export class EmberAdapter extends Adapter {
20342004
);
20352005
});
20362006
} else {
2037-
// coordinator-only, or all
2007+
// coordinator-only (0), or all
20382008
return await this.queue.execute<void>(async () => {
20392009
this.checkInterpanLock();
20402010
await preJoining();
20412011

2042-
// local permit join if `Coordinator`-only requested, else local + broadcast
2043-
const [status] = await this.emberPermitJoining(seconds, networkAddress === ZSpec.COORDINATOR_ADDRESS ? false : true);
2012+
const status = await this.ezsp.ezspPermitJoining(seconds);
20442013

20452014
if (status !== SLStatus.OK) {
2046-
throw new Error(`[ZDO] Failed permit joining request with status=${SLStatus[status]}.`);
2015+
throw new Error(`[ZDO] Failed coordinator permit joining request with status=${SLStatus[status]}.`);
20472016
}
20482017

2049-
// NOTE: because Z2M is refreshing the permit join duration early to prevent it from closing
2050-
// (every 200sec, even if only opened for 254sec), we can't wait for the stack opened status,
2051-
// as it won't trigger again if already opened... so instead we assume it worked
2052-
// NOTE2: with EZSP, 255=forever, and 254=max, but since upstream logic uses fixed 254 with interval refresh,
2053-
// we can't simply bypass upstream calls if called for "forever" to prevent useless NCP calls (3-4 each time),
2054-
// until called with 0 (disable), since we don't know if it was requested for forever or not...
2055-
// TLDR: upstream logic change required to allow this
2056-
// if (seconds) {
2057-
// await this.oneWaitress.startWaitingForEvent(
2058-
// {eventName: OneWaitressEvents.STACK_STATUS_NETWORK_OPENED},
2059-
// DEFAULT_ZCL_REQUEST_TIMEOUT,
2060-
// '[ZDO] Permit Joining',
2061-
// );
2062-
// } else {
2063-
// // NOTE: CLOSED stack status is not triggered if the network was not OPENED in the first place, so don't wait for it
2064-
// // same kind of problem as described above (upstream always tries to close after start, but EZSP already is)
2065-
// }
2018+
logger.debug(`Permit joining on coordinator for ${seconds} sec.`, NS);
2019+
2020+
// broadcast permit joining ZDO
2021+
if (networkAddress === undefined) {
2022+
// `authentication`: TC significance always 1 (zb specs)
2023+
const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.PERMIT_JOINING_REQUEST, seconds, 1, []);
2024+
2025+
const [bcStatus] = await this.sendZDORequest(
2026+
ZSpec.BroadcastAddress.DEFAULT,
2027+
Zdo.ClusterId.PERMIT_JOINING_REQUEST,
2028+
zdoPayload,
2029+
DEFAULT_APS_OPTIONS,
2030+
);
2031+
2032+
if (bcStatus !== SLStatus.OK) {
2033+
// don't throw, coordinator succeeded at least
2034+
logger.error(`[ZDO] Failed broadcast permit joining request with status=${SLStatus[bcStatus]}.`, NS);
2035+
}
2036+
}
20662037
});
20672038
}
20682039
}
@@ -2074,7 +2045,7 @@ export class EmberAdapter extends Adapter {
20742045

20752046
const neighbors: TsType.LQINeighbor[] = [];
20762047
const request = async (startIndex: number): Promise<[tableEntries: number, entryCount: number]> => {
2077-
const zdoPayload = BuffaloZdo.buildLqiTableRequest(startIndex);
2048+
const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.LQI_TABLE_REQUEST, startIndex);
20782049
const [status, apsFrame] = await this.sendZDORequest(
20792050
networkAddress,
20802051
Zdo.ClusterId.LQI_TABLE_REQUEST,
@@ -2130,7 +2101,7 @@ export class EmberAdapter extends Adapter {
21302101

21312102
const table: TsType.RoutingTableEntry[] = [];
21322103
const request = async (startIndex: number): Promise<[tableEntries: number, entryCount: number]> => {
2133-
const zdoPayload = BuffaloZdo.buildRoutingTableRequest(startIndex);
2104+
const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.ROUTING_TABLE_REQUEST, startIndex);
21342105
const [status, apsFrame] = await this.sendZDORequest(
21352106
networkAddress,
21362107
Zdo.ClusterId.ROUTING_TABLE_REQUEST,
@@ -2156,7 +2127,7 @@ export class EmberAdapter extends Adapter {
21562127
for (const entry of result.entryList) {
21572128
table.push({
21582129
destinationAddress: entry.destinationAddress,
2159-
status: TsType.RoutingTableStatus[entry.status], // get str value from enum to satisfy upstream's needs
2130+
status: entry.status,
21602131
nextHop: entry.nextHopAddress,
21612132
});
21622133
}
@@ -2184,7 +2155,7 @@ export class EmberAdapter extends Adapter {
21842155
return await this.queue.execute<TsType.NodeDescriptor>(async () => {
21852156
this.checkInterpanLock();
21862157

2187-
const zdoPayload = BuffaloZdo.buildNodeDescriptorRequest(networkAddress);
2158+
const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.NODE_DESCRIPTOR_REQUEST, networkAddress);
21882159
const [status, apsFrame] = await this.sendZDORequest(
21892160
networkAddress,
21902161
Zdo.ClusterId.NODE_DESCRIPTOR_REQUEST,
@@ -2220,9 +2191,9 @@ export class EmberAdapter extends Adapter {
22202191
}
22212192

22222193
/* istanbul ignore else */
2223-
if (result.serverMask.stackComplianceResivion < CURRENT_ZIGBEE_SPEC_REVISION) {
2194+
if (result.serverMask.stackComplianceRevision < CURRENT_ZIGBEE_SPEC_REVISION) {
22242195
// always 0 before rev. 21 where field was added
2225-
const rev = result.serverMask.stackComplianceResivion < 21 ? 'pre-21' : result.serverMask.stackComplianceResivion;
2196+
const rev = result.serverMask.stackComplianceRevision < 21 ? 'pre-21' : result.serverMask.stackComplianceRevision;
22262197

22272198
logger.warning(
22282199
`[ZDO] Device '${networkAddress}' is only compliant to revision '${rev}' of the ZigBee specification (current revision: ${CURRENT_ZIGBEE_SPEC_REVISION}).`,
@@ -2239,7 +2210,7 @@ export class EmberAdapter extends Adapter {
22392210
return await this.queue.execute<TsType.ActiveEndpoints>(async () => {
22402211
this.checkInterpanLock();
22412212

2242-
const zdoPayload = BuffaloZdo.buildActiveEndpointsRequest(networkAddress);
2213+
const zdoPayload = Zdo.Buffalo.buildRequest(this.hasZdoMessageOverhead, Zdo.ClusterId.ACTIVE_ENDPOINTS_REQUEST, networkAddress);
22432214
const [status, apsFrame] = await this.sendZDORequest(
22442215
networkAddress,
22452216
Zdo.ClusterId.ACTIVE_ENDPOINTS_REQUEST,
@@ -2269,7 +2240,12 @@ export class EmberAdapter extends Adapter {
22692240
return await this.queue.execute<TsType.SimpleDescriptor>(async () => {
22702241
this.checkInterpanLock();
22712242

2272-
const zdoPayload = BuffaloZdo.buildSimpleDescriptorRequest(networkAddress, endpointID);
2243+
const zdoPayload = Zdo.Buffalo.buildRequest(
2244+
this.hasZdoMessageOverhead,
2245+
Zdo.ClusterId.SIMPLE_DESCRIPTOR_REQUEST,
2246+
networkAddress,
2247+
endpointID,
2248+
);
22732249
const [status, apsFrame] = await this.sendZDORequest(
22742250
networkAddress,
22752251
Zdo.ClusterId.SIMPLE_DESCRIPTOR_REQUEST,
@@ -2315,7 +2291,9 @@ export class EmberAdapter extends Adapter {
23152291
return await this.queue.execute<void>(async () => {
23162292
this.checkInterpanLock();
23172293

2318-
const zdoPayload = BuffaloZdo.buildBindRequest(
2294+
const zdoPayload = Zdo.Buffalo.buildRequest(
2295+
this.hasZdoMessageOverhead,
2296+
Zdo.ClusterId.BIND_REQUEST,
23192297
sourceIeeeAddress as EUI64,
23202298
sourceEndpoint,
23212299
clusterID,
@@ -2361,7 +2339,9 @@ export class EmberAdapter extends Adapter {
23612339
return await this.queue.execute<void>(async () => {
23622340
this.checkInterpanLock();
23632341

2364-
const zdoPayload = BuffaloZdo.buildUnbindRequest(
2342+
const zdoPayload = Zdo.Buffalo.buildRequest(
2343+
this.hasZdoMessageOverhead,
2344+
Zdo.ClusterId.UNBIND_REQUEST,
23652345
sourceIeeeAddress as EUI64,
23662346
sourceEndpoint,
23672347
clusterID,
@@ -2399,7 +2379,12 @@ export class EmberAdapter extends Adapter {
23992379
return await this.queue.execute<void>(async () => {
24002380
this.checkInterpanLock();
24012381

2402-
const zdoPayload = BuffaloZdo.buildLeaveRequest(ieeeAddr as EUI64, Zdo.LeaveRequestFlags.WITHOUT_REJOIN);
2382+
const zdoPayload = Zdo.Buffalo.buildRequest(
2383+
this.hasZdoMessageOverhead,
2384+
Zdo.ClusterId.LEAVE_REQUEST,
2385+
ieeeAddr as EUI64,
2386+
Zdo.LeaveRequestFlags.WITHOUT_REJOIN,
2387+
);
24032388
const [status, apsFrame] = await this.sendZDORequest(networkAddress, Zdo.ClusterId.LEAVE_REQUEST, zdoPayload, DEFAULT_APS_OPTIONS);
24042389

24052390
if (status !== SLStatus.OK) {

src/adapter/ember/adapter/oneWaitress.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,7 @@ export class EmberOneWaitress {
126126
if (
127127
sender === waiter.matcher.target &&
128128
apsFrame.profileId === waiter.matcher.apsFrame.profileId &&
129-
apsFrame.clusterId ===
130-
(waiter.matcher.responseClusterId != null ? waiter.matcher.responseClusterId : waiter.matcher.apsFrame.clusterId)
129+
apsFrame.clusterId === (waiter.matcher.responseClusterId ?? waiter.matcher.apsFrame.clusterId)
131130
) {
132131
clearTimeout(waiter.timer);
133132

src/adapter/ezsp/adapter/ezspAdapter.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class EZSPAdapter extends Adapter {
5656

5757
public constructor(networkOptions: NetworkOptions, serialPortOptions: SerialPortOptions, backupPath: string, adapterOptions: AdapterOptions) {
5858
super(networkOptions, serialPortOptions, backupPath, adapterOptions);
59+
this.hasZdoMessageOverhead = true;
5960

6061
this.waitress = new Waitress<Events.ZclPayload, WaitressMatcher>(this.waitressValidator, this.waitressTimeoutFormatter);
6162
this.interpanLock = false;
@@ -65,7 +66,7 @@ class EZSPAdapter extends Adapter {
6566
logger.debug(`Adapter concurrent: ${concurrent}`, NS);
6667
this.queue = new Queue(concurrent);
6768

68-
this.driver = new Driver(this.serialPortOptions, this.networkOptions, this.greenPowerGroup, backupPath);
69+
this.driver = new Driver(this.serialPortOptions, this.networkOptions, backupPath);
6970
this.driver.on('close', this.onDriverClose.bind(this));
7071
this.driver.on('deviceJoined', this.handleDeviceJoin.bind(this));
7172
this.driver.on('deviceLeft', this.handleDeviceLeft.bind(this));

src/adapter/ezsp/driver/driver.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import equals from 'fast-deep-equal/es6';
66

77
import {Wait, Waitress} from '../../../utils';
88
import {logger} from '../../../utils/logger';
9+
import * as ZSpec from '../../../zspec';
910
import {Clusters} from '../../../zspec/zcl/definition/cluster';
1011
import {EZSPAdapterBackup} from '../adapter/backup';
1112
import * as TsType from './../../tstype';
@@ -92,7 +93,6 @@ export class Driver extends EventEmitter {
9293
// @ts-expect-error XXX: init in startup
9394
public ezsp: Ezsp;
9495
private nwkOpt: TsType.NetworkOptions;
95-
private greenPowerGroup: number;
9696
// @ts-expect-error XXX: init in startup
9797
public networkParams: EmberNetworkParameters;
9898
// @ts-expect-error XXX: init in startup
@@ -114,12 +114,11 @@ export class Driver extends EventEmitter {
114114
private serialOpt: TsType.SerialPortOptions;
115115
public backupMan: EZSPAdapterBackup;
116116

117-
constructor(serialOpt: TsType.SerialPortOptions, nwkOpt: TsType.NetworkOptions, greenPowerGroup: number, backupPath: string) {
117+
constructor(serialOpt: TsType.SerialPortOptions, nwkOpt: TsType.NetworkOptions, backupPath: string) {
118118
super();
119119

120120
this.nwkOpt = nwkOpt;
121121
this.serialOpt = serialOpt;
122-
this.greenPowerGroup = greenPowerGroup;
123122
this.waitress = new Waitress<EmberFrame, EmberWaitressMatcher>(this.waitressValidator, this.waitressTimeoutFormatter);
124123
this.backupMan = new EZSPAdapterBackup(this, backupPath);
125124
}
@@ -296,7 +295,7 @@ export class Driver extends EventEmitter {
296295

297296
this.multicast = new Multicast(this);
298297
await this.multicast.startup([]);
299-
await this.multicast.subscribe(this.greenPowerGroup, 242);
298+
await this.multicast.subscribe(ZSpec.GP_GROUP_ID, ZSpec.GP_ENDPOINT);
300299
// await this.multicast.subscribe(1, 901);
301300

302301
return result;

src/adapter/tstype.ts

-12
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,6 @@ interface LQI {
5151
neighbors: LQINeighbor[];
5252
}
5353

54-
enum RoutingTableStatus {
55-
ACTIVE = 0x0,
56-
DISCOVERY_UNDERWAY = 0x1,
57-
DISCOVERY_FAILED = 0x2,
58-
INACTIVE = 0x3,
59-
VALIDATION_UNDERWAY = 0x4,
60-
RESERVED1 = 0x5,
61-
RESERVED2 = 0x6,
62-
RESERVED3 = 0x7,
63-
}
64-
6554
interface RoutingTableEntry {
6655
destinationAddress: number;
6756
status: string;
@@ -124,5 +113,4 @@ export {
124113
StartResult,
125114
RoutingTableEntry,
126115
AdapterOptions,
127-
RoutingTableStatus,
128116
};

0 commit comments

Comments
 (0)