mqtt-devices-parser 1.0.22 → 1.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Changelog.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.24
4
+ src/device/device: fix log
5
+ src/kafka/consumer: increase initialRetryTime from 100 to 300ms
6
+ src/device/device: parseMqttMessage: get remote configs if not known
7
+ synch configs if they mismatch
8
+ models/devices.models: new column synch
9
+ synchs mqtt topics
10
+ Explanation
11
+ **
12
+ When a message is received, it checks if mqtt topic is associated to the device without "set" if present.
13
+ If topic exists and ends with set, updates localData column. If topic exists and doesn't end with set, updates remoteData column.
14
+ If localData was updated sets topic as unsynched
15
+ If remoteData was received and differs from localData and synch is enabled, tries to synch topic
16
+ **
17
+ adds synch and synched columns to mqtt table
18
+ Adds methods getMqttTopic, updateRemoteTopic, setSynchedTopic
19
+ kafka: adds random number to kafka when program is in dev mode.
20
+ Avoids to read all queue messages since last session
21
+ mqtt templates: preparing mqtt topics synch
22
+ new column synch and localData for mqttTemplates table
23
+ new column mqttTemplate_id for mqtt table
24
+ add property "property" to table sensors
25
+ src/device/device: decode json payload and get property associated
26
+ models/devices: add synched column (for future implementations)
27
+
28
+ ## 1.0.23
29
+ src/device/device: fix tech, version and app_version db update
30
+ npm warn audit Updating websocket-stream to 5.3.0, which is a SemVer major change.
31
+
3
32
  ## 1.0.22
4
33
  Adds SSL and SASL support for kafka
5
34
  mqtt.parseMessages removed
package/index.js CHANGED
@@ -174,7 +174,7 @@ function mqtt_connect(){
174
174
  $.mqtt_client.subscribe(project+"/#", (err) => {
175
175
  if(err){
176
176
  console.log("[MQTT] error");
177
- console.err(err);
177
+ console.error(err);
178
178
  }
179
179
  else
180
180
  console.log("[MQTT] subscribed to project:",project);
@@ -230,6 +230,6 @@ function mqtt_connect(){
230
230
 
231
231
  $.mqtt_client.on("error",(error)=>{
232
232
  console.log("[MQTT] error");
233
- console.err(error)
233
+ console.error(error)
234
234
  })
235
235
  }
@@ -71,6 +71,16 @@ module.exports = (sequelize,DataTypes)=>{
71
71
  type: DataTypes.STRING,
72
72
  allowNull: true
73
73
  },
74
+ synch: { // set to 1 to enable auto synch
75
+ type: DataTypes.INTEGER,
76
+ default: 0,
77
+ allowNull: true
78
+ },
79
+ synched: { // handle internally
80
+ type: DataTypes.INTEGER,
81
+ default: 0,
82
+ allowNull: true
83
+ },
74
84
  },
75
85
  {
76
86
  tableName: 'devices',
@@ -32,6 +32,20 @@ module.exports = (sequelize,DataTypes)=>{
32
32
  template_id: {
33
33
  type: DataTypes.INTEGER,
34
34
  allowNull: true,
35
+ },
36
+ mqttTemplate_id: {
37
+ type: DataTypes.INTEGER,
38
+ allowNull: true,
39
+ },
40
+ synch: {
41
+ type: DataTypes.INTEGER,
42
+ default: 0,
43
+ allowNull: true,
44
+ },
45
+ synched: {
46
+ type: DataTypes.INTEGER,
47
+ default: 0,
48
+ allowNull: true,
35
49
  }
36
50
  },
37
51
  {
@@ -13,10 +13,18 @@ module.exports = (sequelize,DataTypes)=>{
13
13
  type: DataTypes.JSON,
14
14
  allowNull: true
15
15
  },
16
+ localData: {
17
+ type: DataTypes.JSON,
18
+ allowNull: true
19
+ },
16
20
  readInterval: {
17
21
  type: DataTypes.INTEGER,
18
22
  allowNull: true
19
23
  },
24
+ synch: {
25
+ type: DataTypes.INTEGER,
26
+ allowNull: true
27
+ },
20
28
  template_id: {
21
29
  type: DataTypes.INTEGER,
22
30
  allowNull: false
@@ -26,6 +26,10 @@ module.exports = (sequelize,DataTypes)=>{
26
26
  type: DataTypes.STRING,
27
27
  allowNull: false,
28
28
  },
29
+ property: {
30
+ type: DataTypes.STRING,
31
+ allowNull: false,
32
+ },
29
33
  graph: {
30
34
  type: DataTypes.JSON,
31
35
  allowNull: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mqtt-devices-parser",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/db/device.js CHANGED
@@ -194,7 +194,6 @@ var self = module.exports = {
194
194
  });
195
195
  },
196
196
 
197
-
198
197
  addLog : async(id,column,value)=>{
199
198
  return new Promise((resolve,reject) => {
200
199
 
@@ -255,26 +254,26 @@ var self = module.exports = {
255
254
 
256
255
  getLocalSettings : async(deviceId)=>{
257
256
 
258
- return new Promise((resolve,reject) => {
257
+ return new Promise((resolve,reject) => {
259
258
 
260
- let query = "SELECT local_settings FROM ?? where id = ?";
261
- let table = ["devices", deviceId];
262
- query = mysql.format(query,table);
259
+ let query = "SELECT local_settings FROM ?? where id = ?";
260
+ let table = ["devices", deviceId];
261
+ query = mysql.format(query,table);
263
262
 
264
- $.db.queryRow(query)
265
- .then( rows => {
266
- if(rows?.length > 0)
267
- return resolve(rows[0]?.local_settings);
268
- else
269
- return resolve(null);
270
- })
271
- .catch( err => {
272
- return reject(err);
273
- });
263
+ $.db.queryRow(query)
264
+ .then( rows => {
265
+ if(rows?.length > 0)
266
+ return resolve(rows[0]?.local_settings);
267
+ else
268
+ return resolve(null);
269
+ })
270
+ .catch( err => {
271
+ return reject(err);
272
+ });
274
273
  });
275
- },
274
+ },
276
275
 
277
- updateLocalSettings : async(settings, deviceId)=>{
276
+ updateLocalSettings : async(settings, deviceId)=>{
278
277
 
279
278
  return new Promise((resolve,reject) => {
280
279
  let obj = {
@@ -299,7 +298,7 @@ var self = module.exports = {
299
298
  });
300
299
  },
301
300
 
302
- getRemoteSettings : async(deviceId)=>{
301
+ getRemoteSettings : async(deviceId)=>{
303
302
 
304
303
  return new Promise((resolve,reject) => {
305
304
 
@@ -408,6 +407,103 @@ var self = module.exports = {
408
407
  return resolve(null);
409
408
  });
410
409
  });
411
- }
410
+ },
411
+
412
+ getMqttTopic : async(deviceId, topic)=>{
413
+ return new Promise((resolve,reject) => {
414
+
415
+ let query = "SELECT * FROM ?? where device_id = ? and topic = ?";
416
+ let args = ["mqtt",deviceId,topic];
417
+ query = mysql.format(query,args);
418
+
419
+ $.db.queryRow(query)
420
+ .then( rows => {
421
+ if(rows.length > 0)
422
+ return resolve(rows[0]);
423
+ else
424
+ return resolve(null);
425
+ })
426
+ .catch( err => {
427
+ console.log(err);
428
+ return resolve(null);
429
+ });
430
+ });
431
+ },
412
432
 
433
+ updateLocalTopic : async(id, data)=>{
434
+
435
+ let jsonData = {
436
+ value : data
437
+ };
438
+
439
+ return new Promise((resolve,reject) => {
440
+ let obj = {
441
+ localData : JSON.stringify(jsonData),
442
+ updatedAt : moment().utc().format('YYYY-MM-DD HH:mm:ss')
443
+ }
444
+
445
+ let filter = {
446
+ id
447
+ };
448
+
449
+ $.db.update("mqtt",obj,filter)
450
+ .then (rows => {
451
+ return resolve(rows[0]);
452
+ })
453
+ .catch(error => {
454
+ return reject(error);
455
+ });
456
+
457
+ });
458
+ },
459
+
460
+ updateRemoteTopic : async(id, data)=>{
461
+
462
+ let jsonData = {
463
+ value : data
464
+ };
465
+
466
+ return new Promise((resolve,reject) => {
467
+ let obj = {
468
+ remoteData : JSON.stringify(jsonData),
469
+ updatedAt : moment().utc().format('YYYY-MM-DD HH:mm:ss')
470
+ }
471
+
472
+ let filter = {
473
+ id
474
+ };
475
+
476
+ $.db.update("mqtt",obj,filter)
477
+ .then (rows => {
478
+ return resolve(rows[0]);
479
+ })
480
+ .catch(error => {
481
+ return reject(error);
482
+ });
483
+
484
+ });
485
+ },
486
+
487
+ setSynchedTopic : async(id, data)=>{
488
+
489
+ return new Promise((resolve,reject) => {
490
+ let obj = {
491
+ synched : data ? 1 : 0,
492
+ updatedAt : moment().utc().format('YYYY-MM-DD HH:mm:ss')
493
+ }
494
+
495
+ let filter = {
496
+ id,
497
+ };
498
+
499
+ $.db.update("mqtt",obj,filter)
500
+ .then (rows => {
501
+ return resolve(rows[0]);
502
+ })
503
+ .catch(error => {
504
+ return reject(error);
505
+ });
506
+
507
+ });
508
+ },
413
509
  }
@@ -1,4 +1,5 @@
1
1
  const moment = require('moment');
2
+ const { isDeepStrictEqual } = require('node:util');
2
3
 
3
4
  var _project = [];
4
5
  const logs_table = "logs_sensor";
@@ -281,6 +282,7 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
281
282
  if(topic.endsWith("/get"))
282
283
  return;
283
284
 
285
+ const topicBck = topic;
284
286
  //console.log("[MQTT] parse topic: ",topic);
285
287
 
286
288
  try{
@@ -296,6 +298,31 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
296
298
  $.db_device.update(device.id, "status", payload);
297
299
  $.db_device.addLog(device.id,"status",payload);
298
300
  }
301
+
302
+ if (payload === "online"){
303
+ let mqtt_prefix = `${project_name}/${device.uid}`;
304
+ // check if remote settings are known, if not require it
305
+ if(!device.remote_settings?.log){
306
+ let topic = `${mqtt_prefix}/settings/log/get`
307
+ $.mqtt_client.publish(topic,"",{qos:1,retain:false});
308
+ }
309
+ if(!device.remote_settings?.keepalive){
310
+ let topic = `${mqtt_prefix}/settings/keepalive/get`
311
+ $.mqtt_client.publish(topic,"",{qos:1,retain:false});
312
+ }
313
+ if(!device.remote_settings?.modem){
314
+ let topic = `${mqtt_prefix}/settings/modem/get`
315
+ $.mqtt_client.publish(topic,"",{qos:1,retain:false});
316
+ }
317
+ if(!device.remote_settings?.wifi){
318
+ let topic = `${mqtt_prefix}/settings/wifi/get`
319
+ $.mqtt_client.publish(topic,"",{qos:1,retain:false});
320
+ }
321
+ if(!device.remote_settings?.mqtt){
322
+ let topic = `${mqtt_prefix}/settings/mqtt/get`
323
+ $.mqtt_client.publish(topic,"",{qos:1,retain:false});
324
+ }
325
+ }
299
326
  return;
300
327
  break;
301
328
  case "model":
@@ -308,14 +335,14 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
308
335
  return;
309
336
  break;
310
337
  case "tech":
311
- if (device?.tech && payload != device.tech) {
338
+ if (payload != null && payload != device?.tech) {
312
339
  $.db_device.update(device.id, "tech", payload);
313
340
  $.db_device.addLog(device.id,"tech",payload);
314
341
  }
315
342
  return;
316
343
  break;
317
344
  case "version":
318
- if (device?.version && payload != device.version) {
345
+ if (payload != null && payload != device?.version) {
319
346
  $.db_device.addLog(device.id,"version",payload);
320
347
  $.db_device.update(device.id, "version", payload);
321
348
  handleFotaSuccess(device.id);
@@ -323,7 +350,7 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
323
350
  return;
324
351
  break;
325
352
  case "app_version":
326
- if (device?.app_version && payload != device.app_version) {
353
+ if (payload != null && payload != device?.app_version) {
327
354
  $.db_device.addLog(device.id,"app_version",payload);
328
355
  $.db_device.update(device.id, "app_version", payload);
329
356
  handleFotaSuccess(device.id);
@@ -370,6 +397,45 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
370
397
  // Optional: default case if needed
371
398
  default:
372
399
  // handle other topics or do nothing
400
+ // check if topic is associated with device
401
+ let findTopic = topicBck;
402
+ if(topicBck.endsWith("/set")) // remove set if exists on topic
403
+ findTopic = getWordBeforeLastSlash(topicBck);
404
+
405
+ const dbTopic = await $.db_device.getMqttTopic(device.id,findTopic)
406
+ if(dbTopic != null){
407
+ if(topicBck.endsWith("/set")){
408
+ // update local topic
409
+ $.db_device.updateLocalTopic(dbTopic.id,payload)
410
+ .then(()=>{
411
+ // set as not synched
412
+ $.db_device.setSynchedTopic(dbTopic.id,false);
413
+ })
414
+ .catch((error)=>{
415
+ console.error(error);
416
+ })
417
+ }else{
418
+ // update remote topic
419
+ $.db_device.updateRemoteTopic(dbTopic.id,payload)
420
+ .then(async (res)=>{
421
+ // check if topics mismatch
422
+ if(isDeepStrictEqual(dbTopic?.localData, dbTopic?.remoteData)){
423
+ $.db_device.setSynchedTopic(dbTopic.id,true);
424
+ }else{
425
+ // set as not synched
426
+ await $.db_device.setSynchedTopic(dbTopic.id,false);
427
+ if(dbTopic?.synch){ // check if sync is enabled
428
+ // synch topic
429
+ synchMqttTopic(device,dbTopic,findTopic);
430
+ }
431
+ }
432
+ })
433
+ .catch((error)=>{
434
+ console.error(error);
435
+ })
436
+
437
+ }
438
+ }
373
439
  break;
374
440
  }
375
441
 
@@ -378,7 +444,26 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
378
444
  }
379
445
  }
380
446
 
381
- async function updateLocalSettings(device, topic, payload){
447
+ async function synchMqttTopic(device,dbTopic,topicBck){
448
+
449
+ const project = await $.db_project.getById(device.project_id);
450
+ let mqtt_prefix = `${project.name}/${device.uid}`;
451
+ let topic = `${mqtt_prefix}/${topicBck}/set`
452
+ let payload = "";
453
+ if(dbTopic?.remoteData && typeof dbTopic?.remoteData === 'object'){
454
+ try{
455
+ payload = JSON.stringify(dbTopic?.remoteData);
456
+ }catch(err){
457
+ console.error(err);
458
+ return;
459
+ }
460
+ }else{
461
+ payload = dbTopic?.remoteData;
462
+ }
463
+ $.mqtt_client.publish(topic,payload,{qos:1,retain:false});
464
+ }
465
+
466
+ async function updateLocalSettings(device,topic,payload){
382
467
 
383
468
  $.db_device.addLog(device.id,"local_settings",JSON.stringify(payload));
384
469
 
@@ -469,11 +554,41 @@ async function updateRemoteSettings(device,topic,payload){
469
554
 
470
555
  try {
471
556
  await $.db_device.updateRemoteSettings(JSON.stringify(settings), device.id);
557
+ if(device?.synch)
558
+ synchSettings(device,route[0]);
472
559
  } catch (err) {
473
560
  console.error("Failed to update local settings:", err);
474
561
  }
475
562
  }
476
563
 
564
+ async function synchSettings(device,key){
565
+
566
+ console.log(`check topic ${key} from ${device.uid}`);
567
+ // Retrieve existing settings
568
+ let localSettings = await $.db_device.getLocalSettings(device.id);
569
+ console.log(localSettings)
570
+ let remoteSettings = await $.db_device.getRemoteSettings(device.id);
571
+ console.log(remoteSettings)
572
+
573
+ //let keys = await checkSettings(localSettings,remoteSettings);
574
+ if(localSettings?.hasOwnProperty(key) && remoteSettings?.hasOwnProperty(key)){
575
+ if(!isDeepStrictEqual(localSettings?.[key], remoteSettings?.[key])){
576
+ const project = await $.db_project.getById(device.project_id);
577
+ let mqtt_prefix = `${project.name}/${device.uid}`;
578
+ let topic = `${mqtt_prefix}/settings/${key}/set`
579
+ try{
580
+ const payload = JSON.stringify(localSettings[key]);
581
+ console.log(`updating key: ${key} with data:`);
582
+ console.log(payload);
583
+ $.mqtt_client.publish(topic,payload,{qos:1,retain:false});
584
+ }catch(err){
585
+ console.error(localSettings[key])
586
+ console.error(err);
587
+ }
588
+ }
589
+ }
590
+ }
591
+
477
592
  async function updateSensor(device,ref,payload){
478
593
  let res = await $.db_device.getSensorByRef(device.id,ref)
479
594
  if(res == null)
@@ -486,6 +601,14 @@ async function updateSensor(device,ref,payload){
486
601
  error = null;
487
602
  timestamp = null;
488
603
 
604
+ if(res?.type.toLowerCase() === "json"){
605
+ if(res?.property && payload.hasOwnProperty(res?.property)){
606
+ object = payload[res.property];
607
+ }else{
608
+ return;
609
+ }
610
+ }
611
+
489
612
  if (typeof object === 'object' && object !== null) {
490
613
  value = object?.value || object?.v;
491
614
  error = object?.error || object?.e;
@@ -553,3 +676,12 @@ function getWordAfterLastSlash(str){
553
676
  }
554
677
  return str.substring(lastSlashIndex + 1);
555
678
  }
679
+
680
+ function getWordBeforeLastSlash(str){
681
+ const lastSlashIndex = str.lastIndexOf('/');
682
+ if (lastSlashIndex === -1) {
683
+ // No slash found, return the original string
684
+ return str;
685
+ }
686
+ return str.substring(0,lastSlashIndex);
687
+ }
@@ -58,9 +58,17 @@ var self = module.exports = {
58
58
 
59
59
  kafka = new Kafka(kafkaConfig);
60
60
 
61
+ let kafkaGroupId = "";
62
+ if(config.env === 'development'){
63
+ const n = Math.floor(Math.random() * 1000) + 1; // 1..1000
64
+ kafkaGroupId = `${config.kafka.groupId}-${String(n)}`;
65
+ }else{
66
+ kafkaGroupId = config.kafka.groupId;
67
+ }
68
+
61
69
  // Create consumer with shared subscription support
62
70
  consumer = kafka.consumer({
63
- groupId: config.kafka.groupId,
71
+ groupId: kafkaGroupId,
64
72
  sessionTimeout: 30000,
65
73
  rebalanceTimeout: 60000,
66
74
  heartbeatInterval: 3000,
@@ -69,7 +77,7 @@ var self = module.exports = {
69
77
  maxBytes: 10485760,
70
78
  maxWaitTimeInMs: 5000,
71
79
  retry: {
72
- initialRetryTime: 100,
80
+ initialRetryTime: 300,
73
81
  retries: 8
74
82
  }
75
83
  });