mqtt-devices-parser 1.0.16 → 1.0.18

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,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.18
4
+ Fix and improve Sensors, checks uid length
5
+ Adds seedCommonLwM2MObjects and seedCommonLwM2MResources functions
6
+ fixes sync project tables. Makes load .models more robust
7
+ Adds templates table
8
+ add template_id to devices table
9
+
10
+ ## 1.0.17
11
+ Fix and improve Sensors, checks uid length
12
+ config/index: When defining a project from here uidPrefix and uidLength must be defined
13
+ models/logs_sensor.models: removes client_id association
14
+ models/sensors.models: removes unique and references properties from model_id, adds device_id
15
+ src/db/db: adds method updateOrInsert
16
+ src/db/device: adds method getSensorByRef
17
+ src/db/model: adds method getSensorByRef
18
+ src/db/project: adds uidPrefix and uidLength properties
19
+ src/db/sensor: insert: adds fields error and obj
20
+ src/device/device:
21
+ - fixes logs_table
22
+ - adds projectOptions
23
+ - checks if device.uidLength <= project.uidLength
24
+ - looks for sensor ref by device_id first and then by model_id if not found. Insert object, value and error fields
25
+ - removes some fota logs
26
+
3
27
  ## 1.0.16
4
28
  changes db tables:
5
29
  - models/devices.models: removes endpoint, adds protocol and psk.
package/config/index.js CHANGED
@@ -26,5 +26,9 @@ module.exports = {
26
26
  sync_main_tables : process.env.sync_main_tables || true,
27
27
  projects: {
28
28
  demo : process.env.demo || false,
29
+ },
30
+ demo : {
31
+ uidPrefix: 'uid:',
32
+ uidLength: 16 // max len for uid
29
33
  }
30
34
  }
package/index.js CHANGED
@@ -94,7 +94,7 @@ var self = module.exports = {
94
94
 
95
95
  //main tables
96
96
  if($.config.sync_main_tables){
97
-
97
+
98
98
  await $.models.load(__dirname+"/models");
99
99
  await $.models.dropTableIndexes();
100
100
  await $.models.sync();
@@ -106,7 +106,7 @@ var self = module.exports = {
106
106
  await $.models.insertUser("client","client_pwd",3);
107
107
  // adds client with credentials admin@admin
108
108
  await $.models.insertClient("admin","admin",user_id); // dashboard login
109
-
109
+
110
110
  for(name of projects){
111
111
  project = $.config[name];
112
112
  if (project) {
@@ -119,9 +119,6 @@ var self = module.exports = {
119
119
 
120
120
  // project tables
121
121
  await self.readModelsInsideProjects(projectsPath);
122
- await $.models.dropTableIndexes();
123
- await $.models.sync();
124
-
125
122
  }
126
123
  }
127
124
 
@@ -20,6 +20,10 @@ module.exports = (sequelize,DataTypes)=>{
20
20
  key: 'id'
21
21
  }
22
22
  },
23
+ template_id: {
24
+ type: DataTypes.INTEGER,
25
+ allowNull: true
26
+ },
23
27
  model_id: {
24
28
  type: DataTypes.INTEGER,
25
29
  references: {
@@ -22,16 +22,14 @@ module.exports = (sequelize,DataTypes)=>{
22
22
  type: DataTypes.STRING,
23
23
  allowNull: true,
24
24
  },
25
- cliend_id: {
26
- type: DataTypes.INTEGER,
27
- unique: false,
25
+ error: {
26
+ type: DataTypes.STRING,
27
+ allowNull: true,
28
+ },
29
+ obj: {
30
+ type: DataTypes.STRING,
28
31
  allowNull: true,
29
- references: {
30
- model: 'clients',
31
- key: 'id'
32
- }
33
32
  },
34
-
35
33
  },
36
34
  {
37
35
  tableName: 'logs_sensor',
package/models/models.js CHANGED
@@ -39,22 +39,28 @@ var self = module.exports = {
39
39
 
40
40
  load: async (modelsPath) => {
41
41
  return new Promise(async (resolve, reject) => {
42
- var files = await fs.readdir(modelsPath);
43
- files = files.filter((file) => {
44
- // Exclude models.js and non-js files
45
- return (
46
- file.indexOf('.') !== 0 &&
47
- file !== 'models.js' &&
48
- file.slice(-9) === 'models.js'
49
- );
50
- });
51
- files.forEach((file, counter) => {
52
- const model = require(path.join(modelsPath, file));
53
- model(sequelize, DataTypes);
54
- if (counter === files.length - 1) {
55
- return resolve();
42
+ try {
43
+ let files = await fs.readdir(modelsPath);
44
+ files = files.filter((file) => {
45
+ // Exclude models.js and non-js files
46
+ return (
47
+ file.indexOf('.') !== 0 &&
48
+ file !== 'models.js' &&
49
+ file.slice(-9) === 'models.js'
50
+ );
51
+ });
52
+
53
+ // Use a for...of loop to load models sequentially
54
+ for (const file of files) {
55
+ console.log(`Sync file ${file}`);
56
+ const model = require(path.join(modelsPath, file));
57
+ await model(sequelize, DataTypes); // Ensure each model is loaded before moving to the next
56
58
  }
57
- });
59
+
60
+ resolve(); // Resolve the Promise after all files are processed
61
+ } catch (error) {
62
+ reject(error); // Reject the Promise on any error
63
+ }
58
64
  });
59
65
  },
60
66
 
@@ -246,5 +252,153 @@ var self = module.exports = {
246
252
  });
247
253
  });
248
254
  },
249
-
255
+
256
+ seedCommonLwM2MObjects: async(objects) => {
257
+ try {
258
+ const results = await Promise.all(
259
+ objects.map(async (object) => {
260
+ const [record, created] = await sequelize.models['lwm2mObjects'].upsert(
261
+ {
262
+ objectId: object.objectId,
263
+ name: object.name,
264
+ description: object.description,
265
+ },
266
+ {
267
+ where: { objectId: object.objectId }, // Ensure the record exists based on objectId
268
+ }
269
+ );
270
+ return { record, created };
271
+ })
272
+ );
273
+ console.log("Insert/Update completed for LwM2M Objects.");
274
+ return results;
275
+ } catch (error) {
276
+ console.error("Error inserting/updating LwM2M Objects:", error.message);
277
+ throw error;
278
+ }
279
+ },
280
+ /*
281
+ seedCommonLwM2MResources: async(resources) => {
282
+ try {
283
+ const results = await Promise.all(
284
+ resources.map(async (resource) => {
285
+ const [record, created] = await sequelize.models['lwm2mResources'].upsert(
286
+ {
287
+ objectId: resource.objectId,
288
+ resourceId: resource.resourceId,
289
+ name: resource.name,
290
+ description: resource.description,
291
+ },
292
+ {
293
+ where: { objectId: object.objectId, resourceId: object.resourceId }, // Ensure the record exists based on objectId and resourceId
294
+ }
295
+ );
296
+ return { record, created };
297
+ })
298
+ );
299
+ console.log("Insert/Update completed for LwM2M Resources.");
300
+ return results;
301
+ } catch (error) {
302
+ console.error("Error inserting/updating LwM2M Resources:", error.message);
303
+ throw error;
304
+ }
305
+ },
306
+ */
307
+
308
+ /**
309
+ * Seed LwM2M resources into the database.
310
+ *
311
+ * Expected DB model: sequelize.models['lwm2mResources']
312
+ * with a composite unique key on (objectId, resourceId) recommended.
313
+ *
314
+ * @param {Array} resources - Either:
315
+ * - Flat: [{ objectId, resourceId, name, description }, ...]
316
+ * - Nested: [{ objectId, name, resources: [{ resourceId, name, description }, ...] }, ...]
317
+ * @param {import('sequelize').Sequelize} sequelize - An initialized Sequelize instance.
318
+ * @returns {Promise<Array<{ objectId:number, resourceId:number, created:boolean, updated:boolean }>>}
319
+ */
320
+ seedCommonLwM2MResources: async (resources) => {
321
+ if (!sequelize || !sequelize.models || !sequelize.models['lwm2mResources']) {
322
+ throw new Error("sequelize instance with model 'lwm2mResources' is required");
323
+ }
324
+ if (!Array.isArray(resources)) {
325
+ throw new Error('resources must be an array');
326
+ }
327
+
328
+ // Normalize input: flatten nested object.resources into a flat {objectId,resourceId,...} list
329
+ const flatResources = [];
330
+ for (const entry of resources) {
331
+ if (entry && Array.isArray(entry.resources)) {
332
+ // Nested structure: object -> resources[]
333
+ for (const res of entry.resources) {
334
+ flatResources.push({
335
+ objectId: entry.objectId,
336
+ resourceId: res.resourceId,
337
+ name: res.name,
338
+ description: res.description
339
+ });
340
+ }
341
+ } else if (
342
+ entry &&
343
+ typeof entry.objectId !== 'undefined' &&
344
+ typeof entry.resourceId !== 'undefined'
345
+ ) {
346
+ // Already flat
347
+ flatResources.push({
348
+ objectId: entry.objectId,
349
+ resourceId: entry.resourceId,
350
+ name: entry.name,
351
+ description: entry.description
352
+ });
353
+ } else {
354
+ // Skip invalid entries but warn
355
+ // eslint-disable-next-line no-console
356
+ console.warn('Skipping invalid resource entry:', entry);
357
+ }
358
+ }
359
+
360
+ const { lwm2mResources } = sequelize.models;
361
+
362
+ const results = [];
363
+ try {
364
+ await sequelize.transaction(async (t) => {
365
+ for (const res of flatResources) {
366
+ const where = { objectId: res.objectId, resourceId: res.resourceId };
367
+
368
+ // Try to find existing record
369
+ const [record, created] = await lwm2mResources.findOrCreate({
370
+ where,
371
+ defaults: {
372
+ name: res.name,
373
+ description: res.description
374
+ },
375
+ transaction: t
376
+ });
377
+
378
+ if (!created) {
379
+ // Update existing with latest values
380
+ await record.update(
381
+ { name: res.name, description: res.description },
382
+ { transaction: t }
383
+ );
384
+ }
385
+
386
+ results.push({
387
+ objectId: res.objectId,
388
+ resourceId: res.resourceId,
389
+ created,
390
+ updated: !created
391
+ });
392
+ }
393
+ });
394
+
395
+ // eslint-disable-next-line no-console
396
+ console.log('Insert/Update completed for LwM2M Resources.');
397
+ return results;
398
+ } catch (error) {
399
+ // eslint-disable-next-line no-console
400
+ console.error('Error inserting/updating LwM2M Resources:', error.message);
401
+ throw error;
402
+ }
403
+ }
250
404
  };
@@ -1,14 +1,13 @@
1
1
 
2
2
  module.exports = (sequelize,DataTypes)=>{
3
3
  return sequelize.define("sensors", {
4
- model_id: {
4
+ model_id: { // reference a model
5
5
  type: DataTypes.INTEGER,
6
- unique: false,
7
- allowNull: false,
8
- references: {
9
- model: 'models',
10
- key: 'id'
11
- }
6
+ allowNull: true,
7
+ },
8
+ device_id: { // reference a device id
9
+ type: DataTypes.INTEGER,
10
+ allowNull: true,
12
11
  },
13
12
  active: {
14
13
  type: DataTypes.BOOLEAN,
@@ -0,0 +1,25 @@
1
+
2
+ module.exports = (sequelize,DataTypes)=>{
3
+ return sequelize.define("templates", {
4
+ tag: {
5
+ type: DataTypes.STRING,
6
+ allowNull: false
7
+ },
8
+ name: {
9
+ type: DataTypes.STRING,
10
+ allowNull: false
11
+ },
12
+ client_id: {
13
+ type: DataTypes.INTEGER,
14
+ allowNull: false
15
+ },
16
+ project_id: {
17
+ type: DataTypes.INTEGER,
18
+ allowNull: false
19
+ },
20
+ },
21
+ {
22
+ tableName: 'templates',
23
+ freezeTableName: true
24
+ })
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mqtt-devices-parser",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/db/db.js CHANGED
@@ -144,6 +144,67 @@ var self = module.exports = {
144
144
  });
145
145
  },
146
146
 
147
+ updateOrInsert: async (table, data, filter) => {
148
+ return new Promise((resolve, reject) => {
149
+ self.getConnection((err, conn) => {
150
+ if (err) return reject(err);
151
+
152
+ let updateQuery = "";
153
+ let updateValues = [];
154
+
155
+ if (typeof data === "object") {
156
+ const keys = Object.keys(data);
157
+ updateValues = Object.values(data);
158
+ updateQuery = `UPDATE ?? SET ${keys.map(key => `${key} = ?`).join(', ')}`;
159
+ } else {
160
+ self.close_db_connection(conn);
161
+ return reject("data passed is not an object");
162
+ }
163
+
164
+ if (typeof filter === "object") {
165
+ const filterKeys = Object.keys(filter);
166
+ const filterValues = Object.values(filter);
167
+ updateQuery += ` WHERE ${filterKeys.map(key => `${key} = ?`).join(' AND ')}`;
168
+ updateValues.push(...filterValues);
169
+ } else {
170
+ self.close_db_connection(conn);
171
+ return reject("filter passed is not an object");
172
+ }
173
+
174
+ updateValues.unshift(table);
175
+ updateQuery = mysql.format(updateQuery, updateValues);
176
+
177
+ conn.query(updateQuery, function (err, rows) {
178
+ if (err) {
179
+ self.close_db_connection(conn);
180
+ return reject(err);
181
+ }
182
+ // If affectedRows = 0, do an insert
183
+ if (rows.affectedRows === 0) {
184
+ // Combine data and filter for insert
185
+ const insertObj = { ...filter, ...data }; // filter first, data overrides on conflict
186
+ const insertKeys = Object.keys(insertObj);
187
+ const insertValues = Object.values(insertObj);
188
+
189
+ const insertQuery = mysql.format(
190
+ `INSERT INTO ?? (${insertKeys.map(() => '??').join(', ')}) VALUES (${insertKeys.map(() => '?').join(', ')})`,
191
+ [table, ...insertKeys, ...insertValues]
192
+ );
193
+
194
+ conn.query(insertQuery, function (insertErr, insertRows) {
195
+ self.close_db_connection(conn);
196
+ if (insertErr) return reject(insertErr);
197
+ else return resolve(insertRows);
198
+ });
199
+ } else {
200
+ self.close_db_connection(conn);
201
+ return resolve(rows);
202
+ }
203
+ });
204
+ });
205
+ });
206
+ },
207
+
147
208
  // update column with json data
148
209
  updateJSON : async (table, column, data, filter) => {
149
210
 
package/src/db/device.js CHANGED
@@ -387,6 +387,27 @@ var self = module.exports = {
387
387
  });
388
388
 
389
389
  });
390
+ },
391
+
392
+ getSensorByRef : async(deviceId, ref)=>{
393
+ return new Promise((resolve,reject) => {
394
+
395
+ let query = "SELECT * FROM ?? where device_id = ? and ref = ?";
396
+ let args = ["sensors",deviceId,ref];
397
+ query = mysql.format(query,args);
398
+
399
+ $.db.queryRow(query)
400
+ .then( rows => {
401
+ if(rows.length > 0)
402
+ return resolve(rows[0]);
403
+ else
404
+ return resolve(null);
405
+ })
406
+ .catch( err => {
407
+ console.log(err);
408
+ return resolve(null);
409
+ });
410
+ });
390
411
  }
391
412
 
392
413
  }
package/src/db/model.js CHANGED
@@ -112,4 +112,25 @@ var self = module.exports = {
112
112
  });
113
113
  },
114
114
 
115
+ getSensorByRef : async(modelId, ref)=>{
116
+ return new Promise((resolve,reject) => {
117
+
118
+ let query = "SELECT * FROM ?? where model_id = ? and ref = ?";
119
+ let args = ["sensors",modelId,ref];
120
+ query = mysql.format(query,args);
121
+
122
+ $.db.queryRow(query)
123
+ .then( rows => {
124
+ if(rows.length > 0)
125
+ return resolve(rows[0]);
126
+ else
127
+ return resolve(null);
128
+ })
129
+ .catch( err => {
130
+ console.log(err);
131
+ return resolve(null);
132
+ });
133
+ });
134
+ }
135
+
115
136
  }
package/src/db/project.js CHANGED
@@ -64,13 +64,15 @@ var self = module.exports = {
64
64
  });
65
65
  },
66
66
 
67
- insert : async(project, project_table, logs_table)=>{
67
+ insert : async(project, project_table, logs_table, projectOptions)=>{
68
68
 
69
69
  return new Promise((resolve,reject) => {
70
70
  let obj = {
71
71
  name : project,
72
72
  project_table : project_table,
73
73
  logs_table : logs_table,
74
+ uidPrefix : projectOptions.uidPrefix,
75
+ uidLength : projectOptions.uidLength,
74
76
  createdAt : moment().utc().format('YYYY-MM-DD HH:mm:ss'),
75
77
  updatedAt : moment().utc().format('YYYY-MM-DD HH:mm:ss')
76
78
  }
package/src/db/sensor.js CHANGED
@@ -95,7 +95,9 @@ var self = module.exports = {
95
95
  let obj = {
96
96
  device_id : deviceId,
97
97
  sensor_id : sensorId,
98
- value : payload,
98
+ value : payload?.value,
99
+ error : payload?.error,
100
+ obj : payload?.object,
99
101
  createdAt : moment().utc().format('YYYY-MM-DD HH:mm:ss'),
100
102
  updatedAt : moment().utc().format('YYYY-MM-DD HH:mm:ss')
101
103
  }
@@ -1,13 +1,12 @@
1
1
  const moment = require('moment');
2
2
 
3
3
  var _project = [];
4
- const logs_table = "sensor_logs";
4
+ const logs_table = "logs_sensor";
5
5
  const semver = require('semver');
6
6
 
7
7
  var self = module.exports = {
8
8
 
9
9
  init : async (config,projects)=>{
10
-
11
10
  return new Promise(async (resolve,reject) => {
12
11
  $.db.connect(config,()=>{
13
12
  console.log("MYSQL is connected");
@@ -19,10 +18,12 @@ var self = module.exports = {
19
18
  };
20
19
  _project[name].module = require(`${BASE_DIR}/projects/${name}/${name}.js`)
21
20
  _project[name].module?.init();
21
+ let projectOptions = config[name];
22
22
  let row = await $.db_project.getByName(name);
23
+
23
24
  if(row == null)
24
- $.db_project.insert(name,name,"logs_"+name);
25
-
25
+ $.db_project.insert(name,name,"logs_"+name,projectOptions);
26
+
26
27
  if(counter == projects.length-1)
27
28
  return resolve();
28
29
  })
@@ -60,7 +61,7 @@ var self = module.exports = {
60
61
  topic = topic.substring(index+1);
61
62
 
62
63
  // check if topic corresponds to a device
63
- if(uid.startsWith(project.uidPrefix) && uid.length == project?.uidLength){
64
+ if(uid.startsWith(project.uidPrefix) && uid.length <= project?.uidLength){
64
65
 
65
66
  let device = await $.db_device.get(uid);
66
67
 
@@ -159,15 +160,40 @@ var self = module.exports = {
159
160
  }
160
161
  }
161
162
  }else if(topic.startsWith("sensor/")){
162
- updateSensor(device,topic,paylaod)
163
+ //updateSensor(device,topic,payload)
163
164
  index = topic.indexOf("/");
164
- if(index == -1)
165
- return;
165
+ if(index != -1){
166
+ const ref = topic.substring(index+1);
167
+ let res = await $.db_device.getSensorByRef(device.id,ref)
168
+ if(res == null)
169
+ res = await $.db_model.getSensorByRef(device.model_id,ref)
170
+ if(res != null){
171
+ object = null;
172
+ value = null;
173
+ error = null;
174
+ try{
175
+ object = JSON.parse(payload);
176
+ }catch(err){
177
+ console.log(err);
178
+ }
179
+ if (typeof object === 'object' && object !== null) {
180
+ value = object?.value;
181
+ error = object?.error;
182
+ }else{
183
+ value = payload;
184
+ }
185
+
186
+ if(value || error)
187
+ object = null;
166
188
 
167
- const name = topic.substring(index+1);
168
- let res = await $.db_sensor.getByRef(topic)
169
- if(res != null){
170
- await $.db_sensor.insert(logs_table,device.id,res.id,payload);
189
+ const data = {
190
+ object,
191
+ value,
192
+ error
193
+ }
194
+
195
+ await $.db_sensor.insert(logs_table,device.id,res.id,data);
196
+ }
171
197
  }
172
198
  }
173
199
 
@@ -314,15 +340,15 @@ var self = module.exports = {
314
340
 
315
341
  for (const model of models) {
316
342
 
317
- console.log("model:",model?.name);
343
+ //console.log("model:",model?.name);
318
344
  if(!model?.id)
319
345
  continue;
320
346
 
321
347
  try{
322
348
  const latestVersion = await $.db_firmware.getLatestVersion(model.id,release);
323
349
  const latestAppVersion = await $.db_firmware.getLatestAppVersion(model.id,release);
324
- console.log("latestVersion:",latestVersion?.version);
325
- console.log("latestAppVersion:",latestAppVersion?.app_version);
350
+ //console.log("latestVersion:",latestVersion?.version);
351
+ //console.log("latestAppVersion:",latestAppVersion?.app_version);
326
352
 
327
353
  const devices = await $.db_device.listByModel(model.id);
328
354