@jotiotech/node-red-sia-premise-device 0.2.1 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jotiotech/node-red-sia-premise-device",
3
- "version": "0.2.1",
3
+ "version": "1.0.0",
4
4
  "engines": {
5
5
  "node": ">=12.0.0"
6
6
  },
@@ -9,7 +9,7 @@
9
9
  align: 'left',
10
10
  icon: "icon.png",
11
11
  inputs: 1,
12
- outputs: 0,
12
+ outputs: 2,
13
13
  label: function () {
14
14
  return this.name || "SIA Device";
15
15
  },
package/src/SIA_device.js CHANGED
@@ -1,98 +1,147 @@
1
- // const { Iconv } = require('iconv');
2
-
1
+ // Const { Iconv } = require('iconv');
3
2
 
4
3
  module.exports = function (RED) {
5
- const tableCRC = [
6
- 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
7
- 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
8
- 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
9
- 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
10
- 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
11
- 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
12
- 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
13
- 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
14
- 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
15
- 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
16
- 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
17
- 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
18
- 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
19
- 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
20
- 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
21
- 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
22
- 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
23
- 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
24
- 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
25
- 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
26
- 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
27
- 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
28
- 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
29
- 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
30
- 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
31
- 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
32
- 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
33
- 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
34
- 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
35
- 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
36
- 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
37
- 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
38
- ];
39
-
40
- const deviceMap = new Map();
41
- const crypto = require('crypto');
42
-
43
-
4
+ const deviceSeqNumbers = new Map();
5
+ const crypto = require('node:crypto');
6
+ const tableCRC = require('./table_crc.js');
44
7
 
45
8
  function SIADeviceNode(config) {
46
9
  RED.nodes.createNode(this, config);
47
10
 
48
11
  // TODO: eventually we should disconnect from the server after each message as there shouldn't be a high volume of messages
49
12
  const server = RED.nodes.getNode(config.server);
50
-
13
+ const pendingMessages = new Map();
51
14
  const siaConfig = server.getSIAConfig();
15
+ const node = this;
52
16
  console.log('SIA Config:', siaConfig);
53
17
 
18
+ // Cleanup on close
54
19
 
55
- server.connect();
20
+ // --- LISTENER FUNCTION ---
21
+ const dataListener = data => {
22
+ const response = data.toString();
23
+ let seq; let type;
24
+
25
+ // Format: <LF><CRC><OLLL><"ID"><seq>...
26
+ const match = response.match(/"(\*?ACK|\*?DUH|NAK)"(\d{4})/);
27
+ console.log('-----------------------------------------\n'
28
+ + ' SERVER RESPONSE \n'
29
+ + '-----------------------------------------');
56
30
 
31
+ console.log('Received message:', response);
57
32
 
33
+ if (!match) {
34
+ return;
35
+ }
58
36
 
59
- function utf8toWin1252(str) {
60
- tmpBuf = Buffer.from(str, 'utf8');
37
+ type = match[1].replace('*', ''); // "ACK", "DUH", or "NAK"
38
+ seq = match[2]; // The 4-digit sequence number as a string
39
+
40
+ // Handle uncorrectable NAK (seq 0000), likely for a timestamp error
41
+ if (type === 'NAK' && seq === '0000') {
42
+ node.warn('Received uncorrelatable NAK (seq 0000). Erroring all pending requests for this node.');
43
+ for (const [key, pending] of pendingMessages.entries()) {
44
+ if (pending.node === node) {
45
+ clearTimeout(pending.timeoutTimer);
46
+ pending.node.error('Received uncorrelatable NAK (seq 0000) from server', pending.originalMsg);
47
+ pendingMessages.delete(key);
48
+ }
49
+ }
50
+ return;
51
+ }
52
+
53
+ if (pendingMessages.has(seq)) {
54
+ const pending = pendingMessages.get(seq);
55
+ if (pending.node !== node) {
56
+ return;
57
+ } // Just in case we ever run multiple nodes
58
+
59
+ clearTimeout(pending.timeoutTimer);
60
+ switch (type) {
61
+ case 'ACK': { // Success!
62
+ pending.node.log(`ACK received for seq ${seq}`);
63
+ pending.node.send([null, {payload:{
64
+ error : false,
65
+ status : "received ACK",
66
+ response: response
67
+ }
68
+ }]); // Send to second output
69
+ break;
70
+ }
71
+
72
+ case 'NAK': { // NAK response
73
+ pending.node.error(`Received NAK from server for seq ${seq}`, pending.originalMsg);
74
+ break;
75
+ }
76
+
77
+ case 'DUH': { // DUH response
78
+ pending.node.error(`Received DUH from server for seq ${seq}`, pending.originalMsg);
79
+ break;
80
+ }
81
+ }
82
+
83
+ pendingMessages.delete(seq);
84
+ } else {
85
+ node.log(`Received unknown or stale response for seq ${seq}`);
86
+ }
87
+ };
88
+
89
+ node.on('close', done => {
90
+ if (server) {
91
+ server.removeListener('data', dataListener);
92
+ }
93
+
94
+ for (const [key, pending] of pendingMessages.entries()) {
95
+ if (pending.node === node) {
96
+ clearTimeout(pending.timeoutTimer);
97
+ pendingMessages.delete(key);
98
+ }
99
+ }
100
+
101
+ done();
102
+ });
103
+
104
+ server.connect();
105
+ server.on('data', dataListener);
106
+
107
+ // --- HELPER FUNCTIONS ---
108
+ function utf8toWin1252(string_) {
109
+ tmpBuf = Buffer.from(string_, 'utf8');
61
110
  return tmpBuf.toString('latin1');
62
111
  }
63
112
 
64
- function calculateCRCIBM16(str){
65
- data = new Buffer.from(str);
66
- let len = data.length;
113
+ function calculateCRCIBM16(string_) {
114
+ data = new Buffer.from(string_);
115
+ let {length} = data;
67
116
  let buffer = 0;
68
117
  let crc;
69
- while (len--) {
70
- crc = ((crc >>> 8) ^ (tableCRC[(crc ^ (data[buffer++])) & 0xff]));
118
+ while (length--) {
119
+ crc = ((crc >>> 8) ^ (tableCRC[(crc ^ (data[buffer++])) & 0xFF]));
71
120
  }
121
+
72
122
  return crc.toString(16).padStart(4, '0').toUpperCase();
73
123
  }
74
124
 
75
- function encrypt(encryptionType, encryptionKey, str) {
76
- try {
77
- const encryptionKeyBuf = new Buffer.from(encryptionKey, 'hex');
78
- let iv = new Buffer.alloc(16);
79
- iv.fill(0);
80
- let cipher = crypto.createCipheriv(encryptionType, encryptionKeyBuf, iv);
81
- cipher.setAutoPadding(false);
82
- let encoded = cipher.update(str, 'utf8', 'hex');
83
- encoded += cipher.final('hex');
84
- return (encoded ? encoded : undefined);
85
- } catch (e) {
86
- return undefined;
87
- }
125
+ function encrypt(encryptionType, encryptionKey, string_) {
126
+ try {
127
+ const encryptionKeyBuf = new Buffer.from(encryptionKey, 'hex');
128
+ const iv = new Buffer.alloc(16);
129
+ iv.fill(0);
130
+ const cipher = crypto.createCipheriv(encryptionType, encryptionKeyBuf, iv);
131
+ cipher.setAutoPadding(false);
132
+ let encoded = cipher.update(string_, 'utf8', 'hex');
133
+ encoded += cipher.final('hex');
134
+ return (encoded ? encoded : undefined);
135
+ } catch {
136
+ return undefined;
137
+ }
88
138
  }
89
139
 
90
-
91
- function generateTimestamp(){
140
+ function generateTimestamp() {
92
141
  const now = new Date();
93
142
 
94
143
  // Pad single-digit numbers with a leading zero
95
- const pad = (num) => num.toString().padStart(2, '0');
144
+ const pad = number_ => number_.toString().padStart(2, '0');
96
145
 
97
146
  const hours = pad(now.getHours());
98
147
  const minutes = pad(now.getMinutes());
@@ -102,115 +151,121 @@ module.exports = function (RED) {
102
151
  const year = now.getFullYear();
103
152
 
104
153
  return `_${hours}:${minutes}:${seconds},${month}-${day}-${year}`;
105
-
106
154
  }
107
155
 
108
- function generatePadding(data){
109
- buffer = new Buffer.from(data); // we need to get byte length of the message body, not string length
110
- let msgLength = buffer.length;
156
+ function generatePadding(data) {
157
+ buffer = new Buffer.from(data); // We need to get byte length of the message body, not string length
158
+ const messageLength = buffer.length;
111
159
 
112
160
  let padding = '';
113
- if(msgLength % 16 !== 0){
114
- const paddingNeeded = 15 - (msgLength % 16);
115
- // generate random uppercase letters (only letters) for paddingNeeded
116
- for(let i=0; i<paddingNeeded; i++){
161
+ if (messageLength % 16 !== 0) {
162
+ const paddingNeeded = 15 - (messageLength % 16);
163
+ // Generate random uppercase letters (only letters) for paddingNeeded
164
+ for (let i = 0; i < paddingNeeded; i++) {
117
165
  const randomChar = String.fromCharCode(65 + Math.floor(Math.random() * 26));
118
166
  padding += randomChar;
119
167
  }
120
- // generate byte array
168
+
169
+ // Generate byte array
121
170
  console.log('Padding needed:', paddingNeeded, 'Generated padding:', padding);
122
- return padding+'|';
123
- }else
124
- return '';
125
- }
171
+ return padding + '|';
172
+ }
126
173
 
174
+ return '';
175
+ }
127
176
 
128
- function validateHexString(str, strName, minLength, maxLength){ // NOTE LGTM
129
- if(str.length < minLength || str.length > maxLength){
130
- console.error(`${strName} must be between ${minLength} and ${maxLength} characters long`);
177
+ function validateHexString(string_, stringName, minLength, maxLength) {
178
+ if (string_.length < minLength || string_.length > maxLength) {
179
+ console.error(`${stringName} must be between ${minLength} and ${maxLength} characters long`);
131
180
  return false;
132
-
133
181
  }
134
182
 
135
- if(str.length !=0 && !/^[0-9A-Fa-f]+$/.test(str)){
136
- console.error(`${strName} must be hexadecimal characters only (0-9, A-F)`);
183
+ if (string_.length > 0 && !/^[\dA-Fa-f]+$/.test(string_)) {
184
+ console.error(`${stringName} must be hexadecimal characters only (0-9, A-F)`);
137
185
  return false;
138
186
  }
187
+
139
188
  return true;
140
189
  }
141
190
 
142
- function iterateMsgCount(id){ // NOTE LGTM
143
- if(!deviceMap.has(id)){
144
- deviceMap.set(id, 1);
145
- }else{
146
- let count = deviceMap.get(id);
147
- count = (count % 9999) + 1; // wrap around at 999
148
- deviceMap.set(id, count);
191
+ function iterateMessageCount(id) {
192
+ if (deviceSeqNumbers.has(id)) {
193
+ let count = deviceSeqNumbers.get(id);
194
+ count = (count % 9999) + 1; // Wrap around at 999
195
+ deviceSeqNumbers.set(id, count);
196
+ } else {
197
+ deviceSeqNumbers.set(id, 1);
149
198
  }
150
199
  }
151
200
 
152
- // utf8toWin1252("a test ñ");
153
-
154
-
155
- this.on('input', message => {
156
- // const payload = message.payload;
157
-
158
- let payload = {
159
- "account" : "AABBCC",
160
- "accountPrefix" : "5678"
161
- }
162
- siaConfig.receiverNumber = "1234";
201
+ // --- PROCESS NEW MESSAGE ---
202
+ node.on('input', message => {
203
+ console.log('-----------------------------------------\n'
204
+ + ' MESSAGE START \n'
205
+ + '-----------------------------------------');
206
+ const payload = message.payload;
163
207
 
164
208
  const deviceAccount = payload.account;
165
209
  const deviceAccountPrefix = payload.accountPrefix || '0';
166
210
 
167
211
  // INPUT VALIDATION
212
+ if (!validateHexString(deviceAccount, 'Device account', 3, 16)) {
213
+ return;
214
+ }
168
215
 
169
- if(!validateHexString(deviceAccount, 'Device account', 3, 16)) return;
170
- if(!validateHexString(deviceAccountPrefix, 'Device account prefix', 1, 6)) return;
171
- if(!validateHexString(siaConfig.receiverNumber, 'Receiver number', 0, 6)) return;
216
+ if (!validateHexString(deviceAccountPrefix, 'Device account prefix', 1, 6)) {
217
+ return;
218
+ }
172
219
 
173
- const deviceIdentifier = 'L'+ deviceAccountPrefix + '#' + deviceAccount;
220
+ if (!validateHexString(siaConfig.receiverNumber, 'Receiver number', 0, 6)) {
221
+ return;
222
+ }
174
223
 
175
- iterateMsgCount(deviceIdentifier);
224
+ const deviceIdentifier = 'L' + deviceAccountPrefix + '#' + deviceAccount;
176
225
 
226
+ iterateMessageCount(deviceIdentifier);
177
227
 
178
- // PART: <"id"><seq><Rrcvr><Lpref><#acct>[
228
+ // PART: <"id"><seq><Rrcvr><Lpref><#acct>[
179
229
  let messageBodyStart = '"'; // <"id"><seq><Rrcvr><Lpref><#acct>[
180
- if(siaConfig.encryptionEnabled)
230
+ if (siaConfig.encryptionEnabled) {
181
231
  messageBodyStart += '*';
232
+ }
233
+
234
+ messageBodyStart += 'SIA-DCS"'; // ID placeholder
235
+ const seqNumber = deviceSeqNumbers.get(deviceIdentifier).toString().padStart(4, '0');
236
+
237
+ messageBodyStart += seqNumber; // Message sequence number
238
+ if (siaConfig.receiverNumber.length > 0) {
239
+ messageBodyStart += 'R' + siaConfig.receiverNumber;
240
+ }
182
241
 
183
- messageBodyStart += 'SIA-DCS"' // ID placeholder
184
- messageBodyStart += deviceMap.get(deviceIdentifier).toString().padStart(4, '0'); // message sequence number
185
- if(siaConfig.receiverNumber.length >0)
186
- messageBodyStart += 'R'+siaConfig.receiverNumber;
187
242
  messageBodyStart += deviceIdentifier;
188
243
  messageBodyStart += '[';
189
244
 
190
-
191
245
  // PART: #<acct>|<data>][<extende data>]
192
- let messageBodyData = '#'+deviceAccount + '|'; // <pad>|...data...][x…data…]
246
+ let messageBodyData = '#' + deviceAccount + '|'; // <pad>|...data...][x…data…]
193
247
 
194
248
  // data format
195
249
  // (N)(id-number)(DCS)(zone)
196
250
  // there's also a format of group-zone
197
251
 
198
- // messageBodyData += "NFA"; //
199
- messageBodyData += "Nri129/FA1234"; //
252
+ // messageBodyData += "Nri129^FA"; //
253
+ messageBodyData += payload.body;
254
+ // messageBodyData += 'Nri129/FA1234'; //
200
255
  // optional extended data can be added here, for example
201
256
 
202
257
  // PART: ]<timestamp>, finalization of body
203
258
  let messageBody = ''; // <"id"><seq><Rrcvr><Lpref><#acct>[<pad>|...data...][x…data…]<timestamp>
204
- let timestamp = generateTimestamp(); // TODO: this should be optional
259
+ const timestamp = generateTimestamp(); // TODO: this should be optional
205
260
 
206
- if(siaConfig.encryptionEnabled){ // everything past [ till <CR> is to be encrypted
207
- let toEncrypt = messageBodyData+']'+timestamp;
261
+ if (siaConfig.encryptionEnabled) { // Everything past [ till <CR> is to be encrypted
262
+ let toEncrypt = messageBodyData + ']' + timestamp;
208
263
  toEncrypt = generatePadding(toEncrypt) + toEncrypt;
209
264
  messageBody = messageBodyStart + encrypt(siaConfig.encryptionType, siaConfig.encryptionKey, toEncrypt);
210
- }else{
265
+ } else {
211
266
  messageBody += messageBodyStart;
212
267
  messageBody += messageBodyData;
213
- messageBody += ']'; // end of data fields
268
+ messageBody += ']'; // End of data fields
214
269
  // optional extended data
215
270
  messageBody += timestamp;
216
271
  }
@@ -218,21 +273,51 @@ module.exports = function (RED) {
218
273
  // Append <LF><crc><>
219
274
  msgCount = (new Buffer.from(messageBody)).length;
220
275
 
221
- let msg = '\n'; // <LF><crc><0LLL><"id"><seq><Rrcvr><Lpref><#acct>[<pad>|...data...][x…data…]<timestamp><CR>
222
- msg += calculateCRCIBM16(messageBody); // CRC is 4 ASCII characters
223
- msg += '0'+msgCount.toString(16).toUpperCase().padStart(3, '0'); // Carriage return
224
- msg += messageBody;
225
- msg += '\r';
226
-
227
- // let hexMsg = '';
228
- // for(let i=0; i<msg.length; i++){
229
- // hexMsg += msg.charCodeAt(i).toString(16).padStart(2, '0').toUpperCase() + ' ';
230
- // }
231
- // console.log('Final message in hex: ', hexMsg);
232
-
233
- server.connect();
234
- server.write(msg);
235
- server.close();
276
+ let message_ = '\n'; // <LF><crc><0LLL><"id"><seq><Rrcvr><Lpref><#acct>[<pad>|...data...][x…data…]<timestamp><CR>
277
+ const crc = calculateCRCIBM16(messageBody);
278
+ console.log("Final message CRC: ", crc);
279
+
280
+ message_ += crc; // CRC is 4 ASCII characters
281
+ message_ += '0' + msgCount.toString(16).toUpperCase().padStart(3, '0'); // Carriage return
282
+ message_ += messageBody;
283
+ message_ += '\r';
284
+
285
+ console.log('Final message:', message_);
286
+ console.log('Sending message to server');
287
+
288
+ pendingMessages.set(seqNumber, {
289
+ node,
290
+ originalMsg: message,
291
+ timestamp: Date.now(),
292
+ timeoutTimer: setTimeout(() => {
293
+ if (pendingMessages.has(seqNumber)) {
294
+ node.error(`Timeout waiting for ACK on seq ${seqNumber}`, message);
295
+ node.status({fill: 'red', shape: 'dot', text: 'Timeout ' + seqNumber});
296
+ node.send([null, {
297
+ payload: {
298
+ error: true,
299
+ status: 'timeout on message',
300
+ message: message
301
+ },
302
+ }]);
303
+ pendingMessages.delete(seqNumber);
304
+ }
305
+ }, 5000), // 5 Second timeout
306
+ });
307
+
308
+ node.log(`Sending SIA message seq: ${seqNumber}`);
309
+ node.status({fill: 'blue', shape: 'dot', text: 'Sent ' + seqNumber});
310
+
311
+
312
+ server.write(message_);
313
+
314
+ node.send([{
315
+ payload: {
316
+ error: false,
317
+ status: 'sent message',
318
+ message: message_
319
+ },
320
+ }, null]); // Send to first output
236
321
  });
237
322
  }
238
323
 
@@ -56,7 +56,7 @@
56
56
  <input type="text" id="node-config-input-name" placeholder="Name">
57
57
  </div>
58
58
  <div class="form-row">
59
- <label for="node-config-input-receiverHost">Receiver IP</label>
59
+ <label for="node-config-input-receiverHost">Receiver</label>
60
60
  <input type="text" id="node-config-input-receiverHost" placeholder="Receiver Host">
61
61
  </div>
62
62
  <div class="form-row">
@@ -65,7 +65,7 @@
65
65
  </div>
66
66
  <div class="form-row">
67
67
  <label for="node-config-input-receiverNumber">Receiver Number</label>
68
- <input type="number" id="node-config-input-receiverNumber" placeholder="Receiver Number">
68
+ <input type="text" id="node-config-input-receiverNumber" placeholder="Receiver Number">
69
69
  </div>
70
70
  <div class="form-row">
71
71
  <label for="node-config-input-encryptionKey">Encryption Key</label>
package/src/SIA_server.js CHANGED
@@ -8,29 +8,36 @@ module.exports = function (RED) {
8
8
  function SIAServer(config) {
9
9
  RED.nodes.createNode(this, config);
10
10
 
11
- this.receiverHost = config.receiverHost;
12
- this.receiverPort = Number.parseInt(config.receiverPort);
13
- this.receiverNumber = config.receiverNumber || '';
11
+ const node = this;
12
+
13
+ node.receiverHost = config.receiverHost;
14
+ node.receiverPort = Number.parseInt(config.receiverPort);
15
+ node.receiverNumber = config.receiverNumber || '';
14
16
  // This.siaAccount = config.siaAccount;
15
- // this.siaAccountPrefix = config.siaAccountPrefix || 'L0';
16
- this.encryptionKey = config.encryptionKey || '';
17
- this.encryptionEnabled = this.encryptionKey.length > 0;
18
-
19
- switch (this.encryptionKey.length) {
20
- case 32:
21
- this.encryptionType = 'aes-128-cbc';
22
- break;
23
- case 48:
24
- this.encryptionType = 'aes-192-cbc';
25
- break;
26
- case 64:
27
- this.encryptionType = 'aes-256-cbc';
28
- break;
29
- default:
30
- return undefined;
31
- }
17
+ // node.siaAccountPrefix = config.siaAccountPrefix || 'L0';
18
+ node.encryptionKey = config.encryptionKey || '';
19
+ node.encryptionEnabled = node.encryptionKey.length > 0;
20
+
21
+ switch (node.encryptionKey.length) {
22
+ case 32: {
23
+ node.encryptionType = 'aes-128-cbc';
24
+ break;
25
+ }
26
+
27
+ case 48: {
28
+ node.encryptionType = 'aes-192-cbc';
29
+ break;
30
+ }
32
31
 
32
+ case 64: {
33
+ node.encryptionType = 'aes-256-cbc';
34
+ break;
35
+ }
33
36
 
37
+ default: {
38
+ node.encryptionType = '';
39
+ }
40
+ }
34
41
 
35
42
  const client = new Net.Socket();
36
43
 
@@ -39,18 +46,39 @@ module.exports = function (RED) {
39
46
  client.destroy();
40
47
  });
41
48
 
42
- this.connect = function () {
49
+ client.on('data', data => {
50
+ // Emits to any SIA-device listening via server.on('data', ...)
51
+ console.log("Received some message", data);
52
+ node.emit('data', data);
53
+ });
54
+
55
+ client.on('error', error => {
56
+ console.error('SIA Socket Error:', error.message);
57
+ node.emit('socketError', error); // Notify devices if needed
58
+ });
59
+
60
+ client.on('connect', () => {
61
+ node.emit('connected');
62
+ });
63
+
64
+ client.on('close', () => {
65
+ node.emit('closed');
66
+ });
67
+
68
+ // -------------
69
+
70
+ node.connect = function () {
43
71
  // If(!client.destroyed && client.readyState == 'open') {
44
- // console.log('Already connected to SIA server at ' + this.receiverHost + ':' + this.receiverPort);
72
+ // console.log('Already connected to SIA server at ' + node.receiverHost + ':' + node.receiverPort);
45
73
  // return;
46
74
  // }
47
75
 
48
- if (!this.receiverHost || !this.receiverPort) {
76
+ if (!node.receiverHost || !node.receiverPort) {
49
77
  console.error('Receiver host or port not set');
50
78
  return;
51
79
  }
52
80
 
53
- console.log('Connecting to SIA server at', this.receiverHost, this.receiverPort);
81
+ console.log('Connecting to SIA server at', node.receiverHost, node.receiverPort);
54
82
 
55
83
  const operation = Retry.operation({
56
84
  retries: 5,
@@ -60,18 +88,16 @@ module.exports = function (RED) {
60
88
  randomize: true,
61
89
  });
62
90
 
63
- const tmpRef = this;
91
+ const temporaryRef = node;
64
92
 
65
93
  let errorListener = null;
66
94
 
67
-
68
95
  const connectCallback = () => {
69
- console.log('Connected to SIA server at', tmpRef.receiverHost, tmpRef.receiverPort);
96
+ console.log('Connected to SIA server at', temporaryRef.receiverHost, temporaryRef.receiverPort);
70
97
  };
71
98
 
72
99
  operation.attempt(currentAttempt => {
73
-
74
- if (errorListener) { // will not be active on first attempt
100
+ if (errorListener) { // Will not be active on first attempt
75
101
  client.off('error', errorListener);
76
102
  client.off('connect', connectCallback);
77
103
  }
@@ -88,27 +114,27 @@ module.exports = function (RED) {
88
114
 
89
115
  client.once('error', errorListener);
90
116
 
91
- client.connect(tmpRef.receiverPort, tmpRef.receiverHost, connectCallback);
117
+ client.connect(temporaryRef.receiverPort, temporaryRef.receiverHost, connectCallback);
92
118
  });
93
119
  };
94
120
 
95
- this.getSIAConfig = function () {
121
+ node.getSIAConfig = function () {
96
122
  return {
97
- receiverNumber: this.receiverNumber,
98
- encryptionKey: this.encryptionKey,
99
- encryptionEnabled: this.encryptionEnabled,
100
- encryptionType: this.encryptionType,
123
+ receiverNumber: node.receiverNumber,
124
+ encryptionKey: node.encryptionKey,
125
+ encryptionEnabled: node.encryptionEnabled,
126
+ encryptionType: node.encryptionType,
101
127
  };
102
128
  };
103
129
 
104
- this.write = function (data) {
130
+ node.write = function (data) {
105
131
  if (!client.destroyed && client.readyState == 'open') {
106
132
  console.log('Sending data to SIA server:', data);
107
133
  client.write(data);
108
134
  }
109
135
  };
110
136
 
111
- this.close = function () {
137
+ node.close = function () {
112
138
  if (!client.destroyed) {
113
139
  client.end();
114
140
  }
@@ -0,0 +1,36 @@
1
+ const tableCRC = [
2
+ 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
3
+ 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
4
+ 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
5
+ 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
6
+ 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
7
+ 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
8
+ 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
9
+ 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
10
+ 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
11
+ 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
12
+ 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
13
+ 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
14
+ 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
15
+ 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
16
+ 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
17
+ 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
18
+ 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
19
+ 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
20
+ 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
21
+ 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
22
+ 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
23
+ 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
24
+ 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
25
+ 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
26
+ 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
27
+ 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
28
+ 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
29
+ 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
30
+ 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
31
+ 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
32
+ 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
33
+ 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
34
+ ];
35
+
36
+ module.exports = tableCRC;
package/src/siacodes.csv DELETED
@@ -1,280 +0,0 @@
1
- AN,ANALOG RESTORAL
2
- AR,AC RESTORAL
3
- AS,ANALOG SERVICE
4
- AT,AC TROUBLE
5
- BA,BURGLARY ALARM
6
- BB,BURGLARY BYPASS
7
- BC,BURGLARY CANCEL
8
- BD,SWINGER TROUBLE
9
- BE,SWINGER TRBL RESTORE
10
- BH,BURG ALARM RESTORE
11
- BJ,BURG TROUBLE RESTORE
12
- BM,BURG ALARM CROSS PNT
13
- BR,BURGLARY RESTORAL
14
- BS,BURGLARY SUPERVISORY
15
- BT,BURGLARY TROUBLE
16
- BU,BURGLARY UNBYPASS
17
- BV,BURGLARY VERIFIED
18
- BX,BURGLARY TEST
19
- BZ,MISSING SUPERVISION
20
- CA,AUTOMATIC CLOSING
21
- CD,CLOSING DELINQUENT
22
- CE,CLOSING EXTEND
23
- CF,FORCED CLOSING
24
- CG,CLOSE AREA
25
- CI,FAIL TO CLOSE
26
- CJ,LATE CLOSE
27
- CK,EARLY CLOSE
28
- CL,CLOSING REPORT
29
- CM,MISSING AL-RECNT CLS
30
- CP,AUTOMATIC CLOSING
31
- CR,RECENT CLOSING
32
- CS,CLOSE KEY SWITCH
33
- CT,LATE TO OPEN
34
- CW,WAS FORCE ARMED
35
- CZ,POINT CLOSING
36
- DA,CARD ASSIGNED
37
- DB,CARD DELETED
38
- DC,ACCESS CLOSED
39
- DD,ACCESS DENIED
40
- DE,REQUEST TO ENTER
41
- DF,DOOR FORCED
42
- DG,ACCESS GRANTED
43
- DH,DOOR LEFT OPEN-RSTRL
44
- DJ,DOOR FORCED-TROUBLE
45
- DK,ACCESS LOCKOUT
46
- DL,DOOR LEFT OPEN-ALARM
47
- DM,DOOR LEFT OPEN-TRBL
48
- DN,DOOR LEFT OPEN
49
- DO,ACCESS OPEN
50
- DP,ACCESS DENIED-BAD TM
51
- DQ,ACCESS DENIED-UN ARM
52
- DR,DOOR RESTORAL
53
- DS,DOOR STATION
54
- DT,ACCESS TROUBLE
55
- DU,DEALER ID#
56
- DV,ACCESS DENIED-UN ENT
57
- DW,ACCESS DENIED-INTRLK
58
- DX,REQUEST TO EXIT
59
- DY,DOOR LOCKED
60
- DZ,ACCESS CLOSED STATE
61
- EA,EXIT ALARM
62
- EE,EXIT_ERROR
63
- ER,EXPANSION RESTORAL
64
- ET,EXPANSION TROUBLE
65
- EX,EXTRNL DEVICE STATE
66
- EZ,MISSING ALARM-EXT ER
67
- FA,FIRE ALARM
68
- FB,FIRE BYPASS
69
- FC,FIRE CANCEL
70
- FH,FIRE ALARM RESTORE
71
- FI,FIRE TEST BEGIN
72
- FJ,FIRE TROUBLE RESTORE
73
- FK,FIRE TEST END
74
- FM,FIRE ALARM CROSS PNT
75
- FR,FIRE RESTORAL
76
- FS,FIRE SUPERVISORY
77
- FT,FIRE TROUBLE
78
- FU,FIRE UNBYPASS
79
- FX,FIRE TEST
80
- FY,MISSING FIRE TROUBLE
81
- FZ,MISSING FIRE SPRV
82
- GA,GAS ALARM
83
- GB,GAS BYPASS
84
- GH,GAS ALARM RESTORE
85
- GJ,GAS TROUBLE RESTORE
86
- GR,GAS RESTORAL
87
- GS,GAS SUPERVISORY
88
- GT,GAS TROUBLE
89
- GU,GAS UNBYPASS
90
- GX,GAS TEST
91
- HA,HOLDUP ALARM
92
- HB,HOLDUP BYPASS
93
- HH,HOLDUP ALARM RESTORE
94
- HJ,HOLDUP TRBL RESTORE
95
- HR,HOLDUP RESTORAL
96
- HS,HOLDUP SUPERVISORY
97
- HT,HOLDUP TROUBLE
98
- HU,HOLDUP UNBYPASS
99
- IA,EQPMT FAIL CONDITION
100
- IR,EQPMT FAIL RESTORE
101
- JA,USER CODE TAMPER
102
- JD,DATE CHANGED
103
- JH,HOLIDAY CHANGED
104
- JK,LATCHKEY ALERT
105
- JL,LOG THRESHOLD
106
- JO,LOG OVERFLOW
107
- JP,USER ON PREMISES
108
- JR,SCHEDULE EXECUTED
109
- JS,SCHEDULE CHANGED
110
- JT,TIME CHANGED
111
- JV,USER CODE CHANGED
112
- JX,USER CODE DELETED
113
- JY,USER CODE ADDED
114
- JZ,USER LEVEL SET
115
- KA,HEAT ALARM
116
- KB,HEAT BYPASS
117
- KH,HEAT ALARM RESTORE
118
- KJ,HEAT TROUBLE RESTORE
119
- KR,HEAT RESTORAL
120
- KS,HEAT SUPERVISORY
121
- KT,HEAT TROUBLE
122
- KU,HEAT UNBYPASS
123
- L_,LISTEN IN + SECONDS
124
- LB,LOCAL PROG. BEGIN
125
- LD,LOCAL PROG. DENIED
126
- LE,LISTEN IN ENDED
127
- LF,LISTEN IN BEGIN
128
- LR,PHONE LINE RESTORAL
129
- LS,LOCAL PROG. SUCCESS
130
- LT,PHONE LINE TROUBLE
131
- LU,LOCAL PROG. FAIL
132
- LX,LOCAL PROG. ENDED
133
- MA,MEDICAL ALARM
134
- MB,MEDICAL BYPASS
135
- MH,MEDIC ALARM RESTORE
136
- MJ,MEDICAL TRBL RESTORE
137
- MR,MEDICAL RESTORAL
138
- MS,MEDICAL SUPERVISORY
139
- MT,MEDICAL TROUBLE
140
- MU,MEDICAL UNBYPASS
141
- NA,NO ACTIVITY
142
- NC,NETWORK CONDITION
143
- NF,FORCED PERIMETER ARM
144
- NL,PERIMETER ARMED
145
- NR,NETWORK RESTORAL
146
- NS,ACTIVITY RESUMED
147
- NT,NETWORK FAILURE
148
- OA,AUTOMATIC OPENING
149
- OC,CANCEL REPORT
150
- OG,OPEN AREA
151
- OH,EARLY TO OPN FROM AL
152
- OI,FAIL TO OPEN
153
- OJ,LATE OPEN
154
- OK,EARLY OPEN
155
- OL,LATE TO OPEN FROM AL
156
- OP,OPENING REPORT
157
- OR,DISARM FROM ALARM
158
- OS,OPEN KEY SWITCH
159
- OT,LATE TO CLOSE
160
- OZ,POINT OPENING
161
- PA,PANIC ALARM
162
- PB,PANIC BYPASS
163
- PH,PANIC ALARM RESTORE
164
- PJ,PANIC TRBL RESTORE
165
- PR,PANIC RESTORAL
166
- PS,PANIC SUPERVISORY
167
- PT,PANIC TROUBLE
168
- PU,PANIC UNBYPASS
169
- QA,EMERGENCY ALARM
170
- QB,EMERGENCY BYPASS
171
- QH,EMRGCY ALARM RESTORE
172
- QJ,EMRGCY TRBL RESTORE
173
- QR,EMERGENCY RESTORAL
174
- QS,EMRGCY SUPERVISORY
175
- QT,EMERGENCY TROUBLE
176
- QU,EMERGENCY UNBYPASS
177
- RA,RMOTE PROG CALL FAIL
178
- RB,REMOTE PROG. BEGIN
179
- RC,RELAY CLOSE
180
- RD,REMOTE PROG. DENIED
181
- RN,REMOTE RESET
182
- RO,RELAY OPEN
183
- RP,AUTOMATIC TEST
184
- RR,RESTORE POWER
185
- RS,REMOTE PROG. SUCCESS
186
- RT,DATA LOST
187
- RU,REMOTE PROG. FAIL
188
- RX,MANUAL TEST
189
- RY,TEST OFF NORMAL
190
- SA,SPRINKLER ALARM
191
- SB,SPRINKLER BYPASS
192
- SH,SPRKLR ALARM RESTORE
193
- SJ,SPRKLR TRBL RESTORE
194
- SR,SPRINKLER RESTORAL
195
- SS,SPRINKLER SUPERVISRY
196
- ST,SPRINKLER TROUBLE
197
- SU,SPRINKLER UNBYPASS
198
- TA,TAMPER ALARM
199
- TB,TAMPER BYPASS
200
- TC,ALL POINTS TESTED
201
- TE,TEST END
202
- TH,TAMPER ALRM RESTORE
203
- TJ,TAMPER TRBL RESTORE
204
- TP,WALK TEST POINT
205
- TR,TAMPER RESTORAL
206
- TS,TEST START
207
- TT,TAMPER TROUBLE
208
- TU,TAMPER UNBYPASS
209
- TX,TEST REPORT
210
- UA,UNTYPED ZONE ALARM
211
- UB,UNTYPED ZONE BYPASS
212
- UH,UNTYPD ALARM RESTORE
213
- UJ,UNTYPED TRBL RESTORE
214
- UR,UNTYPED ZONE RESTORE
215
- US,UNTYPED ZONE SUPRVRY
216
- UT,UNTYPED ZONE TROUBLE
217
- UU,UNTYPED ZONE UNBYPSS
218
- UX,UNDEFINED ALARM
219
- UY,UNTYPED MISSING TRBL
220
- UZ,UNTYPED MISSING ALRM
221
- VI,PRINTER PAPER IN
222
- VO,PRINTER PAPER OUT
223
- VR,PRINTER RESTORE
224
- VT,PRINTER TROUBLE
225
- VX,PRINTER TEST
226
- VY,PRINTER ONLINE
227
- VZ,PRINTER OFFLINE
228
- WA,WATER ALARM
229
- WB,WATER BYPASS
230
- WH,WATER ALARM RESTORE
231
- WJ,WATER TRBL RESTORE
232
- WR,WATER RESTORAL
233
- WS,WATER SUPERVISORY
234
- WT,WATER TROUBLE
235
- WU,WATER UNBYPASS
236
- XA,EXTRA ACCNT REPORT
237
- XE,EXTRA POINT
238
- XF,EXTRA RF POINT
239
- XH,RF INTERFERENCE RST
240
- XI,SENSOR RESET
241
- XJ,RF RCVR TAMPER RST
242
- XL,LOW RF SIGNAL
243
- XM,MISSING ALRM-X POINT
244
- XQ,RF INTERFERENCE
245
- XR,TRANS. BAT. RESTORAL
246
- XS,RF RECEIVER TAMPER
247
- XT,TRANS. BAT. TROUBLE
248
- XW,FORCED POINT
249
- XX,FAIL TO TEST
250
- YA,BELL FAULT
251
- YB,BUSY SECONDS
252
- YC,COMMUNICATIONS FAIL
253
- YD,RCV LINECARD TROUBLE
254
- YE,RCV LINECARD RESTORE
255
- YF,PARA CHECKSUM FAIL
256
- YG,PARAMETER CHANGED
257
- YH,BELL RESTORED
258
- YI,OVERCURRENT TROUBLE
259
- YJ,OVERCURRENT RESTORE
260
- YK,COMM. RESTORAL
261
- YM,SYSTEM BATT MISSING
262
- YN,INVALID REPORT
263
- YO,UNKNOWN MESSAGE
264
- YP,PWR SUPPLY TROUBLE
265
- YQ,PWR SUPPLY RESTORE
266
- YR,SYSTEM BAT. RESTORAL
267
- YS,COMMUNICATIONS TRBL
268
- YT,SYSTEM BAT. TROUBLE
269
- YW,WATCHDOG RESET
270
- YX,SERVICE REQUIRED
271
- YY,STATUS REPORT
272
- YZ,SERVICE COMPLETED
273
- ZA,FREEZE ALARM
274
- ZB,FREEZE BYPASS
275
- ZH,FREEZE ALARM RESTORE
276
- ZJ,FREEZE TRBL RESTORE
277
- ZR,FREEZE RESTORAL
278
- ZS,FREEZE SUPERVISORY
279
- ZT,FREEZE TROUBLE
280
- ZU,FREEZE UNBYPASS