Skip to content

Commit 0d5a8fd

Browse files
committed
Update
1 parent 57aac29 commit 0d5a8fd

File tree

4 files changed

+47
-43
lines changed

4 files changed

+47
-43
lines changed

lib/extension/externalConverters.ts

+21-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {addDefinition, removeDefinition} from 'zigbee-herdsman-converters';
1+
import {addDefinition, removeExternalDefinitions} from 'zigbee-herdsman-converters';
22

33
import logger from '../util/logger';
44
import ExternalJSExtension from './externalJS';
@@ -30,29 +30,33 @@ export default class ExternalConverters extends ExternalJSExtension<ModuleExport
3030
);
3131
}
3232

33+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3334
protected async removeJS(name: string, module: ModuleExports): Promise<void> {
34-
for (const definition of this.getDefinitions(module)) {
35-
// TODO: implement in ZHC
36-
removeDefinition(definition);
37-
}
35+
removeExternalDefinitions(name);
36+
37+
await this.zigbee.resolveDevicesDefinitions(true);
3838
}
3939

4040
protected async loadJS(name: string, module: ModuleExports): Promise<void> {
41-
for (const definition of this.getDefinitions(module)) {
42-
try {
43-
// TODO: `updateDefinition` in ZHC instead? (add if not exist, replace if exist)
44-
removeDefinition(definition);
41+
try {
42+
removeExternalDefinitions(name);
43+
44+
for (const definition of this.getDefinitions(module)) {
45+
definition.externalConverterName = name;
46+
4547
addDefinition(definition);
4648
logger.info(`Loaded external converter '${name}'.`);
47-
} catch (error) {
48-
logger.error(`Failed to load external converter '${name}'`);
49-
logger.error(`Check the code for syntax error and make sure it is up to date with the current Zigbee2MQTT version.`);
50-
logger.error(
51-
`External converters are not meant for long term usage, but for local testing after which a pull request should be created to add out-of-the-box support for the device`,
52-
);
53-
54-
throw error;
5549
}
50+
51+
await this.zigbee.resolveDevicesDefinitions(true);
52+
} catch (error) {
53+
logger.error(`Failed to load external converter '${name}'`);
54+
logger.error(`Check the code for syntax error and make sure it is up to date with the current Zigbee2MQTT version.`);
55+
logger.error(
56+
`External converters are not meant for long term usage, but for local testing after which a pull request should be created to add out-of-the-box support for the device`,
57+
);
58+
59+
throw error;
5660
}
5761
}
5862

lib/model/device.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default class Device {
4848
}
4949
}
5050

51-
async resolveDefinition(ignoreCache = false): Promise<void> {
51+
async resolveDefinition(ignoreCache: boolean = false): Promise<void> {
5252
if (!this.zh.interviewing && (!this.definition || this._definitionModelID !== this.zh.modelID || ignoreCache)) {
5353
this.definition = await zhc.findByDevice(this.zh, true);
5454
this._definitionModelID = this.zh.modelID;

lib/zigbee.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,7 @@ export default class Zigbee {
7272
throw error;
7373
}
7474

75-
for (const device of this.devicesIterator(utils.deviceNotCoordinator)) {
76-
await device.resolveDefinition();
77-
}
75+
await this.resolveDevicesDefinitions();
7876

7977
this.herdsman.on('adapterDisconnected', () => this.eventBus.emitAdapterDisconnected());
8078
this.herdsman.on('lastSeenChanged', (data: ZHEvents.LastSeenChangedPayload) => {
@@ -239,6 +237,12 @@ export default class Zigbee {
239237
await this.herdsman.permitJoin(time, device?.zh);
240238
}
241239

240+
async resolveDevicesDefinitions(ignoreCache: boolean = false): Promise<void> {
241+
for (const device of this.devicesIterator(utils.deviceNotCoordinator)) {
242+
await device.resolveDefinition(ignoreCache);
243+
}
244+
}
245+
242246
@bind private resolveDevice(ieeeAddr: string): Device | undefined {
243247
if (!this.deviceLookup[ieeeAddr]) {
244248
const device = this.herdsman.getDeviceByIeeeAddr(ieeeAddr);

test/extensions/externalConverters.test.ts

+18-22
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ import * as settings from '../../lib/util/settings';
1818

1919
const BASE_DIR = 'external_converters';
2020

21-
// @ts-expect-error TODO: remove, tmp until implemented
22-
zhc.removeDefinition = jest.fn();
23-
2421
describe('Extension: ExternalConverters', () => {
2522
const mockBasePath = path.join(data.mockDir, BASE_DIR);
2623
let controller: Controller;
@@ -32,7 +29,7 @@ describe('Extension: ExternalConverters', () => {
3229
const writeFileSyncSpy = jest.spyOn(fs, 'writeFileSync');
3330

3431
const zhcAddDefinitionSpy = jest.spyOn(zhc, 'addDefinition');
35-
const zhcRemoveDefinitionSpy = jest.spyOn(zhc, 'removeDefinition');
32+
const zhcRemoveExternalDefinitionsSpy = jest.spyOn(zhc, 'removeExternalDefinitions');
3633

3734
const mocksClear = [
3835
mockMQTT.end,
@@ -47,7 +44,7 @@ describe('Extension: ExternalConverters', () => {
4744
rmSyncSpy,
4845
writeFileSyncSpy,
4946
zhcAddDefinitionSpy,
50-
zhcRemoveDefinitionSpy,
47+
zhcRemoveExternalDefinitionsSpy,
5148
];
5249

5350
const useAssets = (): void => {
@@ -72,6 +69,7 @@ describe('Extension: ExternalConverters', () => {
7269
});
7370

7471
beforeEach(async () => {
72+
zhc.removeExternalDefinitions(); // remove all external converters
7573
mocksClear.forEach((m) => m.mockClear());
7674
data.writeDefaultConfiguration();
7775
data.writeDefaultState();
@@ -81,8 +79,10 @@ describe('Extension: ExternalConverters', () => {
8179
controller = new Controller(jest.fn(), jest.fn());
8280
});
8381

84-
afterEach(() => {
82+
afterEach(async () => {
8583
fs.rmSync(mockBasePath, {recursive: true, force: true});
84+
85+
await controller?.stop();
8686
});
8787

8888
it('loads nothing from folder', async () => {
@@ -100,7 +100,7 @@ describe('Extension: ExternalConverters', () => {
100100
await controller.start();
101101
await flushPromises();
102102

103-
expect(getZ2MDevice(devices.external_converter_device)!.definition).toMatchObject({
103+
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
104104
description: 'external',
105105
model: 'external_converter_device',
106106
vendor: 'external',
@@ -115,7 +115,9 @@ describe('Extension: ExternalConverters', () => {
115115
{retain: true, qos: 0},
116116
expect.any(Function),
117117
);
118-
expect(zhcRemoveDefinitionSpy).toHaveBeenCalledTimes(3);
118+
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenCalledTimes(2);
119+
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(1, 'mock-external-converter-multiple.js');
120+
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(2, 'mock-external-converter.js');
119121
expect(zhcAddDefinitionSpy).toHaveBeenNthCalledWith(
120122
1,
121123
expect.objectContaining({
@@ -157,7 +159,7 @@ describe('Extension: ExternalConverters', () => {
157159
await flushPromises();
158160
mocksClear.forEach((m) => m.mockClear());
159161

160-
expect(getZ2MDevice(devices.external_converter_device)!.definition).toMatchObject({
162+
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
161163
description: 'Automatically generated definition',
162164
model: 'external_converter_device',
163165
vendor: '',
@@ -168,15 +170,16 @@ describe('Extension: ExternalConverters', () => {
168170
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/save', stringify({name: converterName, code: converterCode}));
169171
await flushPromises();
170172

171-
expect(getZ2MDevice(devices.external_converter_device)!.definition).toMatchObject({
173+
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
172174
description: 'external',
173175
model: 'external_converter_device',
174176
vendor: 'external',
175177
zigbeeModel: ['external_converter_device'],
176178
});
177179
expect(mkdirSyncSpy).toHaveBeenCalledWith(mockBasePath, {recursive: true});
178180
expect(writeFileSyncSpy).toHaveBeenCalledWith(converterFilePath, converterCode, 'utf8');
179-
expect(zhcRemoveDefinitionSpy).toHaveBeenCalledTimes(1);
181+
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenCalledTimes(1);
182+
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(1, converterName);
180183
expect(zhcAddDefinitionSpy).toHaveBeenCalledWith(
181184
expect.objectContaining({
182185
mock: true,
@@ -197,22 +200,15 @@ describe('Extension: ExternalConverters', () => {
197200
mockMQTTEvents.message('zigbee2mqtt/bridge/request/converter/remove', stringify({name: converterName}));
198201
await flushPromises();
199202

200-
expect(getZ2MDevice(devices.external_converter_device)!.definition).toMatchObject({
203+
expect(getZ2MDevice(devices.external_converter_device).definition).toMatchObject({
201204
description: 'Automatically generated definition',
202205
model: 'external_converter_device',
203206
vendor: '',
204207
zigbeeModel: ['external_converter_device'],
205208
});
206209
expect(rmSyncSpy).toHaveBeenCalledWith(converterFilePath, {force: true});
207-
expect(zhcRemoveDefinitionSpy).toHaveBeenCalledWith(
208-
expect.objectContaining({
209-
mock: true,
210-
zigbeeModel: ['external_converter_device'],
211-
vendor: 'external',
212-
model: 'external_converter_device',
213-
description: 'external',
214-
}),
215-
);
210+
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenCalledTimes(2);
211+
expect(zhcRemoveExternalDefinitionsSpy).toHaveBeenNthCalledWith(2, converterName);
216212
expect(mockMQTT.publish).toHaveBeenCalledWith('zigbee2mqtt/bridge/converters', stringify([]), {retain: true, qos: 0}, expect.any(Function));
217213
});
218214

@@ -299,7 +295,7 @@ describe('Extension: ExternalConverters', () => {
299295

300296
const errorMsg = `Failed to remove definition`;
301297

302-
zhcRemoveDefinitionSpy.mockImplementationOnce(() => {
298+
zhcRemoveExternalDefinitionsSpy.mockImplementationOnce(() => {
303299
throw new Error(errorMsg);
304300
});
305301

0 commit comments

Comments
 (0)