Skip to content

Commit a2102d3

Browse files
authored
fix!: More Green Power fixes (#21)
1 parent 04c5448 commit a2102d3

File tree

7 files changed

+405
-37
lines changed

7 files changed

+405
-37
lines changed

package-lock.json

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "zigbee-on-host",
3-
"version": "0.1.4",
3+
"version": "0.1.5",
44
"description": "ZigBee stack designed to run on a host and communicate with a radio co-processor (RCP)",
55
"engines": {
66
"node": ">=20.17.0"
@@ -35,7 +35,7 @@
3535
"homepage": "https://github.com/Nerivec/zigbee-on-host#readme",
3636
"devDependencies": {
3737
"@biomejs/biome": "1.9.4",
38-
"@types/node": "^22.13.10",
38+
"@types/node": "^22.13.11",
3939
"@vitest/coverage-v8": "^3.0.9",
4040
"serialport": "^13.0.0",
4141
"typescript": "^5.8.2",

src/dev/minimal-adapter.ts

+1
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ export class MinimalAdapter {
222222
await this.driver.formNetwork();
223223
// allow joins on start for 254 seconds
224224
this.driver.allowJoins(0xfe, true);
225+
this.driver.gpEnterCommissioningMode(0xfe);
225226

226227
this.driver.on("frame", this.onFrame.bind(this));
227228
this.driver.on("deviceJoined", this.onDeviceJoined.bind(this));

src/drivers/ot-rcp-driver.ts

+107-24
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import {
7373
encodeZigbeeNWKFrame,
7474
} from "../zigbee/zigbee-nwk.js";
7575
import {
76+
ZigbeeNWKGPCommandId,
7677
ZigbeeNWKGPFrameType,
7778
type ZigbeeNWKGPHeader,
7879
decodeZigbeeNWKGPFrameControl,
@@ -723,7 +724,14 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
723724

724725
private readonly trustCenterPolicies: TrustCenterPolicies;
725726
private macAssociationPermit: boolean;
726-
private permitJoinTimeout: NodeJS.Timeout | undefined;
727+
private allowJoinTimeout: NodeJS.Timeout | undefined;
728+
729+
//----- Green Power (see 14-0563-18)
730+
731+
private gpCommissioningMode: boolean;
732+
private gpCommissioningWindowTimeout: NodeJS.Timeout | undefined;
733+
private gpLastMACSequenceNumber: number;
734+
private gpLastSecurityFrameCounter: number;
727735

728736
//---- NWK
729737

@@ -794,6 +802,11 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
794802
};
795803
this.macAssociationPermit = false;
796804

805+
//---- Green Power
806+
this.gpCommissioningMode = false;
807+
this.gpLastMACSequenceNumber = -1;
808+
this.gpLastSecurityFrameCounter = -1;
809+
797810
//---- NWK
798811
this.netParams = netParams;
799812
this.tcVerifyKeyHash = Buffer.alloc(0); // set by `loadState`
@@ -934,6 +947,7 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
934947
logger.info("======== Driver stopping ========", NS);
935948

936949
this.disallowJoins();
950+
this.gpExitCommissioningMode();
937951

938952
// pre-emptive
939953
this.networkUp = false;
@@ -1101,26 +1115,33 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
11011115
const [nwkGPFCF, nwkGPFCFOutOffset] = decodeZigbeeNWKGPFrameControl(macPayload, 0);
11021116
const [nwkGPHeader, nwkGPHOutOffset] = decodeZigbeeNWKGPHeader(macPayload, nwkGPFCFOutOffset, nwkGPFCF);
11031117

1104-
if (nwkGPHeader.sourceId === undefined) {
1105-
logger.debug(() => "<-~- NWKGP Ignoring frame without srcId", NS);
1118+
if (
1119+
nwkGPHeader.frameControl.frameType !== ZigbeeNWKGPFrameType.DATA &&
1120+
nwkGPHeader.frameControl.frameType !== ZigbeeNWKGPFrameType.MAINTENANCE
1121+
) {
1122+
logger.debug(() => `<-~- NWKGP Ignoring frame with type ${nwkGPHeader.frameControl.frameType}`, NS);
11061123
return;
11071124
}
11081125

1109-
if (nwkGPHeader.frameControl.frameType === ZigbeeNWKGPFrameType.DATA) {
1110-
const nwkGPPayload = decodeZigbeeNWKGPPayload(
1111-
macPayload,
1112-
nwkGPHOutOffset,
1113-
this.netParams.networkKey,
1114-
macHeader.source64,
1115-
nwkGPFCF,
1116-
nwkGPHeader,
1126+
if (this.checkZigbeeNWKGPDuplicate(macHeader, nwkGPHeader)) {
1127+
logger.debug(
1128+
() =>
1129+
`<-~- NWKGP Ignoring duplicate frame macSeqNum=${macHeader.sequenceNumber} nwkGPFC=${nwkGPHeader.securityFrameCounter}`,
1130+
NS,
11171131
);
1118-
1119-
this.processZigbeeNWKGPDataFrame(nwkGPPayload, macHeader, nwkGPHeader, metadata?.rssi ?? 0);
1120-
} else {
1121-
logger.debug(() => `<-~- NWKGP Ignoring frame with type ${nwkGPHeader.frameControl.frameType}`, NS);
11221132
return;
11231133
}
1134+
1135+
const nwkGPPayload = decodeZigbeeNWKGPPayload(
1136+
macPayload,
1137+
nwkGPHOutOffset,
1138+
this.netParams.networkKey,
1139+
macHeader.source64,
1140+
nwkGPFCF,
1141+
nwkGPHeader,
1142+
);
1143+
1144+
this.processZigbeeNWKGPFrame(nwkGPPayload, macHeader, nwkGPHeader, metadata?.rssi ?? 0);
11241145
} else {
11251146
logger.debug(() => `<-x- NWKGP Invalid frame addressing ${macFCF.destAddrMode} (${macHeader.destination16})`, NS);
11261147
return;
@@ -2538,16 +2559,50 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
25382559

25392560
// #region Zigbee NWK GP layer
25402561

2541-
public processZigbeeNWKGPDataFrame(data: Buffer, macHeader: MACHeader, nwkHeader: ZigbeeNWKGPHeader, rssi: number): void {
2562+
public checkZigbeeNWKGPDuplicate(macHeader: MACHeader, nwkHeader: ZigbeeNWKGPHeader): boolean {
2563+
let duplicate = false;
2564+
2565+
if (nwkHeader.securityFrameCounter !== undefined) {
2566+
if (nwkHeader.securityFrameCounter === this.gpLastSecurityFrameCounter) {
2567+
duplicate = true;
2568+
}
2569+
2570+
this.gpLastSecurityFrameCounter = nwkHeader.securityFrameCounter;
2571+
} else if (macHeader.sequenceNumber !== undefined) {
2572+
if (macHeader.sequenceNumber === this.gpLastMACSequenceNumber) {
2573+
duplicate = true;
2574+
}
2575+
2576+
this.gpLastMACSequenceNumber = macHeader.sequenceNumber;
2577+
}
2578+
2579+
return duplicate;
2580+
}
2581+
2582+
/**
2583+
* See 14-0563-19 #A.3.8.2
2584+
* @param data
2585+
* @param macHeader
2586+
* @param nwkHeader
2587+
* @param rssi
2588+
* @returns
2589+
*/
2590+
public processZigbeeNWKGPFrame(data: Buffer, macHeader: MACHeader, nwkHeader: ZigbeeNWKGPHeader, rssi: number): void {
25422591
let offset = 0;
25432592
const cmdId = data.readUInt8(offset);
25442593
offset += 1;
25452594
const framePayload = data.subarray(offset);
25462595

2547-
logger.debug(
2548-
() => `<=== NWKGP[cmdId=${cmdId} dstPANId=${macHeader.destinationPANId} dst64=${macHeader.destination64} srcId=${nwkHeader.sourceId}]`,
2549-
NS,
2550-
);
2596+
if (
2597+
!this.gpCommissioningMode &&
2598+
(cmdId === ZigbeeNWKGPCommandId.COMMISSIONING || cmdId === ZigbeeNWKGPCommandId.SUCCESS || cmdId === ZigbeeNWKGPCommandId.CHANNEL_REQUEST)
2599+
) {
2600+
logger.debug(() => `<=~= NWKGP[cmdId=${cmdId} src=${nwkHeader.sourceId}:${macHeader.source64}] Not in commissioning mode`, NS);
2601+
2602+
return;
2603+
}
2604+
2605+
logger.debug(() => `<=== NWKGP[cmdId=${cmdId} src=${nwkHeader.sourceId}:${macHeader.source64}]`, NS);
25512606

25522607
setImmediate(() => {
25532608
this.emit("gpFrame", cmdId, framePayload, macHeader, nwkHeader, rssi);
@@ -4020,16 +4075,18 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
40204075
* @param duration The length of time in seconds during which the trust center will allow joins.
40214076
* The value 0x00 and 0xff indicate that permission is disabled or enabled, respectively, without a specified time limit.
40224077
* 0xff is clamped to 0xfe for security reasons
4023-
* @param macAssociationPermit If true, also allow association on coordinator itself. If false, only change TC policies
4078+
* @param macAssociationPermit If true, also allow association on coordinator itself.
40244079
*/
40254080
public allowJoins(duration: number, macAssociationPermit: boolean): void {
40264081
if (duration > 0) {
4027-
clearTimeout(this.permitJoinTimeout);
4082+
clearTimeout(this.allowJoinTimeout);
40284083
this.trustCenterPolicies.allowJoins = true;
40294084
this.trustCenterPolicies.allowRejoinsWithWellKnownKey = true;
40304085
this.macAssociationPermit = macAssociationPermit;
40314086

4032-
this.permitJoinTimeout = setTimeout(this.disallowJoins.bind(this), Math.min(duration, 0xfe) * 1000);
4087+
this.allowJoinTimeout = setTimeout(this.disallowJoins.bind(this), Math.min(duration, 0xfe) * 1000);
4088+
4089+
logger.info(`Allowed joins for ${duration} seconds (self=${macAssociationPermit})`, NS);
40334090
} else {
40344091
this.disallowJoins();
40354092
}
@@ -4039,11 +4096,37 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
40394096
* Revert allowing joins (keeps `allowRejoinsWithWellKnownKey=true`).
40404097
*/
40414098
public disallowJoins(): void {
4042-
clearTimeout(this.permitJoinTimeout);
4099+
clearTimeout(this.allowJoinTimeout);
40434100

40444101
this.trustCenterPolicies.allowJoins = false;
40454102
this.trustCenterPolicies.allowRejoinsWithWellKnownKey = true;
40464103
this.macAssociationPermit = false;
4104+
4105+
logger.info("Disallowed joins", NS);
4106+
}
4107+
4108+
/**
4109+
* Put the coordinator in Green Power commissioning mode.
4110+
* @param commissioningWindow Defaults to 180 if unspecified. Max 254
4111+
*/
4112+
public gpEnterCommissioningMode(commissioningWindow = 180): void {
4113+
if (commissioningWindow > 0) {
4114+
this.gpCommissioningMode = true;
4115+
4116+
this.gpCommissioningWindowTimeout = setTimeout(this.gpExitCommissioningMode.bind(this), Math.min(commissioningWindow, 0xfe) * 1000);
4117+
4118+
logger.info(`Entered Green Power commissioning mode for ${commissioningWindow} seconds`, NS);
4119+
} else {
4120+
this.gpExitCommissioningMode();
4121+
}
4122+
}
4123+
4124+
public gpExitCommissioningMode(): void {
4125+
clearTimeout(this.gpCommissioningWindowTimeout);
4126+
4127+
this.gpCommissioningMode = false;
4128+
4129+
logger.info("Exited Green Power commissioning mode", NS);
40474130
}
40484131

40494132
/**

test/data.ts

+45
Original file line numberDiff line numberDiff line change
@@ -2040,3 +2040,48 @@ export const NET4_ROUTE_RECORD_FROM_4B8E_RELAY_CB47 = Buffer.from([
20402040
]);
20412041

20422042
// #endregion
2043+
2044+
// #region
2045+
2046+
// GP-stuff from `ember` Zigbee2MQTT 2.1.3-dev (commit #ef5b3de5)
2047+
2048+
// export const NET5_TC_KEY = Buffer.from([0x5a, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6c, 0x6c, 0x69, 0x61, 0x6e, 0x63, 0x65, 0x30, 0x39]);
2049+
export const NET5_PAN_ID = 0xe6b4;
2050+
export const NET5_EXTENDED_PAN_ID = Buffer.from([0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd]);
2051+
export const NET5_NETWORK_KEY = Buffer.from([0x43, 0xa3, 0x0b, 0xe5, 0x3f, 0xee, 0xd5, 0x21, 0x04, 0xfd, 0x82, 0xd6, 0x57, 0xa3, 0xcb, 0x4a]);
2052+
export const NET5_COORD_EUI64 = Buffer.from([0x6c, 0x5c, 0xb1, 0xff, 0xfe, 0xfc, 0x78, 0x0a]);
2053+
2054+
/**
2055+
* IEEE 802.15.4 Data, Dst: Broadcast
2056+
* Frame Control Field: 0x0801, Frame Type: Data, Destination Addressing Mode: Short/16-bit, Frame Version: IEEE Std 802.15.4-2003, Source Addressing Mode: None
2057+
* .... .... .... .001 = Frame Type: Data (0x1)
2058+
* .... .... .... 0... = Security Enabled: False
2059+
* .... .... ...0 .... = Frame Pending: False
2060+
* .... .... ..0. .... = Acknowledge Request: False
2061+
* .... .... .0.. .... = PAN ID Compression: False
2062+
* .... .... 0... .... = Reserved: False
2063+
* .... ...0 .... .... = Sequence Number Suppression: False
2064+
* .... ..0. .... .... = Information Elements Present: False
2065+
* .... 10.. .... .... = Destination Addressing Mode: Short/16-bit (0x2)
2066+
* ..00 .... .... .... = Frame Version: IEEE Std 802.15.4-2003 (0)
2067+
* 00.. .... .... .... = Source Addressing Mode: None (0x0)
2068+
* Sequence Number: 1
2069+
* Destination PAN: 0xffff
2070+
* Destination: 0xffff
2071+
* FCS: 0x7808 (Correct)
2072+
*
2073+
* ZGP stub NWK header Maintenance
2074+
* Frame Control Field: 0x4d, Frame Type: Maintenance, Auto Commissioning Maintenance
2075+
* .... ..01 = Frame Type: Maintenance (0x1)
2076+
* ..00 11.. = Protocol Version: 3
2077+
* .1.. .... = Auto Commissioning: True
2078+
* 0... .... = NWK Frame Extension: False
2079+
* Command Frame: Channel Request
2080+
* ZGPD Command ID: Channel Request (0xe3)
2081+
* Channel Toggling Behaviour: 0x85
2082+
* .... 0101 = Rx channel in the next attempt: 0x5
2083+
* 1000 .... = Rx channel in the second next attempt: 0x8
2084+
*/
2085+
export const NET5_GP_CHANNEL_REQUEST_BCAST = Buffer.from([0x1, 0x8, 0x1, 0xff, 0xff, 0xff, 0xff, 0x4d, 0xe3, 0x85, 0x8, 0x78]);
2086+
2087+
// #endregion

0 commit comments

Comments
 (0)