Skip to content

Commit

Permalink
fix: Move ZclFrame.fromBuffer() out of adapter code (#1011)
Browse files Browse the repository at this point in the history
* fix: Move `ZclFrame.fromBuffer()` out of adapter code

* updates

* dump

* updates

* fix more tests

* updates

* updates

* fix some coverage

* updates

* zclFrameHeader -> header

* ZclDataPayload -> ZclPayload

* Move write to ZclHeader

* Process feedback

* Process feedback

* update

* update

* fix fromBuffer return type
  • Loading branch information
Koenkk authored Apr 15, 2024
1 parent caaf520 commit d4796de
Show file tree
Hide file tree
Showing 22 changed files with 1,220 additions and 1,144 deletions.
8 changes: 4 additions & 4 deletions src/adapter/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as TsType from './tstype';
import {ZclDataPayload} from './events';
import {ZclPayload} from './events';
import events from 'events';
import {ZclFrame, FrameType, Direction} from '../zcl';
import * as Models from "../models";
Expand Down Expand Up @@ -176,7 +176,7 @@ abstract class Adapter extends events.EventEmitter {
public abstract waitFor(
networkAddress: number, endpoint: number, frameType: FrameType, direction: Direction,
transactionSequenceNumber: number, clusterID: number, commandIdentifier: number, timeout: number,
): {promise: Promise<ZclDataPayload>; cancel: () => void};
): {promise: Promise<ZclPayload>; cancel: () => void};

/**
* ZDO
Expand Down Expand Up @@ -215,7 +215,7 @@ abstract class Adapter extends events.EventEmitter {
public abstract sendZclFrameToEndpoint(
ieeeAddr: string, networkAddress: number, endpoint: number, zclFrame: ZclFrame, timeout: number,
disableResponse: boolean, disableRecovery: boolean, sourceEndpoint?: number,
): Promise<ZclDataPayload>;
): Promise<ZclPayload>;

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

Expand All @@ -231,7 +231,7 @@ abstract class Adapter extends events.EventEmitter {

public abstract sendZclFrameInterPANBroadcast(
zclFrame: ZclFrame, timeout: number
): Promise<ZclDataPayload>;
): Promise<ZclPayload>;

public abstract restoreChannelInterPAN(): Promise<void>;

Expand Down
139 changes: 61 additions & 78 deletions src/adapter/deconz/adapter/deconzAdapter.ts

Large diffs are not rendered by default.

111 changes: 50 additions & 61 deletions src/adapter/ember/adapter/emberAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import SocketPortUtils from '../../socketPortUtils';
import {BackupUtils, RealpathSync, Wait} from "../../../utils";
import {Adapter, TsType} from "../..";
import {Backup, UnifiedBackupStorage} from "../../../models";
import {FrameType, Direction, ZclFrame, Foundation, ManufacturerCode} from "../../../zcl";
import {FrameType, Direction, ZclFrame, ZclHeader, Foundation, ManufacturerCode} from "../../../zcl";
import Cluster from "../../../zcl/definition/cluster";
import {
DeviceAnnouncePayload,
DeviceJoinedPayload,
DeviceLeavePayload,
Events,
RawDataPayload,
ZclDataPayload
ZclPayload
} from "../../events";
import {halCommonCrc16, highByte, highLowToInt, lowByte, lowHighBytes} from "../utils/math";
import {Ezsp, EzspEvents} from "../ezsp/ezsp";
Expand Down Expand Up @@ -601,33 +600,20 @@ export class EmberAdapter extends Adapter {
*/
private async onIncomingMessage(type: EmberIncomingMessageType, apsFrame: EmberApsFrame, lastHopLqi: number, sender: EmberNodeId,
messageContents: Buffer): Promise<void> {
try {
const payload: ZclDataPayload = {
address: sender,
frame: ZclFrame.fromBuffer(apsFrame.clusterId, messageContents),
endpoint: apsFrame.sourceEndpoint,
linkquality: lastHopLqi,
groupID: apsFrame.groupId,
wasBroadcast: ((type === EmberIncomingMessageType.BROADCAST) || (type === EmberIncomingMessageType.BROADCAST_LOOPBACK)),
destinationEndpoint: apsFrame.destinationEndpoint,
};

this.oneWaitress.resolveZCL(payload);
this.emit(Events.zclData, payload);
} catch (error) {
const payload: RawDataPayload = {
clusterID: apsFrame.clusterId,
address: sender,
data: messageContents,
endpoint: apsFrame.sourceEndpoint,
linkquality: lastHopLqi,
groupID: apsFrame.groupId,
wasBroadcast: ((type === EmberIncomingMessageType.BROADCAST) || (type === EmberIncomingMessageType.BROADCAST_LOOPBACK)),
destinationEndpoint: apsFrame.destinationEndpoint,
};
const payload: ZclPayload = {
clusterID: apsFrame.clusterId,
header: ZclHeader.fromBuffer(messageContents),
address: sender,
data: messageContents,
endpoint: apsFrame.sourceEndpoint,
linkquality: lastHopLqi,
groupID: apsFrame.groupId,
wasBroadcast: ((type === EmberIncomingMessageType.BROADCAST) || (type === EmberIncomingMessageType.BROADCAST_LOOPBACK)),
destinationEndpoint: apsFrame.destinationEndpoint,
};

this.emit(Events.rawData, payload);
}
this.oneWaitress.resolveZCL(payload);
this.emit(Events.zclPayload, payload);
}

/**
Expand All @@ -641,8 +627,10 @@ export class EmberAdapter extends Adapter {
*/
private async onTouchlinkMessage(sourcePanId: EmberPanId, sourceAddress: EmberEUI64, groupId: number | null, lastHopLqi: number,
messageContents: Buffer): Promise<void> {
const payload: ZclDataPayload = {
frame: ZclFrame.fromBuffer(Cluster.touchlink.ID, messageContents),
const payload: ZclPayload = {
clusterID: Cluster.touchlink.ID,
data: messageContents,
header: ZclHeader.fromBuffer(messageContents),
address: sourceAddress,
endpoint: 1,// arbitrary since not sent over-the-air
linkquality: lastHopLqi,
Expand All @@ -652,7 +640,7 @@ export class EmberAdapter extends Adapter {
};

this.oneWaitress.resolveZCL(payload);
this.emit(Events.zclData, payload);
this.emit(Events.zclPayload, payload);
}

/**
Expand Down Expand Up @@ -681,20 +669,21 @@ export class EmberAdapter extends Adapter {
gpdHeader.writeUInt8(gpdCommandId, 13);// commandID
gpdHeader.writeUInt8(gpdCommandPayload.length, 14);// payloadSize

const gpFrame = ZclFrame.fromBuffer(Cluster.greenPower.ID, Buffer.concat([gpdHeader, gpdCommandPayload]));
const payload: ZclDataPayload = {
frame: gpFrame,
const data = Buffer.concat([gpdHeader, gpdCommandPayload]);
const payload: ZclPayload = {
header: ZclHeader.fromBuffer(data),
data,
clusterID: Cluster.greenPower.ID,
address: sourceId,
endpoint: GP_ENDPOINT,
linkquality: gpdLink,
groupID: this.greenPowerGroup,
// XXX: upstream sends to `gppNwkAddr` if `wasBroadcast` is false, even if `gppNwkAddr` is null
wasBroadcast: (gpFrame.Payload.gppNwkAddr != null) ? false : true,
wasBroadcast: true,
destinationEndpoint: GP_ENDPOINT,
};

this.oneWaitress.resolveZCL(payload);
this.emit(Events.zclData, payload);
this.emit(Events.zclPayload, payload);
} catch (err) {
logger.error(`<~x~ [GP] Failed creating ZCL payload. Skipping. ${err}`, NS);
return;
Expand Down Expand Up @@ -2981,9 +2970,9 @@ export class EmberAdapter extends Adapter {

/** WARNING: Adapter impl. Starts timer immediately upon returning */
public waitFor(networkAddress: number, endpoint: number, frameType: FrameType, direction: Direction, transactionSequenceNumber: number,
clusterID: number, commandIdentifier: number, timeout: number): {promise: Promise<ZclDataPayload>; cancel: () => void;} {
clusterID: number, commandIdentifier: number, timeout: number): {promise: Promise<ZclPayload>; cancel: () => void;} {
const sourceEndpointInfo = FIXED_ENDPOINTS[0];
const waiter = this.oneWaitress.waitFor<ZclDataPayload>({
const waiter = this.oneWaitress.waitFor<ZclPayload>({
target: networkAddress,
apsFrame: {
clusterId: clusterID,
Expand Down Expand Up @@ -3582,21 +3571,21 @@ export class EmberAdapter extends Adapter {

// queued, non-InterPAN
public async sendZclFrameToEndpoint(ieeeAddr: string, networkAddress: number, endpoint: number, zclFrame: ZclFrame, timeout: number,
disableResponse: boolean, disableRecovery: boolean, sourceEndpoint?: number): Promise<ZclDataPayload> {
disableResponse: boolean, disableRecovery: boolean, sourceEndpoint?: number): Promise<ZclPayload> {
const sourceEndpointInfo = typeof sourceEndpoint === 'number' ?
FIXED_ENDPOINTS.find((epi) => (epi.endpoint === sourceEndpoint)) : FIXED_ENDPOINTS[0];
const command = zclFrame.getCommand();
const command = zclFrame.command;
let commandResponseId: number = null;

if (command.hasOwnProperty('response') && disableResponse === false) {
commandResponseId = command.response;
} else if (!zclFrame.Header.frameControl.disableDefaultResponse) {
} else if (!zclFrame.header.frameControl.disableDefaultResponse) {
commandResponseId = Foundation.defaultRsp.ID;
}

const apsFrame: EmberApsFrame = {
profileId: sourceEndpointInfo.profileId,
clusterId: zclFrame.Cluster.ID,
clusterId: zclFrame.cluster.ID,
sourceEndpoint: sourceEndpointInfo.endpoint,
destinationEndpoint: (typeof endpoint === 'number') ? endpoint : FIXED_ENDPOINTS[0].endpoint,
options: DEFAULT_APS_OPTIONS,
Expand All @@ -3611,7 +3600,7 @@ export class EmberAdapter extends Adapter {

const data = zclFrame.toBuffer();

return new Promise<ZclDataPayload>((resolve, reject): void => {
return new Promise<ZclPayload>((resolve, reject): void => {
this.requestQueue.enqueue(
async (): Promise<EmberStatus> => {
this.checkInterpanLock();
Expand All @@ -3627,7 +3616,7 @@ export class EmberAdapter extends Adapter {
}

logger.debug(
`~~~> [ZCL to=${networkAddress} apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.Header)}]`,
`~~~> [ZCL to=${networkAddress} apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.header)}]`,
NS,
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand All @@ -3647,10 +3636,10 @@ export class EmberAdapter extends Adapter {

if (commandResponseId != null) {
// NOTE: aps sequence number will have been set by send function
const result = (await this.oneWaitress.startWaitingFor<ZclDataPayload>({
const result = (await this.oneWaitress.startWaitingFor<ZclPayload>({
target: networkAddress,
apsFrame,
zclSequence: zclFrame.Header.transactionSequenceNumber,
zclSequence: zclFrame.header.transactionSequenceNumber,
commandIdentifier: commandResponseId,
}, timeout || DEFAULT_ZCL_REQUEST_TIMEOUT));

Expand All @@ -3671,7 +3660,7 @@ export class EmberAdapter extends Adapter {
FIXED_ENDPOINTS.find((epi) => (epi.endpoint === sourceEndpoint)) : FIXED_ENDPOINTS[0];
const apsFrame: EmberApsFrame = {
profileId: sourceEndpointInfo.profileId,
clusterId: zclFrame.Cluster.ID,
clusterId: zclFrame.cluster.ID,
sourceEndpoint: sourceEndpointInfo.endpoint,
destinationEndpoint: FIXED_ENDPOINTS[0].endpoint,
options: DEFAULT_APS_OPTIONS,
Expand All @@ -3695,7 +3684,7 @@ export class EmberAdapter extends Adapter {
}
}

logger.debug(`~~~> [ZCL GROUP apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.Header)}]`, NS);
logger.debug(`~~~> [ZCL GROUP apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.header)}]`, NS);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [status, messageTag] = (await this.ezsp.send(
EmberOutgoingMessageType.MULTICAST,
Expand Down Expand Up @@ -3727,7 +3716,7 @@ export class EmberAdapter extends Adapter {
FIXED_ENDPOINTS.find((epi) => (epi.endpoint === sourceEndpoint)) : FIXED_ENDPOINTS[0];
const apsFrame: EmberApsFrame = {
profileId: sourceEndpointInfo.profileId,
clusterId: zclFrame.Cluster.ID,
clusterId: zclFrame.cluster.ID,
sourceEndpoint: sourceEndpointInfo.endpoint,
destinationEndpoint: (typeof endpoint === 'number') ? endpoint : FIXED_ENDPOINTS[0].endpoint,
options: DEFAULT_APS_OPTIONS,
Expand All @@ -3751,7 +3740,7 @@ export class EmberAdapter extends Adapter {
}
}

logger.debug(`~~~> [ZCL BROADCAST apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.Header)}]`, NS);
logger.debug(`~~~> [ZCL BROADCAST apsFrame=${JSON.stringify(apsFrame)} header=${JSON.stringify(zclFrame.header)}]`, NS);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [status, messageTag] = (await this.ezsp.send(
EmberOutgoingMessageType.BROADCAST,
Expand Down Expand Up @@ -3827,10 +3816,10 @@ export class EmberAdapter extends Adapter {
msgBuffalo.writeIeeeAddr(sourceEui64);// sourceAddress
msgBuffalo.writeUInt16(STUB_NWK_FRAME_CONTROL);// nwkFrameControl
msgBuffalo.writeUInt8((EmberInterpanMessageType.UNICAST | INTERPAN_APS_FRAME_TYPE));// apsFrameControl
msgBuffalo.writeUInt16(zclFrame.Cluster.ID);
msgBuffalo.writeUInt16(zclFrame.cluster.ID);
msgBuffalo.writeUInt16(TOUCHLINK_PROFILE_ID);

logger.debug(`~~~> [ZCL TOUCHLINK to=${ieeeAddress} header=${JSON.stringify(zclFrame.Header)}]`, NS);
logger.debug(`~~~> [ZCL TOUCHLINK to=${ieeeAddress} header=${JSON.stringify(zclFrame.header)}]`, NS);
const status = (await this.ezsp.ezspSendRawMessage(Buffer.concat([msgBuffalo.getWritten(), zclFrame.toBuffer()])));

if (status !== EmberStatus.SUCCESS) {
Expand All @@ -3849,8 +3838,8 @@ export class EmberAdapter extends Adapter {
}

// queued
public async sendZclFrameInterPANBroadcast(zclFrame: ZclFrame, timeout: number): Promise<ZclDataPayload> {
const command = zclFrame.getCommand();
public async sendZclFrameInterPANBroadcast(zclFrame: ZclFrame, timeout: number): Promise<ZclPayload> {
const command = zclFrame.command;

if (!command.hasOwnProperty('response')) {
throw new Error(`Command '${command.name}' has no response, cannot wait for response.`);
Expand All @@ -3859,15 +3848,15 @@ export class EmberAdapter extends Adapter {
// just for waitress
const apsFrame: EmberApsFrame = {
profileId: TOUCHLINK_PROFILE_ID,
clusterId: zclFrame.Cluster.ID,
clusterId: zclFrame.cluster.ID,
sourceEndpoint: 0,
destinationEndpoint: 0,
options: EmberApsOption.NONE,
groupId: EMBER_SLEEPY_BROADCAST_ADDRESS,
sequence: 0,// set by stack
};

return new Promise<ZclDataPayload>((resolve, reject): void => {
return new Promise<ZclPayload>((resolve, reject): void => {
this.requestQueue.enqueue(
async (): Promise<EmberStatus> => {
const msgBuffalo = new EzspBuffalo(Buffer.alloc(MAXIMUM_INTERPAN_LENGTH));
Expand All @@ -3889,7 +3878,7 @@ export class EmberAdapter extends Adapter {

const data = Buffer.concat([msgBuffalo.getWritten(), zclFrame.toBuffer()]);

logger.debug(`~~~> [ZCL TOUCHLINK BROADCAST header=${JSON.stringify(zclFrame.Header)}]`, NS);
logger.debug(`~~~> [ZCL TOUCHLINK BROADCAST header=${JSON.stringify(zclFrame.header)}]`, NS);
const status = (await this.ezsp.ezspSendRawMessage(data));

if (status !== EmberStatus.SUCCESS) {
Expand All @@ -3899,10 +3888,10 @@ export class EmberAdapter extends Adapter {

// NOTE: can use ezspRawTransmitCompleteHandler if needed here

const result = (await this.oneWaitress.startWaitingFor<ZclDataPayload>({
const result = (await this.oneWaitress.startWaitingFor<ZclPayload>({
target: null,
apsFrame: apsFrame,
zclSequence: zclFrame.Header.transactionSequenceNumber,
zclSequence: zclFrame.header.transactionSequenceNumber,
commandIdentifier: command.response,
}, timeout || DEFAULT_ZCL_REQUEST_TIMEOUT * 2));// XXX: touchlink timeout?

Expand Down
12 changes: 7 additions & 5 deletions src/adapter/ember/adapter/oneWaitress.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* istanbul ignore file */
import equals from 'fast-deep-equal/es6';
import {ZclDataPayload} from "../../events";
import {ZclPayload} from "../../events";
import {TOUCHLINK_PROFILE_ID} from "../consts";
import {EmberApsFrame, EmberNodeId} from "../types";
import {EmberZdoStatus} from "../zdo";
Expand Down Expand Up @@ -146,7 +146,9 @@ export class EmberOneWaitress {
return false;
}

public resolveZCL(payload: ZclDataPayload): boolean {
public resolveZCL(payload: ZclPayload): boolean {
if (!payload.header) return false;

for (const [index, waiter] of this.waiters.entries()) {
if (waiter.timedout) {
this.waiters.delete(index);
Expand All @@ -155,9 +157,9 @@ export class EmberOneWaitress {

// no target in touchlink, also no APS sequence, but use the ZCL one instead
if (((waiter.matcher.apsFrame.profileId === TOUCHLINK_PROFILE_ID) || (payload.address === waiter.matcher.target))
&& (!waiter.matcher.zclSequence || (payload.frame.Header.transactionSequenceNumber === waiter.matcher.zclSequence))
&& (!waiter.matcher.commandIdentifier || (payload.frame.Header.commandIdentifier === waiter.matcher.commandIdentifier))
&& (payload.frame.Cluster.ID === waiter.matcher.apsFrame.clusterId)
&& (!waiter.matcher.zclSequence || (payload.header.transactionSequenceNumber === waiter.matcher.zclSequence))
&& (!waiter.matcher.commandIdentifier || (payload.header.commandIdentifier === waiter.matcher.commandIdentifier))
&& (payload.clusterID === waiter.matcher.apsFrame.clusterId)
&& (payload.endpoint === waiter.matcher.apsFrame.destinationEndpoint)) {
clearTimeout(waiter.timer);

Expand Down
22 changes: 6 additions & 16 deletions src/adapter/events.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import {ZclFrame} from '../zcl';
import {ZclHeader} from '../zcl';

enum Events {
networkAddress = "networkAddress",
deviceJoined = "deviceJoined",
zclData = "zclData",
rawData = "rawData",
zclPayload = "zclPayload",
disconnected = "disconnected",
deviceAnnounce = "deviceAnnounce",
deviceLeave = "deviceLeave"
Expand Down Expand Up @@ -33,19 +32,11 @@ type DeviceLeavePayload = {
ieeeAddr?: string;
};

interface ZclDataPayload {
address: number | string;
frame: ZclFrame;
endpoint: number;
linkquality: number;
groupID: number;
wasBroadcast: boolean;
destinationEndpoint: number;
}

interface RawDataPayload {
interface ZclPayload {
clusterID: number;
address: number | string;
header: ZclHeader | undefined;
// This buffer contains the whole ZclFrame (including the ZclHeader)
data: Buffer;
endpoint: number;
linkquality: number;
Expand All @@ -55,6 +46,5 @@ interface RawDataPayload {
}

export {
Events, DeviceJoinedPayload, ZclDataPayload, DeviceAnnouncePayload, NetworkAddressPayload, DeviceLeavePayload,
RawDataPayload,
Events, DeviceJoinedPayload, ZclPayload, DeviceAnnouncePayload, NetworkAddressPayload, DeviceLeavePayload,
};
Loading

0 comments on commit d4796de

Please sign in to comment.