@jotiotech/node-red-sia-premise-device 0.2.2 → 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.2",
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;