@evilbunny/node-red-contrib-zigbee2mqtt 3.2.8 → 3.2.9

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/nodes/server.js CHANGED
@@ -464,19 +464,49 @@ module.exports = function(RED) {
464
464
  }
465
465
 
466
466
  let useProperty = null;
467
- if (node.config.state && node.config.state !== '0') {
468
- if (item.homekit && node.config.state.split("homekit_").join('') in item.homekit) {
469
- payload = item.homekit[node.config.state.split("homekit_").join('')];
470
- useProperty = node.config.state.split("homekit_").join('');
471
- } else if (payload_all && node.config.state in payload_all) {
472
- payload = text = payload_all[node.config.state];
473
- useProperty = node.config.state;
474
- } else {
475
- //state was not found in payload (button case)
467
+ // normalize config.state: supports the new array format, and legacy single-string configs
468
+ let stateList = node.config.state;
469
+ if (typeof stateList === 'string') {
470
+ stateList = (stateList && stateList !== '0') ? [stateList] : [];
471
+ }
472
+ if (!Array.isArray(stateList)) {
473
+ stateList = [];
474
+ }
475
+
476
+ if (stateList.length) {
477
+ let collected = {};
478
+ let foundAny = false;
479
+
480
+ for (let prop of stateList) {
481
+ let homekitKey = prop.split("homekit_").join('');
482
+ if (item.homekit && homekitKey in item.homekit) {
483
+ collected[prop] = item.homekit[homekitKey];
484
+ useProperty = homekitKey;
485
+ foundAny = true;
486
+ } else if (payload_all && prop in payload_all) {
487
+ collected[prop] = payload_all[prop];
488
+ useProperty = prop;
489
+ foundAny = true;
490
+ }
491
+ // if a requested property isn't present on this message, it's just skipped
492
+ // (e.g. a button event won't carry every sensor property)
493
+ }
494
+
495
+ if (!foundAny) {
496
+ //none of the requested properties were found in payload (button case)
476
497
  //payload: { last_seen: '2022-07-27T15:25:22+03:00', linkquality: 36 }
477
498
  //payload: { action: 'single', last_seen: '2022-07-27T15:25:22+03:00', linkquality: 36 }
478
499
  return;
479
500
  }
501
+
502
+ if (stateList.length === 1) {
503
+ // preserve existing behaviour: a single selected property outputs its raw value
504
+ payload = text = collected[stateList[0]];
505
+ } else {
506
+ // multiple selected properties: output an object containing just those keys
507
+ payload = collected;
508
+ useProperty = null; // unit-suffix below only makes sense for a single property
509
+ }
480
510
  } else {
481
511
  payload = payload_all;
482
512
  }
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  },
6
6
  "name": "@evilbunny/node-red-contrib-zigbee2mqtt",
7
7
  "description": "Fork of Zigbee2mqtt connectivity nodes for node-red",
8
- "version": "3.2.8",
8
+ "version": "3.2.9",
9
9
  "publishConfig": {
10
10
  "access": "public"
11
11
  },
@@ -148,11 +148,10 @@ class Zigbee2MqttEditor {
148
148
  numberDisplayed: 1,
149
149
  dropWidth: 320,
150
150
  width: 320,
151
- single: !(typeof $(this).attr('multiple') !== typeof undefined && $(this).attr('multiple') !== false)
151
+ single: false, // always allow selecting multiple attributes; no selection = complete payload
152
+ placeholder: RED._("@evilbunny/node-red-contrib-zigbee2mqtt/server:editor.complete_payload")
152
153
  }).multipleSelect('disable');
153
154
 
154
- that.getDevicePropertyInput().html('<option value="0">'+ RED._("@evilbunny/node-red-contrib-zigbee2mqtt/server:editor.complete_payload")+'</option>');
155
-
156
155
  let html = '';
157
156
  let device = that.getDevice();
158
157
 
@@ -187,11 +186,22 @@ class Zigbee2MqttEditor {
187
186
  });
188
187
  }
189
188
  that.getDevicePropertyInput().multipleSelect('enable');
190
- if (that.getDevicePropertyInput().find('option[value='+that.property+']').length) {
191
- that.getDevicePropertyInput().val(that.property);
192
- } else {
193
- that.getDevicePropertyInput().val(that.getDevicePropertyInput().find('option').eq(0).attr('value'));
189
+
190
+ // restore previously-selected properties (supports legacy single-string values too)
191
+ let selected = that.property;
192
+ if (typeof selected === 'string') {
193
+ selected = selected && selected !== '0' ? [selected] : [];
194
194
  }
195
+ if (!Array.isArray(selected)) {
196
+ selected = [];
197
+ }
198
+ // drop any selections that no longer exist as options for this device
199
+ selected = selected.filter(function(value) {
200
+ return that.getDevicePropertyInput().find('option[value="' + value + '"]').length > 0;
201
+ });
202
+ that.property = selected;
203
+ that.getDevicePropertyInput().multipleSelect('setSelects', selected);
204
+
195
205
  that.getDevicePropertyInput().multipleSelect('refresh');
196
206
  }
197
207
 
@@ -1,416 +0,0 @@
1
- class Zigbee2MqttEditor {
2
- constructor(node, config = {}) {
3
- this.node = node;
4
- this.devices = null;
5
- this.config = Object.assign( {
6
- allow_empty:false
7
- }, config);
8
- this.device_id = node.device_id||null;
9
- this.property = node.state||null;
10
- this.optionsValue = node.optionsValue||null;
11
- this.optionsType = node.optionsType||null;
12
- this.refresh = false;
13
-
14
- return this;
15
- }
16
-
17
- bind() {
18
- let that = this;
19
- that.getRefreshBtn().off('click').on('click', () => {
20
- that.refresh = true;
21
- that.build();
22
- });
23
- that.getServerInput().off('change').on('change', (e) => {
24
- // console.log('bind: getServerInput');
25
- that.property = null;
26
- that.refresh = true;
27
- that.build();
28
- });
29
- that.getDeviceIdInput().off('change').on('change', () => {
30
- that.device_id = that.getDeviceIdInput().multipleSelect('getSelects', 'value');
31
- if (!that.isMultiple()) { //no need to build for multiple
32
- // console.log('bind: getDeviceIdInput');
33
- that.build();
34
- } else {
35
- that.setFriendlyName();
36
- }
37
- });
38
- if (that.getDeviceOptionsInput()) {
39
- that.getDeviceOptionsInput().off('change').on('change', (event, type, value) => {
40
- // console.log('bind: getDeviceOptionsInput');
41
- that.optionsValue = value;
42
- that.optionsType = type;
43
- that.buildDeviceOptionsHelpBlock();
44
- });
45
- }
46
- that.getEnableMultipleCheckbox().off('change').on('change', (e) => {
47
- // that.property = null;
48
- // that.refresh = true;
49
- // console.log('bind: getEnableMultipleCheckbox');
50
- that.build();
51
- });
52
- }
53
-
54
- async build() {
55
- let that = this;
56
- // console.log('build : '+(this.refresh?'true':false));
57
- await that.buildDeviceIdInput().then(()=>{
58
- that.buildDevicePropertyInput();
59
- that.buildDeviceOptionsInput();
60
- });
61
- that.bind();
62
- }
63
-
64
- async buildDeviceIdInput() {
65
- let that = this;
66
- that.getFilterChanges().closest('.form-row').toggle(!that.isMultiple());
67
- // console.log('BUILD buildDeviceIdInput');
68
-
69
- let params = {
70
- single: !that.isMultiple(),
71
- minimumCountSelected: !that.isMultiple()?1:0,
72
- numberDisplayed: 1,
73
- maxHeight: 300,
74
- dropWidth: 320,
75
- width: 320,
76
- filter: true,
77
- formatAllSelected:function(){return RED._("@evilbunny/zigbee2mqtt/server:editor.select_device")}
78
- };
79
- if (that.config.allow_empty && !that.isMultiple()) {
80
- params.formatAllSelected = function(){return RED._("@evilbunny/zigbee2mqtt/server:editor.msg_topic")};
81
- }
82
-
83
- that.getDeviceIdInput().children().remove();
84
- that.getDeviceIdInput().multipleSelect('destroy').multipleSelect(params).multipleSelect('disable');
85
-
86
- let data = await that.getDevices();
87
-
88
- if (that.config.allow_empty && !that.isMultiple()) {
89
- that.getDeviceIdInput().html('<option value="msg.topic">msg.topic</option>');
90
- }
91
-
92
- let html = '';
93
-
94
- //groups
95
- let groups = data[1];
96
- groups.sort(function(a, b) {
97
- return (a.friendly_name || '').localeCompare(b.friendly_name || '', undefined, {sensitivity: 'base'});
98
- });
99
- if (groups.length) {
100
- html = $('<optgroup/>', {label: RED._("@evilbunny/zigbee2mqtt/server:editor.groups")});
101
- html.appendTo(that.getDeviceIdInput());
102
- $.each(groups, function(index, value) {
103
- let text = '';
104
- if ("devices" in value && typeof (value.devices) != 'undefined' && value.devices.length > 0) {
105
- text = ' (' + value.devices.length + ')';
106
- }
107
- $('<option value="' + value.id + '" data-friendly_name="' + value.friendly_name + '">' + value.friendly_name + text + '</option>')
108
- .appendTo(html);
109
- });
110
- }
111
-
112
- //devices
113
- let devices = data[0];
114
- devices.sort(function(a, b) {
115
- return (a.friendly_name || '').localeCompare(b.friendly_name || '', undefined, {sensitivity: 'base'});
116
- });
117
- if (devices.length) {
118
- html = $('<optgroup/>', {label: RED._("@evilbunny/zigbee2mqtt/server:editor.devices")});
119
- html.appendTo(that.getDeviceIdInput());
120
- $.each(devices, function(index, value) {
121
- var model = '';
122
- if ("definition" in value && value.definition && "model" in value.definition && typeof (value.definition.model) !== undefined) {
123
- model = ' (' + value.definition.model + ')';
124
- }
125
- $('<option value="' + value.ieee_address + '" data-friendly_name="' + value.friendly_name + '">' + value.friendly_name + model + '</option>')
126
- .appendTo(html);
127
- });
128
- }
129
-
130
- that.getDeviceIdInput().multipleSelect('enable');
131
- that.getDeviceIdInput().multipleSelect('refresh');
132
-
133
- that.setDeviceValue();
134
- that.setFriendlyName();
135
- return this;
136
- }
137
-
138
- async buildDevicePropertyInput() {
139
- let that = this;
140
- if (!that.getDevicePropertyInput()) return;
141
- that.getDevicePropertyInput().closest('.form-row').toggle(!that.isMultiple());
142
- if (that.isMultiple()) return;
143
-
144
- // console.log('BUILD buildDevicePropertyInput');
145
-
146
- that.getDevicePropertyInput().children().remove();
147
- that.getDevicePropertyInput().multipleSelect('destroy').multipleSelect({
148
- numberDisplayed: 1,
149
- dropWidth: 320,
150
- width: 320,
151
- single: !(typeof $(this).attr('multiple') !== typeof undefined && $(this).attr('multiple') !== false)
152
- }).multipleSelect('disable');
153
-
154
- that.getDevicePropertyInput().html('<option value="0">'+ RED._("@evilbunny/zigbee2mqtt/server:editor.complete_payload")+'</option>');
155
-
156
- let html = '';
157
- let device = that.getDevice();
158
-
159
- if (device && 'definition' in device && device.definition && 'exposes' in device.definition) {
160
- html = $('<optgroup/>', {label: RED._("@evilbunny/zigbee2mqtt/server:editor.zigbee2mqtt")});
161
- html.appendTo(that.getDevicePropertyInput());
162
-
163
- $.each(device.definition.exposes, function(index, value) {
164
- if ('features' in value) {
165
- $.each(value.features, function(index2, value2) {
166
- if ('property' in value2) {
167
- let endpoint = value.endpoint || value2.endpoint;
168
- let label = value2.property + (value2.unit ? ', ' + value2.unit : '');
169
- $('<option value="' + value2.property + '">' + label + '</option>')
170
- .appendTo(html);
171
- }
172
- });
173
- } else if ('property' in value) {
174
- let label = value.property + (value.unit ? ', ' + value.unit : '');
175
- $('<option value="' + value.property + '">' + label + '</option>')
176
- .appendTo(html);
177
- }
178
- });
179
- }
180
-
181
- if (device && 'homekit' in device && device.homekit && Object.keys(device.homekit).length) {
182
- html = $('<optgroup/>', {label: RED._("@evilbunny/zigbee2mqtt/server:editor.homekit")});
183
- html.appendTo(that.getDevicePropertyInput());
184
-
185
- $.each(device.homekit, function (index, value) {
186
- $('<option value="homekit_' + index + '">' + index + '</option>').appendTo(html);
187
- });
188
- }
189
- that.getDevicePropertyInput().multipleSelect('enable');
190
- if (that.getDevicePropertyInput().find('option[value='+that.property+']').length) {
191
- that.getDevicePropertyInput().val(that.property);
192
- } else {
193
- that.getDevicePropertyInput().val(that.getDevicePropertyInput().find('option').eq(0).attr('value'));
194
- }
195
- that.getDevicePropertyInput().multipleSelect('refresh');
196
- }
197
-
198
- buildLabel(name, property, unit) {
199
- let label = name + (unit ? ', ' + unit : '');
200
- if (property !== name) {
201
- // e.g. property "state_l1" vs name "state" -> suffix "l1"
202
- // or property "inching_control_1" vs name "inching_control" -> suffix "1"
203
- let suffix = property.slice(name.length).replace(/^_/, '');
204
- if (suffix) {
205
- label += ' (' + suffix + ')';
206
- }
207
- }
208
- return label;
209
- }
210
-
211
- buildDeviceOptionsInput() {
212
- let that = this;
213
- if (!that.getDeviceOptionsInput()) return;
214
- that.getDeviceOptionsTypeHelpBlock().hide().find('div').text('').closest('.form-tips').find('span').text('');
215
- that.getDeviceOptionsInput().closest('.form-row').toggle(!that.isMultiple());
216
- if (that.isMultiple()) return;
217
-
218
- // console.log('BUILD buildDeviceOptionsInput');
219
- let device = that.getDevice();
220
- let options = [];
221
- options.push({'value': 'nothing', 'label': RED._("@evilbunny/zigbee2mqtt/server:editor.nothing"), options:['']});
222
- options.push('msg');
223
- options.push('json');
224
- if (device && 'definition' in device && device.definition && 'options' in device.definition) {
225
- $.each(device.definition.options, function(k, v) {
226
- options.push({'value': v.property, 'label': v.name});
227
- });
228
- }
229
- that.getDeviceOptionsInput().typedInput({
230
- default: 'nothing',
231
- value: that.optionsType,
232
- typeField: that.getDeviceOptionsTypeInput(),
233
- });
234
- that.getDeviceOptionsInput().typedInput('types', options);
235
- that.getDeviceOptionsInput().typedInput('type', that.optionsType || 'nothing');
236
- that.getDeviceOptionsInput().typedInput('value', that.optionsValue || '');
237
- that.buildDeviceOptionsHelpBlock();
238
- }
239
-
240
- buildDeviceOptionsHelpBlock() {
241
- let that = this;
242
- if (!that.getDeviceOptionsTypeHelpBlock()) return;
243
-
244
- that.getDeviceOptionsTypeHelpBlock().hide().find('div').text('').closest('.form-tips').find('span').text('');
245
- if (that.isMultiple()) return;
246
-
247
- // console.log('BUILD buildDeviceOptionsHelpBlock');
248
-
249
- let device = that.getDevice();
250
- let selectedOption = null;
251
- if (device && 'definition' in device && device.definition && 'options' in device.definition) {
252
- $.each(device.definition.options, function(k, v) {
253
- if ('json' === that.optionsType) {
254
- let json = {};
255
- $.each(device.definition.options, function(k, v2) {
256
- if ('property' in v2) {
257
- let defaultVal = '';
258
- if ('type' in v2) {
259
- if (v2.type==='numeric') {
260
- defaultVal = 0;
261
- if ('value_min' in v2) {
262
- defaultVal = v2.value_min;
263
- }
264
- } else if (v2.type==='binary') {
265
- defaultVal = false;
266
- }
267
- }
268
- json[v2.property] = defaultVal;
269
- }
270
- });
271
- selectedOption = {'name':'JSON', 'description':JSON.stringify(json, null, 4)};
272
- return false;
273
- }
274
- if (v.property === that.optionsType) {
275
- selectedOption = v;
276
- return false;
277
- }
278
- });
279
- }
280
-
281
- if (selectedOption && 'description' in selectedOption && selectedOption.description) {
282
- that.getDeviceOptionsTypeHelpBlock().show().find('div').text(selectedOption.name).closest('.form-tips').find('span').text(selectedOption.description);
283
- }
284
- }
285
-
286
- async getDevices() {
287
- let that = this;
288
- if (that.devices === null || that.refresh) {
289
- const response = await fetch('/zigbee2mqtt-eb/getDevices?' + new URLSearchParams({
290
- controllerID: that.getServerInput().val()
291
- }).toString(), {
292
- method: 'GET',
293
- cache: 'no-cache',
294
- headers: {
295
- 'Content-Type': 'application/json'
296
- }
297
- });
298
- that.refresh = false;
299
- that.devices = await response.json();
300
- return that.devices;
301
- } else {
302
- return await new Promise(function(resolve, reject) {
303
- resolve(that.devices);
304
- });
305
- }
306
- }
307
-
308
- getDevice() {
309
- let that = this;
310
- let devices = that.devices[0];
311
- let device = null;
312
-
313
- if (devices.length && that.device_id) {
314
- let selectedDevice = typeof(that.device_id) === 'object' ? that.device_id[0] : that.device_id;
315
- $.each(devices, function (index, item) {
316
- if (item.ieee_address === selectedDevice) {
317
- device = item;
318
- return false;
319
- }
320
- });
321
- }
322
- return device;
323
- }
324
-
325
- getDeviceIdInput() {
326
- return $('#node-input-device_id');
327
- }
328
-
329
- getDevicePropertyInput() {
330
- let $elem = $('#node-input-state');
331
- return $elem.length?$elem:null;
332
- }
333
-
334
- getDeviceOptionsInput() {
335
- let $elem = $('#node-input-optionsValue');
336
- return $elem.length?$elem:null;
337
- }
338
-
339
- getDeviceOptionsTypeInput() {
340
- let $elem = $('#node-input-optionsType');
341
- return $elem.length?$elem:null;
342
- }
343
-
344
- getDeviceOptionsTypeHelpBlock() {
345
- return $('.optionsType_description');
346
- }
347
-
348
- getDeviceFriendlyNameInput() {
349
- return $('#node-input-friendly_name');
350
- }
351
-
352
- getServerInput() {
353
- return $('#node-input-server');
354
- }
355
-
356
- getRefreshBtn() {
357
- return $('#force-refresh');
358
- }
359
-
360
- getFilterChanges() {
361
- return $('#node-input-filterChanges');
362
- }
363
-
364
- getEnableMultipleCheckbox() {
365
- return $('#node-input-enableMultiple');
366
- }
367
-
368
- isMultiple() {
369
- return this.getEnableMultipleCheckbox().is(':checked');
370
- }
371
-
372
- setDeviceValue() {
373
- let that = this;
374
- if (that.isMultiple()) {
375
- if (typeof(that.device_id) == 'string') {
376
- that.device_id = [that.device_id];
377
- }
378
- if (that.device_id) {
379
- that.getDeviceIdInput().multipleSelect('setSelects', that.device_id);
380
- }
381
- } else if (that.device_id && that.device_id.length) {
382
- if (typeof(that.device_id) == 'object') {
383
- that.device_id = that.device_id[0]; //get the first device
384
- }
385
- if (that.getDeviceIdInput().find('option[value="'+that.device_id+'"]').length) {
386
- that.getDeviceIdInput().val(that.device_id);
387
- }
388
- // that.getDeviceIdInput().multipleSelect('check', that.device_id); //does not work
389
- that.getDeviceIdInput().multipleSelect('refresh');
390
- } else {
391
- that.device_id = null;
392
- }
393
- }
394
-
395
- setFriendlyName() {
396
- let that = this;
397
- if (that.isMultiple()) {
398
- if (typeof(that.device_id) == 'string') {
399
- that.device_id = [that.device_id];
400
- }
401
- if (!that.device_id) {
402
- that.device_id = [];
403
- }
404
- that.getDeviceFriendlyNameInput().val(that.device_id.length + ' ' + RED._("@evilbunny/zigbee2mqtt/server:editor.selected"));
405
- } else if (that.device_id && that.device_id.length) {
406
- if (typeof(that.device_id) == 'object') {
407
- that.device_id = that.device_id[0]; //get the first device
408
- }
409
- if (that.getDeviceIdInput().find('option[value="'+that.device_id+'"]').length) {
410
- that.getDeviceFriendlyNameInput().val(that.getDeviceIdInput().multipleSelect('getSelects', 'text'));
411
- }
412
- } else {
413
- that.getDeviceFriendlyNameInput().val('');
414
- }
415
- }
416
- }