mqtt-devices-parser 1.0.11 → 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 +3 -0
- package/index.js +24 -3
- package/models/devices.models.js +1 -5
- package/models/firmwares.models.js +2 -10
- 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 +93 -7
- 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 +264 -147
package/src/device/device.js
CHANGED
|
@@ -32,83 +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
|
-
checkFota : async ()=>{
|
|
55
|
-
|
|
56
|
-
const devices = await $.db_device.listOnline();
|
|
57
|
-
|
|
58
|
-
const batchSize = 10;
|
|
59
|
-
for (let i = 0; i < devices.length; i += batchSize) {
|
|
60
|
-
const batch = devices.slice(i, i + batchSize);
|
|
61
|
-
await Promise.all(batch.map(async (device) => {
|
|
62
|
-
|
|
63
|
-
if (device.release == null || device.release === "critical")
|
|
64
|
-
{
|
|
65
|
-
// don't update
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (device.fota_tries > 3)
|
|
70
|
-
{
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
let new_firmware = await $.db_firmware.getLatestFWVersion(device.model_id,device.release);
|
|
75
|
-
let new_app = await $.db_firmware.getLatestAppVersion(device.model_id,device.release);
|
|
76
|
-
if(new_firmware == null || new_app == null)
|
|
77
|
-
return;
|
|
78
|
-
|
|
79
|
-
let project_name = "";
|
|
80
|
-
let res = await $.db_project.getById(device.project_id);
|
|
81
|
-
if(res != null)
|
|
82
|
-
project_name = res.name;
|
|
83
|
-
else
|
|
84
|
-
return;
|
|
85
|
-
|
|
86
|
-
let mqtt_prefix = `${project_name}/${device.uid}`;
|
|
87
|
-
if(new_app != null && semver.lt(device.app_version, new_app.app_version))
|
|
88
|
-
{
|
|
89
|
-
console.log(`updating firmware of ${device.uid} to minor version ${new_app.app_version}`);
|
|
90
|
-
let link = `${$.config.web.protocol}${$.config.web.domain}${$.config.web.fw_path}${new_app.filename}/download?token=${new_app.token}`;
|
|
91
|
-
client.publish(mqtt_prefix+"/fw/fota/update/set",`{"url":"${link}"}`,{qos:2,retain:false});
|
|
92
|
-
await $.db_data.update(project_name,device.id,"fota_tries",++device.fota_tries);
|
|
93
|
-
}else{
|
|
94
|
-
if(new_firmware != null && semver.lt(device.version, new_firmware.fw_version))
|
|
95
|
-
{
|
|
96
|
-
console.log(`updating firmware of ${device.uid} to major version ${new_firmware.fw_version}`);
|
|
97
|
-
let link = `${$.config.web.protocol}${$.config.web.domain}${$.config.web.fw_path}${new_firmware.filename}/download?token=${new_firmware.token}`;
|
|
98
|
-
$.mqtt_client.publish(mqtt_prefix+"/fw/fota/update/set",`{"url":"${link}"}`,{qos:2,retain:false});
|
|
99
|
-
await $.db_data.update(project_name,device.id,"fota_tries",++device.fota_tries);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}));
|
|
103
|
-
|
|
104
|
-
// Wait for 1 minute after processing each batch
|
|
105
|
-
if (i + batchSize < devices.length) {
|
|
106
|
-
await new Promise(resolve => setTimeout(resolve, 60000));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
},
|
|
111
|
-
|
|
112
35
|
parseMessage : async (client,topic,payload,retain)=>{
|
|
113
36
|
|
|
114
37
|
let topic_bck = topic;
|
|
@@ -118,23 +41,36 @@ var self = module.exports = {
|
|
|
118
41
|
let index = topic.indexOf("/");
|
|
119
42
|
if(index == -1)
|
|
120
43
|
return;
|
|
44
|
+
|
|
121
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;
|
|
122
52
|
|
|
123
53
|
// --- uid ---
|
|
124
54
|
topic = topic.substring(index+1);
|
|
125
55
|
index = topic.indexOf("/");
|
|
126
56
|
if(index == -1)
|
|
127
57
|
return;
|
|
128
|
-
let uid = topic.substring(0,index);
|
|
129
58
|
|
|
59
|
+
let uid = topic.substring(0,index);
|
|
130
60
|
topic = topic.substring(index+1);
|
|
131
61
|
|
|
132
62
|
// check if topic corresponds to a device
|
|
133
|
-
if(uid.startsWith(
|
|
63
|
+
if(uid.startsWith(project.uidPrefix) && uid.length == project?.uidLength){
|
|
64
|
+
|
|
134
65
|
let device = await $.db_device.get(uid);
|
|
135
66
|
|
|
67
|
+
// Insert device if not exists on db
|
|
136
68
|
if(device == null){
|
|
137
|
-
|
|
69
|
+
let obj = {
|
|
70
|
+
uid : uid,
|
|
71
|
+
accept_release : "prod",
|
|
72
|
+
}
|
|
73
|
+
$.db_device.insert(obj)
|
|
138
74
|
.then(async()=>{
|
|
139
75
|
device = await $.db_device.get(uid);
|
|
140
76
|
})
|
|
@@ -142,37 +78,54 @@ var self = module.exports = {
|
|
|
142
78
|
}
|
|
143
79
|
|
|
144
80
|
// update project id if needed
|
|
145
|
-
let res = await $.db_project.getByName(project_name);
|
|
146
|
-
let project_id = res?.id;
|
|
147
81
|
if(project_id != null && project_id != device?.project_id)
|
|
148
82
|
$.db_device.update(device.id,"project_id",project_id);
|
|
149
83
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
let
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
84
|
+
switch (topic) {
|
|
85
|
+
case "status":
|
|
86
|
+
if (payload === "online" || payload === "offline") {
|
|
87
|
+
await $.db_device.update(device.id, "status", payload);
|
|
88
|
+
}
|
|
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;
|
|
173
119
|
}
|
|
174
120
|
|
|
175
|
-
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);
|
|
176
129
|
let res = await $.db_sensor.getByRef(topic)
|
|
177
130
|
if(res != null){
|
|
178
131
|
await $.db_sensor.insert(logs_table,device.id,res.id,payload);
|
|
@@ -181,60 +134,224 @@ var self = module.exports = {
|
|
|
181
134
|
|
|
182
135
|
// store remote device settings on project table - twin model
|
|
183
136
|
if(topic.endsWith("/set") && payload != "" ){
|
|
137
|
+
self.updateSettings(project_name,device,topic,payload);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if(project[project_name]){
|
|
141
|
+
project[project_name].module.parseMessage(client,project_name,device,topic,payload,retain,()=>{});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
184
144
|
|
|
185
|
-
|
|
145
|
+
},
|
|
186
146
|
|
|
187
|
-
|
|
188
|
-
console.log("topic invalid:",topic);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
// get settings
|
|
192
|
-
let settings = await $.db_project.getSettings(project_name,device.id);
|
|
193
|
-
let obj = settings;
|
|
147
|
+
deleteLogs : async()=>{
|
|
194
148
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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;
|
|
205
233
|
}
|
|
206
|
-
obj = obj[property]; // go deeper
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
let data = {};
|
|
210
|
-
try{
|
|
211
|
-
data = JSON.parse(payload)
|
|
212
|
-
}catch(error){}
|
|
213
|
-
|
|
214
|
-
if (true || (data && typeof payload === 'object' && !Array.isArray(payload) ) ) {
|
|
215
|
-
// Assuming payload is an object with properties to process
|
|
216
|
-
for (const [key, value] of Object.entries(data)) {
|
|
217
|
-
obj[key] = value; // go deeper
|
|
218
|
-
};
|
|
219
|
-
}else{
|
|
220
|
-
obj = payload
|
|
221
|
-
}
|
|
222
234
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
+
}
|
|
228
267
|
}
|
|
268
|
+
}catch(error){
|
|
269
|
+
console.log(error);
|
|
270
|
+
continue;
|
|
229
271
|
}
|
|
272
|
+
}
|
|
273
|
+
},
|
|
230
274
|
|
|
231
|
-
|
|
232
|
-
|
|
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));
|
|
233
333
|
}
|
|
234
334
|
}
|
|
335
|
+
},
|
|
235
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);
|
|
236
347
|
},
|
|
237
348
|
|
|
349
|
+
handleFotaError : async (deviceId, error) => {
|
|
350
|
+
let object = {
|
|
351
|
+
error : error,
|
|
352
|
+
}
|
|
353
|
+
await $.db_fota.updateLog(deviceId,obj);
|
|
354
|
+
}
|
|
238
355
|
}
|
|
239
356
|
|
|
240
357
|
|