Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Move ZclFrame.fromBuffer() out of adapter code #1011

Merged
merged 18 commits into from
Apr 15, 2024
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