@iobroker/dm-utils 1.0.16 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +226 -105
- package/build/ActionContext.d.ts +2 -6
- package/build/DeviceManagement.d.ts +27 -28
- package/build/DeviceManagement.js +229 -106
- package/build/ProgressDialog.d.ts +2 -6
- package/build/types/adapter.d.ts +4 -3
- package/build/types/api.d.ts +58 -0
- package/build/types/base.d.ts +74 -30
- package/build/types/common.d.ts +117 -102
- package/build/types/index.d.ts +1 -1
- package/build/types/index.js +1 -1
- package/package.json +10 -6
|
@@ -1,37 +1,40 @@
|
|
|
1
1
|
import type { AdapterInstance } from '@iobroker/adapter-core';
|
|
2
2
|
import type { ActionContext } from './ActionContext';
|
|
3
3
|
import type { ProgressDialog } from './ProgressDialog';
|
|
4
|
-
import { type DeviceDetails, type
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { type ActionButton, type DeviceDetails, type DeviceId, type DeviceInfo, type DeviceStatus, type ErrorResponse, type InstanceDetails, type JsonFormData, type JsonFormSchema, type RetVal } from './types';
|
|
5
|
+
import type { BackendToGuiCommand, DeviceRefreshResponse, InstanceRefreshResponse } from './types/base';
|
|
6
|
+
import type { ProgressOptions } from './types/common';
|
|
7
|
+
export type DeviceLoadContext<TId extends DeviceId> = {
|
|
8
|
+
addDevice(device: DeviceInfo<TId>): void;
|
|
9
|
+
setTotalDevices(count: number): void;
|
|
10
|
+
};
|
|
11
|
+
export declare abstract class DeviceManagement<TAdapter extends AdapterInstance = AdapterInstance, TId extends DeviceId = string> {
|
|
12
|
+
protected readonly adapter: TAdapter;
|
|
8
13
|
private instanceInfo?;
|
|
9
14
|
private devices?;
|
|
10
|
-
private readonly
|
|
11
|
-
|
|
15
|
+
private readonly communicationStateId;
|
|
16
|
+
private readonly deviceLoadContexts;
|
|
17
|
+
private readonly messageContexts;
|
|
18
|
+
constructor(adapter: TAdapter, communicationStateId?: string | boolean);
|
|
19
|
+
private ensureCommunicationState;
|
|
20
|
+
protected sendCommandToGui(command: BackendToGuiCommand<TId>): Promise<void>;
|
|
12
21
|
protected get log(): ioBroker.Log;
|
|
13
22
|
protected getInstanceInfo(): RetVal<InstanceDetails>;
|
|
14
|
-
protected abstract
|
|
15
|
-
protected
|
|
23
|
+
protected abstract loadDevices(context: DeviceLoadContext<TId>): RetVal<void>;
|
|
24
|
+
protected getDeviceInfo(_deviceId: TId): RetVal<DeviceInfo<TId>>;
|
|
25
|
+
protected getDeviceStatus(_deviceId: TId): RetVal<DeviceStatus | DeviceStatus[]>;
|
|
26
|
+
protected getDeviceDetails(id: TId): RetVal<DeviceDetails<TId> | null | {
|
|
16
27
|
error: string;
|
|
17
28
|
}>;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
protected handleDeviceAction(deviceId: string, actionId: string, context?: ActionContext, options?: {
|
|
23
|
-
value?: number | string | boolean;
|
|
24
|
-
[key: string]: any;
|
|
25
|
-
}): RetVal<ErrorResponse> | RetVal<RefreshResponse>;
|
|
26
|
-
protected handleDeviceControl(deviceId: string, controlId: string, newState: ControlState, context?: MessageContext): RetVal<ErrorResponse | ioBroker.State>;
|
|
27
|
-
protected handleDeviceControlState(deviceId: string, controlId: string, context?: MessageContext): RetVal<ErrorResponse | ioBroker.State>;
|
|
29
|
+
private handleInstanceAction;
|
|
30
|
+
private handleDeviceAction;
|
|
31
|
+
private handleDeviceControl;
|
|
32
|
+
private handleDeviceControlState;
|
|
28
33
|
private onMessage;
|
|
29
34
|
private handleMessage;
|
|
30
|
-
private convertActions;
|
|
31
|
-
private convertControls;
|
|
32
35
|
private sendReply;
|
|
33
36
|
}
|
|
34
|
-
export declare class MessageContext implements ActionContext {
|
|
37
|
+
export declare class MessageContext<TId extends DeviceId> implements ActionContext {
|
|
35
38
|
private readonly adapter;
|
|
36
39
|
private hasOpenProgressDialog;
|
|
37
40
|
private lastMessage?;
|
|
@@ -44,13 +47,9 @@ export declare class MessageContext implements ActionContext {
|
|
|
44
47
|
title?: ioBroker.StringOrTranslated;
|
|
45
48
|
buttons?: (ActionButton | 'apply' | 'cancel' | 'close')[];
|
|
46
49
|
}): Promise<JsonFormData | undefined>;
|
|
47
|
-
openProgress(title:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
label?: string;
|
|
51
|
-
}): Promise<ProgressDialog>;
|
|
52
|
-
sendFinalResult(result: ErrorResponse | RefreshResponse): void;
|
|
53
|
-
sendControlResult(deviceId: string, controlId: string, result: ErrorResponse | ioBroker.State): void;
|
|
50
|
+
openProgress(title: ioBroker.StringOrTranslated, options?: ProgressOptions): Promise<ProgressDialog>;
|
|
51
|
+
sendFinalResult(result: ErrorResponse | DeviceRefreshResponse<'api', TId> | InstanceRefreshResponse): void;
|
|
52
|
+
sendControlResult(deviceId: TId, controlId: string, result: ErrorResponse | ioBroker.State): void;
|
|
54
53
|
handleProgress(message: ioBroker.Message): void;
|
|
55
54
|
private checkPreconditions;
|
|
56
55
|
private send;
|
|
@@ -3,16 +3,62 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MessageContext = exports.DeviceManagement = void 0;
|
|
4
4
|
const types_1 = require("./types");
|
|
5
5
|
class DeviceManagement {
|
|
6
|
-
constructor(adapter) {
|
|
6
|
+
constructor(adapter, communicationStateId) {
|
|
7
7
|
this.adapter = adapter;
|
|
8
|
-
this.
|
|
8
|
+
this.deviceLoadContexts = new Map();
|
|
9
|
+
this.messageContexts = new Map();
|
|
9
10
|
adapter.on('message', this.onMessage.bind(this));
|
|
11
|
+
if (communicationStateId === true) {
|
|
12
|
+
// use standard ID `info.deviceManager`
|
|
13
|
+
this.communicationStateId = 'info.deviceManager';
|
|
14
|
+
}
|
|
15
|
+
else if (communicationStateId) {
|
|
16
|
+
this.communicationStateId = communicationStateId;
|
|
17
|
+
}
|
|
18
|
+
if (this.communicationStateId) {
|
|
19
|
+
this.ensureCommunicationState().catch(e => this.log().error(`Cannot initialize communication state: ${e}`));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async ensureCommunicationState() {
|
|
23
|
+
let stateObj = await this.adapter.getObjectAsync(this.communicationStateId);
|
|
24
|
+
if (!stateObj) {
|
|
25
|
+
stateObj = {
|
|
26
|
+
_id: this.communicationStateId,
|
|
27
|
+
type: 'state',
|
|
28
|
+
common: {
|
|
29
|
+
expert: true,
|
|
30
|
+
name: 'Communication with GUI for device manager',
|
|
31
|
+
type: 'string',
|
|
32
|
+
role: 'state',
|
|
33
|
+
def: '',
|
|
34
|
+
read: true,
|
|
35
|
+
write: false,
|
|
36
|
+
},
|
|
37
|
+
native: {},
|
|
38
|
+
};
|
|
39
|
+
await this.adapter.setObjectAsync(this.communicationStateId, stateObj);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async sendCommandToGui(command) {
|
|
43
|
+
if (this.communicationStateId) {
|
|
44
|
+
await this.adapter.setState(this.communicationStateId, JSON.stringify(command), true);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
throw new Error('Communication state not found');
|
|
48
|
+
}
|
|
10
49
|
}
|
|
11
50
|
get log() {
|
|
12
51
|
return this.adapter.log;
|
|
13
52
|
}
|
|
14
53
|
getInstanceInfo() {
|
|
15
|
-
|
|
54
|
+
// Overload this method if your adapter does not use BackendToGui communication and States/Objects in DeviceInfo
|
|
55
|
+
return { apiVersion: 'v3', communicationStateId: this.communicationStateId || undefined };
|
|
56
|
+
}
|
|
57
|
+
getDeviceInfo(_deviceId) {
|
|
58
|
+
throw new Error('Do not send "infoUpdate" or "delete" command without implementing getDeviceInfo method!');
|
|
59
|
+
}
|
|
60
|
+
getDeviceStatus(_deviceId) {
|
|
61
|
+
throw new Error('Do not send "statusUpdate" command without implementing getDeviceStatus method!');
|
|
16
62
|
}
|
|
17
63
|
getDeviceDetails(id) {
|
|
18
64
|
return { id, schema: {} };
|
|
@@ -38,7 +84,7 @@ class DeviceManagement {
|
|
|
38
84
|
},
|
|
39
85
|
};
|
|
40
86
|
}
|
|
41
|
-
if (!action.handler) {
|
|
87
|
+
if (!('handler' in action) || !action.handler) {
|
|
42
88
|
this.log.warn(`Instance action ${actionId} is disabled because it has no handler`);
|
|
43
89
|
return {
|
|
44
90
|
error: {
|
|
@@ -52,40 +98,41 @@ class DeviceManagement {
|
|
|
52
98
|
handleDeviceAction(deviceId, actionId, context, options) {
|
|
53
99
|
var _a;
|
|
54
100
|
if (!this.devices) {
|
|
55
|
-
this.log.warn(`Device action ${actionId} was called before
|
|
101
|
+
this.log.warn(`Device action ${actionId} was called before loadDevices()`);
|
|
56
102
|
return {
|
|
57
103
|
error: {
|
|
58
104
|
code: types_1.ErrorCodes.E_DEVICE_ACTION_NOT_INITIALIZED,
|
|
59
|
-
message: `Device action ${actionId} was called before
|
|
105
|
+
message: `Device action ${actionId} was called before loadDevices()`,
|
|
60
106
|
},
|
|
61
107
|
};
|
|
62
108
|
}
|
|
63
|
-
const
|
|
109
|
+
const jsonId = JSON.stringify(deviceId);
|
|
110
|
+
const device = this.devices.get(jsonId);
|
|
64
111
|
if (!device) {
|
|
65
|
-
this.log.warn(`Device action ${actionId} was called on unknown device: ${
|
|
112
|
+
this.log.warn(`Device action ${actionId} was called on unknown device: ${jsonId}`);
|
|
66
113
|
return {
|
|
67
114
|
error: {
|
|
68
115
|
code: types_1.ErrorCodes.E_DEVICE_ACTION_DEVICE_UNKNOWN,
|
|
69
|
-
message: `Device action ${actionId} was called on unknown device: ${
|
|
116
|
+
message: `Device action ${actionId} was called on unknown device: ${jsonId}`,
|
|
70
117
|
},
|
|
71
118
|
};
|
|
72
119
|
}
|
|
73
120
|
const action = (_a = device.actions) === null || _a === void 0 ? void 0 : _a.find(a => a.id === actionId);
|
|
74
121
|
if (!action) {
|
|
75
|
-
this.log.warn(`Device action ${actionId} doesn't exist on device ${
|
|
122
|
+
this.log.warn(`Device action ${actionId} doesn't exist on device ${jsonId}`);
|
|
76
123
|
return {
|
|
77
124
|
error: {
|
|
78
125
|
code: types_1.ErrorCodes.E_DEVICE_ACTION_UNKNOWN,
|
|
79
|
-
message: `Device action ${actionId} doesn't exist on device ${
|
|
126
|
+
message: `Device action ${actionId} doesn't exist on device ${jsonId}`,
|
|
80
127
|
},
|
|
81
128
|
};
|
|
82
129
|
}
|
|
83
|
-
if (!action.handler) {
|
|
84
|
-
this.log.warn(`Device action ${actionId} on ${
|
|
130
|
+
if (!('handler' in action) || !action.handler) {
|
|
131
|
+
this.log.warn(`Device action ${actionId} on ${jsonId} is disabled because it has no handler`);
|
|
85
132
|
return {
|
|
86
133
|
error: {
|
|
87
134
|
code: types_1.ErrorCodes.E_DEVICE_ACTION_NO_HANDLER,
|
|
88
|
-
message: `Device action ${actionId} on ${
|
|
135
|
+
message: `Device action ${actionId} on ${jsonId} is disabled because it has no handler`,
|
|
89
136
|
},
|
|
90
137
|
};
|
|
91
138
|
}
|
|
@@ -94,40 +141,41 @@ class DeviceManagement {
|
|
|
94
141
|
handleDeviceControl(deviceId, controlId, newState, context) {
|
|
95
142
|
var _a;
|
|
96
143
|
if (!this.devices) {
|
|
97
|
-
this.log.warn(`Device control ${controlId} was called before
|
|
144
|
+
this.log.warn(`Device control ${controlId} was called before loadDevices()`);
|
|
98
145
|
return {
|
|
99
146
|
error: {
|
|
100
147
|
code: types_1.ErrorCodes.E_DEVICE_CONTROL_NOT_INITIALIZED,
|
|
101
|
-
message: `Device control ${controlId} was called before
|
|
148
|
+
message: `Device control ${controlId} was called before loadDevices()`,
|
|
102
149
|
},
|
|
103
150
|
};
|
|
104
151
|
}
|
|
105
|
-
const
|
|
152
|
+
const jsonId = JSON.stringify(deviceId);
|
|
153
|
+
const device = this.devices.get(jsonId);
|
|
106
154
|
if (!device) {
|
|
107
|
-
this.log.warn(`Device control ${controlId} was called on unknown device: ${
|
|
155
|
+
this.log.warn(`Device control ${controlId} was called on unknown device: ${jsonId}`);
|
|
108
156
|
return {
|
|
109
157
|
error: {
|
|
110
158
|
code: types_1.ErrorCodes.E_DEVICE_CONTROL_DEVICE_UNKNOWN,
|
|
111
|
-
message: `Device control ${controlId} was called on unknown device: ${
|
|
159
|
+
message: `Device control ${controlId} was called on unknown device: ${jsonId}`,
|
|
112
160
|
},
|
|
113
161
|
};
|
|
114
162
|
}
|
|
115
163
|
const control = (_a = device.controls) === null || _a === void 0 ? void 0 : _a.find(a => a.id === controlId);
|
|
116
164
|
if (!control) {
|
|
117
|
-
this.log.warn(`Device control ${controlId} doesn't exist on device ${
|
|
165
|
+
this.log.warn(`Device control ${controlId} doesn't exist on device ${jsonId}`);
|
|
118
166
|
return {
|
|
119
167
|
error: {
|
|
120
168
|
code: types_1.ErrorCodes.E_DEVICE_CONTROL_UNKNOWN,
|
|
121
|
-
message: `Device control ${controlId} doesn't exist on device ${
|
|
169
|
+
message: `Device control ${controlId} doesn't exist on device ${jsonId}`,
|
|
122
170
|
},
|
|
123
171
|
};
|
|
124
172
|
}
|
|
125
173
|
if (!control.handler) {
|
|
126
|
-
this.log.warn(`Device control ${controlId} on ${
|
|
174
|
+
this.log.warn(`Device control ${controlId} on ${jsonId} is disabled because it has no handler`);
|
|
127
175
|
return {
|
|
128
176
|
error: {
|
|
129
177
|
code: types_1.ErrorCodes.E_DEVICE_CONTROL_NO_HANDLER,
|
|
130
|
-
message: `Device control ${controlId} on ${
|
|
178
|
+
message: `Device control ${controlId} on ${jsonId} is disabled because it has no handler`,
|
|
131
179
|
},
|
|
132
180
|
};
|
|
133
181
|
}
|
|
@@ -137,40 +185,41 @@ class DeviceManagement {
|
|
|
137
185
|
handleDeviceControlState(deviceId, controlId, context) {
|
|
138
186
|
var _a;
|
|
139
187
|
if (!this.devices) {
|
|
140
|
-
this.log.warn(`Device get state ${controlId} was called before
|
|
188
|
+
this.log.warn(`Device get state ${controlId} was called before loadDevices()`);
|
|
141
189
|
return {
|
|
142
190
|
error: {
|
|
143
191
|
code: types_1.ErrorCodes.E_DEVICE_GET_STATE_NOT_INITIALIZED,
|
|
144
|
-
message: `Device control ${controlId} was called before
|
|
192
|
+
message: `Device control ${controlId} was called before loadDevices()`,
|
|
145
193
|
},
|
|
146
194
|
};
|
|
147
195
|
}
|
|
148
|
-
const
|
|
196
|
+
const jsonId = JSON.stringify(deviceId);
|
|
197
|
+
const device = this.devices.get(jsonId);
|
|
149
198
|
if (!device) {
|
|
150
|
-
this.log.warn(`Device get state ${controlId} was called on unknown device: ${
|
|
199
|
+
this.log.warn(`Device get state ${controlId} was called on unknown device: ${jsonId}`);
|
|
151
200
|
return {
|
|
152
201
|
error: {
|
|
153
202
|
code: types_1.ErrorCodes.E_DEVICE_GET_STATE_DEVICE_UNKNOWN,
|
|
154
|
-
message: `Device control ${controlId} was called on unknown device: ${
|
|
203
|
+
message: `Device control ${controlId} was called on unknown device: ${jsonId}`,
|
|
155
204
|
},
|
|
156
205
|
};
|
|
157
206
|
}
|
|
158
207
|
const control = (_a = device.controls) === null || _a === void 0 ? void 0 : _a.find(a => a.id === controlId);
|
|
159
208
|
if (!control) {
|
|
160
|
-
this.log.warn(`Device get state ${controlId} doesn't exist on device ${
|
|
209
|
+
this.log.warn(`Device get state ${controlId} doesn't exist on device ${jsonId}`);
|
|
161
210
|
return {
|
|
162
211
|
error: {
|
|
163
212
|
code: types_1.ErrorCodes.E_DEVICE_GET_STATE_UNKNOWN,
|
|
164
|
-
message: `Device control ${controlId} doesn't exist on device ${
|
|
213
|
+
message: `Device control ${controlId} doesn't exist on device ${jsonId}`,
|
|
165
214
|
},
|
|
166
215
|
};
|
|
167
216
|
}
|
|
168
217
|
if (!control.getStateHandler) {
|
|
169
|
-
this.log.warn(`Device get state ${controlId} on ${
|
|
218
|
+
this.log.warn(`Device get state ${controlId} on ${jsonId} is disabled because it has no handler`);
|
|
170
219
|
return {
|
|
171
220
|
error: {
|
|
172
221
|
code: types_1.ErrorCodes.E_DEVICE_GET_STATE_NO_HANDLER,
|
|
173
|
-
message: `Device get state ${controlId} on ${
|
|
222
|
+
message: `Device get state ${controlId} on ${jsonId} is disabled because it has no handler`,
|
|
174
223
|
},
|
|
175
224
|
};
|
|
176
225
|
}
|
|
@@ -183,73 +232,110 @@ class DeviceManagement {
|
|
|
183
232
|
void this.handleMessage(obj).catch(this.log.error);
|
|
184
233
|
}
|
|
185
234
|
async handleMessage(msg) {
|
|
235
|
+
var _a;
|
|
186
236
|
this.log.debug(`DeviceManagement received: ${JSON.stringify(msg)}`);
|
|
187
237
|
switch (msg.command) {
|
|
188
238
|
case 'dm:instanceInfo': {
|
|
189
239
|
this.instanceInfo = await this.getInstanceInfo();
|
|
190
|
-
this.sendReply(Object.assign(Object.assign({}, this.instanceInfo), { actions:
|
|
240
|
+
this.sendReply(Object.assign(Object.assign({}, this.instanceInfo), { actions: convertActions(this.instanceInfo.actions) }), msg);
|
|
191
241
|
return;
|
|
192
242
|
}
|
|
193
|
-
case 'dm:
|
|
194
|
-
const
|
|
195
|
-
this.
|
|
196
|
-
|
|
197
|
-
|
|
243
|
+
case 'dm:loadDevices': {
|
|
244
|
+
const context = new DeviceLoadContextImpl(msg, this.adapter);
|
|
245
|
+
this.deviceLoadContexts.set(msg._id, context);
|
|
246
|
+
await this.loadDevices(context);
|
|
247
|
+
if (context.complete()) {
|
|
248
|
+
this.deviceLoadContexts.delete(msg._id);
|
|
249
|
+
}
|
|
250
|
+
this.devices = context.devices.reduce((map, value) => {
|
|
251
|
+
const jsonId = JSON.stringify(value.id);
|
|
252
|
+
if (map.has(jsonId)) {
|
|
253
|
+
throw new Error(`Device ID ${jsonId} is not unique`);
|
|
198
254
|
}
|
|
199
|
-
map.set(
|
|
255
|
+
map.set(jsonId, value);
|
|
200
256
|
return map;
|
|
201
257
|
}, new Map());
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
case 'dm:deviceInfo': {
|
|
261
|
+
const deviceInfo = await this.getDeviceInfo(msg.message);
|
|
262
|
+
this.sendReply(Object.assign(Object.assign({}, deviceInfo), { actions: convertActions(deviceInfo.actions), controls: convertControls(deviceInfo.controls) }), msg);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
case 'dm:deviceStatus': {
|
|
266
|
+
const deviceStatus = await this.getDeviceStatus(msg.message);
|
|
267
|
+
this.sendReply(deviceStatus, msg);
|
|
205
268
|
return;
|
|
206
269
|
}
|
|
207
270
|
case 'dm:deviceDetails': {
|
|
208
271
|
const details = await this.getDeviceDetails(msg.message);
|
|
209
|
-
this.
|
|
272
|
+
this.sendReply(details, msg);
|
|
210
273
|
return;
|
|
211
274
|
}
|
|
212
275
|
case 'dm:instanceAction': {
|
|
213
276
|
const action = msg.message;
|
|
214
277
|
const context = new MessageContext(msg, this.adapter);
|
|
215
|
-
this.
|
|
278
|
+
this.messageContexts.set(msg._id, context);
|
|
216
279
|
const result = await this.handleInstanceAction(action.actionId, context, { value: action.value });
|
|
217
|
-
this.
|
|
280
|
+
this.messageContexts.delete(msg._id);
|
|
218
281
|
context.sendFinalResult(result);
|
|
219
282
|
return;
|
|
220
283
|
}
|
|
221
284
|
case 'dm:deviceAction': {
|
|
222
285
|
const action = msg.message;
|
|
223
286
|
const context = new MessageContext(msg, this.adapter);
|
|
224
|
-
this.
|
|
287
|
+
this.messageContexts.set(msg._id, context);
|
|
225
288
|
const result = await this.handleDeviceAction(action.deviceId, action.actionId, context, {
|
|
226
289
|
value: action.value,
|
|
227
290
|
});
|
|
228
|
-
this.
|
|
229
|
-
|
|
291
|
+
this.messageContexts.delete(msg._id);
|
|
292
|
+
if ('update' in result) {
|
|
293
|
+
// special handling for update responses (we need to update our cache and convert actions/controls before sending to GUI)
|
|
294
|
+
const update = result.update;
|
|
295
|
+
(_a = this.devices) === null || _a === void 0 ? void 0 : _a.set(JSON.stringify(update.id), update);
|
|
296
|
+
context.sendFinalResult({
|
|
297
|
+
update: Object.assign(Object.assign({}, update), { actions: convertActions(update.actions), controls: convertControls(update.controls) }),
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
context.sendFinalResult(result);
|
|
302
|
+
}
|
|
230
303
|
return;
|
|
231
304
|
}
|
|
232
305
|
case 'dm:deviceControl': {
|
|
233
306
|
const control = msg.message;
|
|
234
307
|
const context = new MessageContext(msg, this.adapter);
|
|
235
|
-
this.
|
|
308
|
+
this.messageContexts.set(msg._id, context);
|
|
236
309
|
const result = await this.handleDeviceControl(control.deviceId, control.controlId, control.state, context);
|
|
237
|
-
this.
|
|
310
|
+
this.messageContexts.delete(msg._id);
|
|
238
311
|
context.sendControlResult(control.deviceId, control.controlId, result);
|
|
239
312
|
return;
|
|
240
313
|
}
|
|
241
314
|
case 'dm:deviceControlState': {
|
|
242
315
|
const control = msg.message;
|
|
243
316
|
const context = new MessageContext(msg, this.adapter);
|
|
244
|
-
this.
|
|
317
|
+
this.messageContexts.set(msg._id, context);
|
|
245
318
|
const result = await this.handleDeviceControlState(control.deviceId, control.controlId, context);
|
|
246
|
-
this.
|
|
319
|
+
this.messageContexts.delete(msg._id);
|
|
247
320
|
context.sendControlResult(control.deviceId, control.controlId, result);
|
|
248
321
|
return;
|
|
249
322
|
}
|
|
323
|
+
case 'dm:deviceLoadProgress': {
|
|
324
|
+
const { origin } = msg.message;
|
|
325
|
+
const context = this.deviceLoadContexts.get(origin);
|
|
326
|
+
if (!context) {
|
|
327
|
+
this.log.warn(`Unknown message origin: ${origin}`);
|
|
328
|
+
this.sendReply({ error: 'Unknown load progress origin' }, msg);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (context.handleProgress(msg)) {
|
|
332
|
+
this.deviceLoadContexts.delete(origin);
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
250
336
|
case 'dm:actionProgress': {
|
|
251
337
|
const { origin } = msg.message;
|
|
252
|
-
const context = this.
|
|
338
|
+
const context = this.messageContexts.get(origin);
|
|
253
339
|
if (!context) {
|
|
254
340
|
this.log.warn(`Unknown message origin: ${origin}`);
|
|
255
341
|
this.sendReply({ error: 'Unknown action origin' }, msg);
|
|
@@ -260,41 +346,57 @@ class DeviceManagement {
|
|
|
260
346
|
}
|
|
261
347
|
}
|
|
262
348
|
}
|
|
263
|
-
convertActions(actions) {
|
|
264
|
-
if (!actions) {
|
|
265
|
-
return undefined;
|
|
266
|
-
}
|
|
267
|
-
// detect duplicate IDs
|
|
268
|
-
const ids = new Set();
|
|
269
|
-
actions.forEach(a => {
|
|
270
|
-
if (ids.has(a.id)) {
|
|
271
|
-
throw new Error(`Action ID ${a.id} is used twice, this would lead to unexpected behavior`);
|
|
272
|
-
}
|
|
273
|
-
ids.add(a.id);
|
|
274
|
-
});
|
|
275
|
-
// remove handler function to send it as JSON
|
|
276
|
-
return actions.map((a) => (Object.assign(Object.assign({}, a), { handler: undefined, disabled: !a.handler })));
|
|
277
|
-
}
|
|
278
|
-
convertControls(controls) {
|
|
279
|
-
if (!controls) {
|
|
280
|
-
return undefined;
|
|
281
|
-
}
|
|
282
|
-
// detect duplicate IDs
|
|
283
|
-
const ids = new Set();
|
|
284
|
-
controls.forEach(a => {
|
|
285
|
-
if (ids.has(a.id)) {
|
|
286
|
-
throw new Error(`Control ID ${a.id} is used twice, this would lead to unexpected behavior`);
|
|
287
|
-
}
|
|
288
|
-
ids.add(a.id);
|
|
289
|
-
});
|
|
290
|
-
// remove handler function to send it as JSON
|
|
291
|
-
return controls.map((a) => (Object.assign(Object.assign({}, a), { handler: undefined, getStateHandler: undefined })));
|
|
292
|
-
}
|
|
293
349
|
sendReply(reply, msg) {
|
|
294
350
|
this.adapter.sendTo(msg.from, msg.command, reply, msg.callback);
|
|
295
351
|
}
|
|
296
352
|
}
|
|
297
353
|
exports.DeviceManagement = DeviceManagement;
|
|
354
|
+
class DeviceLoadContextImpl {
|
|
355
|
+
constructor(msg, adapter) {
|
|
356
|
+
this.adapter = adapter;
|
|
357
|
+
this.minBatchSize = 8;
|
|
358
|
+
this.devices = [];
|
|
359
|
+
this.sendNext = [];
|
|
360
|
+
this.completed = false;
|
|
361
|
+
this.respondTo = msg;
|
|
362
|
+
this.id = msg._id;
|
|
363
|
+
}
|
|
364
|
+
addDevice(device) {
|
|
365
|
+
this.devices.push(device);
|
|
366
|
+
this.sendNext.push(Object.assign(Object.assign({}, device), { actions: convertActions(device.actions), controls: convertControls(device.controls) }));
|
|
367
|
+
this.flush();
|
|
368
|
+
}
|
|
369
|
+
setTotalDevices(count) {
|
|
370
|
+
this.totalDevices = count;
|
|
371
|
+
this.flush();
|
|
372
|
+
}
|
|
373
|
+
complete() {
|
|
374
|
+
this.completed = true;
|
|
375
|
+
return this.flush();
|
|
376
|
+
}
|
|
377
|
+
handleProgress(message) {
|
|
378
|
+
this.respondTo = message;
|
|
379
|
+
return this.flush();
|
|
380
|
+
}
|
|
381
|
+
flush() {
|
|
382
|
+
if (this.sendNext.length <= this.minBatchSize && !this.completed) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
if (!this.respondTo) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
const reply = {
|
|
389
|
+
add: this.sendNext,
|
|
390
|
+
total: this.totalDevices,
|
|
391
|
+
next: this.completed ? undefined : { origin: this.id },
|
|
392
|
+
};
|
|
393
|
+
this.sendNext = [];
|
|
394
|
+
const msg = this.respondTo;
|
|
395
|
+
this.respondTo = undefined;
|
|
396
|
+
this.adapter.sendTo(msg.from, msg.command, reply, msg.callback);
|
|
397
|
+
return this.completed;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
298
400
|
class MessageContext {
|
|
299
401
|
constructor(msg, adapter) {
|
|
300
402
|
this.adapter = adapter;
|
|
@@ -306,9 +408,7 @@ class MessageContext {
|
|
|
306
408
|
const promise = new Promise(resolve => {
|
|
307
409
|
this.progressHandler = () => resolve();
|
|
308
410
|
});
|
|
309
|
-
this.send('message',
|
|
310
|
-
message: text,
|
|
311
|
-
});
|
|
411
|
+
this.send({ type: 'message', message: text });
|
|
312
412
|
return promise;
|
|
313
413
|
}
|
|
314
414
|
showConfirmation(text) {
|
|
@@ -316,9 +416,7 @@ class MessageContext {
|
|
|
316
416
|
const promise = new Promise(resolve => {
|
|
317
417
|
this.progressHandler = msg => resolve(!!msg.confirm);
|
|
318
418
|
});
|
|
319
|
-
this.send('confirm',
|
|
320
|
-
confirm: text,
|
|
321
|
-
});
|
|
419
|
+
this.send({ type: 'confirm', confirm: text });
|
|
322
420
|
return promise;
|
|
323
421
|
}
|
|
324
422
|
showForm(schema, options) {
|
|
@@ -326,7 +424,8 @@ class MessageContext {
|
|
|
326
424
|
const promise = new Promise(resolve => {
|
|
327
425
|
this.progressHandler = msg => resolve(msg.data);
|
|
328
426
|
});
|
|
329
|
-
this.send(
|
|
427
|
+
this.send({
|
|
428
|
+
type: 'form',
|
|
330
429
|
form: Object.assign({ schema }, options),
|
|
331
430
|
});
|
|
332
431
|
return promise;
|
|
@@ -339,9 +438,7 @@ class MessageContext {
|
|
|
339
438
|
const promise = new Promise(resolve => {
|
|
340
439
|
this.progressHandler = () => resolve();
|
|
341
440
|
});
|
|
342
|
-
this.send('progress',
|
|
343
|
-
progress: Object.assign(Object.assign(Object.assign({ title }, options), update), { open: true }),
|
|
344
|
-
}, true);
|
|
441
|
+
this.send({ type: 'progress', progress: update }, true);
|
|
345
442
|
return promise;
|
|
346
443
|
},
|
|
347
444
|
close: () => {
|
|
@@ -351,28 +448,23 @@ class MessageContext {
|
|
|
351
448
|
resolve();
|
|
352
449
|
};
|
|
353
450
|
});
|
|
354
|
-
this.send('progress', {
|
|
355
|
-
progress: { open: false },
|
|
356
|
-
});
|
|
451
|
+
this.send({ type: 'progress', progress: { open: false } });
|
|
357
452
|
return promise;
|
|
358
453
|
},
|
|
359
454
|
};
|
|
360
455
|
const promise = new Promise(resolve => {
|
|
361
456
|
this.progressHandler = () => resolve(dialog);
|
|
362
457
|
});
|
|
363
|
-
this.send('progress', {
|
|
364
|
-
progress: Object.assign(Object.assign({ title }, options), { open: true }),
|
|
365
|
-
}, true);
|
|
458
|
+
this.send({ type: 'progress', progress: Object.assign(Object.assign({ title }, options), { open: true }) }, true);
|
|
366
459
|
return promise;
|
|
367
460
|
}
|
|
368
461
|
sendFinalResult(result) {
|
|
369
|
-
this.send('result',
|
|
370
|
-
result,
|
|
371
|
-
});
|
|
462
|
+
this.send({ type: 'result', result });
|
|
372
463
|
}
|
|
373
464
|
sendControlResult(deviceId, controlId, result) {
|
|
374
465
|
if (typeof result === 'object' && 'error' in result) {
|
|
375
|
-
this.send(
|
|
466
|
+
this.send({
|
|
467
|
+
type: 'result',
|
|
376
468
|
result: {
|
|
377
469
|
error: result.error,
|
|
378
470
|
deviceId,
|
|
@@ -381,7 +473,8 @@ class MessageContext {
|
|
|
381
473
|
});
|
|
382
474
|
}
|
|
383
475
|
else {
|
|
384
|
-
this.send(
|
|
476
|
+
this.send({
|
|
477
|
+
type: 'result',
|
|
385
478
|
result: {
|
|
386
479
|
state: result,
|
|
387
480
|
deviceId,
|
|
@@ -403,11 +496,11 @@ class MessageContext {
|
|
|
403
496
|
throw new Error("Can't show another dialog while a progress dialog is open. Please call 'close()' on the dialog before opening another dialog.");
|
|
404
497
|
}
|
|
405
498
|
}
|
|
406
|
-
send(
|
|
499
|
+
send(message, doNotClose) {
|
|
407
500
|
if (!this.lastMessage) {
|
|
408
501
|
throw new Error("No outstanding message, can't send a new one");
|
|
409
502
|
}
|
|
410
|
-
this.adapter.sendTo(this.lastMessage.from, this.lastMessage.command, Object.assign(Object.assign({}, message), {
|
|
503
|
+
this.adapter.sendTo(this.lastMessage.from, this.lastMessage.command, Object.assign(Object.assign({}, message), { origin: this.lastMessage.message.origin || this.lastMessage._id }), this.lastMessage.callback);
|
|
411
504
|
if (!doNotClose) {
|
|
412
505
|
// "progress" is exception. It will be closed with "close" flag
|
|
413
506
|
this.lastMessage = undefined;
|
|
@@ -415,3 +508,33 @@ class MessageContext {
|
|
|
415
508
|
}
|
|
416
509
|
}
|
|
417
510
|
exports.MessageContext = MessageContext;
|
|
511
|
+
function convertActions(actions) {
|
|
512
|
+
if (!actions) {
|
|
513
|
+
return undefined;
|
|
514
|
+
}
|
|
515
|
+
// detect duplicate IDs
|
|
516
|
+
const ids = new Set();
|
|
517
|
+
actions.forEach(a => {
|
|
518
|
+
if (ids.has(a.id)) {
|
|
519
|
+
throw new Error(`Action ID ${a.id} is used twice, this would lead to unexpected behavior`);
|
|
520
|
+
}
|
|
521
|
+
ids.add(a.id);
|
|
522
|
+
});
|
|
523
|
+
// remove handler function to send it as JSON
|
|
524
|
+
return actions.map((a) => (Object.assign(Object.assign({}, a), { handler: undefined, disabled: !a.handler && !a.url })));
|
|
525
|
+
}
|
|
526
|
+
function convertControls(controls) {
|
|
527
|
+
if (!controls) {
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
// detect duplicate IDs
|
|
531
|
+
const ids = new Set();
|
|
532
|
+
controls.forEach(a => {
|
|
533
|
+
if (ids.has(a.id)) {
|
|
534
|
+
throw new Error(`Control ID ${a.id} is used twice, this would lead to unexpected behavior`);
|
|
535
|
+
}
|
|
536
|
+
ids.add(a.id);
|
|
537
|
+
});
|
|
538
|
+
// remove handler function to send it as JSON
|
|
539
|
+
return controls.map((a) => (Object.assign(Object.assign({}, a), { handler: undefined, getStateHandler: undefined })));
|
|
540
|
+
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
+
import type { ProgressUpdate } from './types/common';
|
|
1
2
|
export interface ProgressDialog {
|
|
2
|
-
update(update:
|
|
3
|
-
title?: ioBroker.StringOrTranslated;
|
|
4
|
-
indeterminate?: boolean;
|
|
5
|
-
value?: number;
|
|
6
|
-
label?: ioBroker.StringOrTranslated;
|
|
7
|
-
}): Promise<void>;
|
|
3
|
+
update(update: ProgressUpdate): Promise<void>;
|
|
8
4
|
close(): Promise<void>;
|
|
9
5
|
}
|