node-red-contrib-zwave-js 8.0.0-beta.4 → 8.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/CHANGELOG.md CHANGED
@@ -11,6 +11,8 @@
11
11
  ]
12
12
  ```
13
13
  previously this was ```[<number>, <Desired Value>, <Number>]```
14
+ - **ValueAPI** is now default on the CMD Factory node.
15
+ If you have problems with this node after the update, please open it up, and select your API, then save.
14
16
 
15
17
  **New Features**
16
18
  - Default scales can now be applied inside the controller config
@@ -19,6 +21,9 @@
19
21
  - 2 new event types have been added : **ALIVE**, **DEAD** - these allow you to monitor if a device has been marked dead
20
22
  or alive accordingly.
21
23
  - Added a color chooser for color input types in the UI
24
+ - Added a new **Driver** function **getLastEvents**
25
+ - Device last seen timestamp is now shown in the UI
26
+ - Node list now contains the date when the device was last seen.
22
27
 
23
28
  **Bug Fixes**
24
29
  - Modal alerts are now rendering HTML content once again
@@ -30,6 +35,7 @@
30
35
  - Current Value(s) who's type is an object have been changed to prompt for a double click in the UI
31
36
  - Association Management has been updated, and changes are now applied in batch.
32
37
  - For battery operated devices, certain UI actions now ask you to wake up said device before anything is comitted.
38
+ - Various performance boosts in the UI
33
39
  - Bump ZWave JS to V10
34
40
  - Bump Winston
35
41
  - Bump ESlint
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "node-red-contrib-zwave-js",
3
- "version": "8.0.0-beta.4",
3
+ "version": "8.0.0",
4
4
  "license": "MIT",
5
5
  "description": "The most powerful, high performing and highly polished Z-Wave node for Node-RED based on Z-Wave JS. If you want a fully featured Z-Wave framework in your Node-RED instance, you have found it.",
6
6
  "dependencies": {
7
7
  "limiter": "^2.1.0",
8
8
  "lodash": "^4.17.21",
9
9
  "serialport": "^10.4.0",
10
- "winston": "^3.8.1",
10
+ "winston": "^3.8.2",
11
11
  "winston-transport": "^4.5.0",
12
- "zwave-js": "^10.0.1"
12
+ "zwave-js": "^10.2.0"
13
13
  },
14
14
  "devDependencies": {
15
- "eslint": "^8.22.0",
15
+ "eslint": "^8.23.1",
16
16
  "prettier": "^2.7.1"
17
17
  },
18
18
  "scripts": {
@@ -27,6 +27,7 @@ function Render(Base) {
27
27
  name: 'Controller',
28
28
  nameOnly: `Controller`,
29
29
  location: `Hopefully away from interference.`,
30
+ lastSeen: '0',
30
31
  fontSize: '12px',
31
32
  icon: `${Base}/Stick.png`,
32
33
  powerSource: { type: 'bus' },
@@ -52,6 +53,12 @@ function Render(Base) {
52
53
  path: []
53
54
  }
54
55
  };
56
+ if(N.lastSeen > 0){
57
+ EL.data.lastSeen = new Date(N.lastSeen).toLocaleString();
58
+ }
59
+ else{
60
+ EL.data.lastSeen = 'Never';
61
+ }
55
62
 
56
63
  Elements.push(EL);
57
64
 
@@ -308,6 +315,9 @@ function LoadData() {
308
315
  $(`<li>Timeouts: ${this.data('statistics').timeoutResponse}</li>`).appendTo(
309
316
  NetStatsDetails
310
317
  );
318
+
319
+ // Node details addition
320
+ $(`<li>Last Seen: ${this.data('lastSeen')}</li>`).appendTo(NodeDetails);
311
321
  }
312
322
 
313
323
  $('#Details').html(Data);
@@ -3,7 +3,6 @@
3
3
  /*eslint no-undef: "warn"*/
4
4
  /*eslint no-unused-vars: "warn"*/
5
5
 
6
-
7
6
  /* UI Inclusion Functions */
8
7
  let StartInclusionExclusion;
9
8
  let StartReplace;
@@ -208,6 +207,11 @@ const DCs = {
208
207
  name: 'getValueMetadata',
209
208
  noWait: false
210
209
  },
210
+ getValueDB: {
211
+ API: 'DriverAPI',
212
+ name: 'getValueDB',
213
+ noWait: false
214
+ },
211
215
  getAvailableFirmwareUpdates: {
212
216
  API: 'ControllerAPI',
213
217
  name: 'getAvailableFirmwareUpdates',
@@ -539,6 +543,14 @@ const ZwaveJsUI = (function () {
539
543
  }
540
544
 
541
545
  async function PerformUpdate() {
546
+ const CurrentFWMode = $('#tabs').tabs('option', 'active');
547
+
548
+ if (CurrentFWMode === 0) {
549
+ const SelectedFW = $('#NODE_FWCV').find(':selected').data('FWTarget');
550
+ PerformUpdateFromService(SelectedFW.node, SelectedFW.file);
551
+ return;
552
+ }
553
+
542
554
  const FE = $('#FILE_FW')[0].files[0];
543
555
  const NID = parseInt($('#NODE_FW option:selected').val());
544
556
  const Target = $('#TARGET_FW').val();
@@ -992,6 +1004,7 @@ const ZwaveJsUI = (function () {
992
1004
  const _Node = {
993
1005
  controller: N.isControllerNode,
994
1006
  nodeId: N.nodeId,
1007
+ lastSeen: N.lastSeen,
995
1008
  name: N.name,
996
1009
  location: N.location,
997
1010
  powerSource: N.powerSource,
@@ -2621,7 +2634,6 @@ const ZwaveJsUI = (function () {
2621
2634
 
2622
2635
  CheckForUpdate = async () => {
2623
2636
  const Node = parseInt($('#NODE_FWC option:selected').val());
2624
-
2625
2637
  const nodeRow = $('#zwave-js-node-list').find(`[data-nodeid='${Node}']`);
2626
2638
 
2627
2639
  if (nodeRow.data().info.status.toUpperCase() === 'ASLEEP') {
@@ -2631,6 +2643,13 @@ const ZwaveJsUI = (function () {
2631
2643
  }
2632
2644
  }
2633
2645
 
2646
+ const Wait = modalPrompt(
2647
+ 'Querying Firmware Update Service..',
2648
+ 'Please wait...',
2649
+ {},
2650
+ false
2651
+ );
2652
+
2634
2653
  ControllerCMD(
2635
2654
  DCs.getAvailableFirmwareUpdates.API,
2636
2655
  DCs.getAvailableFirmwareUpdates.name,
@@ -2639,86 +2658,54 @@ const ZwaveJsUI = (function () {
2639
2658
  DCs.getAvailableFirmwareUpdates.noWait
2640
2659
  )
2641
2660
  .then(({ object }) => {
2661
+ Wait.dialog('destroy');
2662
+
2663
+ const ShowCL = function () {
2664
+ const SelectedFW = $('#NODE_FWCV')
2665
+ .find(':selected')
2666
+ .data('FWTarget');
2667
+ const List = $('<ul>');
2668
+ for (let i = 0; i < SelectedFW.cl.length; i++) {
2669
+ List.append(`<li>${SelectedFW.cl[i]}</li>`);
2670
+ }
2642
2671
 
2643
- if (object.length > 0) {
2644
- $("#FWs").remove();
2645
- const FWList = $('<div id="FWs">');
2646
-
2647
- const FWs = object;
2648
- FWs.forEach((FW) =>{
2649
-
2650
- const Current = nodeRow.data().info.firmwareVersion.toString() === FW.version.toString() ? "(Current)" : "";
2651
-
2652
- FWList.append(`<h3>${FW.version} ${Current}</h3>`);
2653
- const ChangeLog = FW.changelog.split('\n');
2654
-
2655
- const Content = $("<div>");
2656
- const CL = $("<ul>");
2657
-
2658
- ChangeLog.forEach((CLE) =>{
2659
- CL.append(`<li>${CLE}</li>`);
2660
- })
2661
- Content.append(CL);
2662
-
2663
- FW.files.forEach((F) =>{
2664
- const B = $(`<label for="Target_${F.target}">Target ${F.target}</label> <input type="radio" name="fw_target" value="${F.target}" id="Target_${F.target}"> `);
2665
- B.data('FWTarget',{file:F,node:Node});
2666
- Content.append(B)
2667
- //B.checkboxradio();
2668
- })
2669
- FWList.append(Content);
2670
- })
2671
-
2672
- $(`button:contains('Check For Update')`).parent().append(FWList);
2673
- FWList.accordion();
2674
-
2675
-
2676
-
2677
- /*
2678
- //
2679
- const Container = $('<div>');
2680
- Container.append(
2681
- `Current Version <strong>${
2682
- nodeRow.data().info.firmwareVersion
2683
- }</strong> New Version <strong>${
2684
- object[0].version
2685
- }</strong><br /><br />`
2686
- );
2672
+ $('#ChangeLog').empty();
2673
+ $('#ChangeLog').append(List);
2674
+ };
2687
2675
 
2688
- Container.append('<strong>Change Log</strong>');
2676
+ $('#ChangeLog').empty();
2677
+ $('#NODE_FWCV').empty();
2678
+ $('#NODE_FWCV').append('<option>Select Version & File...</option>');
2679
+ $('#NODE_FWCV').off('change');
2680
+ $('#NODE_FWCV').change(ShowCL);
2689
2681
 
2690
- const CLL = $('<ul>');
2691
- const ChangeLogs = object[0].changelog.split('\n');
2692
- ChangeLogs.forEach((CL) => {
2693
- CLL.append(`<li>${CL.replace(/\n/g, '<br />')}</li>`);
2682
+ //
2683
+ if (object.length > 0) {
2684
+ const FWs = object;
2685
+ FWs.forEach((FW) => {
2686
+ const VG = $(`<optgroup label=' --- ${FW.version} --- '>`);
2687
+ FW.files.forEach((F) => {
2688
+ const FWF = $(`<option>`);
2689
+ FWF.data('FWTarget', {
2690
+ file: F,
2691
+ node: Node,
2692
+ cl: FW.changelog.split('\n')
2693
+ });
2694
+ FWF.text(`${FW.version} -> Target : ${F.target}`);
2695
+ VG.append(FWF);
2696
+ });
2697
+ $('#NODE_FWCV').append(VG);
2694
2698
  });
2695
- CLL.appendTo(Container);
2696
-
2697
- const Buttons = {};
2698
-
2699
- for (let i = 0; i < object[0].files.length; i++) {
2700
- const File = object[0].files[i];
2701
- Buttons[`Update (Target ${File.target})`] = function () {
2702
- PerformUpdateFromService(Node, File);
2703
- };
2704
- }
2705
-
2706
- modalPrompt(
2707
- Container,
2708
- 'Firmware Update Available',
2709
- Buttons,
2710
- true,
2711
- true
2712
- );
2713
- */
2714
2699
  } else {
2715
2700
  modalAlert(
2716
2701
  'No firmware updates are available for this Node.',
2717
2702
  'Firmware update check'
2718
2703
  );
2719
2704
  }
2705
+ //
2720
2706
  })
2721
2707
  .catch((err) => {
2708
+ Wait.dialog('destroy');
2722
2709
  modalAlert(
2723
2710
  err.responseText || err.message,
2724
2711
  'Could not check for updates'
@@ -3302,6 +3289,17 @@ const ZwaveJsUI = (function () {
3302
3289
  }
3303
3290
 
3304
3291
  function renderNode(node) {
3292
+ const NameCol = $('<div>')
3293
+ .html(node.name)
3294
+ .addClass('zwave-js-node-row-name');
3295
+
3296
+ if (node.lastSeen !== 0) {
3297
+ const DT = new Date(node.lastSeen);
3298
+ RED.popover.tooltip(NameCol, `Last seen: ${DT.toLocaleString()}`);
3299
+ } else {
3300
+ RED.popover.tooltip(NameCol, `Last seen: Never`);
3301
+ }
3302
+
3305
3303
  return $('<div>')
3306
3304
  .addClass('red-ui-treeList-label zwave-js-node-row')
3307
3305
  .attr('data-nodeid', node.nodeId)
@@ -3332,7 +3330,7 @@ const ZwaveJsUI = (function () {
3332
3330
  })
3333
3331
  .append(
3334
3332
  $('<div>').html(node.nodeId).addClass('zwave-js-node-row-id'),
3335
- $('<div>').html(node.name).addClass('zwave-js-node-row-name'),
3333
+ NameCol,
3336
3334
  $('<div>')
3337
3335
  .html(renderStatusIcon(node.status.toUpperCase()))
3338
3336
  .addClass('zwave-js-node-row-status'),
@@ -3510,13 +3508,13 @@ const ZwaveJsUI = (function () {
3510
3508
  updateNodeFetchStatus('Fetching properties...');
3511
3509
 
3512
3510
  ControllerCMD(
3513
- DCs.getDefinedValueIDs.API,
3514
- DCs.getDefinedValueIDs.name,
3515
- selectedNode,
3511
+ DCs.getValueDB.API,
3512
+ DCs.getValueDB.name,
3516
3513
  undefined,
3517
- DCs.getDefinedValueIDs.noWait
3514
+ [selectedNode],
3515
+ DCs.getValueDB.noWait
3518
3516
  )
3519
- .then(({ object }) => buildPropertyTree(object))
3517
+ .then(({ object }) => buildPropertyTree(object[0].values))
3520
3518
  .catch((err) => {
3521
3519
  modalAlert(
3522
3520
  err.responseText || err.message,
@@ -3542,27 +3540,50 @@ const ZwaveJsUI = (function () {
3542
3540
  }
3543
3541
  updateNodeFetchStatus('');
3544
3542
 
3545
- // Step 1: Make list of all supported command classes
3546
- const data = uniqBy(valueIdList, 'commandClass')
3547
- .sort((a, b) => a.commandClassName.localeCompare(b.commandClassName))
3548
- .map(({ commandClass, commandClassName }) => {
3549
- // Step 2: For each CC, get all associated properties
3550
- const propsInCC = valueIdList.filter(
3551
- (valueId) => valueId.commandClass === commandClass
3552
- );
3553
-
3554
- return {
3555
- element: renderCommandClassElement(commandClass, commandClassName),
3556
- expanded: false,
3557
- children: propsInCC.map((valueId) => {
3558
- return { element: renderPropertyElement(valueId) };
3559
- })
3560
- };
3543
+ const CCList = uniqBy(valueIdList, 'commandClass');
3544
+ CCList.sort((a, b) => a.commandClassName.localeCompare(b.commandClassName));
3545
+
3546
+ const Data = [];
3547
+ CCList.forEach((CC) => {
3548
+ Data.push({
3549
+ element: renderCommandClassElement(
3550
+ CC.commandClass,
3551
+ CC.commandClassName
3552
+ ),
3553
+ expanded: false,
3554
+ children: []
3561
3555
  });
3556
+ });
3562
3557
 
3563
- // Step 3: Render tree
3564
3558
  const propertyList = $('#zwave-js-node-properties');
3565
- propertyList.treeList('data', data);
3559
+ propertyList.treeList('data', Data);
3560
+
3561
+ let Index = 0;
3562
+ CCList.forEach((V) => {
3563
+ const CCProps = valueIdList.filter(
3564
+ (VID) => VID.commandClass === V.commandClass
3565
+ );
3566
+ CCProps.forEach((Prop) => {
3567
+ const Type = Prop.metadata.type;
3568
+ const Writeable = Prop.metadata.writeable;
3569
+ const CV = Prop.currentValue;
3570
+
3571
+ const Child = renderPropertyElement(Prop);
3572
+ propertyList
3573
+ .treeList('data')
3574
+ [Index].treeList.addChild({ element: Child });
3575
+
3576
+ if (Writeable && Type !== 'any') {
3577
+ const icon = Child.prev();
3578
+ icon.empty();
3579
+ $('<i>')
3580
+ .addClass('fa fa-pencil zwave-js-node-property-edit-button')
3581
+ .click(() => showEditor(Prop, CV))
3582
+ .appendTo(icon);
3583
+ }
3584
+ });
3585
+ Index++;
3586
+ });
3566
3587
 
3567
3588
  // Step 4: Add endpoint numbers where applicable
3568
3589
  propertyList
@@ -3619,7 +3640,25 @@ const ZwaveJsUI = (function () {
3619
3640
  return el;
3620
3641
  }
3621
3642
 
3643
+ function splitObject(valueId) {
3644
+ // delete normalizedObject
3645
+ delete valueId.normalizedObject;
3646
+
3647
+ // Value
3648
+ const Value = valueId.currentValue;
3649
+ delete valueId.currentValue;
3650
+
3651
+ // Meta
3652
+ const MetaData = JSON.parse(JSON.stringify(valueId.metadata));
3653
+ delete valueId.metadata;
3654
+
3655
+ return { valueId: valueId, currentValue: Value, metadata: MetaData };
3656
+ }
3657
+
3622
3658
  function renderPropertyElement(valueId) {
3659
+ const Split = splitObject(valueId);
3660
+ valueId = Split.valueId;
3661
+
3623
3662
  const el = $('<div>')
3624
3663
  .addClass('zwave-js-node-property')
3625
3664
  .attr('data-endpoint', valueId.endpoint)
@@ -3640,7 +3679,7 @@ const ZwaveJsUI = (function () {
3640
3679
  .text(label)
3641
3680
  .appendTo(el);
3642
3681
  $('<span>').addClass('zwave-js-node-property-value').appendTo(el);
3643
- getValue(valueId);
3682
+ getValue(valueId, Split.metadata, Split.currentValue, el);
3644
3683
  el.dblclick(function () {
3645
3684
  const data = $(this).data();
3646
3685
  const valueData = $(this).find('.zwave-js-node-property-value').data();
@@ -3679,44 +3718,12 @@ const ZwaveJsUI = (function () {
3679
3718
  return el;
3680
3719
  }
3681
3720
 
3682
- function getValue(valueId) {
3683
- ControllerCMD(
3684
- DCs.getValue.API,
3685
- DCs.getValue.name,
3686
- selectedNode,
3687
- [valueId],
3688
- DCs.getValue.noWait
3689
- )
3690
- .then(({ node, object }) => {
3691
- if (node != selectedNode) {
3692
- return;
3693
- }
3694
- updateValue({ ...valueId, currentValue: object.currentValue });
3695
- ControllerCMD(
3696
- DCs.getValueMetadata.API,
3697
- DCs.getValueMetadata.name,
3698
- selectedNode,
3699
- [valueId],
3700
- DCs.getValueMetadata.noWait
3701
- )
3702
- .then(({ node, object }) => {
3703
- if (!object.metadata || node != selectedNode) {
3704
- return;
3705
- }
3706
- updateMeta(valueId, object.metadata);
3707
- })
3708
- .catch((err) => {
3709
- modalAlert(
3710
- err.responseText || err.message,
3711
- 'Could not fetch value Metadata.'
3712
- );
3713
- throw new Error(err.responseText || err.message);
3714
- });
3715
- })
3716
- .catch((err) => {
3717
- modalAlert(err.responseText || err.message, 'Could not fetch value.');
3718
- throw new Error(err.responseText || err.message);
3719
- });
3721
+ function getValue(valueId, metadata, currentValue, el) {
3722
+ updateValue({ ...valueId, currentValue }, el);
3723
+ if (metadata === undefined) {
3724
+ return;
3725
+ }
3726
+ updateMeta(valueId, metadata, el);
3720
3727
  }
3721
3728
 
3722
3729
  function handleBattery(topic, data) {
@@ -3753,9 +3760,9 @@ const ZwaveJsUI = (function () {
3753
3760
  }
3754
3761
  }
3755
3762
 
3756
- function updateValue(valueId) {
3763
+ function updateValue(valueId, El) {
3757
3764
  // Assumes you already checked if this applies to selectedNode
3758
- const propertyRow = getPropertyRow(valueId);
3765
+ const propertyRow = El || getPropertyRow(valueId);
3759
3766
 
3760
3767
  if (!propertyRow) {
3761
3768
  // AHHH!!! What do we do now?!
@@ -3828,10 +3835,10 @@ const ZwaveJsUI = (function () {
3828
3835
  propertyValue.data('value', value);
3829
3836
  }
3830
3837
 
3831
- function updateMeta(valueId, meta = {}) {
3838
+ function updateMeta(valueId, meta = {}, El) {
3832
3839
  // Assumes you already checked if this applies to selectedNode
3833
3840
 
3834
- const propertyRow = getPropertyRow(valueId);
3841
+ const propertyRow = El || getPropertyRow(valueId);
3835
3842
  const propertyValue = propertyRow.find('.zwave-js-node-property-value');
3836
3843
 
3837
3844
  propertyRow.data('meta', meta);
@@ -3855,14 +3862,15 @@ const ZwaveJsUI = (function () {
3855
3862
  propertyValue.text(`${value} ${meta.unit}`);
3856
3863
  }
3857
3864
 
3858
- // Add "edit" icon, if applicable
3859
- const icon = propertyRow.prev();
3860
- icon.empty();
3861
- if (meta.writeable && meta.type !== 'any')
3862
- $('<i>')
3863
- .addClass('fa fa-pencil zwave-js-node-property-edit-button')
3864
- .click(() => showEditor(valueId, value))
3865
- .appendTo(icon);
3865
+ if (El === undefined) {
3866
+ const icon = propertyRow.prev();
3867
+ icon.empty();
3868
+ if (meta.writeable && meta.type !== 'any')
3869
+ $('<i>')
3870
+ .addClass('fa fa-pencil zwave-js-node-property-edit-button')
3871
+ .click(() => showEditor(valueId, value))
3872
+ .appendTo(icon);
3873
+ }
3866
3874
  }
3867
3875
 
3868
3876
  function showEditor(valueId, value) {
@@ -166,6 +166,7 @@
166
166
  border-color: rgb(75, 75, 75);
167
167
  border-style: solid;
168
168
  }
169
+
169
170
  .zwave-js-ac i {
170
171
  font-size: 16px !important;
171
172
  color: #666;
@@ -216,7 +217,7 @@
216
217
  margin-top: 50px;
217
218
  }
218
219
 
219
- .progressbar > div {
220
+ .progressbar>div {
220
221
  background-color: gray;
221
222
  width: 0%;
222
223
  height: 20px;
@@ -302,3 +303,16 @@ table#zwave-js-associations-table tr:first-of-type {
302
303
  .ui-slider-range {
303
304
  background: rgba(200, 200, 200, 0.3);
304
305
  }
306
+
307
+ #FWForm #tabs ul {
308
+ background-color: #fff !important;
309
+ }
310
+
311
+ #FWForm #tabs ul li.ui-tabs-active {
312
+ background-color: #fff !important;
313
+ }
314
+
315
+ #FWForm #tabs ul li.ui-tabs-active a {
316
+ color: #555 !important;
317
+ font-weight: bold !important;
318
+ }
@@ -1 +1 @@
1
- {"package":{"name":"node-red-contrib-zwave-js","version":"8.0.0"},"P01":{"test":true,"license":"MIT"},"P03":{"test":true},"P04":{"test":true},"P08":{"test":true},"P05":{"test":true},"P06":{"test":true,"versions":["3.0.1"]},"P07":{"test":false,"version":">=14.13.0"},"N01":{"test":true},"N02":{"test":true,"nodes":["zwave-js","zwave-device","event-filter","cmd-factory"]},"D01":{"test":true,"total":6},"D02":{"test":true},"D03":{"test":true}}
1
+ {"package":{"name":"node-red-contrib-zwave-js","version":"8.0.0"},"P01":{"test":true,"license":"MIT"},"P03":{"test":true},"P04":{"test":true},"P08":{"test":true},"P05":{"test":true},"P06":{"test":true,"versions":["3.0.2","2.2.3"]},"P07":{"test":false,"version":">=14.13.0"},"N01":{"test":true},"N02":{"test":true,"nodes":["zwave-js","zwave-device","event-filter","cmd-factory"]},"D01":{"test":true,"total":6},"D02":{"test":true},"D03":{"test":true}}
@@ -726,16 +726,17 @@
726
726
  >
727
727
  <div id="tabs">
728
728
  <ul>
729
- <li><a href="#tabs-1">Local File</a></li>
730
- <li><a href="#tabs-2">Firmware Update Service</a></li>
731
- </ul>
732
- <div id="tabs-1" style="padding:20px">
729
+ <li><a href="#tabs-1">Firmware Update Service</a></li>
730
+ <li><a href="#tabs-2">Local File</a></li>
731
+
732
+ </ul>
733
+ <div id="tabs-1" style="padding:20px">
733
734
  <table>
734
735
  <tbody>
735
736
  <tr>
736
737
  <td>Target Node</td>
737
738
  <td>
738
- <select id='NODE_FW'>
739
+ <select id='NODE_FWC' onchange="CheckForUpdate()">
739
740
  <option>Select Node...</option>
740
741
  {{#each nodes}}
741
742
  {{#if this.length}}
@@ -750,25 +751,24 @@
750
751
  </td>
751
752
  </tr>
752
753
  <tr>
753
- <td>Firmware Update Target</td>
754
+ <td>Firmware Version</td>
754
755
  <td>
755
- <input type='number' value='0' min='0' id='TARGET_FW' />
756
+ <select id='NODE_FWCV'>
757
+ <option>Select Version & File...</option>
758
+ </select>
756
759
  </td>
757
760
  </tr>
758
- <tr>
759
- <td>Firmware Update File</td>
760
- <td><input type='file' id='FILE_FW' /></td>
761
- </tr>
762
761
  </tbody>
763
762
  </table>
764
- </div>
763
+ <div id="ChangeLog"></div>
764
+ </div>
765
765
  <div id="tabs-2" style="padding:20px">
766
766
  <table>
767
767
  <tbody>
768
768
  <tr>
769
769
  <td>Target Node</td>
770
770
  <td>
771
- <select id='NODE_FWC'>
771
+ <select id='NODE_FW'>
772
772
  <option>Select Node...</option>
773
773
  {{#each nodes}}
774
774
  {{#if this.length}}
@@ -783,16 +783,18 @@
783
783
  </td>
784
784
  </tr>
785
785
  <tr>
786
- <td colspan="2"><button
787
- button
788
- style='width:150px; height:30px'
789
- class='ui-button ui-corner-all ui-widget SecrutiyButton CriticalDisable'
790
- onclick='CheckForUpdate()'
791
- >Check For Update</button></td>
786
+ <td>Firmware Update Target</td>
787
+ <td>
788
+ <input type='number' value='0' min='0' id='TARGET_FW' />
789
+ </td>
790
+ </tr>
791
+ <tr>
792
+ <td>Firmware Update File</td>
793
+ <td><input type='file' id='FILE_FW' /></td>
792
794
  </tr>
793
795
  </tbody>
794
796
  </table>
795
- </div>
797
+ </div>
796
798
  </div>
797
799
 
798
800
  <div class='progressbar' id='FWProgress'><div></div></div>
@@ -57,7 +57,7 @@ module.exports = function (RED) {
57
57
  'heal network progress'
58
58
  );
59
59
  const FWK =
60
- 'ceb8fd78b90cceb4d64cddec000630b7d692ac44e2daa0bd6e75047bdce05c7bae3b3925ef8e3e833419f593e62fe8f0';
60
+ '127c49b6f2928a6579e82ecab64a83fc94a6436f03d5cb670b8ac44412687b75f0667843';
61
61
 
62
62
  SetupGlobals(RED);
63
63
 
@@ -1028,6 +1028,7 @@ module.exports = function (RED) {
1028
1028
  isControllerNode: N.isControllerNode,
1029
1029
  supportsBeaming: N.supportsBeaming,
1030
1030
  keepAwake: N.keepAwake,
1031
+ lastSeen: N.ZWNR_lastSeen || 0,
1031
1032
  powerSource: {
1032
1033
  type: N.supportsCC(CommandClasses.Battery)
1033
1034
  ? 'battery'
@@ -1402,6 +1403,25 @@ module.exports = function (RED) {
1402
1403
  );
1403
1404
 
1404
1405
  switch (Method) {
1406
+ case 'getLastEvents':
1407
+ const PL = [];
1408
+ Driver.controller.nodes.forEach((N) => {
1409
+ if(N.isControllerNode){
1410
+ return;
1411
+ }
1412
+ const I = {
1413
+ node: N.id,
1414
+ nodeName: getNodeInfoForPayload(N.id, 'name'),
1415
+ nodeLocation: getNodeInfoForPayload(N.id, 'location'),
1416
+ timestamp: N.ZWNR_lastSeen || 0,
1417
+ event: N.ZWNR_lastEvent,
1418
+ object: N.ZWNR_lastObject
1419
+ };
1420
+ PL.push(I);
1421
+ });
1422
+ Send(undefined, 'LAST_EVENTS_RESULT', PL, send);
1423
+ break;
1424
+
1405
1425
  case 'checkLifelineHealth':
1406
1426
  const NID = Params[0];
1407
1427
  const Rounds = Params[1] || undefined;
@@ -1472,12 +1492,14 @@ module.exports = function (RED) {
1472
1492
  };
1473
1493
  const VIDs = Driver.controller.nodes.get(NID).getDefinedValueIDs();
1474
1494
  VIDs.forEach((VID) => {
1495
+ const M = Driver.controller.nodes.get(NID).getValueMetadata(VID);
1475
1496
  const V = Driver.controller.nodes.get(NID).getValue(VID);
1476
1497
  const VI = {
1477
1498
  ...VID,
1478
1499
  currentValue: V
1479
1500
  };
1480
1501
  VI.normalizedObject = buildNormalized(VI, NID);
1502
+ VI.metadata = M;
1481
1503
  G.values.push(VI);
1482
1504
  });
1483
1505
  Result.push(G);
@@ -1828,6 +1850,20 @@ module.exports = function (RED) {
1828
1850
  'GET_VALUE_METADATA_RESPONSE'
1829
1851
  ];
1830
1852
 
1853
+ const TimestampSubjects = [
1854
+ 'VALUE_NOTIFICATION',
1855
+ 'NOTIFICATION',
1856
+ 'VALUE_UPDATED',
1857
+ 'WAKE_UP',
1858
+ 'ALIVE'
1859
+ ];
1860
+
1861
+ if (TimestampSubjects.includes(Subject)) {
1862
+ Driver.controller.nodes.get(Node.id).ZWNR_lastSeen = PL.timestamp;
1863
+ Driver.controller.nodes.get(Node.id).ZWNR_lastEvent = PL.event;
1864
+ Driver.controller.nodes.get(Node.id).ZWNR_lastObject = PL.object;
1865
+ }
1866
+
1831
1867
  if (AllowedSubjectsForDNs.includes(Subject) && SendDNs) {
1832
1868
  if (IsolatedNodeId !== undefined) {
1833
1869
  Log(