mqtt-devices-parser 1.0.10 → 1.0.12
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 +13 -0
- package/index.js +47 -17
- package/models/devices.models.js +12 -4
- package/models/firmwares.models.js +3 -3
- package/models/fota.models.js +32 -0
- package/models/logs_fota.models.js +41 -0
- package/models/{sensor_logs.models.js → logs_sensor.models.js} +2 -2
- package/models/modelPermissions.models.js +23 -0
- package/models/models.js +238 -211
- package/models/projects.models.js +8 -0
- package/package.json +1 -1
- package/src/db/data.js +89 -85
- package/src/db/db.js +24 -0
- package/src/db/device.js +166 -102
- package/src/db/firmware.js +30 -5
- package/src/db/fota.js +219 -0
- package/src/device/Readme.md +39 -4
- package/src/device/device.js +265 -124
package/src/device/device.js
CHANGED
|
@@ -32,25 +32,6 @@ var self = module.exports = {
|
|
|
32
32
|
|
|
33
33
|
},
|
|
34
34
|
|
|
35
|
-
deleteLogs : async()=>{
|
|
36
|
-
|
|
37
|
-
let tables = await $.db.getTables();
|
|
38
|
-
// Define one week in milliseconds
|
|
39
|
-
// Loop through each table
|
|
40
|
-
for (const tableObj of tables) {
|
|
41
|
-
const tableName = tableObj['Tables_in_mqtt-aedes']
|
|
42
|
-
const oneWeekAgo = moment().utc().subtract(7, 'days').format('YYYY-MM-DD HH:mm:ss');
|
|
43
|
-
if(tableName.startsWith("logs_")){
|
|
44
|
-
const uptimeSeconds = Math.floor(process.uptime());
|
|
45
|
-
console.log(`deleting logs of table ${tableName} older than ${oneWeekAgo}"`);
|
|
46
|
-
await $.db.deleteOldEntries(tableName, { createdAt: oneWeekAgo });
|
|
47
|
-
time = Math.floor(process.uptime()) - uptimeSeconds;
|
|
48
|
-
console.log(`Logs of table ${tableName} deleted in ${time}s`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
},
|
|
53
|
-
|
|
54
35
|
parseMessage : async (client,topic,payload,retain)=>{
|
|
55
36
|
|
|
56
37
|
let topic_bck = topic;
|
|
@@ -60,23 +41,36 @@ var self = module.exports = {
|
|
|
60
41
|
let index = topic.indexOf("/");
|
|
61
42
|
if(index == -1)
|
|
62
43
|
return;
|
|
44
|
+
|
|
63
45
|
let project_name = topic.substring(0,index);
|
|
46
|
+
let project = await $.db_project.getByName(project_name);
|
|
47
|
+
|
|
48
|
+
if(project == null)
|
|
49
|
+
return;
|
|
50
|
+
|
|
51
|
+
let project_id = project?.id;
|
|
64
52
|
|
|
65
53
|
// --- uid ---
|
|
66
54
|
topic = topic.substring(index+1);
|
|
67
55
|
index = topic.indexOf("/");
|
|
68
56
|
if(index == -1)
|
|
69
57
|
return;
|
|
70
|
-
let uid = topic.substring(0,index);
|
|
71
58
|
|
|
59
|
+
let uid = topic.substring(0,index);
|
|
72
60
|
topic = topic.substring(index+1);
|
|
73
61
|
|
|
74
62
|
// check if topic corresponds to a device
|
|
75
|
-
if(uid.startsWith(
|
|
63
|
+
if(uid.startsWith(project.uidPrefix) && uid.length == project?.uidLength){
|
|
64
|
+
|
|
76
65
|
let device = await $.db_device.get(uid);
|
|
77
66
|
|
|
67
|
+
// Insert device if not exists on db
|
|
78
68
|
if(device == null){
|
|
79
|
-
|
|
69
|
+
let obj = {
|
|
70
|
+
uid : uid,
|
|
71
|
+
accept_release : "prod",
|
|
72
|
+
}
|
|
73
|
+
$.db_device.insert(obj)
|
|
80
74
|
.then(async()=>{
|
|
81
75
|
device = await $.db_device.get(uid);
|
|
82
76
|
})
|
|
@@ -84,71 +78,54 @@ var self = module.exports = {
|
|
|
84
78
|
}
|
|
85
79
|
|
|
86
80
|
// update project id if needed
|
|
87
|
-
let res = await $.db_project.getByName(project_name);
|
|
88
|
-
let project_id = res?.id;
|
|
89
81
|
if(project_id != null && project_id != device?.project_id)
|
|
90
|
-
$.db_device.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}else if(topic.endsWith("model")){
|
|
97
|
-
let res = await $.db_model.getByName(payload);
|
|
98
|
-
let model_id = res?.id;
|
|
99
|
-
|
|
100
|
-
if(model_id != null){
|
|
101
|
-
let res = await $.db_device.updateModel(uid,model_id);
|
|
102
|
-
}
|
|
103
|
-
}else if(topic.endsWith("tech")){
|
|
104
|
-
await $.db_device.updateTech(uid,payload);
|
|
105
|
-
}else if(topic.endsWith("fw_version")){
|
|
106
|
-
let fw_version = await $.db.getFieldFromDeviceId(project_name,device.id,"fw_version");
|
|
107
|
-
if(fw_version != payload)
|
|
108
|
-
await $.db_data.update(project_name,device.id,"fota_tries",1); // ! 0 is not working..
|
|
109
|
-
}else if(topic.endsWith("app_version")){
|
|
110
|
-
let app_version = await $.db.getFieldFromDeviceId(project_name,device.id,"app_version");
|
|
111
|
-
if(app_version != payload)
|
|
112
|
-
await $.db_data.update(project_name,device.id,"fota_tries",1); // ! 0 is not working..
|
|
113
|
-
}else if(topic.endsWith("uptime")){ // !! maybe this topic should be change for something ending in fota
|
|
114
|
-
// check fw version
|
|
115
|
-
// get current fw and app version
|
|
116
|
-
let release = await $.db.getFieldFromDeviceId(project_name,device.id,"fw_release");
|
|
117
|
-
if(release == null || release == "critical"){
|
|
118
|
-
// don't update
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
let fota_tries = await $.db.getFieldFromDeviceId(project_name,device.id,"fota_tries");
|
|
122
|
-
if(fota_tries > 3)
|
|
123
|
-
return;
|
|
124
|
-
|
|
125
|
-
let fw_version = await $.db.getFieldFromDeviceId(project_name,device.id,"fw_version");
|
|
126
|
-
let app_version = await $.db.getFieldFromDeviceId(project_name,device.id,"app_version");
|
|
127
|
-
let new_firmware = await $.db_firmware.getLatestFWVersion(device.model_id,release);
|
|
128
|
-
let new_app = await $.db_firmware.getLatestAppVersion(device.model_id,release);
|
|
129
|
-
if(new_firmware == null || new_app == null)
|
|
130
|
-
return;
|
|
131
|
-
|
|
132
|
-
let mqtt_prefix = `${project_name}/${uid}`;
|
|
133
|
-
|
|
134
|
-
if(new_app != null && semver.lt(app_version, new_app.app_version)) {
|
|
135
|
-
console.log(new_app)
|
|
136
|
-
console.log(`updating firmware of ${uid} to minor version ${new_app.app_version}`);
|
|
137
|
-
let link = `${$.config.web.protocol}${$.config.web.domain}${$.config.web.fw_path}${new_app.filename}/download?token=${new_app.token}`;
|
|
138
|
-
client.publish(mqtt_prefix+"/fw/fota/update/set",`{"url":"${link}"}`,{qos:2,retain:false});
|
|
139
|
-
await $.db_data.update(project_name,device.id,"fota_tries",++fota_tries);
|
|
140
|
-
}else{
|
|
141
|
-
if(new_firmware != null && semver.lt(fw_version, new_firmware.fw_version)) {
|
|
142
|
-
console.log(`updating firmware of ${uid} to major version ${new_firmware.fw_version}`);
|
|
143
|
-
let link = `${$.config.web.protocol}${$.config.web.domain}${$.config.web.fw_path}${new_firmware.filename}/download?token=${new_firmware.token}`;
|
|
144
|
-
client.publish(mqtt_prefix+"/fw/fota/update/set",`{"url":"${link}"}`,{qos:2,retain:false});
|
|
145
|
-
await $.db_data.update(project_name,device.id,"fota_tries",++fota_tries);
|
|
82
|
+
$.db_device.update(device.id,"project_id",project_id);
|
|
83
|
+
|
|
84
|
+
switch (topic) {
|
|
85
|
+
case "status":
|
|
86
|
+
if (payload === "online" || payload === "offline") {
|
|
87
|
+
await $.db_device.update(device.id, "status", payload);
|
|
146
88
|
}
|
|
147
|
-
|
|
148
|
-
|
|
89
|
+
break;
|
|
90
|
+
case "model":
|
|
91
|
+
let res = await $.db_model.getByName(payload);
|
|
92
|
+
let model_id = res?.id;
|
|
93
|
+
if (model_id != null) {
|
|
94
|
+
await $.db_device.update(device.id, "model_id", model_id);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case "tech":
|
|
98
|
+
await $.db_device.update(device.id, "tech", payload);
|
|
99
|
+
break;
|
|
100
|
+
case "version":
|
|
101
|
+
if (device?.version && payload != device.version) {
|
|
102
|
+
await $.db_device.update(device.id, "version", payload);
|
|
103
|
+
await self.handleFotaSuccess(device.id);
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
case "app_version":
|
|
107
|
+
if (device?.app_version && payload != device.app_version) {
|
|
108
|
+
await $.db_device.update(device.id, "app_version", payload);
|
|
109
|
+
await self.handleFotaSuccess(device.id);
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
case "fw/fota/update/status":
|
|
113
|
+
await self.handleFotaError(device.id, payload);
|
|
114
|
+
break;
|
|
115
|
+
// Optional: default case if needed
|
|
116
|
+
default:
|
|
117
|
+
// handle other topics or do nothing
|
|
118
|
+
break;
|
|
149
119
|
}
|
|
150
120
|
|
|
151
|
-
if(
|
|
121
|
+
if(topic.startsWith("sensor/")){
|
|
122
|
+
|
|
123
|
+
updateSensor(device,topic,paylaod)
|
|
124
|
+
index = topic.indexOf("/");
|
|
125
|
+
if(index == -1)
|
|
126
|
+
return;
|
|
127
|
+
|
|
128
|
+
const name = topic.substring(index+1);
|
|
152
129
|
let res = await $.db_sensor.getByRef(topic)
|
|
153
130
|
if(res != null){
|
|
154
131
|
await $.db_sensor.insert(logs_table,device.id,res.id,payload);
|
|
@@ -157,60 +134,224 @@ var self = module.exports = {
|
|
|
157
134
|
|
|
158
135
|
// store remote device settings on project table - twin model
|
|
159
136
|
if(topic.endsWith("/set") && payload != "" ){
|
|
137
|
+
self.updateSettings(project_name,device,topic,payload);
|
|
138
|
+
}
|
|
160
139
|
|
|
161
|
-
|
|
140
|
+
if(project[project_name]){
|
|
141
|
+
project[project_name].module.parseMessage(client,project_name,device,topic,payload,retain,()=>{});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
162
144
|
|
|
163
|
-
|
|
164
|
-
console.log("topic invalid:",topic);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
// get settings
|
|
168
|
-
let settings = await $.db_project.getSettings(project_name,device.id);
|
|
169
|
-
let obj = settings;
|
|
145
|
+
},
|
|
170
146
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
147
|
+
deleteLogs : async()=>{
|
|
148
|
+
|
|
149
|
+
let tables = await $.db.getTables();
|
|
150
|
+
// Define one week in milliseconds
|
|
151
|
+
// Loop through each table
|
|
152
|
+
for (const tableObj of tables) {
|
|
153
|
+
const tableName = tableObj['Tables_in_mqtt-aedes']
|
|
154
|
+
const oneWeekAgo = moment().utc().subtract(7, 'days').format('YYYY-MM-DD HH:mm:ss');
|
|
155
|
+
if(tableName.startsWith("logs_")){
|
|
156
|
+
const uptimeSeconds = Math.floor(process.uptime());
|
|
157
|
+
console.log(`deleting logs of table ${tableName} older than ${oneWeekAgo}"`);
|
|
158
|
+
await $.db.deleteOldEntries(tableName, { createdAt: oneWeekAgo });
|
|
159
|
+
time = Math.floor(process.uptime()) - uptimeSeconds;
|
|
160
|
+
console.log(`Logs of table ${tableName} deleted in ${time}s`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
updateSettings : async (project_name,device,topic,payload)=>{
|
|
166
|
+
let route = topic.split("/");
|
|
167
|
+
|
|
168
|
+
if(route == null){
|
|
169
|
+
console.log("topic invalid:",topic);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// get settings
|
|
173
|
+
let settings = await $.db_project.getSettings(project_name,device.id);
|
|
174
|
+
let obj = settings;
|
|
175
|
+
|
|
176
|
+
if(obj == null){
|
|
177
|
+
obj = {};
|
|
178
|
+
}
|
|
179
|
+
// Traverse through all route parts except the last one
|
|
180
|
+
route.slice(0, -1).forEach(property => {
|
|
181
|
+
if (!obj.hasOwnProperty(property) ||
|
|
182
|
+
( obj.hasOwnProperty(property) && typeof obj[property] !== 'object'))
|
|
183
|
+
{
|
|
184
|
+
obj[property] = {}; // create nested object if missing or not an object
|
|
185
|
+
}
|
|
186
|
+
obj = obj[property]; // go deeper
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
let data = {};
|
|
190
|
+
try{
|
|
191
|
+
data = JSON.parse(payload)
|
|
192
|
+
}catch(error){}
|
|
193
|
+
|
|
194
|
+
if ((data && typeof payload === 'object' && !Array.isArray(payload) ) ) {
|
|
195
|
+
// Assuming payload is an object with properties to process
|
|
196
|
+
for (const [key, value] of Object.entries(data)) {
|
|
197
|
+
obj[key] = value; // go deeper
|
|
198
|
+
};
|
|
199
|
+
}else{
|
|
200
|
+
obj = payload
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// update on device fw settings
|
|
204
|
+
try{
|
|
205
|
+
await $.db_project.updateSettings(project_name,JSON.stringify(settings),device.id);
|
|
206
|
+
}catch(error){
|
|
207
|
+
console.log(error);
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
checkFota : async (release = "dev")=>{
|
|
212
|
+
|
|
213
|
+
const models = await $.db_model.getAll();
|
|
214
|
+
|
|
215
|
+
for (const model of models) {
|
|
216
|
+
|
|
217
|
+
if(!model?.id)
|
|
218
|
+
continue;
|
|
219
|
+
|
|
220
|
+
try{
|
|
221
|
+
const latestVersion = await $.db_firmware.getLatestVersion(model.id,release);
|
|
222
|
+
const latestAppVersion = await $.db_firmware.getLatestAppVersion(model.id,release);
|
|
223
|
+
|
|
224
|
+
const devices = await $.db_device.listByModel(model.id);
|
|
225
|
+
|
|
226
|
+
if(devices == null)
|
|
227
|
+
continue;
|
|
228
|
+
|
|
229
|
+
for (const device of devices) {
|
|
230
|
+
|
|
231
|
+
if(device?.accept_release != release){
|
|
232
|
+
continue;
|
|
181
233
|
}
|
|
182
|
-
obj = obj[property]; // go deeper
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
let data = {};
|
|
186
|
-
try{
|
|
187
|
-
data = JSON.parse(payload)
|
|
188
|
-
}catch(error){}
|
|
189
|
-
|
|
190
|
-
if (true || (data && typeof payload === 'object' && !Array.isArray(payload) ) ) {
|
|
191
|
-
// Assuming payload is an object with properties to process
|
|
192
|
-
for (const [key, value] of Object.entries(data)) {
|
|
193
|
-
obj[key] = value; // go deeper
|
|
194
|
-
};
|
|
195
|
-
}else{
|
|
196
|
-
obj = payload
|
|
197
|
-
}
|
|
198
234
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
235
|
+
let obj = null;
|
|
236
|
+
// insert filename on fota table for this device or update.
|
|
237
|
+
if(device?.app_version != latestAppVersion.app_version){
|
|
238
|
+
obj = {
|
|
239
|
+
model_id : device.model_id,
|
|
240
|
+
target_version : latestVersion.version,
|
|
241
|
+
target_app_version : latestAppVersion.app_version,
|
|
242
|
+
target_release : device.accept_release,
|
|
243
|
+
firmware_id : latestAppVersion.id,
|
|
244
|
+
nAttempts : 0,
|
|
245
|
+
fUpdate : true,
|
|
246
|
+
};
|
|
247
|
+
}else if(latestVersion?.version && (device?.version != latestVersion.version)){
|
|
248
|
+
obj = {
|
|
249
|
+
model_id : device.model_id,
|
|
250
|
+
target_version : latestVersion.version,
|
|
251
|
+
target_app_version : latestAppVersion.app_version,
|
|
252
|
+
target_release : device.accept_release,
|
|
253
|
+
firmware_id : latestVersion.id,
|
|
254
|
+
nAttempts : 0,
|
|
255
|
+
fUpdate : true,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
if(obj != null){
|
|
259
|
+
try{
|
|
260
|
+
let res = await $.db_fota.getEntry(device.id,obj);
|
|
261
|
+
if(res == null)
|
|
262
|
+
$.db_fota.update(device.id,obj);
|
|
263
|
+
}catch(error){
|
|
264
|
+
console.log(error)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
204
267
|
}
|
|
268
|
+
}catch(error){
|
|
269
|
+
console.log(error);
|
|
270
|
+
continue;
|
|
205
271
|
}
|
|
272
|
+
}
|
|
273
|
+
},
|
|
206
274
|
|
|
207
|
-
|
|
208
|
-
|
|
275
|
+
|
|
276
|
+
triggerFota : async (release = "dev")=>{
|
|
277
|
+
|
|
278
|
+
const devices = await $.db_fota.getUpdatable(release);
|
|
279
|
+
|
|
280
|
+
const batchSize = 10;
|
|
281
|
+
for (let i = 0; i < devices.length; i += batchSize) {
|
|
282
|
+
const batch = devices.slice(i, i + batchSize);
|
|
283
|
+
await Promise.all(batch.map(async (device) => {
|
|
284
|
+
|
|
285
|
+
const firmware = await $.db_firmware.getById(device?.firmware_id)
|
|
286
|
+
if(firmware != null){
|
|
287
|
+
const project = await $.db_project.getById(device.project_id);
|
|
288
|
+
const project_name = project?.name;
|
|
289
|
+
const model = await $.db_model.getById(firmware.model_id);
|
|
290
|
+
const model_name = model?.name;
|
|
291
|
+
let topic = "";
|
|
292
|
+
if(model_name == "sniffer"){
|
|
293
|
+
// get sniffer info associated to device
|
|
294
|
+
const sniffer = await $.db_data.getGwAssociatedToDevice("sniffer",device.uid);
|
|
295
|
+
if(sniffer == null) return;
|
|
296
|
+
// get device_uid from devices table
|
|
297
|
+
const gw = await $.db_device.getById(sniffer?.id)
|
|
298
|
+
if(gw == null) return;
|
|
299
|
+
let mqtt_prefix = `${project_name}/${gw.uid}`;
|
|
300
|
+
topic = mqtt_prefix+"/app/sniffer/fota/update/set";
|
|
301
|
+
}
|
|
302
|
+
else{
|
|
303
|
+
let mqtt_prefix = `${project_name}/${device.uid}`;
|
|
304
|
+
topic = mqtt_prefix+"/fw/fota/update/set";
|
|
305
|
+
}
|
|
306
|
+
let link = `${$.config.web.protocol}${$.config.web.domain}${$.config.web.fw_path}${firmware?.filename}/download?token=${firmware?.token}`;
|
|
307
|
+
console.log(`Requesting firmware update of ${device.uid} to ${firmware?.filename}`);
|
|
308
|
+
$.mqtt_client.publish(topic,`{"url":"${link}"}`,{qos:1,retain:false});
|
|
309
|
+
|
|
310
|
+
let obj = {
|
|
311
|
+
nAttempts : ++device.nAttempts
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
await $.db_fota.update(device.id,obj);
|
|
315
|
+
|
|
316
|
+
obj = {
|
|
317
|
+
device_id : device.id,
|
|
318
|
+
local_version : device.version,
|
|
319
|
+
local_app_version : device.app_version,
|
|
320
|
+
target_version : firmware.version,
|
|
321
|
+
target_app_version : firmware.app_version,
|
|
322
|
+
target_file : firmware.filename,
|
|
323
|
+
nAttempt : device.nAttempts
|
|
324
|
+
}
|
|
325
|
+
await $.db_fota.newLog(device.id,obj);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
}));
|
|
329
|
+
|
|
330
|
+
// Wait for 1 minute after processing each batch
|
|
331
|
+
if (i + batchSize < devices.length) {
|
|
332
|
+
await new Promise(resolve => setTimeout(resolve, 60000));
|
|
209
333
|
}
|
|
210
334
|
}
|
|
335
|
+
},
|
|
211
336
|
|
|
337
|
+
handleFotaSuccess : async (deviceId) => {
|
|
338
|
+
let object = {
|
|
339
|
+
"nAttempts" : 0,
|
|
340
|
+
"fUpdate" : 0,
|
|
341
|
+
}
|
|
342
|
+
await $.db_fota.update(deviceId,object);
|
|
343
|
+
object = {
|
|
344
|
+
success : 1,
|
|
345
|
+
}
|
|
346
|
+
await $.db_fota.updateLog(deviceId,obj);
|
|
212
347
|
},
|
|
213
348
|
|
|
349
|
+
handleFotaError : async (deviceId, error) => {
|
|
350
|
+
let object = {
|
|
351
|
+
error : error,
|
|
352
|
+
}
|
|
353
|
+
await $.db_fota.updateLog(deviceId,obj);
|
|
354
|
+
}
|
|
214
355
|
}
|
|
215
356
|
|
|
216
357
|
|