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 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;
@@ -30,7 +30,7 @@ module.exports = (sequelize,DataTypes)=>{
30
30
  type: DataTypes.STRING,
31
31
  allowNull: true,
32
32
  },
33
- localUnixTs: {
33
+ remoteUnixTs: {
34
34
  type: DataTypes.BIGINT,
35
35
  allowNull: true,
36
36
  }
@@ -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: true,
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: true,
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: false,
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.24",
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.3.0"
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
- // Prepare data for insertion
295
- for (let key in dataObject) {
296
- if (db_columns.hasOwnProperty(key)) {
297
- let value = dataObject[key];
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
- // Convert object to JSON string if needed
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
- getSensorByRef : async(deviceId, ref)=>{
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[0]);
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
- getSensorByRef : async(modelId, ref)=>{
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[0]);
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
- localUnixTs : payload?.timestamp, // local timestamp
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
  }
@@ -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 = getWordAfterSlash(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 = getWordAfterSlash(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 = getWordAfterSlash(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 = getWordAfterSlash(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 = getWordAfterSlash(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,topicBck){
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}/${topicBck}/set`
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
- }