node-red-contrib-alarm-ultimate 0.1.1 → 0.1.2

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.
@@ -467,4 +467,55 @@ describe('AlarmSystemUltimate node', function () {
467
467
  })
468
468
  .catch(done);
469
469
  });
470
+
471
+ it('emits zone open/close events while disarmed', function (done) {
472
+ const flowId = 'alarm-zone-events';
473
+ const flow = [
474
+ { id: flowId, type: 'tab', label: 'alarm-zone-events' },
475
+ {
476
+ id: 'alarm',
477
+ type: 'AlarmSystemUltimate',
478
+ z: flowId,
479
+ controlTopic: 'alarm',
480
+ requireCodeForDisarm: false,
481
+ zones: '{"id":"front","name":"Front","topic":"sensor/frontdoor","type":"perimeter","entry":false}',
482
+ wires: [[], [], [], [], ['zoneEvents']],
483
+ },
484
+ { id: 'zoneEvents', type: 'helper', z: flowId },
485
+ ];
486
+
487
+ loadAlarm(flow)
488
+ .then(() => {
489
+ const alarm = helper.getNode('alarm');
490
+ const zoneEvents = helper.getNode('zoneEvents');
491
+
492
+ const seen = [];
493
+ zoneEvents.on('input', (msg) => {
494
+ seen.push(msg);
495
+ });
496
+
497
+ // Default mode is disarmed. We should still see zone_open/zone_close.
498
+ alarm.receive({ topic: 'sensor/frontdoor', payload: true });
499
+ setTimeout(() => {
500
+ alarm.receive({ topic: 'sensor/frontdoor', payload: false });
501
+ }, 30);
502
+
503
+ setTimeout(() => {
504
+ try {
505
+ const events = seen.map((m) => m.event).filter(Boolean);
506
+ expect(events).to.include('zone_open');
507
+ expect(events).to.include('zone_close');
508
+ const openEvt = seen.find((m) => m && m.event === 'zone_open');
509
+ expect(openEvt).to.be.an('object');
510
+ expect(openEvt.payload).to.be.an('object');
511
+ expect(openEvt.payload.zone).to.be.an('object');
512
+ expect(openEvt.payload.zone.topic).to.equal('sensor/frontdoor');
513
+ done();
514
+ } catch (err) {
515
+ done(err);
516
+ }
517
+ }, 120);
518
+ })
519
+ .catch(done);
520
+ });
470
521
  });
@@ -0,0 +1,243 @@
1
+ 'use strict';
2
+
3
+ const { expect } = require('chai');
4
+ const { helper } = require('./helpers');
5
+
6
+ const adapterNode = require('../nodes/AlarmUltimateInputAdapter.js');
7
+
8
+ function loadAdapter(flow, credentials) {
9
+ const normalizedFlow = flow.map((node, index) => {
10
+ if (
11
+ node &&
12
+ node.type &&
13
+ node.type !== 'tab' &&
14
+ node.type !== 'subflow' &&
15
+ node.type !== 'group' &&
16
+ node.z &&
17
+ !(Object.prototype.hasOwnProperty.call(node, 'x') && Object.prototype.hasOwnProperty.call(node, 'y'))
18
+ ) {
19
+ return { ...node, x: 100 + index * 10, y: 100 + index * 10 };
20
+ }
21
+ return node;
22
+ });
23
+ return helper.load(adapterNode, normalizedFlow, credentials || {});
24
+ }
25
+
26
+ describe('AlarmUltimateInputAdapter node', function () {
27
+ this.timeout(5000);
28
+
29
+ before(function (done) {
30
+ helper.startServer(done);
31
+ });
32
+
33
+ after(function (done) {
34
+ helper.stopServer(done);
35
+ });
36
+
37
+ afterEach(function () {
38
+ return helper.unload();
39
+ });
40
+
41
+ it('applies built-in preset (Home Assistant on/off)', function (done) {
42
+ const flowId = 'adapter1';
43
+ const flow = [
44
+ { id: flowId, type: 'tab', label: 'adapter1' },
45
+ {
46
+ id: 'adapter',
47
+ type: 'AlarmUltimateInputAdapter',
48
+ z: flowId,
49
+ presetSource: 'builtin',
50
+ presetId: 'home_assistant_on_off',
51
+ wires: [['out']],
52
+ },
53
+ { id: 'out', type: 'helper', z: flowId },
54
+ ];
55
+
56
+ loadAdapter(flow)
57
+ .then(() => {
58
+ const adapter = helper.getNode('adapter');
59
+ const out = helper.getNode('out');
60
+
61
+ out.on('input', (msg) => {
62
+ try {
63
+ expect(msg).to.be.an('object');
64
+ expect(msg.topic).to.equal('sensor/frontdoor');
65
+ expect(msg.payload).to.equal(true);
66
+ done();
67
+ } catch (err) {
68
+ done(err);
69
+ }
70
+ });
71
+
72
+ adapter.receive({ topic: 'sensor/frontdoor', payload: 'on' });
73
+ })
74
+ .catch(done);
75
+ });
76
+
77
+ it('applies built-in preset (KNX Ultimate)', function (done) {
78
+ const flowId = 'adapter3';
79
+ const flow = [
80
+ { id: flowId, type: 'tab', label: 'adapter3' },
81
+ {
82
+ id: 'adapter',
83
+ type: 'AlarmUltimateInputAdapter',
84
+ z: flowId,
85
+ presetSource: 'builtin',
86
+ presetId: 'knx_ultimate',
87
+ wires: [['out']],
88
+ },
89
+ { id: 'out', type: 'helper', z: flowId },
90
+ ];
91
+
92
+ loadAdapter(flow)
93
+ .then(() => {
94
+ const adapter = helper.getNode('adapter');
95
+ const out = helper.getNode('out');
96
+
97
+ out.on('input', (msg) => {
98
+ try {
99
+ expect(msg).to.be.an('object');
100
+ expect(msg.topic).to.equal('0/1/2');
101
+ expect(msg.payload).to.equal(false);
102
+ done();
103
+ } catch (err) {
104
+ done(err);
105
+ }
106
+ });
107
+
108
+ adapter.receive({
109
+ topic: '0/1/2',
110
+ payload: false,
111
+ previouspayload: true,
112
+ payloadmeasureunit: '%',
113
+ payloadsubtypevalue: 'Start',
114
+ devicename: 'Dinning table lamp',
115
+ gainfo: {
116
+ maingroupname: 'Light actuators',
117
+ middlegroupname: 'First flow lights',
118
+ ganame: 'Table Light',
119
+ maingroupnumber: '1',
120
+ middlegroupnumber: '1',
121
+ ganumber: '0',
122
+ },
123
+ knx: {
124
+ event: 'GroupValue_Write',
125
+ dpt: '1.001',
126
+ dptdesc: 'Humidity',
127
+ source: '15.15.22',
128
+ destination: '0/1/2',
129
+ rawValue: { 0: '0x0' },
130
+ },
131
+ });
132
+ })
133
+ .catch(done);
134
+ });
135
+
136
+ it('applies built-in preset (AX Pro from Hikvision-Ultimate)', function (done) {
137
+ const flowId = 'adapter4';
138
+ const flow = [
139
+ { id: flowId, type: 'tab', label: 'adapter4' },
140
+ {
141
+ id: 'adapter',
142
+ type: 'AlarmUltimateInputAdapter',
143
+ z: flowId,
144
+ presetSource: 'builtin',
145
+ presetId: 'axpro_hikvision_ultimate',
146
+ wires: [['out']],
147
+ },
148
+ { id: 'out', type: 'helper', z: flowId },
149
+ ];
150
+
151
+ loadAdapter(flow)
152
+ .then(() => {
153
+ const adapter = helper.getNode('adapter');
154
+ const out = helper.getNode('out');
155
+
156
+ out.on('input', (msg) => {
157
+ try {
158
+ expect(msg).to.be.an('object');
159
+ expect(msg.topic).to.equal('Cancello#2/7/12');
160
+ expect(msg.payload).to.equal(false);
161
+ expect(msg.zoneUpdate).to.be.an('object');
162
+ expect(msg.zoneUpdate.id).to.equal(9);
163
+ done();
164
+ } catch (err) {
165
+ done(err);
166
+ }
167
+ });
168
+
169
+ adapter.receive({
170
+ payload: {
171
+ zoneUpdate: {
172
+ id: 9,
173
+ name: 'Cancello#2/7/12',
174
+ status: 'online',
175
+ sensorStatus: 'normal',
176
+ magnetOpenStatus: false,
177
+ tamperEvident: false,
178
+ shielded: false,
179
+ bypassed: false,
180
+ armed: false,
181
+ isArming: false,
182
+ alarm: false,
183
+ subSystemNo: 5,
184
+ linkageSubSystem: [5],
185
+ detectorType: 'magneticContact',
186
+ stayAway: false,
187
+ zoneType: 'Instant',
188
+ accessModuleType: 'localTransmitter',
189
+ moduleChannel: 9,
190
+ zoneAttrib: 'wired',
191
+ deviceNo: 21,
192
+ abnormalOrNot: false,
193
+ },
194
+ },
195
+ _msgid: '5f34ea7333772aeb',
196
+ });
197
+ })
198
+ .catch(done);
199
+ });
200
+
201
+ it('applies user preset stored in node config', function (done) {
202
+ const flowId = 'adapter2';
203
+ const userCode = [
204
+ 'if (!msg || typeof msg !== "object") return;',
205
+ 'const topic = msg.payload && msg.payload.topic ? msg.payload.topic : msg.topic;',
206
+ 'const open = msg.payload && msg.payload.state === "open";',
207
+ 'return { topic, payload: open };',
208
+ ].join('\n');
209
+
210
+ const flow = [
211
+ { id: flowId, type: 'tab', label: 'adapter2' },
212
+ {
213
+ id: 'adapter',
214
+ type: 'AlarmUltimateInputAdapter',
215
+ z: flowId,
216
+ presetSource: 'user',
217
+ presetId: 'custom',
218
+ userCode,
219
+ wires: [['out']],
220
+ },
221
+ { id: 'out', type: 'helper', z: flowId },
222
+ ];
223
+
224
+ loadAdapter(flow)
225
+ .then(() => {
226
+ const adapter = helper.getNode('adapter');
227
+ const out = helper.getNode('out');
228
+
229
+ out.on('input', (msg) => {
230
+ try {
231
+ expect(msg.topic).to.equal('sensor/door');
232
+ expect(msg.payload).to.equal(true);
233
+ done();
234
+ } catch (err) {
235
+ done(err);
236
+ }
237
+ });
238
+
239
+ adapter.receive({ payload: { topic: 'sensor/door', state: 'open' } });
240
+ })
241
+ .catch(done);
242
+ });
243
+ });
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const { expect } = require('chai');
3
4
  const { helper } = require('./helpers');
4
5
 
5
6
  const alarmNode = require('../nodes/AlarmSystemUltimate.js');
@@ -119,9 +120,11 @@ describe('Alarm Ultimate output-only nodes', function () {
119
120
  try {
120
121
  if (String(msg.reason || '').startsWith('init') && msg.payload === false) {
121
122
  seen.initialZoneClosed = true;
123
+ expect(msg.topic).to.equal('alarm/zone/sensor/frontdoor');
122
124
  }
123
125
  if (msg.payload === true && msg.zone && msg.zone.id === 'front') {
124
126
  seen.zoneOpen = true;
127
+ expect(msg.topic).to.equal('alarm/zone/sensor/frontdoor');
125
128
  }
126
129
  maybeDone();
127
130
  } catch (err) {