Skip to content

Commit

Permalink
fix: Allow to ignore cache for device interview (#1109)
Browse files Browse the repository at this point in the history
* fix: Allow to ignore cache for device interview

* fix coverage
  • Loading branch information
Koenkk authored Jul 12, 2024
1 parent 09aec85 commit 3b55e54
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 31 deletions.
10 changes: 5 additions & 5 deletions src/controller/model/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ class Device extends Entity {
* Zigbee functions
*/

public async interview(): Promise<void> {
public async interview(ignoreCache: boolean = false): Promise<void> {
if (this.interviewing) {
const message = `Interview - interview already in progress for '${this.ieeeAddr}'`;
logger.debug(message, NS);
Expand All @@ -696,7 +696,7 @@ class Device extends Entity {
logger.debug(`Interview - start device '${this.ieeeAddr}'`, NS);

try {
await this.interviewInternal();
await this.interviewInternal(ignoreCache);
logger.debug(`Interview - completed for device '${this.ieeeAddr}'`, NS);
this._interviewCompleted = true;
} catch (e) {
Expand Down Expand Up @@ -785,7 +785,7 @@ class Device extends Entity {
}
}

private async interviewInternal(): Promise<void> {
private async interviewInternal(ignoreCache: boolean): Promise<void> {
const nodeDescriptorQuery = async (): Promise<void> => {
const nodeDescriptor = await Entity.adapter.nodeDescriptor(this.networkAddress);
this._manufacturerID = nodeDescriptor.manufacturerCode;
Expand All @@ -795,7 +795,7 @@ class Device extends Entity {

const hasNodeDescriptor = (): boolean => this._manufacturerID != null && this._type != null;

if (!hasNodeDescriptor()) {
if (ignoreCache || !hasNodeDescriptor()) {
for (let attempt = 0; attempt < 6; attempt++) {
try {
await nodeDescriptorQuery();
Expand Down Expand Up @@ -878,7 +878,7 @@ class Device extends Entity {
// are not mandatory in ZCL specification.
if (endpoint.supportsInputCluster('genBasic')) {
for (const [key, item] of Object.entries(Device.ReportablePropertiesMapping)) {
if (!this[item.key]) {
if (ignoreCache || !this[item.key]) {
try {
let result: KeyValue;
try {
Expand Down
67 changes: 41 additions & 26 deletions test/controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,30 @@ const mockAdapterGetCoordinator = jest.fn().mockReturnValue({
{ID: 2, profileID: 3, deviceID: 5, inputClusters: [1], outputClusters: [0]},
],
});
const mockAdapterNodeDescriptor = jest.fn().mockImplementation(async (networkAddress) => {
const descriptor = mockDevices[networkAddress].nodeDescriptor;
if (typeof descriptor === 'string' && descriptor.startsWith('xiaomi')) {
const frame = mockZclFrame.create(0, 1, true, null, 10, 'readRsp', 0, [{attrId: 5, status: 0, dataType: 66, attrData: 'lumi.occupancy'}]);
await mockAdapterEvents['zclPayload']({
wasBroadcast: false,
address: networkAddress,
clusterID: frame.cluster.ID,
data: frame.toBuffer(),
header: frame.header,
endpoint: 1,
linkquality: 50,
groupID: 1,
});

if (descriptor.endsWith('error')) {
throw new Error('failed');
} else {
return {type: 'EndDevice', manufacturerCode: 1219};
}
} else {
return descriptor;
}
});

const mockAdapterGetNetworkParameters = jest.fn().mockReturnValue({panID: 1, extendedPanID: 3, channel: 15});
const mockAdapterBind = jest.fn();
Expand Down Expand Up @@ -187,6 +211,7 @@ const mocksClear = [
mockAdapterReset,
mocksendZclFrameToGroup,
mockSetChannelInterPAN,
mockAdapterNodeDescriptor,
mocksendZclFrameInterPANToIeeeAddr,
mocksendZclFrameInterPANBroadcast,
mockRestoreChannelInterPAN,
Expand Down Expand Up @@ -507,32 +532,7 @@ jest.mock('../src/adapter/z-stack/adapter/zStackAdapter', () => {
setTransmitPower: mockAdapterSetTransmitPower,
supportsChangeChannel: mockAdapterSupportsChangeChannel,
changeChannel: mockAdapterChangeChannel,
nodeDescriptor: async (networkAddress) => {
const descriptor = mockDevices[networkAddress].nodeDescriptor;
if (typeof descriptor === 'string' && descriptor.startsWith('xiaomi')) {
const frame = mockZclFrame.create(0, 1, true, null, 10, 'readRsp', 0, [
{attrId: 5, status: 0, dataType: 66, attrData: 'lumi.occupancy'},
]);
await mockAdapterEvents['zclPayload']({
wasBroadcast: false,
address: networkAddress,
clusterID: frame.cluster.ID,
data: frame.toBuffer(),
header: frame.header,
endpoint: 1,
linkquality: 50,
groupID: 1,
});

if (descriptor.endsWith('error')) {
throw new Error('failed');
} else {
return {type: 'EndDevice', manufacturerCode: 1219};
}
} else {
return descriptor;
}
},
nodeDescriptor: mockAdapterNodeDescriptor,
activeEndpoints: (networkAddress) => {
if (mockDevices[networkAddress].activeEndpoints === 'error') {
throw new Error('timeout');
Expand Down Expand Up @@ -3741,6 +3741,21 @@ describe('Controller', () => {
});
});

it('Should use cached node descriptor when device is re-interviewed, but retrieve it when ignoreCache=true', async () => {
await controller.start();
await mockAdapterEvents['deviceJoined']({networkAddress: 129, ieeeAddr: '0x129'});
expect(mockAdapterNodeDescriptor).toHaveBeenCalledTimes(1);

// Re-join should use cached node descriptor
await mockAdapterEvents['deviceJoined']({networkAddress: 129, ieeeAddr: '0x129'});
expect(mockAdapterNodeDescriptor).toHaveBeenCalledTimes(1);

// Interview with ignoreCache=true should read node descriptor
const device = controller.getDeviceByIeeeAddr('0x129');
device.interview(true);
expect(mockAdapterNodeDescriptor).toHaveBeenCalledTimes(2);
});

it('Receive zclData report from unkown attribute', async () => {
await controller.start();
await mockAdapterEvents['deviceJoined']({networkAddress: 129, ieeeAddr: '0x129'});
Expand Down

0 comments on commit 3b55e54

Please sign in to comment.