@jotiotech/node-red-sia-premise-device 0.2.2 → 1.0.1

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/README.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # SIA Device
2
2
 
3
- TODO
3
+ Simple node-red library that manages connection to SIA server and helps with creation of packets to send to if from connected devices.
4
+ It just acts as a middle man that for now takes the SIA body and creates rest of the packet to send to the server and handles the ACK or lack of ACK from it.
5
+
6
+ For now just basic body is supported (not extended one with things like position and what not) and the user needs to create the body in correct SIA format that will get interpreted by the server.
7
+ However in future releases it's planned to have a much more streamline approach where user just sends code, name, id, zone area and what not and node will create the body by itself.
8
+
9
+ Server is configured in it's special node, on the input to SIA Device node user must input following parameters. Account setting will be provided by your SIA server administrator.
10
+
11
+ ```
12
+ payload.account - hex string with account number
13
+ payload.accountPrefix - hex string with account prefix
14
+ payload.body - SIA payload body, e.g. Nri129^^pi4^FA
15
+ ```
4
16
 
5
- Warning: ONLY ASCII characters are supported, if you ever try to use UTF8 it will not be converted to Windows-1252 and on the receiving end you will end up with gibberish.
6
17
 
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.1",
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,145 @@
1
- // const { Iconv } = require('iconv');
2
-
3
-
4
1
  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
-
2
+ const deviceSeqNumbers = new Map();
3
+ const crypto = require('node:crypto');
4
+ const tableCRC = require('./table_crc.js');
44
5
 
45
6
  function SIADeviceNode(config) {
46
7
  RED.nodes.createNode(this, config);
47
8
 
48
9
  // TODO: eventually we should disconnect from the server after each message as there shouldn't be a high volume of messages
49
10
  const server = RED.nodes.getNode(config.server);
50
-
11
+ const pendingMessages = new Map();
51
12
  const siaConfig = server.getSIAConfig();
13
+ const node = this;
52
14
  console.log('SIA Config:', siaConfig);
53
15
 
16
+ // Cleanup on close
54
17
 
55
- server.connect();
18
+ // --- LISTENER FUNCTION ---
19
+ const dataListener = data => {
20
+ const response = data.toString();
21
+ let seq; let type;
22
+
23
+ // Format: <LF><CRC><OLLL><"ID"><seq>...
24
+ const match = response.match(/"(\*?ACK|\*?DUH|NAK)"(\d{4})/);
25
+ console.log('-----------------------------------------\n'
26
+ + ' SERVER RESPONSE \n'
27
+ + '-----------------------------------------');
56
28
 
29
+ console.log('Received message:', response);
57
30
 
31
+ if (!match) {
32
+ return;
33
+ }
34
+
35
+ type = match[1].replace('*', ''); // "ACK", "DUH", or "NAK"
36
+ seq = match[2]; // The 4-digit sequence number as a string
37
+
38
+ // Handle uncorrectable NAK (seq 0000), likely for a timestamp error
39
+ if (type === 'NAK' && seq === '0000') {
40
+ node.warn('Received uncorrelatable NAK (seq 0000). Erroring all pending requests for this node.');
41
+ for (const [key, pending] of pendingMessages.entries()) {
42
+ if (pending.node === node) {
43
+ clearTimeout(pending.timeoutTimer);
44
+ pending.node.error('Received uncorrelatable NAK (seq 0000) from server', pending.originalMsg);
45
+ pendingMessages.delete(key);
46
+ }
47
+ }
48
+ return;
49
+ }
50
+
51
+ if (pendingMessages.has(seq)) {
52
+ const pending = pendingMessages.get(seq);
53
+ if (pending.node !== node) {
54
+ return;
55
+ } // Just in case we ever run multiple nodes
56
+
57
+ clearTimeout(pending.timeoutTimer);
58
+ switch (type) {
59
+ case 'ACK': { // Success!
60
+ pending.node.log(`ACK received for seq ${seq}`);
61
+ pending.node.send([null, {payload:{
62
+ error : false,
63
+ status : "received ACK",
64
+ response: response
65
+ }
66
+ }]); // Send to second output
67
+ break;
68
+ }
69
+
70
+ case 'NAK': { // NAK response
71
+ pending.node.error(`Received NAK from server for seq ${seq}`, pending.originalMsg);
72
+ break;
73
+ }
74
+
75
+ case 'DUH': { // DUH response
76
+ pending.node.error(`Received DUH from server for seq ${seq}`, pending.originalMsg);
77
+ break;
78
+ }
79
+ }
58
80
 
59
- function utf8toWin1252(str) {
60
- tmpBuf = Buffer.from(str, 'utf8');
81
+ pendingMessages.delete(seq);
82
+ } else {
83
+ node.log(`Received unknown or stale response for seq ${seq}`);
84
+ }
85
+ };
86
+
87
+ node.on('close', done => {
88
+ if (server) {
89
+ server.removeListener('data', dataListener);
90
+ }
91
+
92
+ for (const [key, pending] of pendingMessages.entries()) {
93
+ if (pending.node === node) {
94
+ clearTimeout(pending.timeoutTimer);
95
+ pendingMessages.delete(key);
96
+ }
97
+ }
98
+
99
+ done();
100
+ });
101
+
102
+ server.connect();
103
+ server.on('data', dataListener);
104
+
105
+ // --- HELPER FUNCTIONS ---
106
+ function utf8toWin1252(string_) {
107
+ tmpBuf = Buffer.from(string_, 'utf8');
61
108
  return tmpBuf.toString('latin1');
62
109
  }
63
110
 
64
- function calculateCRCIBM16(str){
65
- data = new Buffer.from(str);
66
- let len = data.length;
111
+ function calculateCRCIBM16(string_) {
112
+ data = new Buffer.from(string_);
113
+ let {length} = data;
67
114
  let buffer = 0;
68
115
  let crc;
69
- while (len--) {
70
- crc = ((crc >>> 8) ^ (tableCRC[(crc ^ (data[buffer++])) & 0xff]));
116
+ while (length--) {
117
+ crc = ((crc >>> 8) ^ (tableCRC[(crc ^ (data[buffer++])) & 0xFF]));
71
118
  }
119
+
72
120
  return crc.toString(16).padStart(4, '0').toUpperCase();
73
121
  }
74
122
 
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
- }
123
+ function encrypt(encryptionType, encryptionKey, string_) {
124
+ try {
125
+ const encryptionKeyBuf = new Buffer.from(encryptionKey, 'hex');
126
+ const iv = new Buffer.alloc(16);
127
+ iv.fill(0);
128
+ const cipher = crypto.createCipheriv(encryptionType, encryptionKeyBuf, iv);
129
+ cipher.setAutoPadding(false);
130
+ let encoded = cipher.update(string_, 'utf8', 'hex');
131
+ encoded += cipher.final('hex');
132
+ return (encoded ? encoded : undefined);
133
+ } catch {
134
+ return undefined;
135
+ }
88
136
  }
89
137
 
90
-
91
- function generateTimestamp(){
138
+ function generateTimestamp() {
92
139
  const now = new Date();
93
140
 
94
141
  // Pad single-digit numbers with a leading zero
95
- const pad = (num) => num.toString().padStart(2, '0');
142
+ const pad = number_ => number_.toString().padStart(2, '0');
96
143
 
97
144
  const hours = pad(now.getHours());
98
145
  const minutes = pad(now.getMinutes());
@@ -102,115 +149,121 @@ module.exports = function (RED) {
102
149
  const year = now.getFullYear();
103
150
 
104
151
  return `_${hours}:${minutes}:${seconds},${month}-${day}-${year}`;
105
-
106
152
  }
107
153
 
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;
154
+ function generatePadding(data) {
155
+ buffer = new Buffer.from(data); // We need to get byte length of the message body, not string length
156
+ const messageLength = buffer.length;
111
157
 
112
158
  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++){
159
+ if (messageLength % 16 !== 0) {
160
+ const paddingNeeded = 15 - (messageLength % 16);
161
+ // Generate random uppercase letters (only letters) for paddingNeeded
162
+ for (let i = 0; i < paddingNeeded; i++) {
117
163
  const randomChar = String.fromCharCode(65 + Math.floor(Math.random() * 26));
118
164
  padding += randomChar;
119
165
  }
120
- // generate byte array
166
+
167
+ // Generate byte array
121
168
  console.log('Padding needed:', paddingNeeded, 'Generated padding:', padding);
122
- return padding+'|';
123
- }else
124
- return '';
125
- }
169
+ return padding + '|';
170
+ }
126
171
 
172
+ return '';
173
+ }
127
174
 
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`);
175
+ function validateHexString(string_, stringName, minLength, maxLength) {
176
+ if (string_.length < minLength || string_.length > maxLength) {
177
+ console.error(`${stringName} must be between ${minLength} and ${maxLength} characters long`);
131
178
  return false;
132
-
133
179
  }
134
180
 
135
- if(str.length !=0 && !/^[0-9A-Fa-f]+$/.test(str)){
136
- console.error(`${strName} must be hexadecimal characters only (0-9, A-F)`);
181
+ if (string_.length > 0 && !/^[\dA-Fa-f]+$/.test(string_)) {
182
+ console.error(`${stringName} must be hexadecimal characters only (0-9, A-F)`);
137
183
  return false;
138
184
  }
185
+
139
186
  return true;
140
187
  }
141
188
 
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);
189
+ function iterateMessageCount(id) {
190
+ if (deviceSeqNumbers.has(id)) {
191
+ let count = deviceSeqNumbers.get(id);
192
+ count = (count % 9999) + 1; // Wrap around at 999
193
+ deviceSeqNumbers.set(id, count);
194
+ } else {
195
+ deviceSeqNumbers.set(id, 1);
149
196
  }
150
197
  }
151
198
 
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";
199
+ // --- PROCESS NEW MESSAGE ---
200
+ node.on('input', message => {
201
+ console.log('-----------------------------------------\n'
202
+ + ' MESSAGE START \n'
203
+ + '-----------------------------------------');
204
+ const payload = message.payload;
163
205
 
164
206
  const deviceAccount = payload.account;
165
207
  const deviceAccountPrefix = payload.accountPrefix || '0';
166
208
 
167
209
  // INPUT VALIDATION
210
+ if (!validateHexString(deviceAccount, 'Device account', 3, 16)) {
211
+ return;
212
+ }
168
213
 
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;
214
+ if (!validateHexString(deviceAccountPrefix, 'Device account prefix', 1, 6)) {
215
+ return;
216
+ }
172
217
 
173
- const deviceIdentifier = 'L'+ deviceAccountPrefix + '#' + deviceAccount;
218
+ if (!validateHexString(siaConfig.receiverNumber, 'Receiver number', 0, 6)) {
219
+ return;
220
+ }
174
221
 
175
- iterateMsgCount(deviceIdentifier);
222
+ const deviceIdentifier = 'L' + deviceAccountPrefix + '#' + deviceAccount;
176
223
 
224
+ iterateMessageCount(deviceIdentifier);
177
225
 
178
- // PART: <"id"><seq><Rrcvr><Lpref><#acct>[
226
+ // PART: <"id"><seq><Rrcvr><Lpref><#acct>[
179
227
  let messageBodyStart = '"'; // <"id"><seq><Rrcvr><Lpref><#acct>[
180
- if(siaConfig.encryptionEnabled)
228
+ if (siaConfig.encryptionEnabled) {
181
229
  messageBodyStart += '*';
230
+ }
231
+
232
+ messageBodyStart += 'SIA-DCS"'; // ID placeholder
233
+ const seqNumber = deviceSeqNumbers.get(deviceIdentifier).toString().padStart(4, '0');
234
+
235
+ messageBodyStart += seqNumber; // Message sequence number
236
+ if (siaConfig.receiverNumber.length > 0) {
237
+ messageBodyStart += 'R' + siaConfig.receiverNumber;
238
+ }
182
239
 
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
240
  messageBodyStart += deviceIdentifier;
188
241
  messageBodyStart += '[';
189
242
 
190
-
191
243
  // PART: #<acct>|<data>][<extende data>]
192
- let messageBodyData = '#'+deviceAccount + '|'; // <pad>|...data...][x…data…]
244
+ let messageBodyData = '#' + deviceAccount + '|'; // <pad>|...data...][x…data…]
193
245
 
194
246
  // data format
195
247
  // (N)(id-number)(DCS)(zone)
196
248
  // there's also a format of group-zone
197
249
 
198
- // messageBodyData += "NFA"; //
199
- messageBodyData += "Nri129/FA1234"; //
250
+ // messageBodyData += "Nri129^FA"; //
251
+ messageBodyData += payload.body;
252
+ // messageBodyData += 'Nri129/FA1234'; //
200
253
  // optional extended data can be added here, for example
201
254
 
202
255
  // PART: ]<timestamp>, finalization of body
203
256
  let messageBody = ''; // <"id"><seq><Rrcvr><Lpref><#acct>[<pad>|...data...][x…data…]<timestamp>
204
- let timestamp = generateTimestamp(); // TODO: this should be optional
257
+ const timestamp = generateTimestamp(); // TODO: this should be optional
205
258
 
206
- if(siaConfig.encryptionEnabled){ // everything past [ till <CR> is to be encrypted
207
- let toEncrypt = messageBodyData+']'+timestamp;
259
+ if (siaConfig.encryptionEnabled) { // Everything past [ till <CR> is to be encrypted
260
+ let toEncrypt = messageBodyData + ']' + timestamp;
208
261
  toEncrypt = generatePadding(toEncrypt) + toEncrypt;
209
262
  messageBody = messageBodyStart + encrypt(siaConfig.encryptionType, siaConfig.encryptionKey, toEncrypt);
210
- }else{
263
+ } else {
211
264
  messageBody += messageBodyStart;
212
265
  messageBody += messageBodyData;
213
- messageBody += ']'; // end of data fields
266
+ messageBody += ']'; // End of data fields
214
267
  // optional extended data
215
268
  messageBody += timestamp;
216
269
  }
@@ -218,21 +271,51 @@ module.exports = function (RED) {
218
271
  // Append <LF><crc><>
219
272
  msgCount = (new Buffer.from(messageBody)).length;
220
273
 
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();
274
+ let message_ = '\n'; // <LF><crc><0LLL><"id"><seq><Rrcvr><Lpref><#acct>[<pad>|...data...][x…data…]<timestamp><CR>
275
+ const crc = calculateCRCIBM16(messageBody);
276
+ console.log("Final message CRC: ", crc);
277
+
278
+ message_ += crc; // CRC is 4 ASCII characters
279
+ message_ += '0' + msgCount.toString(16).toUpperCase().padStart(3, '0'); // Carriage return
280
+ message_ += messageBody;
281
+ message_ += '\r';
282
+
283
+ console.log('Final message:', message_);
284
+ console.log('Sending message to server');
285
+
286
+ pendingMessages.set(seqNumber, {
287
+ node,
288
+ originalMsg: message,
289
+ timestamp: Date.now(),
290
+ timeoutTimer: setTimeout(() => {
291
+ if (pendingMessages.has(seqNumber)) {
292
+ node.error(`Timeout waiting for ACK on seq ${seqNumber}`, message);
293
+ node.status({fill: 'red', shape: 'dot', text: 'Timeout ' + seqNumber});
294
+ node.send([null, {
295
+ payload: {
296
+ error: true,
297
+ status: 'timeout on message',
298
+ message: message
299
+ },
300
+ }]);
301
+ pendingMessages.delete(seqNumber);
302
+ }
303
+ }, 5000), // 5 Second timeout
304
+ });
305
+
306
+ node.log(`Sending SIA message seq: ${seqNumber}`);
307
+ node.status({fill: 'blue', shape: 'dot', text: 'Sent ' + seqNumber});
308
+
309
+
310
+ server.write(message_);
311
+
312
+ node.send([{
313
+ payload: {
314
+ error: false,
315
+ status: 'sent message',
316
+ message: message_
317
+ },
318
+ }, null]); // Send to first output
236
319
  });
237
320
  }
238
321
 
@@ -1,12 +1,3 @@
1
- <!--
2
- Copyright (c) since the year 2016 Klaus Landsdorf (http://plus4nodered.com/)
3
- Copyright 2016 - Jason D. Harper, Argonne National Laboratory
4
- Copyright 2015,2016 - Mika Karaila, Valmet Automation Inc.
5
- All rights reserved.
6
- node-red-contrib-modbus
7
-
8
- @author <a href="mailto:klaus.landsdorf@bianco-royal.de">Klaus Landsdorf</a> (Bianco Royal)
9
- -->
10
1
  <script type="text/javascript">
11
2
  RED.nodes.registerType('SIA-server', {
12
3
  category: 'config',
@@ -56,7 +47,7 @@
56
47
  <input type="text" id="node-config-input-name" placeholder="Name">
57
48
  </div>
58
49
  <div class="form-row">
59
- <label for="node-config-input-receiverHost">Receiver IP</label>
50
+ <label for="node-config-input-receiverHost">Receiver</label>
60
51
  <input type="text" id="node-config-input-receiverHost" placeholder="Receiver Host">
61
52
  </div>
62
53
  <div class="form-row">
@@ -65,7 +56,7 @@
65
56
  </div>
66
57
  <div class="form-row">
67
58
  <label for="node-config-input-receiverNumber">Receiver Number</label>
68
- <input type="number" id="node-config-input-receiverNumber" placeholder="Receiver Number">
59
+ <input type="text" id="node-config-input-receiverNumber" placeholder="Receiver Number">
69
60
  </div>
70
61
  <div class="form-row">
71
62
  <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;