Skip to content

Commit

Permalink
fix: endpoint device & group members typing
Browse files Browse the repository at this point in the history
  • Loading branch information
Nerivec committed Mar 6, 2025
1 parent fb27845 commit 63e679d
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/controller/model/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ export class Device extends Entity<ControllerEventMap> {
const commandHasResponse = frame.command.response != undefined;
const disableDefaultResponse = frame.header.frameControl.disableDefaultResponse;
/* v8 ignore next */
const disableTuyaDefaultResponse = endpoint.getDevice().manufacturerName?.startsWith('_TZ') && process.env['DISABLE_TUYA_DEFAULT_RESPONSE'];
const disableTuyaDefaultResponse = endpoint.getDevice()!.manufacturerName?.startsWith('_TZ') && process.env['DISABLE_TUYA_DEFAULT_RESPONSE'];
// Sometimes messages are received twice, prevent responding twice
const alreadyResponded = this._lastDefaultResponseSequenceNumber === frame.header.transactionSequenceNumber;

Expand Down
29 changes: 24 additions & 5 deletions src/controller/model/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,11 @@ export class Endpoint extends Entity {
}

get configuredReportings(): ConfiguredReporting[] {
const device = this.getDevice();
assert(device, `Cannot get configured reportings for unknown/deleted device ${this.deviceIeeeAddress}`);

return this._configuredReportings.map((entry, index) => {
const cluster = Zcl.Utils.getCluster(entry.cluster, entry.manufacturerCode, this.getDevice().customClusters);
const cluster = Zcl.Utils.getCluster(entry.cluster, entry.manufacturerCode, device.customClusters);
const attribute: ZclTypes.Attribute = cluster.hasAttribute(entry.attrId)
? cluster.getAttribute(entry.attrId)
: {ID: entry.attrId, name: `attr${index}`, type: Zcl.DataType.UNKNOWN, manufacturerCode: undefined};
Expand Down Expand Up @@ -165,8 +168,8 @@ export class Endpoint extends Entity {
/**
* Get device of this endpoint
*/
public getDevice(): Device {
return Device.byIeeeAddr(this.deviceIeeeAddress)!; // XXX: no way for device to not exist?
public getDevice(): Device | undefined {
return Device.byIeeeAddr(this.deviceIeeeAddress);
}

/**
Expand Down Expand Up @@ -309,6 +312,8 @@ export class Endpoint extends Entity {
): Promise<Type> {
const logPrefix = `Request Queue (${this.deviceIeeeAddress}/${this.ID}): `;
const device = this.getDevice();
assert(device, `Cannot send to unknown/deleted device ${this.deviceIeeeAddress}`);

const request = new Request(func, frame, device.pendingRequestTimeout, options.sendPolicy);

if (request.sendPolicy !== 'bulk') {
Expand Down Expand Up @@ -428,6 +433,8 @@ export class Endpoint extends Entity {

public async read(clusterKey: number | string, attributes: (string | number)[], options?: Options): Promise<KeyValue> {
const device = this.getDevice();
assert(device, `Cannot read from unknown/deleted device ${this.deviceIeeeAddress}`);

const cluster = this.getCluster(clusterKey, device);
const optionsWithDefaults = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
optionsWithDefaults.manufacturerCode = this.ensureManufacturerCodeIsUniqueAndGet(
Expand Down Expand Up @@ -575,7 +582,7 @@ export class Endpoint extends Entity {
}

public save(): void {
this.getDevice().save();
this.getDevice()?.save();
}

public async unbind(clusterKey: number | string, target: Endpoint | Group | number): Promise<void> {
Expand Down Expand Up @@ -734,6 +741,8 @@ export class Endpoint extends Entity {
assert(options?.transactionSequenceNumber === undefined, 'Use parameter');

const device = this.getDevice();
assert(device, `Cannot respond to unknown/deleted device ${this.deviceIeeeAddress}`);

const cluster = this.getCluster(clusterKey, device);
const command = cluster.getCommandResponse(commandKey);
transactionSequenceNumber = transactionSequenceNumber || ZclTransactionSequenceNumber.next();
Expand Down Expand Up @@ -790,6 +799,8 @@ export class Endpoint extends Entity {
timeout: number,
): {promise: Promise<{header: Zcl.Header; payload: KeyValue}>; cancel: () => void} {
const device = this.getDevice();
assert(device, `Cannot wait for unknown/deleted device ${this.deviceIeeeAddress}`);

const cluster = this.getCluster(clusterKey, device);
const command = cluster.getCommand(commandKey);
const waiter = Entity.adapter!.waitFor(
Expand Down Expand Up @@ -883,7 +894,11 @@ export class Endpoint extends Entity {
}

private getCluster(clusterKey: number | string, device: Device | undefined = undefined): ZclTypes.Cluster {
device = device ?? this.getDevice();
if (!device) {
device = this.getDevice();
assert(device, `Cannot get cluster for unknown/deleted device ${this.deviceIeeeAddress}`);
}

return Zcl.Utils.getCluster(clusterKey, device.manufacturerID, device.customClusters);
}

Expand Down Expand Up @@ -922,6 +937,8 @@ export class Endpoint extends Entity {
frameType: Zcl.FrameType = Zcl.FrameType.GLOBAL,
): Promise<void | Zcl.Frame> {
const device = this.getDevice();
assert(device, `Cannot send to unknown/deleted device ${this.deviceIeeeAddress}`);

const cluster = this.getCluster(clusterKey, device);
const command = frameType == Zcl.FrameType.GLOBAL ? Zcl.Utils.getGlobalCommand(commandKey) : cluster.getCommand(commandKey);
const hasResponse = frameType == Zcl.FrameType.GLOBAL ? true : command.response != undefined;
Expand Down Expand Up @@ -972,6 +989,8 @@ export class Endpoint extends Entity {
options?: Options,
): Promise<void> {
const device = this.getDevice();
assert(device, `Cannot send to unknown/deleted device ${this.deviceIeeeAddress}`);

const cluster = this.getCluster(clusterKey, device);
const command = cluster.getCommand(commandKey);
const optionsWithDefaults = this.getOptionsWithDefaults(options, true, Zcl.Direction.CLIENT_TO_SERVER, cluster.manufacturerCode);
Expand Down
43 changes: 28 additions & 15 deletions src/controller/model/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ interface OptionsWithDefaults extends Options {
export class Group extends Entity {
private databaseID: number;
public readonly groupID: number;
private readonly _members: Set<Endpoint>;
get members(): Endpoint[] {
return Array.from(this._members).filter((e) => e.getDevice());
}
private readonly _members: Endpoint[];
// Can be used by applications to store data.
public readonly meta: KeyValue;

Expand All @@ -38,7 +35,12 @@ export class Group extends Entity {
private static readonly groups: Map<number /* groupID */, Group> = new Map();
private static loadedFromDatabase: boolean = false;

private constructor(databaseID: number, groupID: number, members: Set<Endpoint>, meta: KeyValue) {
/** Member endpoints with valid devices (not unknown/deleted) */
get members(): Endpoint[] {
return this._members.filter((e) => e.getDevice() !== undefined);
}

private constructor(databaseID: number, groupID: number, members: Endpoint[], meta: KeyValue) {
super();
this.databaseID = databaseID;
this.groupID = groupID;
Expand All @@ -59,7 +61,8 @@ export class Group extends Entity {
}

private static fromDatabaseEntry(entry: DatabaseEntry): Group {
const members = new Set<Endpoint>();
// db is expected to never contain duplicate, so no need for explicit check
const members: Endpoint[] = [];

for (const member of entry.members) {
const device = Device.byIeeeAddr(member.deviceIeeeAddr);
Expand All @@ -68,7 +71,7 @@ export class Group extends Entity {
const endpoint = device.getEndpoint(member.endpointID);

if (endpoint) {
members.add(endpoint);
members.push(endpoint);
}
}
}
Expand All @@ -79,8 +82,12 @@ export class Group extends Entity {
private toDatabaseRecord(): DatabaseEntry {
const members: DatabaseEntry['members'] = [];

for (const member of this.members) {
members.push({deviceIeeeAddr: member.getDevice().ieeeAddr, endpointID: member.ID});
for (const member of this._members) {
const device = member.getDevice();

if (device) {
members.push({deviceIeeeAddr: device.ieeeAddr, endpointID: member.ID});
}
}

return {id: this.databaseID, type: 'Group', groupID: this.groupID, members, meta: this.meta};
Expand Down Expand Up @@ -133,7 +140,7 @@ export class Group extends Entity {
}

const databaseID = Entity.database!.newID();
const group = new Group(databaseID, groupID, new Set(), {});
const group = new Group(databaseID, groupID, [], {});
Entity.database!.insert(group.toDatabaseRecord());

Group.groups.set(group.groupID, group);
Expand Down Expand Up @@ -163,17 +170,23 @@ export class Group extends Entity {
}

public addMember(endpoint: Endpoint): void {
this._members.add(endpoint);
this.save();
if (!this._members.includes(endpoint)) {
this._members.push(endpoint);
this.save();
}
}

public removeMember(endpoint: Endpoint): void {
this._members.delete(endpoint);
this.save();
const i = this._members.indexOf(endpoint);

if (i > -1) {
this._members.splice(i, 1);
this.save();
}
}

public hasMember(endpoint: Endpoint): boolean {
return this._members.has(endpoint);
return this._members.includes(endpoint);
}

/*
Expand Down
6 changes: 3 additions & 3 deletions test/controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6852,15 +6852,15 @@ describe('Controller', () => {
expect((await controller.getGroups()).length).toBe(2);

const group1 = controller.getGroupByID(1)!;
expect(deepClone(group1)).toStrictEqual(deepClone({_events: {}, _eventsCount: 0, databaseID: 2, groupID: 1, _members: new Set(), meta: {}}));
expect(deepClone(group1)).toStrictEqual(deepClone({_events: {}, _eventsCount: 0, databaseID: 2, groupID: 1, _members: [], meta: {}}));
const group2 = controller.getGroupByID(2)!;
expect(deepClone(group2)).toStrictEqual(
deepClone({
_events: {},
_eventsCount: 0,
databaseID: 5,
groupID: 2,
_members: new Set([
_members: [
{
meta: {},
_binds: [],
Expand All @@ -6877,7 +6877,7 @@ describe('Controller', () => {
pendingRequests: {ID: 1, deviceIeeeAddress: '0x000b57fffec6a5b2', sendInProgress: false},
profileID: 49246,
},
]),
],
meta: {},
}),
);
Expand Down

0 comments on commit 63e679d

Please sign in to comment.