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 +12 -1
- package/Release.md +2 -1
- package/models/logs_mqtt.models.js +29 -0
- package/models/logs_mqtt_msgs.models.js +29 -0
- package/models/sensors.models.js +1 -1
- package/package.json +1 -1
- package/src/db/device.js +50 -0
- package/src/db/fota.js +8 -1
- package/src/device/device.js +67 -12
- package/src/device/device.test.js +2 -1
- package/src/kafka/consumer.js +1 -0
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
|
@@ -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
|
+
}
|
package/models/sensors.models.js
CHANGED
package/package.json
CHANGED
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
|
-
|
|
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{
|
package/src/device/device.js
CHANGED
|
@@ -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
|
-
|
|
326
|
-
|
|
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?.
|
|
557
|
+
if(dbTopic?.localData && typeof dbTopic?.localData === 'object'){
|
|
529
558
|
try{
|
|
530
|
-
|
|
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?.
|
|
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
|
-
|
|
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
|
+
}
|
package/src/kafka/consumer.js
CHANGED