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

Move led control to firmware #442

Merged
merged 1 commit into from
Nov 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/adapter/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,6 @@ abstract class Adapter extends events.EventEmitter {

public abstract reset(type: 'soft' | 'hard'): Promise<void>;

public abstract supportsLED(): Promise<boolean>;

public abstract setLED(enabled: boolean): Promise<void>;

public abstract supportsBackup(): Promise<boolean>;

public abstract backup(): Promise<Models.Backup>;
Expand Down
8 changes: 0 additions & 8 deletions src/adapter/deconz/adapter/deconzAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,6 @@ class DeconzAdapter extends Adapter {
return Promise.reject();
}

public async setLED(enabled: boolean): Promise<void> {
return Promise.reject();
}

public async lqi(networkAddress: number): Promise<LQI> {
const neighbors: LQINeighbor[] = [];

Expand Down Expand Up @@ -993,10 +989,6 @@ class DeconzAdapter extends Adapter {
}
}

public async supportsLED(): Promise<boolean> {
return false;
}

public async restoreChannelInterPAN(): Promise<void> {
throw new Error("not supported");
}
Expand Down
8 changes: 0 additions & 8 deletions src/adapter/ezsp/adapter/ezspAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,6 @@ class EZSPAdapter extends Adapter {
return Promise.reject();
}

public async supportsLED(): Promise<boolean> {
return false;
}

public async setLED(enabled: boolean): Promise<void> {
return Promise.reject();
}

public async lqi(networkAddress: number): Promise<LQI> {
return this.driver.queue.execute<LQI>(async (): Promise<LQI> => {
const neighbors: LQINeighbor[] = [];
Expand Down
58 changes: 41 additions & 17 deletions src/adapter/z-stack/adapter/zStackAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class ZStackAdapter extends Adapter {
};
private closing: boolean;
private queue: Queue;
private supportsLED_: boolean;
private supportsLED: boolean = null;
private interpanLock: boolean;
private interpanEndpointRegistered: boolean;
private waitress: Waitress<Events.ZclDataPayload, WaitressMatcher>;
Expand Down Expand Up @@ -112,9 +112,6 @@ class ZStackAdapter extends Adapter {
this.version = {"transportrev":2, "product":0, "majorrel":2, "minorrel":0, "maintrel":0, "revision":""};
}

const zStack3x0 = this.version.product === ZnpVersion.zStack3x0;
this.supportsLED_ = !zStack3x0 || (zStack3x0 && parseInt(this.version.revision) >= 20210430);

const concurrent = this.adapterOptions && this.adapterOptions.concurrent ?
this.adapterOptions.concurrent :
(this.version.product === ZnpVersion.zStack3x0 ? 16 : 2);
Expand All @@ -135,7 +132,16 @@ class ZStackAdapter extends Adapter {
},
this.logger
);
return this.adapterManager.start();

const startResult = this.adapterManager.start();

if (this.adapterOptions.disableLED) {
// Wait a bit for adapter to startup, otherwise led doesn't disable (tested with CC2531)
await Wait(200);
await this.setLED('disable');
}

return startResult;
}

public async stop(): Promise<void> {
Expand Down Expand Up @@ -197,6 +203,7 @@ class ZStackAdapter extends Adapter {
this.checkInterpanLock();
const payload = {addrmode, dstaddr , duration: seconds, tcsignificance: 0};
await this.znp.request(Subsystem.ZDO, 'mgmtPermitJoinReq', payload);
await this.setLED(seconds == 0 ? 'off' : 'on');
});
}

Expand All @@ -212,19 +219,36 @@ class ZStackAdapter extends Adapter {
}
}

public async supportsLED(): Promise<boolean> {
return this.supportsLED_;
}
private async setLED(action: 'disable' | 'on' | 'off'): Promise<void> {
if (this.supportsLED == null) {
// Only zStack3x0 with 20210430 and greater support LED
const zStack3x0 = this.version.product === ZnpVersion.zStack3x0;
this.supportsLED = !zStack3x0 || (zStack3x0 && parseInt(this.version.revision) >= 20210430);
}

public async setLED(enabled: boolean): Promise<void> {
this.znp.request(Subsystem.UTIL, 'ledControl', {ledid: 3, mode: enabled ? 1 : 0}, null, 500).catch(() => {
// We cannot 100% correctly determine if an adapter supports LED. E.g. the zStack 1.2 20190608 fw supports
// led on the CC2531 but not on the CC2530. Therefore if a led request fails never thrown an error
// but instead mark the led as unsupported.
// https://github.com/Koenkk/zigbee-herdsman/issues/377
// https://github.com/Koenkk/zigbee2mqtt/issues/7693
this.supportsLED_ = false;
});
if (!this.supportsLED || (this.adapterOptions.disableLED && action !== 'disable')) {
return;
}

// Firmwares build on and after 20211029 should handle LED themselves
const firmwareControlsLed = parseInt(this.version.revision) >= 20211029;
const lookup = {
'disable': firmwareControlsLed ? {ledid: 0xFF, mode: 5} : {ledid: 3, mode: 0},
'on': firmwareControlsLed ? null : {ledid: 3, mode: 1},
'off': firmwareControlsLed ? null : {ledid: 3, mode: 0},
};

const payload = lookup[action];
if (payload) {
this.znp.request(Subsystem.UTIL, 'ledControl', payload, null, 500).catch(() => {
// We cannot 100% correctly determine if an adapter supports LED. E.g. the zStack 1.2 20190608
// fw supports led on the CC2531 but not on the CC2530. Therefore if a led request fails never thrown
// an error but instead mark the led as unsupported.
// https://github.com/Koenkk/zigbee-herdsman/issues/377
// https://github.com/Koenkk/zigbee2mqtt/issues/7693
this.supportsLED = false;
});
}
}

private async requestNetworkAddress(ieeeAddr: string): Promise<number> {
Expand Down
8 changes: 0 additions & 8 deletions src/adapter/zigate/adapter/zigateAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,6 @@ class ZiGateAdapter extends Adapter {
return Promise.resolve();
};

public async supportsLED(): Promise<boolean> {
return false;
};

public setLED(enabled: boolean): Promise<void> {
return Promise.reject();
};

public async getNetworkParameters(): Promise<TsType.NetworkParameters> {
debug.log('getNetworkParameters');
return this.driver.sendCommand(ZiGateCommandCode.GetNetworkState, {}, 10000)
Expand Down
21 changes: 0 additions & 21 deletions src/controller/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,6 @@ class Controller extends events.EventEmitter {
if (permit) {
await this.adapter.permitJoin(254, !device ? null : device.networkAddress);
await this.greenPower.permitJoin(254);
if ((await this.adapter.supportsLED()) && !this.options.adapter.disableLED) {
await this.adapter.setLED(true);
}

// Zigbee 3 networks automatically close after max 255 seconds, keep network open.
this.permitJoinNetworkClosedTimer = setInterval(async (): Promise<void> => {
Expand All @@ -245,9 +242,6 @@ class Controller extends events.EventEmitter {
this.emit(Events.Events.permitJoinChanged, data);
} else {
debug.log('Disable joining');
if ((await this.adapter.supportsLED()) && !this.options.adapter.disableLED) {
await this.adapter.setLED(false);
}
await this.greenPower.permitJoin(0);
await this.adapter.permitJoin(0, null);
const data: Events.PermitJoinChangedPayload = {permitted: false, reason, timeout: this.permitJoinTimeout};
Expand Down Expand Up @@ -377,28 +371,13 @@ class Controller extends events.EventEmitter {
return Group.create(groupID);
}

/**
* Check if the adapters supports LED
*/
public async supportsLED(): Promise<boolean> {
return this.adapter.supportsLED();
}

/**
* Set transmit power of the adapter
*/
public async setTransmitPower(value: number): Promise<void> {
return this.adapter.setTransmitPower(value);
}

/**
* Enable/Disable the LED
*/
public async setLED(enabled: boolean): Promise<void> {
if (!(await this.supportsLED())) throw new Error(`Adapter doesn't support LED`);
await this.adapter.setLED(enabled);
}

private onNetworkAddress(payload: AdapterEvents.NetworkAddressPayload): void {
debug.log(`Network address '${payload.ieeeAddr}'`);
const device = Device.byIeeeAddr(payload.ieeeAddr);
Expand Down
97 changes: 66 additions & 31 deletions test/adapter/z-stack/adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,6 @@ class ZnpRequestMockBuilder {
throw new Error(msg);
}
const response = responder.exec(message.payload, this);
// console.log(message, response);
return response;
}

Expand Down Expand Up @@ -1716,10 +1715,63 @@ describe("zstack-adapter", () => {
expect(result).toBe("reset");
});

it("LED behaviour: disable LED true, firmware not handling leds", async () => {
basicMocks();
adapter = new ZStackAdapter(networkOptions, serialPortOptions, "backup.json", {disableLED: true});
await adapter.start();
expect(mockZnpRequest).toBeCalledWith(Subsystem.UTIL, 'ledControl', {ledid: 3, mode: 0}, null, 500);
mockZnpRequest.mockClear();
await adapter.permitJoin(255, 0);
expect(mockZnpRequest).not.toBeCalledWith(Subsystem.UTIL, 'ledControl', expect.any(Object), null, 500);
mockZnpRequest.mockClear();
await adapter.permitJoin(0, 0);
expect(mockZnpRequest).not.toBeCalledWith(Subsystem.UTIL, 'ledControl', expect.any(Object), null, 500);
});

it("LED behaviour: disable LED false, firmware not handling leds", async () => {
basicMocks();
adapter = new ZStackAdapter(networkOptions, serialPortOptions, "backup.json", {disableLED: false});
await adapter.start();
expect(mockZnpRequest).not.toBeCalledWith(Subsystem.UTIL, 'ledControl', expect.any(Object), null, 500);
mockZnpRequest.mockClear();
await adapter.permitJoin(255, 0);
expect(mockZnpRequest).toBeCalledWith(Subsystem.UTIL, 'ledControl', {ledid: 3, mode: 1}, null, 500);
mockZnpRequest.mockClear();
await adapter.permitJoin(0, 0);
expect(mockZnpRequest).toBeCalledWith(Subsystem.UTIL, 'ledControl', {ledid: 3, mode: 0}, null, 500);
});

it("LED behaviour: disable LED true, firmware handling leds", async () => {
mockZnpRequestWith(
baseZnpRequestMock.clone()
.handle(Subsystem.SYS, "version", payload => { return { payload: { product: ZnpVersion.zStack30x, revision: 20211030 } }})
);
adapter = new ZStackAdapter(networkOptions, serialPortOptions, "backup.json", {disableLED: true});
await adapter.start();
expect(mockZnpRequest).toBeCalledWith(Subsystem.UTIL, 'ledControl', {ledid: 0xFF, mode: 5}, null, 500);
mockZnpRequest.mockClear();
await adapter.permitJoin(255, 0);
expect(mockZnpRequest).not.toBeCalledWith(Subsystem.UTIL, 'ledControl', expect.any(Object), null, 500);
mockZnpRequest.mockClear();
await adapter.permitJoin(0, 0);
expect(mockZnpRequest).not.toBeCalledWith(Subsystem.UTIL, 'ledControl', expect.any(Object), null, 500);
});


it("LED behaviour: disable LED false, firmware handling leds", async () => {
mockZnpRequestWith(
baseZnpRequestMock.clone()
.handle(Subsystem.SYS, "version", payload => { return { payload: { product: ZnpVersion.zStack30x, revision: 20211030 } }})
);
adapter = new ZStackAdapter(networkOptions, serialPortOptions, "backup.json", {disableLED: false});
await adapter.start();
expect(mockZnpRequest).not.toBeCalledWith(Subsystem.UTIL, 'ledControl', expect.any(Object), null, 500);
mockZnpRequest.mockClear();
await adapter.permitJoin(255, 0);
expect(mockZnpRequest).not.toBeCalledWith(Subsystem.UTIL, 'ledControl', expect.any(Object), null, 500);
mockZnpRequest.mockClear();
await adapter.permitJoin(0, 0);
expect(mockZnpRequest).not.toBeCalledWith(Subsystem.UTIL, 'ledControl', expect.any(Object), null, 500);
});

/* Original Tests */
it('Is valid path', async () => {
Expand Down Expand Up @@ -1917,17 +1969,19 @@ describe("zstack-adapter", () => {
await adapter.start();
mockZnpRequest.mockClear();
await adapter.permitJoin(100, null);
expect(mockZnpRequest).toBeCalledTimes(1);
expect(mockZnpRequest).toBeCalledTimes(2);
expect(mockZnpRequest).toBeCalledWith(Subsystem.ZDO, 'mgmtPermitJoinReq', {addrmode: 0x0F, dstaddr: 0xFFFC , duration: 100, tcsignificance: 0 });
expect(mockZnpRequest).toBeCalledWith(Subsystem.UTIL, 'ledControl', {ledid: 3, mode: 1}, null, 500);
});

it('Permit join specific networkAddress', async () => {
basicMocks();
await adapter.start();
mockZnpRequest.mockClear();
await adapter.permitJoin(102, 42102);
expect(mockZnpRequest).toBeCalledTimes(1);
expect(mockZnpRequest).toBeCalledTimes(2);
expect(mockZnpRequest).toBeCalledWith(Subsystem.ZDO, 'mgmtPermitJoinReq', {addrmode: 2, dstaddr: 42102 , duration: 102, tcsignificance: 0 });
expect(mockZnpRequest).toBeCalledWith(Subsystem.UTIL, 'ledControl', {ledid: 3, mode: 1}, null, 500);
});

it('Get coordinator version', async () => {
Expand Down Expand Up @@ -1964,38 +2018,19 @@ describe("zstack-adapter", () => {
expect(mockZnpRequest).toBeCalledWith(Subsystem.SYS, 'stackTune', {operation: 0, value: 15});
});

it('Disable led', async () => {
it('Support LED should go to false when LED request fails', async () => {
basicMocks();
await adapter.start();
mockZnpRequest.mockClear();
await adapter.setLED(false);
expect(mockZnpRequest).toBeCalledTimes(1);
mockZnpRequest.mockImplementation((_, cmd) => new Promise((resolve, reject) => {
if (cmd == 'ledControl') reject('FAILED');
else resolve(null);
}));
await adapter.permitJoin(0, 0);
expect(mockZnpRequest).toBeCalledWith(Subsystem.UTIL, 'ledControl', {ledid: 3, mode: 0}, null, 500);
});

it('Enable led', async () => {
basicMocks();
await adapter.start();
mockZnpRequest.mockClear();
await adapter.setLED(true);
expect(mockZnpRequest).toBeCalledTimes(1);
expect(mockZnpRequest).toBeCalledWith(Subsystem.UTIL, 'ledControl', {ledid: 3, mode: 1}, null, 500);
});

it('Supports led', async () => {
basicMocks();
await adapter.start();
expect(await adapter.supportsLED()).toBeTruthy();
});

it('Support LED should go to false when LED request fails', async () => {
basicMocks();
await adapter.start();
expect(await adapter.supportsLED()).toBeTruthy();
mockZnpRequest.mockClear();
mockZnpRequest.mockImplementationOnce(() => new Promise((resolve, reject) => reject("FAILED")));
await adapter.setLED(true);
expect(await adapter.supportsLED()).toBeFalsy();
await adapter.permitJoin(0, 0);
expect(mockZnpRequest).not.toBeCalledWith(Subsystem.UTIL, 'ledControl', {ledid: 3, mode: 0}, null, 500);
});

it('Node descriptor', async () => {
Expand Down