mqtt-devices-parser 1.0.24 → 1.0.26
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 +37 -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 +10 -2
- package/src/aux/parser.js +36 -0
- package/src/db/data.js +71 -10
- 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 +130 -145
package/Changelog.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.26
|
|
4
|
+
feat: src/device/device: request fw/wifi/get
|
|
5
|
+
on device online reporting and if device is connected through wifi
|
|
6
|
+
fix: add logs in json format to logs_fw table
|
|
7
|
+
forces: usage of "ws": "^7.5.10" for websocket-stream package
|
|
8
|
+
vulnerabilities: 0 found
|
|
9
|
+
|
|
10
|
+
## 1.0.25
|
|
11
|
+
|
|
12
|
+
Sensors template (#9)
|
|
13
|
+
|
|
14
|
+
* new table sensorsTemplate
|
|
15
|
+
|
|
16
|
+
* store topics which match a sensor
|
|
17
|
+
for each mqtt msg received, check if there is a sensor associated to that topic.
|
|
18
|
+
If there is, update current value and insert new log
|
|
19
|
+
|
|
20
|
+
* db: models/sensors
|
|
21
|
+
changes localUnixTs field to remoteUnixTs
|
|
22
|
+
adds value, error
|
|
23
|
+
changes property: allow null
|
|
24
|
+
adds foreign keys to model_id and device_id
|
|
25
|
+
|
|
26
|
+
* handle change db column localUnixTs to remoteUnixTs
|
|
27
|
+
|
|
28
|
+
* fix getSensorsByRef
|
|
29
|
+
ref is not unique so it can have more than 1 row
|
|
30
|
+
|
|
31
|
+
* src/db/data: Implements missing function pathIntoObject
|
|
32
|
+
|
|
33
|
+
* src/aux/parser: adds functions to handle mqtt topics parsing
|
|
34
|
+
index: uses global $.parser as a pointer to class src/aux/parser
|
|
35
|
+
src/device/device: fixes null iterations, sets updateSensor and handleMqttTopic as public functions. Removes local mqtt parser functions. Adapts calls to those functions
|
|
36
|
+
|
|
37
|
+
mqtt: always check if topic is registered
|
|
38
|
+
store values in db
|
|
39
|
+
|
|
3
40
|
## 1.0.24
|
|
4
41
|
src/device/device: fix log
|
|
5
42
|
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mqtt-devices-parser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
"path": "^0.12.7",
|
|
22
22
|
"semver": "^7.3.8",
|
|
23
23
|
"sequelize": "^6.32.1",
|
|
24
|
-
"websocket-stream": "^5.
|
|
24
|
+
"websocket-stream": "^5.4.0"
|
|
25
|
+
},
|
|
26
|
+
"overrides": {
|
|
27
|
+
"websocket-stream": {
|
|
28
|
+
"ws": "^7.5.10"
|
|
29
|
+
},
|
|
30
|
+
"sequelize": {
|
|
31
|
+
"uuid": "^11.1.1"
|
|
32
|
+
}
|
|
25
33
|
}
|
|
26
34
|
}
|
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
|
@@ -275,7 +275,7 @@ var self = module.exports = {
|
|
|
275
275
|
});
|
|
276
276
|
},
|
|
277
277
|
|
|
278
|
-
addJsonLog: async (table, deviceId, dataObject) => {
|
|
278
|
+
addJsonLog: async (table, deviceId, dataObject, column = "") => {
|
|
279
279
|
return new Promise((resolve, reject) => {
|
|
280
280
|
if (!dataObject || typeof dataObject !== 'object')
|
|
281
281
|
return reject("Not an object");
|
|
@@ -291,17 +291,22 @@ var self = module.exports = {
|
|
|
291
291
|
if (db_columns == null)
|
|
292
292
|
return reject(`No columns for table: ${table}`);
|
|
293
293
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
294
|
+
if(column != ""){
|
|
295
|
+
if (db_columns.hasOwnProperty(column))
|
|
296
|
+
obj[column] = JSON.stringify(dataObject);
|
|
297
|
+
}else{
|
|
298
|
+
// Prepare data for insertion
|
|
299
|
+
for (let key in dataObject) {
|
|
300
|
+
if (db_columns.hasOwnProperty(key)) {
|
|
301
|
+
let value = dataObject[key];
|
|
302
|
+
|
|
303
|
+
// Convert object to JSON string if needed
|
|
304
|
+
if (typeof value === 'object') {
|
|
305
|
+
value = JSON.stringify(value);
|
|
306
|
+
}
|
|
298
307
|
|
|
299
|
-
|
|
300
|
-
if (typeof value === 'object') {
|
|
301
|
-
value = JSON.stringify(value);
|
|
308
|
+
obj[key] = value;
|
|
302
309
|
}
|
|
303
|
-
|
|
304
|
-
obj[key] = value;
|
|
305
310
|
}
|
|
306
311
|
}
|
|
307
312
|
|
|
@@ -372,3 +377,59 @@ var self = module.exports = {
|
|
|
372
377
|
});
|
|
373
378
|
}
|
|
374
379
|
};
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Build a nested object from a path string and a leaf value.
|
|
383
|
+
*
|
|
384
|
+
* Example:
|
|
385
|
+
* pathIntoObject('foo/bar/baz', 123) -> { foo: { bar: { baz: 123 } } }
|
|
386
|
+
*
|
|
387
|
+
* Notes:
|
|
388
|
+
* - By default, splits on '/'.
|
|
389
|
+
* - Leading/trailing/duplicate delimiters are ignored (e.g., '/a//b/' -> ['a','b']).
|
|
390
|
+
* - Value is used as-is. If you pass JSON.stringify(data), the leaf will be that string.
|
|
391
|
+
*
|
|
392
|
+
* @param {string} path - The path to turn into nested object keys (e.g., 'a/b/c').
|
|
393
|
+
* @param {*} value - The value to place at the final key.
|
|
394
|
+
* @param {Object} [options]
|
|
395
|
+
* @param {string} [options.delimiter='/'] - Path delimiter.
|
|
396
|
+
* @returns {*}
|
|
397
|
+
*/
|
|
398
|
+
const parser = {
|
|
399
|
+
pathIntoObject(path, value, options = {}) {
|
|
400
|
+
const delimiter = options.delimiter || '/';
|
|
401
|
+
|
|
402
|
+
if (typeof path !== 'string') {
|
|
403
|
+
throw new TypeError('path must be a string');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Normalize path: remove empty segments caused by leading/trailing or duplicate delimiters
|
|
407
|
+
const segments = path
|
|
408
|
+
.split(delimiter)
|
|
409
|
+
.map(s => s.trim())
|
|
410
|
+
.filter(Boolean);
|
|
411
|
+
|
|
412
|
+
// If no segments, just return the value directly
|
|
413
|
+
if (segments.length === 0) return value;
|
|
414
|
+
|
|
415
|
+
// Build nested object
|
|
416
|
+
let root = {};
|
|
417
|
+
let cursor = root;
|
|
418
|
+
|
|
419
|
+
for (let i = 0; i < segments.length; i++) {
|
|
420
|
+
const key = segments[i];
|
|
421
|
+
const isLast = i === segments.length - 1;
|
|
422
|
+
|
|
423
|
+
if (isLast) {
|
|
424
|
+
cursor[key] = value;
|
|
425
|
+
} else {
|
|
426
|
+
// Create the next level if not present
|
|
427
|
+
cursor[key] = {};
|
|
428
|
+
cursor = cursor[key];
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return root;
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
|
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
|
|
@@ -322,8 +412,11 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
322
412
|
let topic = `${mqtt_prefix}/settings/mqtt/get`
|
|
323
413
|
$.mqtt_client.publish(topic,"",{qos:1,retain:false});
|
|
324
414
|
}
|
|
415
|
+
if(device?.tech.toLowerCase() === "wifi"){
|
|
416
|
+
let topic = `${mqtt_prefix}/fw/wifi/get`
|
|
417
|
+
$.mqtt_client.publish(topic,"",{qos:1,retain:false});
|
|
418
|
+
}
|
|
325
419
|
}
|
|
326
|
-
return;
|
|
327
420
|
break;
|
|
328
421
|
case "model":
|
|
329
422
|
let res = await $.db_model.getByName(payload);
|
|
@@ -332,14 +425,12 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
332
425
|
$.db_device.update(device.id, "model_id", model_id);
|
|
333
426
|
$.db_device.addLog(device.id,"model_id",model_id);
|
|
334
427
|
}
|
|
335
|
-
return;
|
|
336
428
|
break;
|
|
337
429
|
case "tech":
|
|
338
430
|
if (payload != null && payload != device?.tech) {
|
|
339
431
|
$.db_device.update(device.id, "tech", payload);
|
|
340
432
|
$.db_device.addLog(device.id,"tech",payload);
|
|
341
433
|
}
|
|
342
|
-
return;
|
|
343
434
|
break;
|
|
344
435
|
case "version":
|
|
345
436
|
if (payload != null && payload != device?.version) {
|
|
@@ -347,7 +438,6 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
347
438
|
$.db_device.update(device.id, "version", payload);
|
|
348
439
|
handleFotaSuccess(device.id);
|
|
349
440
|
}
|
|
350
|
-
return;
|
|
351
441
|
break;
|
|
352
442
|
case "app_version":
|
|
353
443
|
if (payload != null && payload != device?.app_version) {
|
|
@@ -355,23 +445,23 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
355
445
|
$.db_device.update(device.id, "app_version", payload);
|
|
356
446
|
handleFotaSuccess(device.id);
|
|
357
447
|
}
|
|
358
|
-
return;
|
|
359
448
|
break;
|
|
360
449
|
case "fw":
|
|
361
450
|
if(topic === "fota/update/status"){
|
|
362
451
|
handleFotaError(device.id, payload);
|
|
363
452
|
}else{
|
|
453
|
+
let word = $.parser.getFirstWord(topic);
|
|
364
454
|
if(typeof payload === 'object' && payload !== null){
|
|
365
455
|
try{
|
|
366
456
|
let rows = await $.db_data.updateJson("fw",device.id,payload);
|
|
367
|
-
rows = await $.db_data.addJsonLog("logs_fw",device.id,payload);
|
|
457
|
+
rows = await $.db_data.addJsonLog("logs_fw",device.id,payload,word);
|
|
368
458
|
}catch(error){
|
|
369
459
|
console.error(error)
|
|
370
460
|
}
|
|
371
461
|
}else if(typeof payload !== 'object' && payload !== null){
|
|
372
462
|
// change it to topic.startsWith("fw")
|
|
373
463
|
try{
|
|
374
|
-
const column = getWordAfterLastSlash(topic);
|
|
464
|
+
const column = $.parser.getWordAfterLastSlash(topic);
|
|
375
465
|
let rows = await $.db_data.update("fw",device.id,column,payload);
|
|
376
466
|
rows = await $.db_data.addLog("logs_fw",device.id,column,payload);
|
|
377
467
|
}catch(error){
|
|
@@ -379,7 +469,6 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
379
469
|
}
|
|
380
470
|
}
|
|
381
471
|
}
|
|
382
|
-
return;
|
|
383
472
|
break;
|
|
384
473
|
case "settings":
|
|
385
474
|
if(topic.endsWith("/set")){
|
|
@@ -387,68 +476,39 @@ async function parseMqttMessage(client, project_name, device, topic, payload, re
|
|
|
387
476
|
}else{
|
|
388
477
|
updateRemoteSettings(device,topic,payload);
|
|
389
478
|
}
|
|
390
|
-
return;
|
|
391
|
-
break;
|
|
392
|
-
case "sensor":
|
|
393
|
-
let ref = getFirstWord(topic)
|
|
394
|
-
updateSensor(device,ref,payload)
|
|
395
|
-
return;
|
|
396
479
|
break;
|
|
397
480
|
// Optional: default case if needed
|
|
398
481
|
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
482
|
break;
|
|
440
483
|
}
|
|
441
484
|
|
|
485
|
+
// check if topic is associated with device
|
|
486
|
+
let findTopic = topicBck;
|
|
487
|
+
let set = false;
|
|
488
|
+
if(topicBck.endsWith("/set")){ // remove set if exists on topic
|
|
489
|
+
set = true;
|
|
490
|
+
findTopic = $.parser.getWordBeforeLastSlash(topicBck);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const dbTopic = await $.db_device.getMqttTopic(device.id,findTopic)
|
|
494
|
+
if(dbTopic != null){
|
|
495
|
+
self.handleMqttTopic(device,dbTopic,payload,set);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// check if topic is a sensor
|
|
499
|
+
if(!set)
|
|
500
|
+
self.updateSensor(device,topicBck,payload);
|
|
501
|
+
|
|
442
502
|
if(_project[project_name]){
|
|
443
503
|
_project[project_name]?.module?.parseMessage(client,project_name,device,`${word}/${topic}`,payload,retain,()=>{});
|
|
444
504
|
}
|
|
445
505
|
}
|
|
446
506
|
|
|
447
|
-
async function synchMqttTopic(device,dbTopic
|
|
507
|
+
async function synchMqttTopic(device,dbTopic){
|
|
448
508
|
|
|
449
509
|
const project = await $.db_project.getById(device.project_id);
|
|
450
510
|
let mqtt_prefix = `${project.name}/${device.uid}`;
|
|
451
|
-
let topic = `${mqtt_prefix}/${
|
|
511
|
+
let topic = `${mqtt_prefix}/${dbTopic.topic}/set`
|
|
452
512
|
let payload = "";
|
|
453
513
|
if(dbTopic?.remoteData && typeof dbTopic?.remoteData === 'object'){
|
|
454
514
|
try{
|
|
@@ -589,47 +649,6 @@ async function synchSettings(device,key){
|
|
|
589
649
|
}
|
|
590
650
|
}
|
|
591
651
|
|
|
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
652
|
|
|
634
653
|
function handleFotaSuccess (deviceId){
|
|
635
654
|
let object = {
|
|
@@ -650,38 +669,4 @@ function handleFotaError (deviceId, error){
|
|
|
650
669
|
$.db_fota.updateLog(deviceId,object);
|
|
651
670
|
}
|
|
652
671
|
|
|
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
672
|
|
|
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
|
-
}
|