mqtt-devices-parser 1.0.28 → 1.0.30

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,12 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.30
4
+ fix: tests, recover "settings" parsing, deal with new call addMqttMsgLog
5
+ doc: update Release
6
+ fix: handle ER_DUP_ENTRY race condition in fota.update() (#14)
7
+ perf(src/device/device): clean and parse mqtt payload
8
+
9
+ ## 1.0.29
10
+ fix(models/sensors): allow null model_id
11
+ fix: synching mqtt topics
12
+ feat: stores mqtt messages
13
+
3
14
  ## 1.0.28
4
15
  test: Add comprehensive Jest test suite for device.js module (#2)
5
16
  feat: scope FOTA checks to device variant_id (#11)
6
17
  doc(device): recover readme add test.md file
7
18
  fix(models/variants): remove unique true from column name
8
19
  ci: fix vulnerabilities
9
-
20
+
10
21
  ## 1.0.27
11
22
  feat(db): adds variants table
12
23
 
package/Release.md CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  ## Before release
4
4
 
5
+ run npm test
5
6
  increases version on package.json
6
- run npm install
7
+ run npm install (check vulnerabilities)
7
8
  edit changelog
8
9
  commit and push to repo
9
10
 
@@ -0,0 +1,29 @@
1
+
2
+ module.exports = (sequelize,DataTypes)=>{
3
+ return sequelize.define("logs_mqtt", {
4
+ mqtt_id: {
5
+ type: DataTypes.INTEGER,
6
+ allowNull: false,
7
+ },
8
+ device_id: {
9
+ type: DataTypes.INTEGER,
10
+ allowNull: false,
11
+ },
12
+ source: {
13
+ type: DataTypes.STRING,
14
+ allowNull: false
15
+ },
16
+ action: {
17
+ type: DataTypes.STRING,
18
+ allowNull: false
19
+ },
20
+ payload: {
21
+ type: DataTypes.STRING,
22
+ allowNull: true
23
+ },
24
+ },
25
+ {
26
+ tableName: 'logs_mqtt',
27
+ freezeTableName: true
28
+ })
29
+ }
@@ -0,0 +1,29 @@
1
+
2
+ module.exports = (sequelize,DataTypes)=>{
3
+ return sequelize.define("logs_mqtt_msgs", {
4
+ device_id: {
5
+ type: DataTypes.INTEGER,
6
+ allowNull: false,
7
+ },
8
+ topic: {
9
+ type: DataTypes.STRING,
10
+ allowNull: false
11
+ },
12
+ payload: {
13
+ type: DataTypes.STRING,
14
+ allowNull: true
15
+ },
16
+ qos: {
17
+ type: DataTypes.INTEGER,
18
+ allowNull: false
19
+ },
20
+ retain: {
21
+ type: DataTypes.INTEGER,
22
+ allowNull: false
23
+ },
24
+ },
25
+ {
26
+ tableName: 'logs_mqtt_msgs',
27
+ freezeTableName: true
28
+ })
29
+ }
@@ -3,7 +3,7 @@ module.exports = (sequelize,DataTypes)=>{
3
3
  return sequelize.define("sensors", {
4
4
  model_id: { // reference a model
5
5
  type: DataTypes.INTEGER,
6
- allowNull: false,
6
+ allowNull: true,
7
7
  references: {
8
8
  model: 'models',
9
9
  key: 'id'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mqtt-devices-parser",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/db/device.js CHANGED
@@ -216,6 +216,56 @@ var self = module.exports = {
216
216
  });
217
217
  },
218
218
 
219
+ addMqttLog : async(deviceId,dbTopicId,from,action,payload)=>{
220
+ return new Promise((resolve,reject) => {
221
+
222
+ const timestamp = moment().utc().format('YYYY-MM-DD HH:mm:ss')
223
+ let obj = {
224
+ createdAt : timestamp,
225
+ updatedAt : timestamp
226
+ };
227
+
228
+ obj['device_id'] = deviceId;
229
+ obj['mqtt_id'] = dbTopicId;
230
+ obj['source'] = from;
231
+ obj['action'] = action;
232
+ obj['payload'] = payload;
233
+
234
+ $.db.insert("logs_mqtt",obj)
235
+ .then (rows => {
236
+ return resolve(rows);
237
+ })
238
+ .catch(error => {
239
+ return reject(error);
240
+ });
241
+ });
242
+ },
243
+
244
+ addMqttMsgLog : async(deviceId,topic,payload,retain,qos)=>{
245
+ return new Promise((resolve,reject) => {
246
+
247
+ const timestamp = moment().utc().format('YYYY-MM-DD HH:mm:ss')
248
+ let obj = {
249
+ createdAt : timestamp,
250
+ updatedAt : timestamp
251
+ };
252
+
253
+ obj['device_id'] = deviceId;
254
+ obj['topic'] = topic;
255
+ obj['payload'] = payload;
256
+ obj['retain'] = retain;
257
+ obj['qos'] = qos;
258
+
259
+ $.db.insert("logs_mqtt_msgs",obj)
260
+ .then (rows => {
261
+ return resolve(rows);
262
+ })
263
+ .catch(error => {
264
+ return reject(error);
265
+ });
266
+ });
267
+ },
268
+
219
269
  addLogIfChanged : async(id,column,value)=>{
220
270
  return new Promise(async (resolve,reject) => {
221
271
 
package/src/db/fota.js CHANGED
@@ -96,7 +96,14 @@ var self = module.exports = {
96
96
  return resolve(rows[0]);
97
97
  })
98
98
  .catch(error => {
99
- return reject(error);
99
+ if(error.code === 'ER_DUP_ENTRY'){
100
+ let filter = { device_id : deviceId };
101
+ $.db.update("fota",obj,filter)
102
+ .then(rows => { return resolve(rows[0]); })
103
+ .catch(err => { return reject(err); });
104
+ }else{
105
+ return reject(error);
106
+ }
100
107
  });
101
108
 
102
109
  }else{
@@ -33,7 +33,7 @@ var self = module.exports = {
33
33
  });
34
34
  },
35
35
 
36
- parseMessage : async (client,topic,payload,retain)=>{
36
+ parseMessage : async (client,topic,payload,retain,qos=0)=>{
37
37
 
38
38
  let topic_bck = topic;
39
39
  let project_name = null;
@@ -77,6 +77,7 @@ var self = module.exports = {
77
77
  if(device.protocol.toLowerCase() === "lwm2m"){
78
78
  parseLwm2mMessage(client, project_name, device, topic, payload, action);
79
79
  }else if(device.protocol.toLowerCase() === "mqtt"){
80
+ $.db_device.addMqttMsgLog(device.id,topic,payload,retain,qos);
80
81
  parseMqttMessage(client, project_name, device, topic, payload, retain);
81
82
  }
82
83
  },
@@ -266,7 +267,6 @@ var self = module.exports = {
266
267
  }
267
268
  },
268
269
 
269
-
270
270
  updateSensor : async (device,ref,payload)=>{
271
271
 
272
272
  let sensors = await $.db_device.getSensorsByRef(device.id,ref)
@@ -319,17 +319,36 @@ var self = module.exports = {
319
319
 
320
320
  handleMqttTopic : async(device, dbTopic, payload, set)=>{
321
321
  if(set){
322
+ if(payload === "" || payload == null){
323
+ // acknowledgment
324
+ $.db_device.setSynchedTopic(dbTopic.id,true);
325
+ $.db_device.updateRemoteTopic(dbTopic.id,dbTopic?.localData?.value ? dbTopic.localData.value : dbTopic?.localData);
326
+ let from = "device";
327
+ let action = "ack"
328
+ $.db_device.addMqttLog(device.id,dbTopic.id,from,action,null);
329
+ return;
330
+ }
322
331
  // update local topic
332
+ let from = "server";
333
+ let action = "write";
334
+ $.db_device.addMqttLog(device.id,dbTopic.id,from,action,JSON.stringify(payload));
335
+
323
336
  $.db_device.updateLocalTopic(dbTopic.id,payload)
324
337
  .then(()=>{
325
- // set as not synched
326
- $.db_device.setSynchedTopic(dbTopic.id,false);
338
+ if(isDeepStrictEqual(payload, dbTopic?.remoteData))
339
+ $.db_device.setSynchedTopic(dbTopic.id,true);
340
+ else
341
+ $.db_device.setSynchedTopic(dbTopic.id,false);
327
342
  })
328
343
  .catch((error)=>{
329
344
  console.error(error);
330
345
  })
331
346
  }else{
332
347
  // update remote topic
348
+ let from = "device";
349
+ let action = "update";
350
+ $.db_device.addMqttLog(device.id,dbTopic.id,from,action,JSON.stringify(payload));
351
+
333
352
  $.db_device.updateRemoteTopic(dbTopic.id,payload)
334
353
  .then(async (res)=>{
335
354
  // check if topics mismatch
@@ -383,14 +402,24 @@ async function parseLwm2mMessage(client, project_name, device, topic, payload, a
383
402
 
384
403
  async function parseMqttMessage(client, project_name, device, topic, payload, retain){
385
404
 
386
- if(topic.endsWith("/get"))
405
+ if(topic.endsWith("/get")){
406
+ let findTopic = $.parser.getWordBeforeLastSlash(topic);
407
+ const dbTopic = await $.db_device.getMqttTopic(device.id,findTopic)
408
+ if(dbTopic != null){
409
+ let from = "server";
410
+ let action = "request";
411
+ let data = null;
412
+ $.db_device.addMqttLog(device.id,dbTopic.id,from,action,data);
413
+ }
387
414
  return;
415
+ }
388
416
 
389
417
  const topicBck = topic;
390
418
  //console.log("[MQTT] parse topic: ",topic);
391
419
 
392
420
  try{
393
421
  payload = JSON.parse(payload);
422
+ payload = cleanAndParse(payload);
394
423
  }catch(error){}
395
424
 
396
425
  let word = $.parser.getFirstWord(topic);
@@ -485,7 +514,7 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
485
514
  }
486
515
  }
487
516
  break;
488
- case "settings":
517
+ case "settings": // deprecated
489
518
  if(topic.endsWith("/set")){
490
519
  updateLocalSettings(device,topic,payload);
491
520
  }else{
@@ -525,19 +554,25 @@ async function synchMqttTopic(device,dbTopic){
525
554
  let mqtt_prefix = `${project.name}/${device.uid}`;
526
555
  let topic = `${mqtt_prefix}/${dbTopic.topic}/set`
527
556
  let payload = "";
528
- if(dbTopic?.remoteData && typeof dbTopic?.remoteData === 'object'){
557
+ if(dbTopic?.localData && typeof dbTopic?.localData === 'object'){
529
558
  try{
530
- payload = JSON.stringify(dbTopic?.remoteData);
559
+ if(dbTopic?.localData?.value)
560
+ payload = JSON.stringify(dbTopic?.localData?.value);
561
+ else
562
+ payload = JSON.stringify(dbTopic?.localData);
531
563
  }catch(err){
532
564
  console.error(err);
533
565
  return;
534
566
  }
535
567
  }else{
536
- payload = dbTopic?.remoteData;
568
+ payload = dbTopic?.localData;
537
569
  }
538
570
  $.mqtt_client.publish(topic,payload,{qos:1,retain:false});
539
571
  }
540
572
 
573
+ // keep a struct with all settings configured on the device
574
+ // only topics <project>/<uid>/settings are stored on this struct
575
+ // easier way to return all settings at once
541
576
  async function updateLocalSettings(device,topic,payload){
542
577
 
543
578
  $.db_device.addLog(device.id,"local_settings",JSON.stringify(payload));
@@ -586,6 +621,9 @@ async function updateLocalSettings(device,topic,payload){
586
621
  }
587
622
  }
588
623
 
624
+ // keep a struct with all settings configured on the device
625
+ // only topics <project>/<uid>/settings are stored on this struct
626
+ // easier way to return all settings at once
589
627
  async function updateRemoteSettings(device,topic,payload){
590
628
 
591
629
  $.db_device.addLog(device.id,"remote_settings",JSON.stringify(payload));
@@ -629,13 +667,15 @@ async function updateRemoteSettings(device,topic,payload){
629
667
 
630
668
  try {
631
669
  await $.db_device.updateRemoteSettings(JSON.stringify(settings), device.id);
632
- if(device?.synch)
670
+ // deprecated, only defined topics with synch enabled can be synched
671
+ if(device?.synch && false)
633
672
  synchSettings(device,route[0]);
634
673
  } catch (err) {
635
674
  console.error("Failed to update local settings:", err);
636
675
  }
637
676
  }
638
677
 
678
+ // deprecated - only defined mqtt topics can be synched
639
679
  async function synchSettings(device,key){
640
680
 
641
681
  console.log(`check topic ${key} from ${device.uid}`);
@@ -646,6 +686,7 @@ async function synchSettings(device,key){
646
686
  console.log(remoteSettings)
647
687
 
648
688
  //let keys = await checkSettings(localSettings,remoteSettings);
689
+ /*
649
690
  if(localSettings?.hasOwnProperty(key) && remoteSettings?.hasOwnProperty(key)){
650
691
  if(!isDeepStrictEqual(localSettings?.[key], remoteSettings?.[key])){
651
692
  const project = await $.db_project.getById(device.project_id);
@@ -662,9 +703,9 @@ async function synchSettings(device,key){
662
703
  }
663
704
  }
664
705
  }
706
+ */
665
707
  }
666
708
 
667
-
668
709
  function handleFotaSuccess (deviceId){
669
710
  let object = {
670
711
  "nAttempts" : 0,
@@ -684,4 +725,18 @@ function handleFotaError (deviceId, error){
684
725
  $.db_fota.updateLog(deviceId,object);
685
726
  }
686
727
 
687
-
728
+ function cleanAndParse(payload) {
729
+ if (typeof payload === 'string') {
730
+ let cleaned = payload.trim();
731
+ if (cleaned.startsWith('"') && cleaned.endsWith('"')) {
732
+ cleaned = cleaned.slice(1, -1);
733
+ }
734
+ cleaned = cleaned.replace(/\\"/g, '"');
735
+ try {
736
+ return JSON.parse(cleaned);
737
+ } catch (e) {
738
+ return payload; // fallback: return original if still invalid
739
+ }
740
+ }
741
+ return payload;
742
+ }
@@ -29,7 +29,8 @@ global.$ = {
29
29
  updateLocalTopic: jest.fn(),
30
30
  setSynchedTopic: jest.fn(),
31
31
  updateRemoteTopic: jest.fn(),
32
- getAssociatedDevice: jest.fn()
32
+ getAssociatedDevice: jest.fn(),
33
+ addMqttMsgLog: jest.fn()
33
34
  },
34
35
  db_model: {
35
36
  getByName: jest.fn(),
@@ -140,6 +140,7 @@ var self = module.exports = {
140
140
 
141
141
  try{
142
142
  payload = JSON.parse(payload);
143
+ //console.log(`[KAFKA] payload: ${payload.mqtt.payload}`);
143
144
  }catch(error){
144
145
  console.log(`[KAFKA] error: ${payload}`);
145
146
  console.log(error)