Skip to content

Commit

Permalink
feat: Zigbee specification revamp (#1042)
Browse files Browse the repository at this point in the history
* Move zcl into zspec folder.

* [First draft] Add Zdo spec.

* Add utils tests.

* Ignore lack of tests for ZDO buffalo for now.

* Reorganize zcl folders & imports/exports. Add enums/consts.

* Add zcl tests. Fix typing. Add MI_STRUCT buffalo type. Fix ToD/Date read.

* Fix status error typing.

* Cleanup base index exports

* zclFrame code cleanup

* Improve ZCL utils performance (clusters/custom clusters)

* Lower "unknown attribute" to debug

* Add request-type tests for BuffaloZdo; fix identified issues. Fix some typing.

* Added rest of tests for BuffaloZdo. More fixes & cleanup.

* Add utils.

* Update custom cluster test

* Move zcl into zspec folder.

* [First draft] Add Zdo spec.

* Add utils tests.

* Ignore lack of tests for ZDO buffalo for now.

* Reorganize zcl folders & imports/exports. Add enums/consts.

* Add zcl tests. Fix typing. Add MI_STRUCT buffalo type. Fix ToD/Date read.

* Fix status error typing.

* Cleanup base index exports

* zclFrame code cleanup

* Improve ZCL utils performance (clusters/custom clusters)

* Lower "unknown attribute" to debug

* Add request-type tests for BuffaloZdo; fix identified issues. Fix some typing.

* Added rest of tests for BuffaloZdo. More fixes & cleanup.

* Add utils.

* Update custom cluster test

* Fix rebase

* Fix rebase

* Add test for complete cluster override.

* Fix merge.

---------

Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
  • Loading branch information
Nerivec and Koenkk committed May 12, 2024
1 parent f0f41de commit 7316247
Show file tree
Hide file tree
Showing 79 changed files with 8,870 additions and 1,149 deletions.
14 changes: 7 additions & 7 deletions src/adapter/adapter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as TsType from './tstype';
import {ZclPayload} from './events';
import events from 'events';
import {ZclFrame, FrameType, Direction} from '../zcl';
import * as Zcl from '../zspec/zcl';
import * as Models from "../models";
import Bonjour, {Service} from 'bonjour-service';
import {logger} from '../utils/logger';
Expand Down Expand Up @@ -175,7 +175,7 @@ abstract class Adapter extends events.EventEmitter {
public abstract addInstallCode(ieeeAddress: string, key: Buffer): Promise<void>;

public abstract waitFor(
networkAddress: number, endpoint: number, frameType: FrameType, direction: Direction,
networkAddress: number, endpoint: number, frameType: Zcl.FrameType, direction: Zcl.Direction,
transactionSequenceNumber: number, clusterID: number, commandIdentifier: number, timeout: number,
): {promise: Promise<ZclPayload>; cancel: () => void};

Expand Down Expand Up @@ -214,24 +214,24 @@ abstract class Adapter extends events.EventEmitter {
*/

public abstract sendZclFrameToEndpoint(
ieeeAddr: string, networkAddress: number, endpoint: number, zclFrame: ZclFrame, timeout: number,
ieeeAddr: string, networkAddress: number, endpoint: number, zclFrame: Zcl.Frame, timeout: number,
disableResponse: boolean, disableRecovery: boolean, sourceEndpoint?: number,
): Promise<ZclPayload>;

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

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

/**
* InterPAN
*/

public abstract setChannelInterPAN(channel: number): Promise<void>;

public abstract sendZclFrameInterPANToIeeeAddr(zclFrame: ZclFrame, ieeeAddress: string): Promise<void>;
public abstract sendZclFrameInterPANToIeeeAddr(zclFrame: Zcl.Frame, ieeeAddress: string): Promise<void>;

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

public abstract restoreChannelInterPAN(): Promise<void>;
Expand Down
28 changes: 14 additions & 14 deletions src/adapter/deconz/adapter/deconzAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '../../tstype';
import Adapter from '../../adapter';
import Driver from '../driver/driver';
import {ZclFrame, FrameType, Direction, ZclHeader} from '../../../zcl';
import * as Zcl from '../../../zspec/zcl';
import * as Events from '../../events';
import processFrame from '../driver/frameParser';
import {Queue, Waitress, Wait} from '../../../utils';
Expand All @@ -24,7 +24,7 @@ interface WaitressMatcher {
address: number | string;
endpoint: number;
transactionSequenceNumber?: number;
frameType: FrameType;
frameType: Zcl.FrameType;
clusterID: number;
commandIdentifier: number;
direction: number;
Expand Down Expand Up @@ -580,7 +580,7 @@ class DeconzAdapter extends Adapter {
}

public waitFor(
networkAddress: number, endpoint: number, frameType: FrameType, direction: Direction,
networkAddress: number, endpoint: number, frameType: Zcl.FrameType, direction: Zcl.Direction,
transactionSequenceNumber: number, clusterID: number, commandIdentifier: number, timeout: number,
): {promise: Promise<Events.ZclPayload>; cancel: () => void} {
const payload = {
Expand All @@ -593,7 +593,7 @@ class DeconzAdapter extends Adapter {
}

public async sendZclFrameToEndpoint(
ieeeAddr: string, networkAddress: number, endpoint: number, zclFrame: ZclFrame, timeout: number,
ieeeAddr: string, networkAddress: number, endpoint: number, zclFrame: Zcl.Frame, timeout: number,
disableResponse: boolean, disableRecovery: boolean, sourceEndpoint?: number,
): Promise<Events.ZclPayload> {

Expand Down Expand Up @@ -646,7 +646,7 @@ class DeconzAdapter extends Adapter {
address: (data.srcAddrMode === 0x02) ? data.srcAddr16 : null,
data: buffer,
clusterID: zclFrame.cluster.ID,
header: ZclHeader.fromBuffer(buffer),
header: Zcl.Header.fromBuffer(buffer),
endpoint: data.srcEndpoint,
linkquality: data.lqi,
groupID: (data.srcAddrMode === 0x01) ? data.srcAddr16 : null,
Expand All @@ -665,7 +665,7 @@ class DeconzAdapter extends Adapter {
}
}

public async sendZclFrameToGroup(groupID: number, zclFrame: ZclFrame): Promise<void> {
public async sendZclFrameToGroup(groupID: number, zclFrame: Zcl.Frame): Promise<void> {
const transactionID = this.nextTransactionID();
const request: ApsDataRequest = {};
let pay = zclFrame.toBuffer();
Expand Down Expand Up @@ -695,7 +695,7 @@ class DeconzAdapter extends Adapter {
}
}

public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number, destination: BroadcastAddress): Promise<void> {
public async sendZclFrameToAll(endpoint: number, zclFrame: Zcl.Frame, sourceEndpoint: number, destination: BroadcastAddress): Promise<void> {
const transactionID = this.nextTransactionID();
const request: ApsDataRequest = {};
let pay = zclFrame.toBuffer();
Expand Down Expand Up @@ -1020,18 +1020,18 @@ class DeconzAdapter extends Adapter {
throw new Error("not supported");
}

public async sendZclFrameInterPANToIeeeAddr(zclFrame: ZclFrame, ieeeAddr: string): Promise<void> {
public async sendZclFrameInterPANToIeeeAddr(zclFrame: Zcl.Frame, ieeeAddr: string): Promise<void> {
throw new Error("not supported");
}

public async sendZclFrameInterPANBroadcast(
zclFrame: ZclFrame, timeout: number
zclFrame: Zcl.Frame, timeout: number
): Promise<Events.ZclPayload> {
throw new Error("not supported");
}

public async sendZclFrameInterPANBroadcastWithResponse(
zclFrame: ZclFrame, timeout: number
zclFrame: Zcl.Frame, timeout: number
): Promise<Events.ZclPayload> {
throw new Error("not supported");
}
Expand All @@ -1052,7 +1052,7 @@ class DeconzAdapter extends Adapter {
throw new Error("not supported");
}

public async sendZclFrameInterPANIeeeAddr(zclFrame: ZclFrame, ieeeAddr: any): Promise<void> {
public async sendZclFrameInterPANIeeeAddr(zclFrame: Zcl.Frame, ieeeAddr: any): Promise<void> {
throw new Error("not supported");
}

Expand Down Expand Up @@ -1084,7 +1084,7 @@ class DeconzAdapter extends Adapter {

const payBuf = Buffer.from(gpFrame);
const payload: Events.ZclPayload = {
header: ZclHeader.fromBuffer(payBuf),
header: Zcl.Header.fromBuffer(payBuf),
data: payBuf,
clusterID: ind.clusterId,
address: ind.srcId,
Expand All @@ -1101,13 +1101,13 @@ class DeconzAdapter extends Adapter {

private checkReceivedDataPayload(resp: ReceivedDataResponse) {
let srcAddr: any = null;
let header: ZclHeader = null;
let header: Zcl.Header = null;
const payBuf = resp != null ? Buffer.from(resp.asduPayload) : null;

if (resp != null) {
srcAddr = (resp.srcAddr16 != null) ? resp.srcAddr16 : resp.srcAddr64;
if (resp.profileId != 0x00) {
header = ZclHeader.fromBuffer(payBuf);
header = Zcl.Header.fromBuffer(payBuf);
}
}

Expand Down
1 change: 0 additions & 1 deletion src/adapter/deconz/driver/frameParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import PARAM from './constants';
import { busyQueue, apsBusyQueue, readyToSend, enableRTS, disableRTS, enableRtsTimeout } from './driver';
import { Request, ReceivedDataResponse, DataStateResponse, Command, ParamMac, ParamPanId, ParamNwkAddr, ParamExtPanId, ParamChannel, ParamChannelMask, ParamPermitJoin, ParamNetworkKey, gpDataInd } from './constants';
import * as Events from '../../events';
import {ZclFrame} from '../../../zcl';
import {logger} from '../../../utils/logger';

const NS = 'zh:deconz:frameparser';
Expand Down
37 changes: 18 additions & 19 deletions src/adapter/ember/adapter/emberAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import SocketPortUtils from '../../socketPortUtils';
import {BackupUtils, RealpathSync, Wait} from "../../../utils";
import {Adapter, TsType} from "../..";
import {Backup, UnifiedBackupStorage} from "../../../models";
import {FrameType, Direction, ZclFrame, ZclHeader, Foundation, ManufacturerCode} from "../../../zcl";
import Clusters from "../../../zcl/definition/cluster";
import * as Zcl from "../../../zspec/zcl";
import {
DeviceAnnouncePayload,
DeviceJoinedPayload,
Expand Down Expand Up @@ -391,15 +390,15 @@ const DEFAULT_NETWORK_REQUEST_TIMEOUT = 10000;// nothing on the network to bothe
/** Time between watchdog counters reading/clearing */
const WATCHDOG_COUNTERS_FEED_INTERVAL = 3600000;// every hour...
/** Default manufacturer code reported by coordinator. */
const DEFAULT_MANUFACTURER_CODE = ManufacturerCode.SILICON_LABORATORIES;
const DEFAULT_MANUFACTURER_CODE = Zcl.ManufacturerCode.SILICON_LABORATORIES;
/**
* Workaround for devices that require a specific manufacturer code to be reported by coordinator while interviewing...
* - Lumi/Aqara devices do not work properly otherwise (missing features): https://github.com/Koenkk/zigbee2mqtt/issues/9274
*/
const WORKAROUND_JOIN_MANUF_IEEE_PREFIX_TO_CODE: {[ieeePrefix: string]: ManufacturerCode} = {
const WORKAROUND_JOIN_MANUF_IEEE_PREFIX_TO_CODE: {[ieeePrefix: string]: Zcl.ManufacturerCode} = {
// NOTE: Lumi has a new prefix registered since 2021, in case they start using that one with new devices, it might need to be added here too...
// "0x18c23c" https://maclookup.app/vendors/lumi-united-technology-co-ltd
"0x54ef44": ManufacturerCode.LUMI_UNITED_TECHOLOGY_LTD_SHENZHEN,
"0x54ef44": Zcl.ManufacturerCode.LUMI_UNITED_TECHOLOGY_LTD_SHENZHEN,
};

/**
Expand All @@ -409,7 +408,7 @@ const WORKAROUND_JOIN_MANUF_IEEE_PREFIX_TO_CODE: {[ieeePrefix: string]: Manufact
*/
export class EmberAdapter extends Adapter {
/** Current manufacturer code assigned to the coordinator. Used for join workarounds... */
private manufacturerCode: ManufacturerCode;
private manufacturerCode: Zcl.ManufacturerCode;
/** Key in STACK_CONFIGS */
public readonly stackConfig: 'default' | 'zigbeed';
/** EMBER_LOW_RAM_CONCENTRATOR or EMBER_HIGH_RAM_CONCENTRATOR. */
Expand Down Expand Up @@ -585,7 +584,7 @@ export class EmberAdapter extends Adapter {
messageContents: Buffer): Promise<void> {
const payload: ZclPayload = {
clusterID: apsFrame.clusterId,
header: ZclHeader.fromBuffer(messageContents),
header: Zcl.Header.fromBuffer(messageContents),
address: sender,
data: messageContents,
endpoint: apsFrame.sourceEndpoint,
Expand All @@ -611,9 +610,9 @@ export class EmberAdapter extends Adapter {
private async onTouchlinkMessage(sourcePanId: EmberPanId, sourceAddress: EmberEUI64, groupId: number | null, lastHopLqi: number,
messageContents: Buffer): Promise<void> {
const payload: ZclPayload = {
clusterID: Clusters.touchlink.ID,
clusterID: Zcl.Clusters.touchlink.ID,
data: messageContents,
header: ZclHeader.fromBuffer(messageContents),
header: Zcl.Header.fromBuffer(messageContents),
address: sourceAddress,
endpoint: 1,// arbitrary since not sent over-the-air
linkquality: lastHopLqi,
Expand Down Expand Up @@ -654,9 +653,9 @@ export class EmberAdapter extends Adapter {

const data = Buffer.concat([gpdHeader, gpdCommandPayload]);
const payload: ZclPayload = {
header: ZclHeader.fromBuffer(data),
header: Zcl.Header.fromBuffer(data),
data,
clusterID: Clusters.greenPower.ID,
clusterID: Zcl.Clusters.greenPower.ID,
address: sourceId,
endpoint: GP_ENDPOINT,
linkquality: gpdLink,
Expand Down Expand Up @@ -706,7 +705,7 @@ export class EmberAdapter extends Adapter {
await new Promise<void>((resolve, reject): void => {
this.requestQueue.enqueue(
async (): Promise<EmberStatus> => {
logger.debug(`[WORKAROUND] Setting coordinator manufacturer code to ${ManufacturerCode[joinManufCode]}.`, NS);
logger.debug(`[WORKAROUND] Setting coordinator manufacturer code to ${Zcl.ManufacturerCode[joinManufCode]}.`, NS);
await this.ezsp.ezspSetManufacturerCode(joinManufCode);

this.manufacturerCode = joinManufCode;
Expand Down Expand Up @@ -2954,7 +2953,7 @@ 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,
public waitFor(networkAddress: number, endpoint: number, frameType: Zcl.FrameType, direction: Zcl.Direction, transactionSequenceNumber: number,
clusterID: number, commandIdentifier: number, timeout: number): {promise: Promise<ZclPayload>; cancel: () => void;} {
const sourceEndpointInfo = FIXED_ENDPOINTS[0];
const waiter = this.oneWaitress.waitFor<ZclPayload>({
Expand Down Expand Up @@ -3566,7 +3565,7 @@ export class EmberAdapter extends Adapter {
//---- ZCL

// queued, non-InterPAN
public async sendZclFrameToEndpoint(ieeeAddr: string, networkAddress: number, endpoint: number, zclFrame: ZclFrame, timeout: number,
public async sendZclFrameToEndpoint(ieeeAddr: string, networkAddress: number, endpoint: number, zclFrame: Zcl.Frame, timeout: number,
disableResponse: boolean, disableRecovery: boolean, sourceEndpoint?: number): Promise<ZclPayload> {
const sourceEndpointInfo = typeof sourceEndpoint === 'number' ?
FIXED_ENDPOINTS.find((epi) => (epi.endpoint === sourceEndpoint)) : FIXED_ENDPOINTS[0];
Expand All @@ -3576,7 +3575,7 @@ export class EmberAdapter extends Adapter {
if (command.hasOwnProperty('response') && disableResponse === false) {
commandResponseId = command.response;
} else if (!zclFrame.header.frameControl.disableDefaultResponse) {
commandResponseId = Foundation.defaultRsp.ID;
commandResponseId = Zcl.Foundation.defaultRsp.ID;
}

const apsFrame: EmberApsFrame = {
Expand Down Expand Up @@ -3651,7 +3650,7 @@ export class EmberAdapter extends Adapter {
}

// queued, non-InterPAN
public async sendZclFrameToGroup(groupID: number, zclFrame: ZclFrame, sourceEndpoint?: number): Promise<void> {
public async sendZclFrameToGroup(groupID: number, zclFrame: Zcl.Frame, sourceEndpoint?: number): Promise<void> {
const sourceEndpointInfo = typeof sourceEndpoint === 'number' ?
FIXED_ENDPOINTS.find((epi) => (epi.endpoint === sourceEndpoint)) : FIXED_ENDPOINTS[0];
const apsFrame: EmberApsFrame = {
Expand Down Expand Up @@ -3707,7 +3706,7 @@ export class EmberAdapter extends Adapter {
}

// queued, non-InterPAN
public async sendZclFrameToAll(endpoint: number, zclFrame: ZclFrame, sourceEndpoint: number, destination: BroadcastAddress): Promise<void> {
public async sendZclFrameToAll(endpoint: number, zclFrame: Zcl.Frame, 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 Down Expand Up @@ -3794,7 +3793,7 @@ export class EmberAdapter extends Adapter {
}

// queued
public async sendZclFrameInterPANToIeeeAddr(zclFrame: ZclFrame, ieeeAddress: string): Promise<void> {
public async sendZclFrameInterPANToIeeeAddr(zclFrame: Zcl.Frame, ieeeAddress: string): Promise<void> {
return new Promise<void>((resolve, reject): void => {
this.requestQueue.enqueue(
async (): Promise<EmberStatus> => {
Expand Down Expand Up @@ -3834,7 +3833,7 @@ export class EmberAdapter extends Adapter {
}

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

if (!command.hasOwnProperty('response')) {
Expand Down
2 changes: 1 addition & 1 deletion src/adapter/ember/adapter/endpoints.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Clusters from '../../../zcl/definition/cluster';
import {Clusters} from '../../../zspec/zcl/definition/cluster';
import {GP_ENDPOINT, GP_PROFILE_ID, HA_PROFILE_ID} from '../consts';
import {ClusterId, EmberMulticastId, ProfileId} from '../types';

Expand Down
2 changes: 1 addition & 1 deletion src/adapter/ember/ezsp/ezsp.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* istanbul ignore file */
import EventEmitter from "events";
import {SerialPortOptions} from "../../tstype";
import Clusters from "../../../zcl/definition/cluster";
import {Clusters} from "../../../zspec/zcl/definition/cluster";
import {byteToBits, getMacCapFlags, highByte, highLowToInt, lowByte, lowHighBits} from "../utils/math";
import {
EmberOutgoingMessageType,
Expand Down
4 changes: 2 additions & 2 deletions src/adapter/events.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ZclHeader} from '../zcl';
import {Header as ZclHeader} from '../zspec/zcl';

enum Events {
networkAddress = "networkAddress",
Expand Down Expand Up @@ -36,7 +36,7 @@ interface ZclPayload {
clusterID: number;
address: number | string;
header: ZclHeader | undefined;
// This buffer contains the whole ZclFrame (including the ZclHeader)
// This buffer contains the whole Zcl.Frame (including the ZclHeader)
data: Buffer;
endpoint: number;
linkquality: number;
Expand Down
Loading

0 comments on commit 7316247

Please sign in to comment.