@@ -73,6 +73,7 @@ import {
73
73
encodeZigbeeNWKFrame ,
74
74
} from "../zigbee/zigbee-nwk.js" ;
75
75
import {
76
+ ZigbeeNWKGPCommandId ,
76
77
ZigbeeNWKGPFrameType ,
77
78
type ZigbeeNWKGPHeader ,
78
79
decodeZigbeeNWKGPFrameControl ,
@@ -723,7 +724,14 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
723
724
724
725
private readonly trustCenterPolicies : TrustCenterPolicies ;
725
726
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 ;
727
735
728
736
//---- NWK
729
737
@@ -794,6 +802,11 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
794
802
} ;
795
803
this . macAssociationPermit = false ;
796
804
805
+ //---- Green Power
806
+ this . gpCommissioningMode = false ;
807
+ this . gpLastMACSequenceNumber = - 1 ;
808
+ this . gpLastSecurityFrameCounter = - 1 ;
809
+
797
810
//---- NWK
798
811
this . netParams = netParams ;
799
812
this . tcVerifyKeyHash = Buffer . alloc ( 0 ) ; // set by `loadState`
@@ -934,6 +947,7 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
934
947
logger . info ( "======== Driver stopping ========" , NS ) ;
935
948
936
949
this . disallowJoins ( ) ;
950
+ this . gpExitCommissioningMode ( ) ;
937
951
938
952
// pre-emptive
939
953
this . networkUp = false ;
@@ -1101,26 +1115,33 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
1101
1115
const [ nwkGPFCF , nwkGPFCFOutOffset ] = decodeZigbeeNWKGPFrameControl ( macPayload , 0 ) ;
1102
1116
const [ nwkGPHeader , nwkGPHOutOffset ] = decodeZigbeeNWKGPHeader ( macPayload , nwkGPFCFOutOffset , nwkGPFCF ) ;
1103
1117
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 ) ;
1106
1123
return ;
1107
1124
}
1108
1125
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 ,
1117
1131
) ;
1118
-
1119
- this . processZigbeeNWKGPDataFrame ( nwkGPPayload , macHeader , nwkGPHeader , metadata ?. rssi ?? 0 ) ;
1120
- } else {
1121
- logger . debug ( ( ) => `<-~- NWKGP Ignoring frame with type ${ nwkGPHeader . frameControl . frameType } ` , NS ) ;
1122
1132
return ;
1123
1133
}
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 ) ;
1124
1145
} else {
1125
1146
logger . debug ( ( ) => `<-x- NWKGP Invalid frame addressing ${ macFCF . destAddrMode } (${ macHeader . destination16 } )` , NS ) ;
1126
1147
return ;
@@ -2538,16 +2559,50 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
2538
2559
2539
2560
// #region Zigbee NWK GP layer
2540
2561
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 {
2542
2591
let offset = 0 ;
2543
2592
const cmdId = data . readUInt8 ( offset ) ;
2544
2593
offset += 1 ;
2545
2594
const framePayload = data . subarray ( offset ) ;
2546
2595
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 ) ;
2551
2606
2552
2607
setImmediate ( ( ) => {
2553
2608
this . emit ( "gpFrame" , cmdId , framePayload , macHeader , nwkHeader , rssi ) ;
@@ -4020,16 +4075,18 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
4020
4075
* @param duration The length of time in seconds during which the trust center will allow joins.
4021
4076
* The value 0x00 and 0xff indicate that permission is disabled or enabled, respectively, without a specified time limit.
4022
4077
* 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.
4024
4079
*/
4025
4080
public allowJoins ( duration : number , macAssociationPermit : boolean ) : void {
4026
4081
if ( duration > 0 ) {
4027
- clearTimeout ( this . permitJoinTimeout ) ;
4082
+ clearTimeout ( this . allowJoinTimeout ) ;
4028
4083
this . trustCenterPolicies . allowJoins = true ;
4029
4084
this . trustCenterPolicies . allowRejoinsWithWellKnownKey = true ;
4030
4085
this . macAssociationPermit = macAssociationPermit ;
4031
4086
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 ) ;
4033
4090
} else {
4034
4091
this . disallowJoins ( ) ;
4035
4092
}
@@ -4039,11 +4096,37 @@ export class OTRCPDriver extends EventEmitter<AdapterDriverEventMap> {
4039
4096
* Revert allowing joins (keeps `allowRejoinsWithWellKnownKey=true`).
4040
4097
*/
4041
4098
public disallowJoins ( ) : void {
4042
- clearTimeout ( this . permitJoinTimeout ) ;
4099
+ clearTimeout ( this . allowJoinTimeout ) ;
4043
4100
4044
4101
this . trustCenterPolicies . allowJoins = false ;
4045
4102
this . trustCenterPolicies . allowRejoinsWithWellKnownKey = true ;
4046
4103
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 ) ;
4047
4130
}
4048
4131
4049
4132
/**
0 commit comments