mqtt-devices-parser 1.0.24 → 1.0.25
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 +30 -0
- package/index.js +1 -0
- package/models/logs_sensor.models.js +1 -1
- package/models/sensors.models.js +23 -3
- package/models/sensorsTemplate.models.js +43 -0
- package/package.json +1 -1
- package/src/aux/parser.js +36 -0
- package/src/db/data.js +56 -0
- package/src/db/device.js +2 -2
- package/src/db/model.js +2 -2
- package/src/db/sensor.js +14 -1
- package/src/device/device.js +124 -144
package/Changelog.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.25
|
|
4
|
+
|
|
5
|
+
Sensors template (#9)
|
|
6
|
+
|
|
7
|
+
* new table sensorsTemplate
|
|
8
|
+
|
|
9
|
+
* store topics which match a sensor
|
|
10
|
+
for each mqtt msg received, check if there is a sensor associated to that topic.
|
|
11
|
+
If there is, update current value and insert new log
|
|
12
|
+
|
|
13
|
+
* db: models/sensors
|
|
14
|
+
changes localUnixTs field to remoteUnixTs
|
|
15
|
+
adds value, error
|
|
16
|
+
changes property: allow null
|
|
17
|
+
adds foreign keys to model_id and device_id
|
|
18
|
+
|
|
19
|
+
* handle change db column localUnixTs to remoteUnixTs
|
|
20
|
+
|
|
21
|
+
* fix getSensorsByRef
|
|
22
|
+
ref is not unique so it can have more than 1 row
|
|
23
|
+
|
|
24
|
+
* src/db/data: Implements missing function pathIntoObject
|
|
25
|
+
|
|
26
|
+
* src/aux/parser: adds functions to handle mqtt topics parsing
|
|
27
|
+
index: uses global $.parser as a pointer to class src/aux/parser
|
|
28
|
+
src/device/device: fixes null iterations, sets updateSensor and handleMqttTopic as public functions. Removes local mqtt parser functions. Adapts calls to those functions
|
|
29
|
+
|
|
30
|
+
mqtt: always check if topic is registered
|
|
31
|
+
store values in db
|
|
32
|
+
|
|
3
33
|
## 1.0.24
|
|
4
34
|
src/device/device: fix log
|
|
5
35
|
src/kafka/consumer: increase initialRetryTime from 100 to 300ms
|
package/index.js
CHANGED
|
@@ -17,6 +17,7 @@ $.db_firmware = require('./src/db/firmware');
|
|
|
17
17
|
$.db_sensor = require('./src/db/sensor');
|
|
18
18
|
$.db_fota = require('./src/db/fota');
|
|
19
19
|
$.mqtt_client = null;
|
|
20
|
+
$.parser = require('./src/aux/parser')
|
|
20
21
|
|
|
21
22
|
const packageJson = require(__dirname+'/package.json');
|
|
22
23
|
const packageVersion = packageJson.version;
|
package/models/sensors.models.js
CHANGED
|
@@ -3,11 +3,19 @@ module.exports = (sequelize,DataTypes)=>{
|
|
|
3
3
|
return sequelize.define("sensors", {
|
|
4
4
|
model_id: { // reference a model
|
|
5
5
|
type: DataTypes.INTEGER,
|
|
6
|
-
allowNull:
|
|
6
|
+
allowNull: false,
|
|
7
|
+
references: {
|
|
8
|
+
model: 'models',
|
|
9
|
+
key: 'id'
|
|
10
|
+
}
|
|
7
11
|
},
|
|
8
12
|
device_id: { // reference a device id
|
|
9
13
|
type: DataTypes.INTEGER,
|
|
10
|
-
allowNull:
|
|
14
|
+
allowNull: false,
|
|
15
|
+
references: {
|
|
16
|
+
model: 'devices',
|
|
17
|
+
key: 'id'
|
|
18
|
+
}
|
|
11
19
|
},
|
|
12
20
|
active: {
|
|
13
21
|
type: DataTypes.BOOLEAN,
|
|
@@ -28,7 +36,19 @@ module.exports = (sequelize,DataTypes)=>{
|
|
|
28
36
|
},
|
|
29
37
|
property: {
|
|
30
38
|
type: DataTypes.STRING,
|
|
31
|
-
allowNull:
|
|
39
|
+
allowNull: true
|
|
40
|
+
},
|
|
41
|
+
value: {
|
|
42
|
+
type: DataTypes.STRING,
|
|
43
|
+
allowNull: true
|
|
44
|
+
},
|
|
45
|
+
error: {
|
|
46
|
+
type: DataTypes.STRING,
|
|
47
|
+
allowNull: true,
|
|
48
|
+
},
|
|
49
|
+
remoteUnixTs: {
|
|
50
|
+
type: DataTypes.BIGINT,
|
|
51
|
+
allowNull: true,
|
|
32
52
|
},
|
|
33
53
|
graph: {
|
|
34
54
|
type: DataTypes.JSON,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
module.exports = (sequelize,DataTypes)=>{
|
|
3
|
+
return sequelize.define("sensorsTemplate", {
|
|
4
|
+
model_id: { // reference a model
|
|
5
|
+
type: DataTypes.INTEGER,
|
|
6
|
+
allowNull: false,
|
|
7
|
+
references: {
|
|
8
|
+
model: 'models',
|
|
9
|
+
key: 'id'
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
active: {
|
|
13
|
+
type: DataTypes.BOOLEAN,
|
|
14
|
+
allowNull: false,
|
|
15
|
+
defaultValue: true
|
|
16
|
+
},
|
|
17
|
+
ref: {
|
|
18
|
+
type: DataTypes.STRING,
|
|
19
|
+
allowNull: false,
|
|
20
|
+
},
|
|
21
|
+
name: {
|
|
22
|
+
type: DataTypes.STRING,
|
|
23
|
+
allowNull: false,
|
|
24
|
+
},
|
|
25
|
+
type: {
|
|
26
|
+
type: DataTypes.STRING,
|
|
27
|
+
allowNull: false,
|
|
28
|
+
},
|
|
29
|
+
property: {
|
|
30
|
+
type: DataTypes.STRING,
|
|
31
|
+
allowNull: true
|
|
32
|
+
},
|
|
33
|
+
graph: {
|
|
34
|
+
type: DataTypes.JSON,
|
|
35
|
+
allowNull: true,
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
tableName: 'sensorsTemplate',
|
|
40
|
+
freezeTableName: true
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
package/package.json
CHANGED
package/src/aux/parser.js
CHANGED
|
@@ -16,5 +16,41 @@ module.exports = {
|
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
18
|
return obj;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
getFirstWord : (str)=>{
|
|
22
|
+
const slashIndex = str.indexOf('/');
|
|
23
|
+
if (slashIndex === -1) {
|
|
24
|
+
// No slash found, return the original string
|
|
25
|
+
return str;
|
|
26
|
+
}
|
|
27
|
+
return str.substring(0,slashIndex);
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
getTopicAfterSlash : (str)=>{
|
|
31
|
+
const slashIndex = str.indexOf('/');
|
|
32
|
+
if (slashIndex === -1) {
|
|
33
|
+
// No slash found, return empty string
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
return str.substring(slashIndex + 1);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
getWordAfterLastSlash : (str)=>{
|
|
40
|
+
const lastSlashIndex = str.lastIndexOf('/');
|
|
41
|
+
if (lastSlashIndex === -1) {
|
|
42
|
+
// No slash found, return the original string
|
|
43
|
+
return str;
|
|
44
|
+
}
|
|
45
|
+
return str.substring(lastSlashIndex + 1);
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
getWordBeforeLastSlash : (str)=>{
|
|
49
|
+
const lastSlashIndex = str.lastIndexOf('/');
|
|
50
|
+
if (lastSlashIndex === -1) {
|
|
51
|
+
// No slash found, return the original string
|
|
52
|
+
return str;
|
|
53
|
+
}
|
|
54
|
+
return str.substring(0,lastSlashIndex);
|
|
19
55
|
}
|
|
20
56
|
}
|
package/src/db/data.js
CHANGED
|
@@ -372,3 +372,59 @@ var self = module.exports = {
|
|
|
372
372
|
});
|
|
373
373
|
}
|
|
374
374
|
};
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Build a nested object from a path string and a leaf value.
|
|
378
|
+
*
|
|
379
|
+
* Example:
|
|
380
|
+
* pathIntoObject('foo/bar/baz', 123) -> { foo: { bar: { baz: 123 } } }
|
|
381
|
+
*
|
|
382
|
+
* Notes:
|
|
383
|
+
* - By default, splits on '/'.
|
|
384
|
+
* - Leading/trailing/duplicate delimiters are ignored (e.g., '/a//b/' -> ['a','b']).
|
|
385
|
+
* - Value is used as-is. If you pass JSON.stringify(data), the leaf will be that string.
|
|
386
|
+
*
|
|
387
|
+
* @param {string} path - The path to turn into nested object keys (e.g., 'a/b/c').
|
|
388
|
+
* @param {*} value - The value to place at the final key.
|
|
389
|
+
* @param {Object} [options]
|
|
390
|
+
* @param {string} [options.delimiter='/'] - Path delimiter.
|
|
391
|
+
* @returns {*}
|
|
392
|
+
*/
|
|
393
|
+
const parser = {
|
|
394
|
+
pathIntoObject(path, value, options = {}) {
|
|
395
|
+
const delimiter = options.delimiter || '/';
|
|
396
|
+
|
|
397
|
+
if (typeof path !== 'string') {
|
|
398
|
+
throw new TypeError('path must be a string');
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Normalize path: remove empty segments caused by leading/trailing or duplicate delimiters
|
|
402
|
+
const segments = path
|
|
403
|
+
.split(delimiter)
|
|
404
|
+
.map(s => s.trim())
|
|
405
|
+
.filter(Boolean);
|
|
406
|
+
|
|
407
|
+
// If no segments, just return the value directly
|
|
408
|
+
if (segments.length === 0) return value;
|
|
409
|
+
|
|
410
|
+
// Build nested object
|
|
411
|
+
let root = {};
|
|
412
|
+
let cursor = root;
|
|
413
|
+
|
|
414
|
+
for (let i = 0; i < segments.length; i++) {
|
|
415
|
+
const key = segments[i];
|
|
416
|
+
const isLast = i === segments.length - 1;
|
|
417
|
+
|
|
418
|
+
if (isLast) {
|
|
419
|
+
cursor[key] = value;
|
|
420
|
+
} else {
|
|
421
|
+
// Create the next level if not present
|
|
422
|
+
cursor[key] = {};
|
|
423
|
+
cursor = cursor[key];
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return root;
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
|
package/src/db/device.js
CHANGED
|
@@ -388,7 +388,7 @@ var self = module.exports = {
|
|
|
388
388
|
});
|
|
389
389
|
},
|
|
390
390
|
|
|
391
|
-
|
|
391
|
+
getSensorsByRef : async(deviceId, ref)=>{
|
|
392
392
|
return new Promise((resolve,reject) => {
|
|
393
393
|
|
|
394
394
|
let query = "SELECT * FROM ?? where device_id = ? and ref = ?";
|
|
@@ -398,7 +398,7 @@ var self = module.exports = {
|
|
|
398
398
|
$.db.queryRow(query)
|
|
399
399
|
.then( rows => {
|
|
400
400
|
if(rows.length > 0)
|
|
401
|
-
return resolve(rows
|
|
401
|
+
return resolve(rows);
|
|
402
402
|
else
|
|
403
403
|
return resolve(null);
|
|
404
404
|
})
|
package/src/db/model.js
CHANGED
|
@@ -112,7 +112,7 @@ var self = module.exports = {
|
|
|
112
112
|
});
|
|
113
113
|
},
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
getSensorsByRef : async(modelId, ref)=>{
|
|
116
116
|
return new Promise((resolve,reject) => {
|
|
117
117
|
|
|
118
118
|
let query = "SELECT * FROM ?? where model_id = ? and ref = ?";
|
|
@@ -122,7 +122,7 @@ var self = module.exports = {
|
|
|
122
122
|
$.db.queryRow(query)
|
|
123
123
|
.then( rows => {
|
|
124
124
|
if(rows.length > 0)
|
|
125
|
-
return resolve(rows
|
|
125
|
+
return resolve(rows);
|
|
126
126
|
else
|
|
127
127
|
return resolve(null);
|
|
128
128
|
})
|
package/src/db/sensor.js
CHANGED
|
@@ -26,6 +26,7 @@ var self = module.exports = {
|
|
|
26
26
|
});
|
|
27
27
|
},
|
|
28
28
|
|
|
29
|
+
// not used !!
|
|
29
30
|
getByRef : async (ref)=>{
|
|
30
31
|
|
|
31
32
|
return new Promise((resolve,reject) => {
|
|
@@ -98,7 +99,7 @@ var self = module.exports = {
|
|
|
98
99
|
value : payload?.value,
|
|
99
100
|
error : payload?.error,
|
|
100
101
|
obj : payload?.object,
|
|
101
|
-
|
|
102
|
+
remoteUnixTs : payload?.timestamp, // local timestamp
|
|
102
103
|
createdAt : moment().utc().format('YYYY-MM-DD HH:mm:ss'),
|
|
103
104
|
updatedAt : moment().utc().format('YYYY-MM-DD HH:mm:ss')
|
|
104
105
|
}
|
|
@@ -113,4 +114,16 @@ var self = module.exports = {
|
|
|
113
114
|
});
|
|
114
115
|
},
|
|
115
116
|
|
|
117
|
+
update : async(table,obj,filter)=>{
|
|
118
|
+
return new Promise((resolve,reject) => {
|
|
119
|
+
|
|
120
|
+
$.db.update(table,obj,filter)
|
|
121
|
+
.then (rows => {
|
|
122
|
+
return resolve(rows);
|
|
123
|
+
})
|
|
124
|
+
.catch(error => {
|
|
125
|
+
return reject(error);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
},
|
|
116
129
|
}
|
package/src/device/device.js
CHANGED
|
@@ -2,6 +2,7 @@ const moment = require('moment');
|
|
|
2
2
|
const { isDeepStrictEqual } = require('node:util');
|
|
3
3
|
|
|
4
4
|
var _project = [];
|
|
5
|
+
const table = "sensors"
|
|
5
6
|
const logs_table = "logs_sensor";
|
|
6
7
|
const semver = require('semver');
|
|
7
8
|
|
|
@@ -30,8 +31,6 @@ var self = module.exports = {
|
|
|
30
31
|
})
|
|
31
32
|
})
|
|
32
33
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
34
|
},
|
|
36
35
|
|
|
37
36
|
parseMessage : async (client,topic,payload,retain)=>{
|
|
@@ -49,22 +48,22 @@ var self = module.exports = {
|
|
|
49
48
|
}catch(err){};
|
|
50
49
|
|
|
51
50
|
// --- project ---
|
|
52
|
-
project_name = getFirstWord(topic);
|
|
51
|
+
project_name = $.parser.getFirstWord(topic);
|
|
53
52
|
project = await $.db_project.getByName(project_name);
|
|
54
53
|
if(project == null)
|
|
55
54
|
return;
|
|
56
55
|
|
|
57
56
|
project_id = project?.id;
|
|
58
|
-
topic =
|
|
57
|
+
topic = $.parser.getTopicAfterSlash(topic)
|
|
59
58
|
|
|
60
59
|
// check if is from lwm2m gw
|
|
61
60
|
if(topic.startsWith("responses") || topic.startsWith("requests")){
|
|
62
|
-
action = getFirstWord(topic);
|
|
63
|
-
topic =
|
|
61
|
+
action = $.parser.getFirstWord(topic);
|
|
62
|
+
topic = $.parser.getTopicAfterSlash(topic);
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
// --- uid ---
|
|
67
|
-
uid = getFirstWord(topic);
|
|
66
|
+
uid = $.parser.getFirstWord(topic);
|
|
68
67
|
// check if topic corresponds to a device
|
|
69
68
|
if(!uid.startsWith(project.uidPrefix) || uid.length > project?.uidLength)
|
|
70
69
|
return;
|
|
@@ -73,14 +72,13 @@ var self = module.exports = {
|
|
|
73
72
|
if(!device)
|
|
74
73
|
return;
|
|
75
74
|
|
|
76
|
-
topic =
|
|
75
|
+
topic = $.parser.getTopicAfterSlash(topic);
|
|
77
76
|
|
|
78
77
|
if(device.protocol.toLowerCase() === "lwm2m"){
|
|
79
78
|
parseLwm2mMessage(client, project_name, device, topic, payload, action);
|
|
80
79
|
}else if(device.protocol.toLowerCase() === "mqtt"){
|
|
81
80
|
parseMqttMessage(client, project_name, device, topic, payload, retain);
|
|
82
81
|
}
|
|
83
|
-
|
|
84
82
|
},
|
|
85
83
|
|
|
86
84
|
deleteLogs : async()=>{
|
|
@@ -105,6 +103,9 @@ var self = module.exports = {
|
|
|
105
103
|
|
|
106
104
|
const models = await $.db_model.getAll();
|
|
107
105
|
|
|
106
|
+
if(!models || !models?.length)
|
|
107
|
+
return;
|
|
108
|
+
|
|
108
109
|
for (const model of models) {
|
|
109
110
|
|
|
110
111
|
//console.log("model:",model?.name);
|
|
@@ -181,6 +182,9 @@ var self = module.exports = {
|
|
|
181
182
|
|
|
182
183
|
const devices = await $.db_fota.getUpdatable(release);
|
|
183
184
|
|
|
185
|
+
if(!devices || !!devices?.length)
|
|
186
|
+
return;
|
|
187
|
+
|
|
184
188
|
const batchSize = 10;
|
|
185
189
|
for (let i = 0; i < devices.length; i += batchSize) {
|
|
186
190
|
const batch = devices.slice(i, i + batchSize);
|
|
@@ -247,6 +251,91 @@ var self = module.exports = {
|
|
|
247
251
|
}
|
|
248
252
|
},
|
|
249
253
|
|
|
254
|
+
|
|
255
|
+
updateSensor : async (device,ref,payload)=>{
|
|
256
|
+
|
|
257
|
+
let sensors = await $.db_device.getSensorsByRef(device.id,ref)
|
|
258
|
+
|
|
259
|
+
if(!sensors?.length)
|
|
260
|
+
return;
|
|
261
|
+
|
|
262
|
+
object = payload;
|
|
263
|
+
value = null;
|
|
264
|
+
error = null;
|
|
265
|
+
timestamp = null;
|
|
266
|
+
|
|
267
|
+
sensors.map( (sensor,index) =>{
|
|
268
|
+
let value = null;
|
|
269
|
+
let error = null;
|
|
270
|
+
let remoteUnixTs = null;
|
|
271
|
+
if(sensor?.type === "json" && typeof object === 'object'){
|
|
272
|
+
if(object.hasOwnProperty(sensor?.property)){
|
|
273
|
+
value = object[sensor.property];
|
|
274
|
+
}
|
|
275
|
+
}else{
|
|
276
|
+
if (typeof object === 'object') {
|
|
277
|
+
value = object?.value || object?.v;
|
|
278
|
+
error = object?.error || object?.e;
|
|
279
|
+
remoteUnixTs = object?.timestamp || objects?.ts;
|
|
280
|
+
}else{
|
|
281
|
+
value = payload;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if(value || error){
|
|
285
|
+
|
|
286
|
+
if(!remoteUnixTs)
|
|
287
|
+
remoteUnixTs = moment().unix()
|
|
288
|
+
|
|
289
|
+
const data = {
|
|
290
|
+
value,
|
|
291
|
+
error,
|
|
292
|
+
remoteUnixTs
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let filter = {
|
|
296
|
+
id : sensor.id
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
$.db_sensor.update(table,data,filter);
|
|
300
|
+
$.db_sensor.insert(logs_table,device.id,sensor.id,data);
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
handleMqttTopic : async(device, dbTopic, payload, set)=>{
|
|
306
|
+
if(set){
|
|
307
|
+
// update local topic
|
|
308
|
+
$.db_device.updateLocalTopic(dbTopic.id,payload)
|
|
309
|
+
.then(()=>{
|
|
310
|
+
// set as not synched
|
|
311
|
+
$.db_device.setSynchedTopic(dbTopic.id,false);
|
|
312
|
+
})
|
|
313
|
+
.catch((error)=>{
|
|
314
|
+
console.error(error);
|
|
315
|
+
})
|
|
316
|
+
}else{
|
|
317
|
+
// update remote topic
|
|
318
|
+
$.db_device.updateRemoteTopic(dbTopic.id,payload)
|
|
319
|
+
.then(async (res)=>{
|
|
320
|
+
// check if topics mismatch
|
|
321
|
+
if(isDeepStrictEqual(dbTopic?.localData, dbTopic?.remoteData)){
|
|
322
|
+
$.db_device.setSynchedTopic(dbTopic.id,true);
|
|
323
|
+
}else{
|
|
324
|
+
// set as not synched
|
|
325
|
+
await $.db_device.setSynchedTopic(dbTopic.id,false);
|
|
326
|
+
if(dbTopic?.synch){ // check if sync is enabled
|
|
327
|
+
// synch topic
|
|
328
|
+
synchMqttTopic(device,dbTopic);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
.catch((error)=>{
|
|
333
|
+
console.error(error);
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
|
|
250
339
|
}
|
|
251
340
|
|
|
252
341
|
async function parseLwm2mMessage(client, project_name, device, topic, payload, action){
|
|
@@ -257,8 +346,8 @@ async function parseLwm2mMessage(client, project_name, device, topic, payload, a
|
|
|
257
346
|
payload = JSON.parse(payload);
|
|
258
347
|
}catch(error){}
|
|
259
348
|
|
|
260
|
-
let word = getFirstWord(topic);
|
|
261
|
-
topic =
|
|
349
|
+
let word = $.parser.getFirstWord(topic);
|
|
350
|
+
topic = $.parser.getTopicAfterSlash(topic);
|
|
262
351
|
|
|
263
352
|
switch (word) {
|
|
264
353
|
case "status":
|
|
@@ -269,7 +358,7 @@ async function parseLwm2mMessage(client, project_name, device, topic, payload, a
|
|
|
269
358
|
return;
|
|
270
359
|
break;
|
|
271
360
|
case "sensor":
|
|
272
|
-
updateSensor(device,topic,payload)
|
|
361
|
+
self.updateSensor(device,topic,payload)
|
|
273
362
|
}
|
|
274
363
|
|
|
275
364
|
if(_project[project_name]){
|
|
@@ -289,8 +378,8 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
289
378
|
payload = JSON.parse(payload);
|
|
290
379
|
}catch(error){}
|
|
291
380
|
|
|
292
|
-
let word = getFirstWord(topic);
|
|
293
|
-
topic =
|
|
381
|
+
let word = $.parser.getFirstWord(topic);
|
|
382
|
+
topic = $.parser.getTopicAfterSlash(topic);
|
|
294
383
|
|
|
295
384
|
switch (word) {
|
|
296
385
|
case "status":
|
|
@@ -299,6 +388,7 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
299
388
|
$.db_device.addLog(device.id,"status",payload);
|
|
300
389
|
}
|
|
301
390
|
|
|
391
|
+
// list mqtt topics and do calls to readable topics
|
|
302
392
|
if (payload === "online"){
|
|
303
393
|
let mqtt_prefix = `${project_name}/${device.uid}`;
|
|
304
394
|
// check if remote settings are known, if not require it
|
|
@@ -323,7 +413,6 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
323
413
|
$.mqtt_client.publish(topic,"",{qos:1,retain:false});
|
|
324
414
|
}
|
|
325
415
|
}
|
|
326
|
-
return;
|
|
327
416
|
break;
|
|
328
417
|
case "model":
|
|
329
418
|
let res = await $.db_model.getByName(payload);
|
|
@@ -332,14 +421,12 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
332
421
|
$.db_device.update(device.id, "model_id", model_id);
|
|
333
422
|
$.db_device.addLog(device.id,"model_id",model_id);
|
|
334
423
|
}
|
|
335
|
-
return;
|
|
336
424
|
break;
|
|
337
425
|
case "tech":
|
|
338
426
|
if (payload != null && payload != device?.tech) {
|
|
339
427
|
$.db_device.update(device.id, "tech", payload);
|
|
340
428
|
$.db_device.addLog(device.id,"tech",payload);
|
|
341
429
|
}
|
|
342
|
-
return;
|
|
343
430
|
break;
|
|
344
431
|
case "version":
|
|
345
432
|
if (payload != null && payload != device?.version) {
|
|
@@ -347,7 +434,6 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
347
434
|
$.db_device.update(device.id, "version", payload);
|
|
348
435
|
handleFotaSuccess(device.id);
|
|
349
436
|
}
|
|
350
|
-
return;
|
|
351
437
|
break;
|
|
352
438
|
case "app_version":
|
|
353
439
|
if (payload != null && payload != device?.app_version) {
|
|
@@ -355,7 +441,6 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
355
441
|
$.db_device.update(device.id, "app_version", payload);
|
|
356
442
|
handleFotaSuccess(device.id);
|
|
357
443
|
}
|
|
358
|
-
return;
|
|
359
444
|
break;
|
|
360
445
|
case "fw":
|
|
361
446
|
if(topic === "fota/update/status"){
|
|
@@ -371,7 +456,7 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
371
456
|
}else if(typeof payload !== 'object' && payload !== null){
|
|
372
457
|
// change it to topic.startsWith("fw")
|
|
373
458
|
try{
|
|
374
|
-
const column = getWordAfterLastSlash(topic);
|
|
459
|
+
const column = $.parser.getWordAfterLastSlash(topic);
|
|
375
460
|
let rows = await $.db_data.update("fw",device.id,column,payload);
|
|
376
461
|
rows = await $.db_data.addLog("logs_fw",device.id,column,payload);
|
|
377
462
|
}catch(error){
|
|
@@ -379,7 +464,6 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
379
464
|
}
|
|
380
465
|
}
|
|
381
466
|
}
|
|
382
|
-
return;
|
|
383
467
|
break;
|
|
384
468
|
case "settings":
|
|
385
469
|
if(topic.endsWith("/set")){
|
|
@@ -387,68 +471,39 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
387
471
|
}else{
|
|
388
472
|
updateRemoteSettings(device,topic,payload);
|
|
389
473
|
}
|
|
390
|
-
return;
|
|
391
|
-
break;
|
|
392
|
-
case "sensor":
|
|
393
|
-
let ref = getFirstWord(topic)
|
|
394
|
-
updateSensor(device,ref,payload)
|
|
395
|
-
return;
|
|
396
474
|
break;
|
|
397
475
|
// Optional: default case if needed
|
|
398
476
|
default:
|
|
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
|
-
}
|
|
439
477
|
break;
|
|
440
478
|
}
|
|
441
479
|
|
|
480
|
+
// check if topic is associated with device
|
|
481
|
+
let findTopic = topicBck;
|
|
482
|
+
let set = false;
|
|
483
|
+
if(topicBck.endsWith("/set")){ // remove set if exists on topic
|
|
484
|
+
set = true;
|
|
485
|
+
findTopic = $.parser.getWordBeforeLastSlash(topicBck);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const dbTopic = await $.db_device.getMqttTopic(device.id,findTopic)
|
|
489
|
+
if(dbTopic != null){
|
|
490
|
+
self.handleMqttTopic(device,dbTopic,payload,set);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// check if topic is a sensor
|
|
494
|
+
if(!set)
|
|
495
|
+
self.updateSensor(device,topicBck,payload);
|
|
496
|
+
|
|
442
497
|
if(_project[project_name]){
|
|
443
498
|
_project[project_name]?.module?.parseMessage(client,project_name,device,`${word}/${topic}`,payload,retain,()=>{});
|
|
444
499
|
}
|
|
445
500
|
}
|
|
446
501
|
|
|
447
|
-
async function synchMqttTopic(device,dbTopic
|
|
502
|
+
async function synchMqttTopic(device,dbTopic){
|
|
448
503
|
|
|
449
504
|
const project = await $.db_project.getById(device.project_id);
|
|
450
505
|
let mqtt_prefix = `${project.name}/${device.uid}`;
|
|
451
|
-
let topic = `${mqtt_prefix}/${
|
|
506
|
+
let topic = `${mqtt_prefix}/${dbTopic.topic}/set`
|
|
452
507
|
let payload = "";
|
|
453
508
|
if(dbTopic?.remoteData && typeof dbTopic?.remoteData === 'object'){
|
|
454
509
|
try{
|
|
@@ -589,47 +644,6 @@ async function synchSettings(device,key){
|
|
|
589
644
|
}
|
|
590
645
|
}
|
|
591
646
|
|
|
592
|
-
async function updateSensor(device,ref,payload){
|
|
593
|
-
let res = await $.db_device.getSensorByRef(device.id,ref)
|
|
594
|
-
if(res == null)
|
|
595
|
-
res = await $.db_model.getSensorByRef(device.model_id,ref)
|
|
596
|
-
if(res == null)
|
|
597
|
-
return;
|
|
598
|
-
|
|
599
|
-
object = payload;
|
|
600
|
-
value = null;
|
|
601
|
-
error = null;
|
|
602
|
-
timestamp = null;
|
|
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
|
-
|
|
612
|
-
if (typeof object === 'object' && object !== null) {
|
|
613
|
-
value = object?.value || object?.v;
|
|
614
|
-
error = object?.error || object?.e;
|
|
615
|
-
timestamp = object?.timestamp || objects?.ts;
|
|
616
|
-
}else{
|
|
617
|
-
value = payload;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if(value || error)
|
|
621
|
-
object = null;
|
|
622
|
-
|
|
623
|
-
const data = {
|
|
624
|
-
object,
|
|
625
|
-
value,
|
|
626
|
-
error,
|
|
627
|
-
timestamp
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
$.db_sensor.insert(logs_table,device.id,res.id,data);
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
647
|
|
|
634
648
|
function handleFotaSuccess (deviceId){
|
|
635
649
|
let object = {
|
|
@@ -650,38 +664,4 @@ function handleFotaError (deviceId, error){
|
|
|
650
664
|
$.db_fota.updateLog(deviceId,object);
|
|
651
665
|
}
|
|
652
666
|
|
|
653
|
-
function getFirstWord(str){
|
|
654
|
-
const slashIndex = str.indexOf('/');
|
|
655
|
-
if (slashIndex === -1) {
|
|
656
|
-
// No slash found, return the original string
|
|
657
|
-
return str;
|
|
658
|
-
}
|
|
659
|
-
return str.substring(0,slashIndex);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
function getWordAfterSlash(str){
|
|
663
|
-
const slashIndex = str.indexOf('/');
|
|
664
|
-
if (slashIndex === -1) {
|
|
665
|
-
// No slash found, return empty string
|
|
666
|
-
return "";
|
|
667
|
-
}
|
|
668
|
-
return str.substring(slashIndex + 1);
|
|
669
|
-
}
|
|
670
667
|
|
|
671
|
-
function getWordAfterLastSlash(str){
|
|
672
|
-
const lastSlashIndex = str.lastIndexOf('/');
|
|
673
|
-
if (lastSlashIndex === -1) {
|
|
674
|
-
// No slash found, return the original string
|
|
675
|
-
return str;
|
|
676
|
-
}
|
|
677
|
-
return str.substring(lastSlashIndex + 1);
|
|
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
|
-
}
|