Skip to content

Commit

Permalink
feat: Add zclCommandBroadcast to endpoint (#1028)
Browse files Browse the repository at this point in the history
* feat: Add optional `inclSleepyZED` parameter

* feat: Add `inclSleepyZED` to z-stack

* feat: Add `inclSleepyZED` to ezsp

* feat: Add `inclSleepyZED` to zigate

* feat: Add `inclSleepyZED` to ember

* feat: Add `inclSleepyZED` to deconz

* fix: Add test for z-stack

* fix: Additional test for z-stack

* Support custom clusters (#1019)

* Support custom clusters

* WIP

* updates

* updates

* updates

* update

* updates

* Process feedback

* fix lint

* fix: Add `destinationBroadcastAddress`

* Update zStackAdapter.ts

* Update emberAdapter.ts

* Update ezspAdapter.ts

* Update zigateAdapter.ts

* Update deconzAdapter.ts

* Update adapter.ts

* Add broadcast ability from device. Add `zspec` folder.

* Rename `triggerBroadcast` to `broadcastCommand`

* Get `manufacturerCode` from provided `clusterKey`

* Get cluster from `clusterKey`

* Test for specific`manufacturerCode`

* Refactor `broadcastCommand` based on `zclCommand`

* Add missing `Options` interface

* Updates

* Feedback update.

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
Co-authored-by: Nerivec <62446222+Nerivec@users.noreply.github.com>
  • Loading branch information
3 people committed Apr 28, 2024
1 parent 86c080e commit ccfd299
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 62 deletions.
3 changes: 2 additions & 1 deletion src/adapter/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {ZclFrame, FrameType, Direction} from '../zcl';
import * as Models from "../models";
import Bonjour, {Service} from 'bonjour-service';
import {logger} from '../utils/logger';
import {BroadcastAddress} from '../zspec/enums';

const NS = 'zh:adapter';

Expand Down Expand Up @@ -219,7 +220,7 @@ abstract class Adapter extends events.EventEmitter {

public abstract sendZclFrameToGroup(groupID: number, zclFrame: ZclFrame, sourceEndpoint?: number): Promise<void>;

public abstract sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number): Promise<void>;
public abstract sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number, destination: BroadcastAddress): Promise<void>;

/**
* InterPAN
Expand Down
5 changes: 3 additions & 2 deletions src/adapter/deconz/adapter/deconzAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import PARAM from '../driver/constants';
import { WaitForDataRequest, ApsDataRequest, ReceivedDataResponse, gpDataInd } from '../driver/constants';
import * as Models from "../../../models";
import {logger} from '../../../utils/logger';
import {BroadcastAddress} from '../../../zspec/enums';

const NS = 'zh:deconz';
var frameParser = require('../driver/frameParser');
Expand Down Expand Up @@ -694,7 +695,7 @@ class DeconzAdapter extends Adapter {
}
}

public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number): Promise<void> {
public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number, destination: BroadcastAddress): Promise<void> {
const transactionID = this.nextTransactionID();
const request: ApsDataRequest = {};
let pay = zclFrame.toBuffer();
Expand All @@ -704,7 +705,7 @@ class DeconzAdapter extends Adapter {

request.requestId = transactionID;
request.destAddrMode = PARAM.PARAM.addressMode.NWK_ADDR;
request.destAddr16 = 0xFFFD;
request.destAddr16 = destination;
request.destEndpoint = endpoint;
request.profileId = sourceEndpoint === 242 && endpoint === 242 ? 0xa1e0 : 0x104;
request.clusterId = zclFrame.cluster.ID;
Expand Down
5 changes: 3 additions & 2 deletions src/adapter/ember/adapter/emberAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ import {aesMmoHashInit, initNetworkCache, initSecurityManagerContext} from "../u
import {randomBytes} from "crypto";
import {EmberOneWaitress, OneWaitressEvents} from "./oneWaitress";
import {logger} from "../../../utils/logger";
import {BroadcastAddress} from '../../../zspec/enums';
// import {EmberTokensManager} from "./tokensManager";

const NS = 'zh:ember';
Expand Down Expand Up @@ -3711,7 +3712,7 @@ export class EmberAdapter extends Adapter {
}

// queued, non-InterPAN
public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number): Promise<void> {
public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number, destination: BroadcastAddress): Promise<void> {
const sourceEndpointInfo = typeof sourceEndpoint === 'number' ?
FIXED_ENDPOINTS.find((epi) => (epi.endpoint === sourceEndpoint)) : FIXED_ENDPOINTS[0];
const apsFrame: EmberApsFrame = {
Expand All @@ -3720,7 +3721,7 @@ export class EmberAdapter extends Adapter {
sourceEndpoint: sourceEndpointInfo.endpoint,
destinationEndpoint: (typeof endpoint === 'number') ? endpoint : FIXED_ENDPOINTS[0].endpoint,
options: DEFAULT_APS_OPTIONS,
groupId: EMBER_RX_ON_WHEN_IDLE_BROADCAST_ADDRESS,
groupId: destination,
sequence: 0,// set by stack
};
const data = zclFrame.toBuffer();
Expand Down
5 changes: 3 additions & 2 deletions src/adapter/ezsp/adapter/ezspAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import SerialPortUtils from '../../serialPortUtils';
import SocketPortUtils from '../../socketPortUtils';
import {EZSPZDOResponseFrameData} from '../driver/ezsp';
import {logger} from '../../../utils/logger';
import {BroadcastAddress} from '../../../zspec/enums';

const NS = 'zh:ezsp';

Expand Down Expand Up @@ -510,14 +511,14 @@ class EZSPAdapter extends Adapter {
});
}

public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number): Promise<void> {
public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number, destination: BroadcastAddress): Promise<void> {
return this.queue.execute<void>(async () => {
this.checkInterpanLock();
const frame = this.driver.makeApsFrame(zclFrame.cluster.ID, false);
frame.profileId = sourceEndpoint === 242 && endpoint === 242 ? 0xA1E0 : 0x0104;
frame.sourceEndpoint = sourceEndpoint;
frame.destinationEndpoint = endpoint;
frame.groupId = 0xFFFD;
frame.groupId = destination;

await this.driver.mrequest(frame, zclFrame.toBuffer());

Expand Down
5 changes: 3 additions & 2 deletions src/adapter/z-stack/adapter/zStackAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {ZnpAdapterManager} from "./manager";
import * as Models from "../../../models";
import assert from 'assert';
import {logger} from '../../../utils/logger';
import {BroadcastAddress} from '../../../zspec/enums';

const NS = "zh:zstack";
const Subsystem = UnpiConstants.Subsystem;
Expand Down Expand Up @@ -549,11 +550,11 @@ class ZStackAdapter extends Adapter {
});
}

public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number): Promise<void> {
public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number, destination: BroadcastAddress): Promise<void> {
return this.queue.execute<void>(async () => {
this.checkInterpanLock();
await this.dataRequestExtended(
AddressMode.ADDR_16BIT, 0xFFFD, endpoint, 0, sourceEndpoint,
AddressMode.ADDR_16BIT, destination, endpoint, 0, sourceEndpoint,
zclFrame.cluster.ID, Constants.AF.DEFAULT_RADIUS, zclFrame.toBuffer(), 3000, false, 0
);

Expand Down
5 changes: 3 additions & 2 deletions src/adapter/zigate/adapter/zigateAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import ZiGateObject from "../driver/ziGateObject";
import {Buffalo} from "../../../buffalo";
import * as Models from "../../../models";
import {logger} from '../../../utils/logger';
import {BroadcastAddress} from '../../../zspec/enums';

const NS = 'zh:zigate';
const default_bind_group = 901; // https://github.com/Koenkk/zigbee-herdsman-converters/blob/master/lib/constants.js#L3
Expand Down Expand Up @@ -592,7 +593,7 @@ class ZiGateAdapter extends Adapter {
}
}

public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number): Promise<void> {
public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number, destination: BroadcastAddress): Promise<void> {
return this.queue.execute<void>(async () => {
if (sourceEndpoint !== 0x01 /*&& sourceEndpoint !== 242*/) { // @todo on zigate firmware without gp causes hang
logger.error(`source endpoint ${sourceEndpoint}, not supported`, NS);
Expand All @@ -602,7 +603,7 @@ class ZiGateAdapter extends Adapter {
const data = zclFrame.toBuffer();
const payload: RawAPSDataRequestPayload = {
addressMode: ADDRESS_MODE.short, //nwk
targetShortAddress: 0xFFFD,
targetShortAddress: destination,
sourceEndpoint: sourceEndpoint,
destinationEndpoint: endpoint,
profileID: /*sourceEndpoint === 242 ? 0xa1e0 :*/ 0x0104,
Expand Down
9 changes: 5 additions & 4 deletions src/controller/greenPower.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import events from 'events';
import {GreenPowerEvents, GreenPowerDeviceJoinedPayload} from './tstype';
import {logger} from '../utils/logger';
import {Clusters} from '../zcl';
import {BroadcastAddress} from '../zspec/enums';

const NS = 'zh:controller:greenpower';

Expand Down Expand Up @@ -80,7 +81,7 @@ class GreenPower extends events.EventEmitter {
// the proxy MAY send it as unicast to selected proxy.
// This attempts to mirror logic from commit 92f77cc5.
if (dataPayload.wasBroadcast) {
return this.adapter.sendZclFrameToAll(242, replyFrame, 242);
return this.adapter.sendZclFrameToAll(242, replyFrame, 242, BroadcastAddress.RX_ON_WHEN_IDLE);
} else {
return this.adapter.sendZclFrameToEndpoint(null, frame.payload.gppNwkAddr, 242, replyFrame, 10000, false, false, 242);
}
Expand Down Expand Up @@ -134,7 +135,7 @@ class GreenPower extends events.EventEmitter {
null, ZclTransactionSequenceNumber.next(), 'response', Clusters.greenPower.ID, payloadReply,
{},
);
await this.adapter.sendZclFrameToAll(242, replyFrame, 242);
await this.adapter.sendZclFrameToAll(242, replyFrame, 242, BroadcastAddress.RX_ON_WHEN_IDLE);

const payloadPairing = {
options: 0b0000000110101000, // Disable encryption
Expand Down Expand Up @@ -199,7 +200,7 @@ class GreenPower extends events.EventEmitter {
{},
);

await this.adapter.sendZclFrameToAll(242, replyFrame, 242);
await this.adapter.sendZclFrameToAll(242, replyFrame, 242, BroadcastAddress.RX_ON_WHEN_IDLE);
break;
/* istanbul ignore next */
case 0xA1: // GP Manufacturer-specific Attribute Reporting
Expand Down Expand Up @@ -227,7 +228,7 @@ class GreenPower extends events.EventEmitter {
);

if (networkAddress === null) {
await this.adapter.sendZclFrameToAll(242, frame, 242);
await this.adapter.sendZclFrameToAll(242, frame, 242, BroadcastAddress.RX_ON_WHEN_IDLE);
} else {
await this.adapter.sendZclFrameToEndpoint(null, networkAddress,242,frame,10000,false,false,242);
}
Expand Down
3 changes: 2 additions & 1 deletion src/controller/model/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {logger} from '../../utils/logger';
import {ClusterDefinition, CustomClusters} from '../../zcl/definition/tstype';
import {Clusters} from '../../zcl';
import {isClusterName} from '../../zcl/utils';
import {BroadcastAddress} from '../../zspec/enums';

/**
* @ignore
Expand Down Expand Up @@ -764,7 +765,7 @@ class Device extends Entity {
ZclTransactionSequenceNumber.next(), 'pairing', 33, payload, this.customClusters,
);

await Entity.adapter.sendZclFrameToAll(242, frame, 242);
await Entity.adapter.sendZclFrameToAll(242, frame, 242, BroadcastAddress.RX_ON_WHEN_IDLE);
} else await Entity.adapter.removeDevice(this.networkAddress, this.ieeeAddr);
this.removeFromDatabase();
}
Expand Down
27 changes: 25 additions & 2 deletions src/controller/model/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Group from './group';
import Device from './device';
import assert from 'assert';
import {logger} from '../../utils/logger';
import {BroadcastAddress} from '../../zspec/enums';

const NS = 'zh:controller:endpoint';

Expand Down Expand Up @@ -612,7 +613,7 @@ class Endpoint extends Entity {
await this.sendRequest(frame, options, async (f) => {
// Broadcast Green Power responses
if (this.ID === 242) {
await Entity.adapter.sendZclFrameToAll(242, f, 242);
await Entity.adapter.sendZclFrameToAll(242, f, 242, BroadcastAddress.RX_ON_WHEN_IDLE);
} else {
await Entity.adapter.sendZclFrameToEndpoint(
this.deviceIeeeAddress, this.deviceNetworkAddress, this.ID, f, options.timeout,
Expand Down Expand Up @@ -663,7 +664,7 @@ class Endpoint extends Entity {
direction,
srcEndpoint: null,
reservedBits: 0,
manufacturerCode: manufacturerCode ? manufacturerCode : null,
manufacturerCode: manufacturerCode ?? null,
transactionSequenceNumber: null,
writeUndiv: false,
...providedOptions
Expand Down Expand Up @@ -776,6 +777,28 @@ class Endpoint extends Entity {
throw error;
}
}

public async zclCommandBroadcast(endpoint: number, destination: BroadcastAddress, clusterKey: number | string,
commandKey: number | string, payload: unknown, options?: Options): Promise<void> {
const device = this.getDevice();
const cluster = this.getCluster(clusterKey, device);
const command = cluster.getCommand(commandKey);
options = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
const sourceEndpoint = options.srcEndpoint ?? this.ID;

const frame = Zcl.ZclFrame.create(
Zcl.FrameType.SPECIFIC, options.direction, true, options.manufacturerCode,
options.transactionSequenceNumber ?? ZclTransactionSequenceNumber.next(),
command.name, cluster.name, payload, device.customClusters, options.reservedBits
);

const log = `ZCL command broadcast ${this.deviceIeeeAddress}/${sourceEndpoint} to ${destination}/${endpoint} ` +
`${cluster.name}.${command.name}(${JSON.stringify({payload, options})})`;
logger.debug(log, NS);

// if endpoint===0xFF ("broadcast endpoint"), deliver to all endpoints supporting cluster, should be avoided whenever possible
await Entity.adapter.sendZclFrameToAll(endpoint, frame, sourceEndpoint, destination);
}
}

export default Endpoint;
Empty file added src/zspec/consts.ts
Empty file.
16 changes: 16 additions & 0 deletions src/zspec/enums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* ZigBee Broadcast Addresses
*
* ZigBee specifies three different broadcast addresses that reach different collections of nodes.
* Broadcasts are normally sent only to routers.
* Broadcasts can also be forwarded to end devices, either all of them or only those that do not sleep.
* Broadcasting to end devices is both significantly more resource-intensive and significantly less reliable than broadcasting to routers.
*/
export enum BroadcastAddress {
/** Broadcast to all routers. */
DEFAULT = 0xFFFC,
/** Broadcast to all non-sleepy devices. */
RX_ON_WHEN_IDLE = 0xFFFD,
/** Broadcast to all devices, including sleepy end devices. */
SLEEPY = 0xFFFF,
};
Empty file added src/zspec/zcl/.gitkeep
Empty file.
Empty file added src/zspec/zdo/.gitkeep
Empty file.

0 comments on commit ccfd299

Please sign in to comment.