iobroker.zigbee 1.8.3 → 1.8.5
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 +3 -0
- package/admin/admin.js +512 -493
- package/admin/index_m.html +1171 -1001
- package/admin/tab_m.html +44 -3
- package/docs/de/img/CC2531.png +0 -0
- package/docs/de/img/CC2538_CC2592_PA.PNG +0 -0
- package/docs/de/img/CC2591.png +0 -0
- package/docs/de/img/boards.jpg +0 -0
- package/docs/de/img/cc26x2r.PNG +0 -0
- package/docs/de/img/results.jpg +0 -0
- package/docs/de/img/sku_429478_2.png +0 -0
- package/docs/de/img/sku_429601_2.png +0 -0
- package/docs/de/readme.md +27 -0
- package/docs/en/img/CC2531.png +0 -0
- package/docs/en/img/CC2591.png +0 -0
- package/docs/en/img/deconz.png +0 -0
- package/docs/en/img/sku_429478_2.png +0 -0
- package/docs/en/img/sku_429601_2.png +0 -0
- package/docs/en/readme.md +30 -0
- package/docs/flashing_via_arduino_(en).md +110 -0
- package/docs/ru/img/CC2531.png +0 -0
- package/docs/ru/img/CC2591.png +0 -0
- package/docs/ru/img/sku_429478_2.png +0 -0
- package/docs/ru/img/sku_429601_2.png +0 -0
- package/docs/ru/readme.md +28 -0
- package/docs/tutorial/CC2530_20190425.zip +0 -0
- package/docs/tutorial/CC2530_CC2591_20190515.zip +0 -0
- package/docs/tutorial/CC2530_CC2592_20190515.zip +0 -0
- package/docs/tutorial/CC2531_20190425.zip +0 -0
- package/docs/tutorial/adm5_1.PNG +0 -0
- package/docs/tutorial/adm5_2.PNG +0 -0
- package/docs/tutorial/cat.PNG +0 -0
- package/docs/tutorial/groups-1.png +0 -0
- package/docs/tutorial/groups-2.png +0 -0
- package/docs/tutorial/inst.PNG +0 -0
- package/docs/tutorial/reflash-finish.PNG +0 -0
- package/docs/tutorial/reflash-step0.png +0 -0
- package/docs/tutorial/reflash-step1.PNG +0 -0
- package/docs/tutorial/reflash-step2.PNG +0 -0
- package/docs/tutorial/settings.png +0 -0
- package/docs/tutorial/tab-dev-1.png +0 -0
- package/docs/tutorial/zigbee.png +0 -0
- package/docs/tutorial/zigbee15.png +0 -0
- package/io-package.json +17 -25
- package/lib/backup.js +2 -2
- package/lib/binding.js +32 -37
- package/lib/colors.js +163 -158
- package/lib/commands.js +100 -91
- package/lib/developer.js +9 -12
- package/lib/devices.js +168 -178
- package/lib/exclude.js +30 -36
- package/lib/exposes.js +163 -139
- package/lib/groups.js +81 -83
- package/lib/json.js +5 -6
- package/lib/networkmap.js +2 -3
- package/lib/ota.js +34 -18
- package/lib/rgb.js +114 -72
- package/lib/seriallist.js +25 -20
- package/lib/states.js +511 -526
- package/lib/statescontroller.js +206 -183
- package/lib/utils.js +24 -23
- package/lib/zbBaseExtension.js +4 -4
- package/lib/zbDelayedAction.js +5 -13
- package/lib/zbDeviceAvailability.js +69 -65
- package/lib/zbDeviceConfigure.js +9 -21
- package/lib/zbDeviceEvent.js +3 -4
- package/lib/zigbeecontroller.js +133 -128
- package/main.js +169 -154
- package/package.json +27 -13
- package/.eslintignore +0 -2
- package/.eslintrc.json +0 -37
- package/.github/FUNDING.yml +0 -3
- package/.github/auto-merge.yml +0 -17
- package/.github/dependabot.yml +0 -24
- package/.github/stale.yml +0 -13
- package/.github/workflows/codeql.yml +0 -41
- package/.github/workflows/dependabot-automerge.yml +0 -22
- package/.github/workflows/test-and-release.yml +0 -149
- package/.releaseconfig.json +0 -3
- package/.travis/wiki.sh +0 -28
- package/.travis.yml +0 -41
- package/gulpfile.js +0 -464
- package/test/integration.js +0 -5
- package/test/mocha.custom.opts +0 -2
- package/test/mocha.setup.js +0 -14
- package/test/package.js +0 -5
- package/test/unit.js +0 -5
package/lib/statescontroller.js
CHANGED
|
@@ -7,9 +7,8 @@ const getZbId = require('./utils').getZbId;
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const request = require('request');
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
let savedDeviceNames = {};
|
|
11
|
+
let knownUndefinedDevices = {};
|
|
13
12
|
|
|
14
13
|
class StatesController extends EventEmitter {
|
|
15
14
|
constructor(adapter) {
|
|
@@ -18,19 +17,16 @@ class StatesController extends EventEmitter {
|
|
|
18
17
|
this.adapter.on('stateChange', this.onStateChange.bind(this));
|
|
19
18
|
this.query_device_block = [];
|
|
20
19
|
this.debugDevices = undefined;
|
|
21
|
-
|
|
20
|
+
const fn = adapter.expandFileName('dev_names.json');
|
|
22
21
|
this.dev_names_fn = fn.replace('.', '_');
|
|
23
22
|
this.retTimeoutHandle = null;
|
|
24
23
|
fs.readFile(this.dev_names_fn, (err, data) => {
|
|
25
24
|
if (!err) {
|
|
26
|
-
try
|
|
27
|
-
{
|
|
25
|
+
try {
|
|
28
26
|
savedDeviceNames = JSON.parse(data);
|
|
29
|
-
}
|
|
30
|
-
catch
|
|
31
|
-
{
|
|
27
|
+
} catch {
|
|
32
28
|
savedDeviceNames = {};
|
|
33
|
-
}
|
|
29
|
+
}
|
|
34
30
|
}
|
|
35
31
|
});
|
|
36
32
|
}
|
|
@@ -55,68 +51,77 @@ class StatesController extends EventEmitter {
|
|
|
55
51
|
this.adapter.sendError(error, message);
|
|
56
52
|
}
|
|
57
53
|
|
|
58
|
-
retainDeviceNames()
|
|
59
|
-
{
|
|
54
|
+
retainDeviceNames() {
|
|
60
55
|
clearTimeout(this.retTimeoutHandle);
|
|
61
|
-
this.retTimeoutHanlde = setTimeout(()=> {
|
|
62
|
-
fs.writeFile(this.dev_names_fn, JSON.stringify(savedDeviceNames, null, 2),
|
|
63
|
-
if (err)
|
|
64
|
-
this.error(
|
|
65
|
-
else
|
|
56
|
+
this.retTimeoutHanlde = setTimeout(() => {
|
|
57
|
+
fs.writeFile(this.dev_names_fn, JSON.stringify(savedDeviceNames, null, 2), err => {
|
|
58
|
+
if (err) {
|
|
59
|
+
this.error(`error saving device names: ${JSON.stringify(err)}`);
|
|
60
|
+
} else {
|
|
66
61
|
this.debug('saved device names');
|
|
67
|
-
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}, 5000);
|
|
68
65
|
}
|
|
69
66
|
|
|
70
67
|
getDebugDevices() {
|
|
71
68
|
this.debugDevices = [];
|
|
72
|
-
this.adapter.getState(this.adapter.namespace
|
|
69
|
+
this.adapter.getState(`${this.adapter.namespace}.info.debugmessages`, (err, state) => {
|
|
73
70
|
if (state) {
|
|
74
|
-
if (typeof
|
|
75
|
-
|
|
71
|
+
if (typeof state.val === 'string' && state.val.length > 2) {
|
|
72
|
+
this.debugDevices = state.val.split(';');
|
|
73
|
+
}
|
|
74
|
+
this.info(`debug devices set to ${JSON.stringify(this.debugDevices)}`);
|
|
76
75
|
} else {
|
|
77
76
|
this.adapter.setObject('info.debugmessages', {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
type: 'state',
|
|
78
|
+
common: {
|
|
79
|
+
name: 'Log changes as warnings for',
|
|
80
|
+
role: '',
|
|
81
|
+
type: 'string',
|
|
82
|
+
read: true,
|
|
83
|
+
write: true,
|
|
85
84
|
},
|
|
86
|
-
|
|
85
|
+
native: {},
|
|
87
86
|
});
|
|
88
87
|
}
|
|
89
88
|
});
|
|
90
89
|
this.adapter.setObject('info.undefinedDevices', {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
type: 'state',
|
|
91
|
+
common: {
|
|
92
|
+
name: 'Recorded undefined devices',
|
|
93
|
+
role: '',
|
|
94
|
+
type: 'string',
|
|
95
|
+
read: true,
|
|
96
|
+
write: false,
|
|
98
97
|
},
|
|
99
|
-
|
|
98
|
+
native: {},
|
|
100
99
|
});
|
|
101
100
|
this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
onStateChange(id, state){
|
|
105
|
-
if (!this.adapter.zbController || !this.adapter.zbController.connected())
|
|
106
|
-
|
|
103
|
+
onStateChange(id, state) {
|
|
104
|
+
if (!this.adapter.zbController || !this.adapter.zbController.connected()) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (this.debugDevices === undefined) {
|
|
108
|
+
this.getDebugDevices();
|
|
109
|
+
}
|
|
107
110
|
if (state && !state.ack) {
|
|
108
|
-
if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection'))
|
|
111
|
+
if (id.endsWith('pairingCountdown') || id.endsWith('pairingMessage') || id.endsWith('connection')) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
109
114
|
if (id.endsWith('debugmessages')) {
|
|
110
|
-
if
|
|
115
|
+
if (typeof state.val === 'string' && state.val.length > 2) {
|
|
111
116
|
this.debugDevices = state.val.split(';');
|
|
112
|
-
|
|
117
|
+
|
|
118
|
+
} else {
|
|
113
119
|
this.debugDevices = [];
|
|
114
120
|
}
|
|
115
121
|
return;
|
|
116
122
|
}
|
|
117
123
|
for (const addressPart of this.debugDevices) {
|
|
118
|
-
if (typeof
|
|
119
|
-
{
|
|
124
|
+
if (typeof id === 'string' && id.includes(addressPart)) {
|
|
120
125
|
this.warn(`ELEVATED: User stateChange ${id} ${JSON.stringify(state)}`);
|
|
121
126
|
break;
|
|
122
127
|
}
|
|
@@ -134,7 +139,9 @@ class StatesController extends EventEmitter {
|
|
|
134
139
|
this.adapter.getObject(devId, (err, obj) => {
|
|
135
140
|
if (obj) {
|
|
136
141
|
const model = obj.common.type;
|
|
137
|
-
if (!model)
|
|
142
|
+
if (!model) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
138
145
|
if (obj.common.deactivated) {
|
|
139
146
|
this.debug('State Change detected on deactivated Device - ignored');
|
|
140
147
|
return;
|
|
@@ -142,9 +149,8 @@ class StatesController extends EventEmitter {
|
|
|
142
149
|
if (model === 'group') {
|
|
143
150
|
deviceId = parseInt(deviceId.replace('0xgroup_', ''));
|
|
144
151
|
}
|
|
145
|
-
this.collectOptions(id.split('.')[2], model, options =>
|
|
146
|
-
this.publishFromState(deviceId, model, stateKey, state, options);
|
|
147
|
-
});
|
|
152
|
+
this.collectOptions(id.split('.')[2], model, options =>
|
|
153
|
+
this.publishFromState(deviceId, model, stateKey, state, options));
|
|
148
154
|
}
|
|
149
155
|
});
|
|
150
156
|
}
|
|
@@ -153,12 +159,12 @@ class StatesController extends EventEmitter {
|
|
|
153
159
|
async collectOptions(devId, model, callback) {
|
|
154
160
|
const result = {};
|
|
155
161
|
// find model states for options and get it values
|
|
156
|
-
const devStates = await this.getDevStates('0x'+devId, model);
|
|
162
|
+
const devStates = await this.getDevStates('0x' + devId, model);
|
|
157
163
|
if (!devStates) {
|
|
158
164
|
callback(result);
|
|
159
165
|
return;
|
|
160
166
|
}
|
|
161
|
-
const states = devStates.states.filter(
|
|
167
|
+
const states = devStates.states.filter(statedesc => statedesc.isOption || statedesc.inOptions);
|
|
162
168
|
if (!states) {
|
|
163
169
|
callback(result);
|
|
164
170
|
return;
|
|
@@ -167,7 +173,7 @@ class StatesController extends EventEmitter {
|
|
|
167
173
|
try {
|
|
168
174
|
const len = states.length;
|
|
169
175
|
states.forEach(statedesc => {
|
|
170
|
-
const id = this.adapter.namespace
|
|
176
|
+
const id = `${this.adapter.namespace}.${devId}.${statedesc.id}`;
|
|
171
177
|
this.adapter.getState(id, (err, state) => {
|
|
172
178
|
cnt = cnt + 1;
|
|
173
179
|
if (!err && state) {
|
|
@@ -178,29 +184,29 @@ class StatesController extends EventEmitter {
|
|
|
178
184
|
}
|
|
179
185
|
});
|
|
180
186
|
});
|
|
181
|
-
if (!len)
|
|
182
|
-
|
|
187
|
+
if (!len) {
|
|
188
|
+
callback(result);
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
183
191
|
this.sendError(error);
|
|
184
192
|
this.error(`Error collectOptions for ${devId}. Error: ${error.stack}`);
|
|
185
|
-
|
|
193
|
+
}
|
|
186
194
|
}
|
|
187
195
|
|
|
188
196
|
async getDevStates(deviceId, model) {
|
|
189
197
|
try {
|
|
190
|
-
let states
|
|
198
|
+
let states;
|
|
191
199
|
let stateModel;
|
|
192
200
|
if (model === 'group') {
|
|
193
201
|
states = statesMapping.groupStates;
|
|
194
202
|
} else {
|
|
195
203
|
stateModel = statesMapping.findModel(model);
|
|
196
204
|
if (!stateModel) {
|
|
197
|
-
if (knownUndefinedDevices[deviceId])
|
|
198
|
-
{
|
|
205
|
+
if (knownUndefinedDevices[deviceId]) {
|
|
199
206
|
knownUndefinedDevices[deviceId]++;
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
207
|
+
} else {
|
|
202
208
|
knownUndefinedDevices[deviceId] = 1;
|
|
203
|
-
this.error(
|
|
209
|
+
this.error(`Device ${deviceId} "${model}" not described in statesMapping.`);
|
|
204
210
|
}
|
|
205
211
|
this.adapter.setStateAsync(`info.undefinedDevices`, JSON.stringify(knownUndefinedDevices), true);
|
|
206
212
|
states = statesMapping.commonStates;
|
|
@@ -209,11 +215,12 @@ class StatesController extends EventEmitter {
|
|
|
209
215
|
}
|
|
210
216
|
if (typeof states === 'function' && !states.prototype) {
|
|
211
217
|
const entity = await this.adapter.zbController.resolveEntity(deviceId);
|
|
212
|
-
if (entity)
|
|
218
|
+
if (entity) {
|
|
213
219
|
states = states(entity);
|
|
220
|
+
}
|
|
214
221
|
}
|
|
215
222
|
}
|
|
216
|
-
return {states
|
|
223
|
+
return {states, stateModel};
|
|
217
224
|
} catch (error) {
|
|
218
225
|
this.sendError(error);
|
|
219
226
|
this.error(`Error getDevStates for ${deviceId}. Error: ${error.stack}`);
|
|
@@ -224,8 +231,7 @@ class StatesController extends EventEmitter {
|
|
|
224
231
|
if (this.debugDevices === undefined) this.getDebugDevices();
|
|
225
232
|
this.debug(`Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
226
233
|
for (const addressPart of this.debugDevices) {
|
|
227
|
-
if (typeof
|
|
228
|
-
{
|
|
234
|
+
if (typeof deviceId === 'string' && deviceId.includes(addressPart)) {
|
|
229
235
|
this.warn(`ELEVATED Change state '${stateKey}' at device ${deviceId} type '${model}'`);
|
|
230
236
|
break;
|
|
231
237
|
}
|
|
@@ -234,8 +240,8 @@ class StatesController extends EventEmitter {
|
|
|
234
240
|
if (!devStates) {
|
|
235
241
|
return;
|
|
236
242
|
}
|
|
237
|
-
const commonStates = statesMapping.commonStates.find(
|
|
238
|
-
const stateDesc = (commonStates === undefined ? devStates.states.find(
|
|
243
|
+
const commonStates = statesMapping.commonStates.find(statedesc => stateKey === statedesc.id);
|
|
244
|
+
const stateDesc = (commonStates === undefined ? devStates.states.find(statedesc => stateKey === statedesc.id) : commonStates);
|
|
239
245
|
const stateModel = devStates.stateModel;
|
|
240
246
|
if (!stateDesc) {
|
|
241
247
|
this.error(`No state available for '${model}' with key '${stateKey}'`);
|
|
@@ -243,20 +249,20 @@ class StatesController extends EventEmitter {
|
|
|
243
249
|
}
|
|
244
250
|
|
|
245
251
|
const value = state.val;
|
|
246
|
-
if (value === undefined || value === '')
|
|
252
|
+
if (value === undefined || value === '') {
|
|
247
253
|
return;
|
|
254
|
+
}
|
|
248
255
|
let stateList = [{stateDesc: stateDesc, value: value, index: 0, timeout: 0}];
|
|
249
256
|
if (stateModel && stateModel.linkedStates) {
|
|
250
|
-
stateModel.linkedStates.forEach(
|
|
257
|
+
stateModel.linkedStates.forEach(linkedFunct => {
|
|
251
258
|
try {
|
|
252
259
|
if (typeof linkedFunct === 'function') {
|
|
253
260
|
const res = linkedFunct(stateDesc, value, options, this.adapter.config.disableQueue);
|
|
254
261
|
if (res) {
|
|
255
262
|
stateList = stateList.concat(res);
|
|
256
263
|
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
this.warn('publish from State - LinkedState is not a function ' + JSON.stringify(linkedFunct));
|
|
264
|
+
} else {
|
|
265
|
+
this.warn(`publish from State - LinkedState is not a function ${JSON.stringify(linkedFunct)}`);
|
|
260
266
|
}
|
|
261
267
|
} catch (e) {
|
|
262
268
|
this.sendError(e);
|
|
@@ -265,17 +271,14 @@ class StatesController extends EventEmitter {
|
|
|
265
271
|
|
|
266
272
|
});
|
|
267
273
|
// sort by index
|
|
268
|
-
stateList.sort((a, b) =>
|
|
269
|
-
return a.index - b.index;
|
|
270
|
-
});
|
|
274
|
+
stateList.sort((a, b) => a.index - b.index);
|
|
271
275
|
}
|
|
272
276
|
|
|
273
|
-
// holds the states for
|
|
277
|
+
// holds the states for read after write requests
|
|
274
278
|
let readAfterWriteStates = [];
|
|
275
279
|
if (stateModel && stateModel.readAfterWriteStates) {
|
|
276
|
-
stateModel.readAfterWriteStates.forEach((readAfterWriteStateDesc) =>
|
|
277
|
-
readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id);
|
|
278
|
-
});
|
|
280
|
+
stateModel.readAfterWriteStates.forEach((readAfterWriteStateDesc) =>
|
|
281
|
+
readAfterWriteStates = readAfterWriteStates.concat(readAfterWriteStateDesc.id));
|
|
279
282
|
}
|
|
280
283
|
|
|
281
284
|
this.emit('changed', deviceId, model, stateModel, stateList, options);
|
|
@@ -287,42 +290,42 @@ class StatesController extends EventEmitter {
|
|
|
287
290
|
}
|
|
288
291
|
|
|
289
292
|
setDeviceActivated(id, active) {
|
|
290
|
-
|
|
293
|
+
this.adapter.extendObject(id, {common: {deactivated: active}});
|
|
291
294
|
}
|
|
292
295
|
|
|
293
296
|
storeDeviceName(id, name) {
|
|
294
|
-
|
|
295
|
-
|
|
297
|
+
savedDeviceNames[id.replace(`${this.adapter.namespace}.`, '')] = name;
|
|
298
|
+
this.retainDeviceNames();
|
|
296
299
|
}
|
|
297
300
|
|
|
298
301
|
verifyDeviceName(id, name) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
302
|
+
const savedId = id.replace(`${this.adapter.namespace}.`, '');
|
|
303
|
+
if (!savedDeviceNames.hasOwnProperty(savedId)) {
|
|
304
|
+
savedDeviceNames[savedId] = name;
|
|
305
|
+
this.retainDeviceNames();
|
|
306
|
+
}
|
|
307
|
+
return savedDeviceNames[savedId];
|
|
305
308
|
}
|
|
306
309
|
|
|
307
310
|
deleteDeviceStates(devId, callback) {
|
|
308
311
|
this.adapter.getStatesOf(devId, (err, states) => {
|
|
309
312
|
if (!err && states) {
|
|
310
|
-
states.forEach(
|
|
311
|
-
this.adapter.deleteState(devId, null, state._id);
|
|
312
|
-
});
|
|
313
|
+
states.forEach(state =>
|
|
314
|
+
this.adapter.deleteState(devId, null, state._id));
|
|
313
315
|
}
|
|
314
|
-
this.adapter.deleteDevice(devId, () =>
|
|
315
|
-
callback && callback();
|
|
316
|
-
});
|
|
316
|
+
this.adapter.deleteDevice(devId, () =>
|
|
317
|
+
callback && callback());
|
|
317
318
|
});
|
|
318
319
|
}
|
|
319
320
|
|
|
320
321
|
async deleteDeviceStatesAsync(devId) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
322
|
+
const states = await this.adapter.getStatesOf(devId);
|
|
323
|
+
if (states) {
|
|
324
|
+
for (const state of states) {
|
|
325
|
+
await this.adapter.deleteState(devId, null, state._id);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
await this.adapter.deleteDevice(devId);
|
|
326
329
|
}
|
|
327
330
|
|
|
328
331
|
// eslint-disable-next-line no-unused-vars
|
|
@@ -338,20 +341,22 @@ class StatesController extends EventEmitter {
|
|
|
338
341
|
if (arr[1] === undefined) {
|
|
339
342
|
this.warn(`unable to extract id from state ${statename}`);
|
|
340
343
|
const idx = statename.lastIndexOf('.');
|
|
341
|
-
if (idx > -1)
|
|
344
|
+
if (idx > -1) {
|
|
345
|
+
statename = statename.slice(idx + 1);
|
|
346
|
+
}
|
|
342
347
|
} else {
|
|
343
348
|
statename = arr[1];
|
|
344
349
|
}
|
|
345
|
-
if (commonStates.find(
|
|
346
|
-
devStates.states.find(
|
|
350
|
+
if (commonStates.find(statedesc => statename === statedesc.id) === undefined &&
|
|
351
|
+
devStates.states.find(statedesc => statename === statedesc.id) === undefined
|
|
352
|
+
) {
|
|
347
353
|
if (state.common.hasOwnProperty('custom') && !force) {
|
|
348
354
|
this.info(`keeping disconnected state ${JSON.stringify(statename)} of ${devId} `);
|
|
349
355
|
} else {
|
|
350
356
|
this.info(`deleting disconnected state ${JSON.stringify(statename)} of ${devId} `);
|
|
351
357
|
this.adapter.deleteState(devId, null, state._id);
|
|
352
358
|
}
|
|
353
|
-
}
|
|
354
|
-
else {
|
|
359
|
+
} else {
|
|
355
360
|
this.debug(`keeping connecte state ${JSON.stringify(statename)} of ${devId} `);
|
|
356
361
|
}
|
|
357
362
|
});
|
|
@@ -408,10 +413,11 @@ class StatesController extends EventEmitter {
|
|
|
408
413
|
let hasChanges = false;
|
|
409
414
|
if (stobj) {
|
|
410
415
|
// update state - not change name and role (user can it changed)
|
|
411
|
-
if (stobj.common.name)
|
|
416
|
+
if (stobj.common.name) {
|
|
412
417
|
delete new_common.name;
|
|
413
|
-
else
|
|
414
|
-
new_common.name = new_name
|
|
418
|
+
} else {
|
|
419
|
+
new_common.name = `${new_name} ${new_common.name}`;
|
|
420
|
+
}
|
|
415
421
|
delete new_common.role;
|
|
416
422
|
|
|
417
423
|
// check whether any common property is different
|
|
@@ -432,54 +438,58 @@ class StatesController extends EventEmitter {
|
|
|
432
438
|
|
|
433
439
|
// only change object when any common property has changed
|
|
434
440
|
if (hasChanges) {
|
|
435
|
-
this.adapter.extendObject(id, {type: 'state', common: new_common, native: {}
|
|
436
|
-
value !== undefined && this.setState_typed(id, value, true,
|
|
437
|
-
});
|
|
441
|
+
this.adapter.extendObject(id, {type: 'state', common: new_common, native: {}}, () =>
|
|
442
|
+
value !== undefined && this.setState_typed(id, value, true, stobj ? stobj.common.type : new_common.type));
|
|
438
443
|
} else if (value !== undefined) {
|
|
439
444
|
this.setState_typed(id, value, true, stobj.common.type);
|
|
440
445
|
}
|
|
441
446
|
|
|
442
447
|
});
|
|
448
|
+
} else {
|
|
449
|
+
this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
|
|
443
450
|
}
|
|
444
|
-
else this.debug(`UpdateState: Device is deactivated ${devId} ${JSON.stringify(obj)}`);
|
|
445
451
|
} else {
|
|
446
452
|
this.debug(`UpdateState: missing device ${devId} ${JSON.stringify(obj)}`);
|
|
447
453
|
}
|
|
448
454
|
});
|
|
449
455
|
}
|
|
450
456
|
|
|
451
|
-
setState_typed(id, value, ack, type, callback)
|
|
452
|
-
{
|
|
457
|
+
setState_typed(id, value, ack, type, callback) {
|
|
453
458
|
// never set a null or undefined value
|
|
454
459
|
if (value === null || value === undefined) return;
|
|
455
460
|
if (!type) {
|
|
456
|
-
this.debug(
|
|
461
|
+
this.debug('SetState_typed called without type');
|
|
457
462
|
// identify datatype, recursively call this function with set datatype
|
|
458
463
|
this.adapter.getObject(id, (err, obj) => {
|
|
459
|
-
if (obj && obj.common)
|
|
460
|
-
|
|
461
|
-
else {
|
|
464
|
+
if (obj && obj.common) {
|
|
465
|
+
this.setState_typed(id, value, ack, obj.common.type, callback);
|
|
466
|
+
} else {
|
|
462
467
|
this.setState_typed(id, value, ack, 'noobj', callback);
|
|
463
468
|
}
|
|
464
469
|
});
|
|
465
470
|
return;
|
|
466
471
|
}
|
|
467
|
-
if (typeof value
|
|
468
|
-
this.debug(
|
|
472
|
+
if (typeof value !== type) {
|
|
473
|
+
this.debug(`SetState_typed : converting ${JSON.stringify(value)} for ${id} from ${typeof value} to ${type}`);
|
|
469
474
|
switch (type) {
|
|
470
475
|
case 'number':
|
|
471
476
|
value = parseFloat(value);
|
|
472
|
-
if (isNaN
|
|
477
|
+
if (isNaN(value)) {
|
|
478
|
+
value = 0;
|
|
479
|
+
}
|
|
473
480
|
break;
|
|
474
481
|
case 'string':
|
|
475
|
-
case 'text':
|
|
476
|
-
|
|
482
|
+
case 'text':
|
|
483
|
+
value = JSON.stringify(value);
|
|
484
|
+
break;
|
|
485
|
+
case 'boolean': {
|
|
477
486
|
if (typeof value == 'number') {
|
|
478
|
-
value =
|
|
487
|
+
value = value !== 0;
|
|
479
488
|
break;
|
|
480
489
|
}
|
|
481
490
|
const sval = JSON.stringify(value).toLowerCase().trim();
|
|
482
|
-
value =
|
|
491
|
+
value = sval === 'true' || sval === 'yes' || sval === 'on';
|
|
492
|
+
}
|
|
483
493
|
break;
|
|
484
494
|
}
|
|
485
495
|
}
|
|
@@ -491,60 +501,68 @@ class StatesController extends EventEmitter {
|
|
|
491
501
|
const id = '' + dev_id;
|
|
492
502
|
const modelDesc = statesMapping.findModel(model);
|
|
493
503
|
let icon = (modelDesc && modelDesc.icon) ? modelDesc.icon : 'img/unknown.png';
|
|
494
|
-
|
|
504
|
+
|
|
495
505
|
// download icon if it external and not undef
|
|
496
|
-
if (model === undefined) {
|
|
506
|
+
if (model === undefined) {
|
|
497
507
|
this.warn(`download icon ${__dev_name} for undefined Device not available. Check your devices.`);
|
|
498
508
|
} else {
|
|
499
|
-
const model_modif = model.replace(/\//g, '-');
|
|
500
|
-
const pathToIcon = this.adapter.adapterDir
|
|
509
|
+
const model_modif = model.replace(/\//g, '-');
|
|
510
|
+
const pathToIcon = `${this.adapter.adapterDir}/admin/img/${model_modif}.png`;
|
|
501
511
|
|
|
502
|
-
if (icon.startsWith('http')) {
|
|
503
|
-
try {
|
|
512
|
+
if (icon.startsWith('http')) {
|
|
513
|
+
try {
|
|
504
514
|
if (!fs.existsSync(pathToIcon)) {
|
|
505
515
|
this.warn(`download icon from ${icon} saved into ${pathToIcon}`);
|
|
506
|
-
this.downloadIcon(icon, pathToIcon);
|
|
516
|
+
this.downloadIcon(icon, pathToIcon);
|
|
507
517
|
}
|
|
508
|
-
icon =
|
|
518
|
+
icon = `img/${model_modif}.png`;
|
|
509
519
|
} catch (e) {
|
|
510
520
|
this.debug(`ERROR : icon not found from ${icon} saved into ${pathToIcon}`);
|
|
511
521
|
}
|
|
512
522
|
}
|
|
513
523
|
}
|
|
514
|
-
|
|
524
|
+
|
|
515
525
|
this.adapter.setObjectNotExists(id, {
|
|
516
526
|
type: 'device',
|
|
517
527
|
// actually this is an error, so device.common has no attribute type. It must be in native part
|
|
518
|
-
common: {
|
|
528
|
+
common: {
|
|
529
|
+
name: __dev_name,
|
|
530
|
+
type: model,
|
|
531
|
+
icon,
|
|
532
|
+
color: null,
|
|
533
|
+
statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
|
|
534
|
+
},
|
|
519
535
|
native: {id: dev_id}
|
|
520
536
|
}, () => {
|
|
521
537
|
// update type and icon
|
|
522
|
-
this.adapter.extendObject(id, {
|
|
538
|
+
this.adapter.extendObject(id, {
|
|
539
|
+
common: {
|
|
540
|
+
type: model,
|
|
541
|
+
icon,
|
|
542
|
+
color: null,
|
|
543
|
+
statusStates: {onlineId: `${this.adapter.namespace}.${dev_id}.available`}
|
|
544
|
+
}
|
|
545
|
+
}, callback);
|
|
523
546
|
});
|
|
524
547
|
}
|
|
525
548
|
|
|
526
549
|
async downloadIcon(url, image_path) {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
resolve();
|
|
542
|
-
});
|
|
543
|
-
});
|
|
544
|
-
});
|
|
545
|
-
}
|
|
550
|
+
if (!fs.existsSync(image_path)) {
|
|
551
|
+
return new Promise((resolve, reject) => {
|
|
552
|
+
request.head(url, (err, res, body) => {
|
|
553
|
+
if (err) {
|
|
554
|
+
return reject(err);
|
|
555
|
+
}
|
|
556
|
+
const stream = request(url);
|
|
557
|
+
stream.pipe(
|
|
558
|
+
fs.createWriteStream(image_path)
|
|
559
|
+
.on('error', err => reject(err)))
|
|
560
|
+
.on('close', () => resolve());
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
}
|
|
546
564
|
}
|
|
547
|
-
|
|
565
|
+
|
|
548
566
|
async syncDevStates(dev, model) {
|
|
549
567
|
const devId = dev.ieeeAddr.substr(2);
|
|
550
568
|
// devId - iobroker device id
|
|
@@ -555,20 +573,25 @@ class StatesController extends EventEmitter {
|
|
|
555
573
|
const states = statesMapping.commonStates.concat(devStates.states);
|
|
556
574
|
|
|
557
575
|
for (const stateInd in states) {
|
|
558
|
-
if (!states.hasOwnProperty(stateInd))
|
|
576
|
+
if (!states.hasOwnProperty(stateInd)) {
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
559
579
|
|
|
560
580
|
const statedesc = states[stateInd];
|
|
561
|
-
if (statedesc === undefined)
|
|
562
|
-
{
|
|
581
|
+
if (statedesc === undefined) {
|
|
563
582
|
this.error(`syncDevStates: Illegal state in ${JSON.stringify(dev)} - ${JSON.stringify(stateInd)}`);
|
|
564
583
|
return;
|
|
565
584
|
}
|
|
566
585
|
// Filter out non routers or devices that are battery driven for the availability flag
|
|
567
|
-
if (statedesc.id === 'available')
|
|
568
|
-
if (!(dev.type === 'Router') || dev.powerSource === 'Battery')
|
|
586
|
+
if (statedesc.id === 'available') {
|
|
587
|
+
if (!(dev.type === 'Router') || dev.powerSource === 'Battery') {
|
|
569
588
|
continue;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
570
591
|
// lazy states
|
|
571
|
-
if (statedesc.lazy)
|
|
592
|
+
if (statedesc.lazy) {
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
572
595
|
|
|
573
596
|
const common = {
|
|
574
597
|
name: statedesc.name,
|
|
@@ -586,19 +609,16 @@ class StatesController extends EventEmitter {
|
|
|
586
609
|
}
|
|
587
610
|
}
|
|
588
611
|
|
|
589
|
-
|
|
590
612
|
async getExcludeExposes(allExcludesObj) {
|
|
591
613
|
statesMapping.fillStatesWithExposes(allExcludesObj);
|
|
592
614
|
}
|
|
593
615
|
|
|
594
|
-
|
|
595
616
|
async publishToState(devId, model, payload) {
|
|
596
|
-
const devStates = await this.getDevStates(
|
|
597
|
-
let has_debug=false;
|
|
617
|
+
const devStates = await this.getDevStates(`0x${devId}`, model);
|
|
618
|
+
let has_debug = false;
|
|
598
619
|
if (this.debugDevices === undefined) this.getDebugDevices();
|
|
599
620
|
for (const addressPart of this.debugDevices) {
|
|
600
|
-
if (typeof
|
|
601
|
-
{
|
|
621
|
+
if (typeof devId === 'string' && devId.includes(addressPart)) {
|
|
602
622
|
if (payload.hasOwnProperty('msg_from_zigbee')) break;
|
|
603
623
|
this.warn(`ELEVATED publishToState: message received '${JSON.stringify(payload)}' from device ${devId} type '${model}'`);
|
|
604
624
|
has_debug = true;
|
|
@@ -611,8 +631,8 @@ class StatesController extends EventEmitter {
|
|
|
611
631
|
// find states for payload
|
|
612
632
|
if (devStates.states !== undefined) {
|
|
613
633
|
const states = statesMapping.commonStates.concat(
|
|
614
|
-
devStates.states.filter(
|
|
615
|
-
|
|
634
|
+
devStates.states.filter(statedesc => payload.hasOwnProperty(statedesc.prop || statedesc.id)));
|
|
635
|
+
|
|
616
636
|
for (const stateInd in states) {
|
|
617
637
|
const statedesc = states[stateInd];
|
|
618
638
|
let value;
|
|
@@ -622,10 +642,12 @@ class StatesController extends EventEmitter {
|
|
|
622
642
|
value = payload[statedesc.prop || statedesc.id];
|
|
623
643
|
}
|
|
624
644
|
// checking value
|
|
625
|
-
if (value === undefined || value === null)
|
|
645
|
+
if (value === undefined || value === null) {
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
626
648
|
let stateID = statedesc.id;
|
|
627
649
|
|
|
628
|
-
if (has_debug && statedesc.id
|
|
650
|
+
if (has_debug && statedesc.id !== 'msg_from_zigbee') {
|
|
629
651
|
this.warn(`ELEVATED publishToState: value generated '${JSON.stringify(value)}' from device ${devId} for '${statedesc.name}'`);
|
|
630
652
|
}
|
|
631
653
|
|
|
@@ -640,24 +662,25 @@ class StatesController extends EventEmitter {
|
|
|
640
662
|
min: statedesc.min,
|
|
641
663
|
max: statedesc.max,
|
|
642
664
|
};
|
|
643
|
-
if ( (typeof(value)== 'object') && (value.hasOwnProperty('stateid'))) {
|
|
644
|
-
stateID = stateID + '.' + value.stateid;
|
|
645
|
-
if (value.hasOwnProperty('unit')) common.unit = value.unit;
|
|
646
|
-
common.name = (value.name? value.name:value.stateid);
|
|
647
|
-
common.role = (value.role ? 'value.'+value.role:'number');
|
|
648
|
-
value = value.value;
|
|
649
665
|
|
|
666
|
+
if (typeof value === 'object' && value.hasOwnProperty('stateid')) {
|
|
667
|
+
stateID = `${stateID}.${value.stateid}`;
|
|
668
|
+
if (value.hasOwnProperty('unit')) {
|
|
669
|
+
common.unit = value.unit;
|
|
670
|
+
}
|
|
671
|
+
common.name = value.name ? value.name : value.stateid;
|
|
672
|
+
common.role = value.role ? `value.${value.role}` : 'number';
|
|
673
|
+
value = value.value;
|
|
650
674
|
}
|
|
651
|
-
|
|
675
|
+
|
|
676
|
+
// if needs to return value to back after timeout
|
|
652
677
|
if (statedesc.isEvent) {
|
|
653
678
|
this.updateStateWithTimeout(devId, statedesc.id, value, common, 300, !value);
|
|
654
679
|
} else {
|
|
655
680
|
if (statedesc.prepublish) {
|
|
656
|
-
this.collectOptions(devId, model,
|
|
657
|
-
statedesc.prepublish(devId, value,
|
|
658
|
-
this.updateState(devId, stateID, newvalue, common);
|
|
659
|
-
}, options);
|
|
660
|
-
});
|
|
681
|
+
this.collectOptions(devId, model, options =>
|
|
682
|
+
statedesc.prepublish(devId, value, newvalue =>
|
|
683
|
+
this.updateState(devId, stateID, newvalue, common), options));
|
|
661
684
|
} else {
|
|
662
685
|
this.updateState(devId, stateID, value, common);
|
|
663
686
|
}
|