iobroker.lorawan 0.1.12 → 0.1.13
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/README.md +3 -0
- package/io-package.json +15 -14
- package/lib/modules/directorieshandler.js +82 -99
- package/lib/modules/downlinkConfighandler.js +7 -7
- package/lib/modules/messagehandler.js +23 -17
- package/main.js +116 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,9 @@ For now there is documentation in English here: http://www.hafenmeister.com/Lora
|
|
|
22
22
|
Placeholder for the next version (at the beginning of the line):
|
|
23
23
|
### **WORK IN PROGRESS**
|
|
24
24
|
-->
|
|
25
|
+
### 0.1.13 (2024-02-12)
|
|
26
|
+
* (BenAhrdt) building of directory changed and message implemented
|
|
27
|
+
|
|
25
28
|
### 0.1.12 (2024-02-09)
|
|
26
29
|
* (BenAhrdt) default value crc config bug fixed
|
|
27
30
|
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "lorawan",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.13",
|
|
5
5
|
"news": {
|
|
6
|
+
"0.1.13": {
|
|
7
|
+
"en": "building of directory changed and message implemented",
|
|
8
|
+
"de": "aufbau des geänderten verzeichnisses und der implementierten meldung",
|
|
9
|
+
"ru": "создание измененного каталога и реализовано сообщение",
|
|
10
|
+
"pt": "construção de diretório alterado e mensagem implementada",
|
|
11
|
+
"nl": "opbouw van map gewijzigd en bericht geïmplementeerd",
|
|
12
|
+
"fr": "la construction du répertoire modifié et le message mis en œuvre",
|
|
13
|
+
"it": "costruzione della directory modificata e messa in atto",
|
|
14
|
+
"es": "la construcción del directorio cambiado y el mensaje implementado",
|
|
15
|
+
"pl": "budowa zmienionych katalogów i wdrożona wiadomość",
|
|
16
|
+
"uk": "створення каталогу змінено та повідомлення",
|
|
17
|
+
"zh-cn": "更改目录构建并执行消息"
|
|
18
|
+
},
|
|
6
19
|
"0.1.12": {
|
|
7
20
|
"en": "default value crc config bug fixed",
|
|
8
21
|
"de": "standardwert crc config bug behoben",
|
|
@@ -80,19 +93,6 @@
|
|
|
80
93
|
"pl": "zmienić filtr na statechange",
|
|
81
94
|
"uk": "зміна фільтра на державну зміну",
|
|
82
95
|
"zh-cn": "状态更改过滤器"
|
|
83
|
-
},
|
|
84
|
-
"0.1.6": {
|
|
85
|
-
"en": "implments byte swap",
|
|
86
|
-
"de": "implikationen byte swap",
|
|
87
|
-
"ru": "свопа",
|
|
88
|
-
"pt": "impelimentos por swap",
|
|
89
|
-
"nl": "implments byte swap",
|
|
90
|
-
"fr": "swap par octets",
|
|
91
|
-
"it": "importazioni di swap",
|
|
92
|
-
"es": "swap byte",
|
|
93
|
-
"pl": "implikacje swap bajtów",
|
|
94
|
-
"uk": "напляскване",
|
|
95
|
-
"zh-cn": "杂质字节互换"
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"title": "LoRaWAN",
|
|
@@ -141,6 +141,7 @@
|
|
|
141
141
|
"type": "protocols",
|
|
142
142
|
"compact": true,
|
|
143
143
|
"connectionType": "cloud",
|
|
144
|
+
"messagebox": true,
|
|
144
145
|
"dataSource": "push",
|
|
145
146
|
"adminUI": {
|
|
146
147
|
"config": "json"
|
|
@@ -7,7 +7,7 @@ class directorieshandlerClass {
|
|
|
7
7
|
// used dataentries in directory structurt
|
|
8
8
|
this.searchableAttributeNames = {
|
|
9
9
|
apllicationId: "applicationId",
|
|
10
|
-
|
|
10
|
+
deviceEUI: "devEui",
|
|
11
11
|
deviceId: "deviceId"
|
|
12
12
|
};
|
|
13
13
|
|
|
@@ -32,67 +32,63 @@ class directorieshandlerClass {
|
|
|
32
32
|
// declare the directory structre
|
|
33
33
|
this.directories = {
|
|
34
34
|
application:{
|
|
35
|
-
|
|
35
|
+
objectStateId : async (topic,message) =>{
|
|
36
36
|
return await this.getAttributValue(topic,message,this.searchableAttributeNames.apllicationId);
|
|
37
37
|
},
|
|
38
38
|
objectCommonName: "application",
|
|
39
39
|
devices:{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return await this.getAttributValue(topic,message,this.searchableAttributeNames.
|
|
40
|
+
deviceEUI:{
|
|
41
|
+
objectStateId : async (topic,message) =>{
|
|
42
|
+
return await this.getAttributValue(topic,message,this.searchableAttributeNames.deviceEUI);
|
|
43
43
|
},
|
|
44
|
-
objectCommonName:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
objectCommonName: async (topic,message) =>{
|
|
45
|
+
return await this.getAttributValue(topic,message,this.searchableAttributeNames.deviceId);
|
|
46
|
+
},
|
|
47
|
+
objectType: "device",
|
|
48
|
+
configuration:{
|
|
49
|
+
devicetype:{
|
|
50
|
+
isState: true,
|
|
51
|
+
stateCommonType: "string",
|
|
52
|
+
stateCommonWrite: true,
|
|
53
|
+
stateCommonRole: "text"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
uplink:{
|
|
57
|
+
raw:{
|
|
58
|
+
},
|
|
59
|
+
decoded:{
|
|
60
|
+
},
|
|
61
|
+
remaining:{
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
downlink:{
|
|
65
|
+
raw:{
|
|
66
|
+
},
|
|
67
|
+
control:{
|
|
48
68
|
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
configuration:{
|
|
52
|
-
devicetype:{
|
|
69
|
+
nextSend:{
|
|
70
|
+
hex:{
|
|
53
71
|
isState: true,
|
|
72
|
+
stateCommonDef: "0",
|
|
54
73
|
stateCommonType: "string",
|
|
55
|
-
stateCommonWrite: true,
|
|
56
|
-
stateCommonRole: "text"
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
uplink:{
|
|
60
|
-
raw:{
|
|
61
|
-
},
|
|
62
|
-
decoded:{
|
|
63
|
-
},
|
|
64
|
-
remaining:{
|
|
65
74
|
}
|
|
66
75
|
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
nextSend:{
|
|
73
|
-
hex:{
|
|
74
|
-
isState: true,
|
|
75
|
-
stateCommonDef: "0",
|
|
76
|
-
stateCommonType: "string",
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
lastSend:{
|
|
80
|
-
hex:{
|
|
81
|
-
isState: true,
|
|
82
|
-
stateCommonDef: "0",
|
|
83
|
-
stateCommonType: "string"
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
remaining:{
|
|
76
|
+
lastSend:{
|
|
77
|
+
hex:{
|
|
78
|
+
isState: true,
|
|
79
|
+
stateCommonDef: "0",
|
|
80
|
+
stateCommonType: "string"
|
|
87
81
|
}
|
|
88
82
|
},
|
|
83
|
+
remaining:{
|
|
84
|
+
}
|
|
89
85
|
},
|
|
90
86
|
}
|
|
91
87
|
}
|
|
92
88
|
}
|
|
93
89
|
};
|
|
94
90
|
this.ignoredElementNames ={
|
|
95
|
-
|
|
91
|
+
objectStateId: "objectStateId",
|
|
96
92
|
objectCommonName: "objectCommonName",
|
|
97
93
|
objectType: "objectType"
|
|
98
94
|
};
|
|
@@ -136,20 +132,37 @@ class directorieshandlerClass {
|
|
|
136
132
|
// if there is an declared ObjectStateName (must be a function)=> take it
|
|
137
133
|
let objectId = `${startDirectory}.${elementName}`;
|
|
138
134
|
let internalObjectId = elementName;
|
|
139
|
-
if(obj[elementName].
|
|
140
|
-
internalObjectId = `${await obj[elementName].
|
|
135
|
+
if(obj[elementName].objectStateId){
|
|
136
|
+
internalObjectId = `${await obj[elementName].objectStateId(topic,message)}`;
|
|
141
137
|
objectId = `${startDirectory}.${internalObjectId}`;
|
|
142
138
|
}
|
|
143
139
|
if(objectId.indexOf(".") === 0){
|
|
144
140
|
objectId = objectId.substring(1,objectId.length);
|
|
145
141
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
142
|
+
let objectCommonName = "";
|
|
143
|
+
if(obj[elementName].objectCommonName && typeof obj[elementName].objectCommonName === "function"){
|
|
144
|
+
objectCommonName = await obj[elementName].objectCommonName(topic,message);
|
|
145
|
+
await this.adapter.extendObject(objectId,{
|
|
146
|
+
type: obj[elementName].objectType? obj[elementName].objectType : "folder",
|
|
147
|
+
common: {
|
|
148
|
+
name: objectCommonName
|
|
149
|
+
},
|
|
150
|
+
native : {},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else{
|
|
154
|
+
if(obj[elementName].objectCommonName){
|
|
155
|
+
objectCommonName = obj[elementName].objectCommonName;
|
|
156
|
+
}
|
|
157
|
+
await this.adapter.setObjectNotExistsAsync(objectId,{
|
|
158
|
+
type: obj[elementName].objectType? obj[elementName].objectType : "folder",
|
|
159
|
+
common: {
|
|
160
|
+
name: objectCommonName
|
|
161
|
+
},
|
|
162
|
+
native : {},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// Jump into next step (next directory / attribute)
|
|
153
166
|
await this.generateRekursivObjects(obj[elementName],objectId,topic,message);
|
|
154
167
|
}
|
|
155
168
|
else{
|
|
@@ -168,8 +181,8 @@ class directorieshandlerClass {
|
|
|
168
181
|
stateCommonWrite = obj[elementName].stateCommonWrite ? obj[elementName].stateCommonWrite : stateCommonWrite;
|
|
169
182
|
stateCommonRole = obj[elementName].stateCommonRole ? obj[elementName].stateCommonRole : stateCommonRole;
|
|
170
183
|
}
|
|
171
|
-
if(obj[elementName].
|
|
172
|
-
internalObjectId = `${await obj[elementName].
|
|
184
|
+
if(obj[elementName].objectStateId){
|
|
185
|
+
internalObjectId = `${await obj[elementName].objectStateId(topic,message)}`;
|
|
173
186
|
objectId = `${startDirectory}.${internalObjectId}`;
|
|
174
187
|
}
|
|
175
188
|
}
|
|
@@ -285,7 +298,7 @@ class directorieshandlerClass {
|
|
|
285
298
|
case this.searchableAttributeNames.apllicationId:
|
|
286
299
|
return topicResolved?.applicationId;
|
|
287
300
|
|
|
288
|
-
case this.searchableAttributeNames.
|
|
301
|
+
case this.searchableAttributeNames.deviceEUI:
|
|
289
302
|
return message.end_device_ids.dev_eui;
|
|
290
303
|
|
|
291
304
|
case this.searchableAttributeNames.deviceId:
|
|
@@ -312,8 +325,8 @@ class directorieshandlerClass {
|
|
|
312
325
|
const topicResolved = this.getTopicResolved(topic);
|
|
313
326
|
if(typeof message !== "string"){
|
|
314
327
|
switch(resolvetype){
|
|
315
|
-
case this.searchableAttributeNames.
|
|
316
|
-
return `${topicResolved?.applicationId}.devices.${message.end_device_ids.dev_eui}
|
|
328
|
+
case this.searchableAttributeNames.deviceEUI:
|
|
329
|
+
return `${topicResolved?.applicationId}.devices.${message.end_device_ids.dev_eui}`;
|
|
317
330
|
|
|
318
331
|
default:
|
|
319
332
|
return message;
|
|
@@ -369,33 +382,15 @@ class directorieshandlerClass {
|
|
|
369
382
|
try{
|
|
370
383
|
this.adapter.log.silly(`attribute ${resolvetype} is requested for chirpstack`);
|
|
371
384
|
const topicResolved = this.getTopicResolved(topic);
|
|
372
|
-
let devId = undefined;
|
|
373
385
|
switch(resolvetype){
|
|
374
386
|
case this.searchableAttributeNames.apllicationId:
|
|
375
387
|
return topicResolved?.applicationId;
|
|
376
388
|
|
|
377
|
-
case this.searchableAttributeNames.
|
|
378
|
-
return topicResolved?.
|
|
389
|
+
case this.searchableAttributeNames.deviceEUI:
|
|
390
|
+
return topicResolved?.deviceEUI;
|
|
379
391
|
|
|
380
392
|
case this.searchableAttributeNames.deviceId:
|
|
381
|
-
|
|
382
|
-
devId = message.deviceInfo.deviceName;
|
|
383
|
-
}
|
|
384
|
-
else if(topicResolved?.messageType === "down")
|
|
385
|
-
{
|
|
386
|
-
const adapterObjectsAtStart = await this.adapter.getAdapterObjectsAsync();
|
|
387
|
-
for(const adapterObject of Object.values(adapterObjectsAtStart)){
|
|
388
|
-
if(adapterObject.type === "channel" && adapterObject.common.name !== "Information"){
|
|
389
|
-
adapterObject._id = this.adapter.removeNamespace(adapterObject._id);
|
|
390
|
-
const baseDeviceInfo = this.adapter.getBaseDeviceInfo(`${adapterObject._id}.deviceInfo`);
|
|
391
|
-
if(adapterObject._id.indexOf(topicResolved.deviceUid) !== -1){
|
|
392
|
-
devId = baseDeviceInfo.device_id;
|
|
393
|
-
break;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
return devId;
|
|
393
|
+
return message.deviceInfo.deviceName;
|
|
399
394
|
|
|
400
395
|
default:
|
|
401
396
|
this.adapter.log.warn(`No attribute with the name ${resolvetype} found.`);
|
|
@@ -417,30 +412,18 @@ class directorieshandlerClass {
|
|
|
417
412
|
this.adapter.log.silly(`directory ${resolvetype} is requested for chirpstack`);
|
|
418
413
|
const topicResolved = this.getTopicResolved(topic);
|
|
419
414
|
let devUid = undefined;
|
|
420
|
-
if(topicResolved?.messageType
|
|
415
|
+
if(topicResolved?.messageType !== "down"){
|
|
421
416
|
devUid = message.deviceInfo.devEui;
|
|
422
417
|
}
|
|
423
|
-
else
|
|
424
|
-
{
|
|
418
|
+
else{
|
|
425
419
|
devUid = message.devEui;
|
|
426
420
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
default:
|
|
434
|
-
return message;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
else{
|
|
421
|
+
switch(resolvetype){
|
|
422
|
+
case this.searchableAttributeNames.deviceEUI:
|
|
423
|
+
return `${topicResolved?.applicationId}.devices.${devUid}`;
|
|
424
|
+
|
|
425
|
+
default:
|
|
438
426
|
return message;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
else{
|
|
442
|
-
this.adapter.log.warn(`no devEui found in message`);
|
|
443
|
-
this.adapter.log.warn(`message: ${message}`);
|
|
444
427
|
}
|
|
445
428
|
}
|
|
446
429
|
catch(error){
|
|
@@ -458,7 +441,7 @@ class directorieshandlerClass {
|
|
|
458
441
|
const topicElements = topic.split("/");
|
|
459
442
|
const topicResolved = {
|
|
460
443
|
applicationId: topicElements[1],
|
|
461
|
-
|
|
444
|
+
deviceEUI: topicElements[3],
|
|
462
445
|
messageType: topicElements[topicElements.length - 1]
|
|
463
446
|
};
|
|
464
447
|
// clean up application id
|
|
@@ -170,9 +170,9 @@ class downlinkConfighandlerClass {
|
|
|
170
170
|
// Select downlinktopic in case of origin
|
|
171
171
|
switch(this.adapter.config.origin){
|
|
172
172
|
case this.adapter.origin.ttn:
|
|
173
|
-
return this.
|
|
173
|
+
return this.getTtnDownlinkTopicFromDirectory(changeInfo,suffix);
|
|
174
174
|
case this.adapter.origin.chirpstack:
|
|
175
|
-
return this.
|
|
175
|
+
return this.getChirpstackDownlinkTopicFromDirectory(changeInfo,suffix);
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -303,14 +303,14 @@ class downlinkConfighandlerClass {
|
|
|
303
303
|
* *********************** Downlinktopic *****************************
|
|
304
304
|
* ******************************************************************/
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
getTtnDownlinkTopicFromDirectory(changeInfo,suffix){
|
|
307
307
|
this.adapter.log.silly(`the downlinktopic for ttn is requested`);
|
|
308
308
|
const topicElements = {
|
|
309
309
|
Version : "v3",
|
|
310
310
|
applicationId : `/${changeInfo.applicationId}`,
|
|
311
311
|
applicationFrom : "@ttn",
|
|
312
312
|
devices : `/devices`,
|
|
313
|
-
|
|
313
|
+
deviceId : `/${changeInfo.deviceId}`,
|
|
314
314
|
suffix : suffix
|
|
315
315
|
};
|
|
316
316
|
let downlink = "";
|
|
@@ -349,13 +349,13 @@ class downlinkConfighandlerClass {
|
|
|
349
349
|
* *********************** Downlinktopic *****************************
|
|
350
350
|
* ******************************************************************/
|
|
351
351
|
|
|
352
|
-
|
|
352
|
+
getChirpstackDownlinkTopicFromDirectory(changeInfo,suffix){
|
|
353
353
|
this.adapter.log.silly(`the downlinktopic for chirpstack is requested`);
|
|
354
354
|
const topicElements = {
|
|
355
355
|
Version : "application",
|
|
356
356
|
applicationId : `/${changeInfo.applicationId}`,
|
|
357
357
|
device : `/device`,
|
|
358
|
-
|
|
358
|
+
deviceEUI : `/${changeInfo.deviceEUI}`,
|
|
359
359
|
command: `/command`,
|
|
360
360
|
suffix : suffix
|
|
361
361
|
};
|
|
@@ -375,7 +375,7 @@ class downlinkConfighandlerClass {
|
|
|
375
375
|
|
|
376
376
|
const payloadInBase64 = Buffer.from(payloadInHex, "hex").toString("base64");
|
|
377
377
|
// retun the whole downlink
|
|
378
|
-
return {devEui:changeInfo.
|
|
378
|
+
return {devEui:changeInfo.deviceEUI,confirmed:downlinkConfig.confirmed,fPort:downlinkConfig.port,data:payloadInBase64};
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
381
|
|
|
@@ -34,7 +34,7 @@ class messagehandlerClass {
|
|
|
34
34
|
try{
|
|
35
35
|
const adapterObjectsAtStart = await this.adapter.getAdapterObjectsAsync();
|
|
36
36
|
for(const adapterObject of Object.values(adapterObjectsAtStart)){
|
|
37
|
-
if(adapterObject.type === "
|
|
37
|
+
if(adapterObject.type === "device"){
|
|
38
38
|
await this.fillWithDownlinkConfig(adapterObject._id);
|
|
39
39
|
//await this.addDirectoriesToPresentDirectory(`${stateId}`); Not used yet (Maybe for thefuture with more folders)
|
|
40
40
|
}
|
|
@@ -59,8 +59,8 @@ class messagehandlerClass {
|
|
|
59
59
|
|
|
60
60
|
//Add directories at startup (so theyare present before next upload)
|
|
61
61
|
async addDirectoriesToPresentDirectory(startDirectory){
|
|
62
|
-
await this.directoryhandler.generateRekursivObjects(this.directoryhandler.directories.application.devices.
|
|
63
|
-
await this.directoryhandler.generateRekursivObjects(this.directoryhandler.directories.application.devices.
|
|
62
|
+
await this.directoryhandler.generateRekursivObjects(this.directoryhandler.directories.application.devices.deviceEUI.deviceId.downlink.nextSend,`${startDirectory}.${this.directoryhandler.reachableSubfolders.downlinkNextSend}`,"","");
|
|
63
|
+
await this.directoryhandler.generateRekursivObjects(this.directoryhandler.directories.application.devices.deviceEUI.deviceId.downlink.lastSend,`${startDirectory}.${this.directoryhandler.reachableSubfolders.downlinkLastSend}`,"","");
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
async fillWithDownlinkConfig(deviceStartdirectory){
|
|
@@ -169,9 +169,18 @@ class messagehandlerClass {
|
|
|
169
169
|
const messageType = topic.substring(topic.lastIndexOf("/") + 1 ,topic.length);
|
|
170
170
|
this.adapter.log.silly(`the messagetype ${messageType} was determined`);
|
|
171
171
|
// generate startdirectory of device
|
|
172
|
-
const deviceStartdirectory = await this.directoryhandler.getObjectDirectory(topic,message,this.directoryhandler.searchableAttributeNames.
|
|
172
|
+
const deviceStartdirectory = await this.directoryhandler.getObjectDirectory(topic,message,this.directoryhandler.searchableAttributeNames.deviceEUI);
|
|
173
173
|
this.adapter.log.silly(`the startdirectory ${deviceStartdirectory} was determined`);
|
|
174
174
|
|
|
175
|
+
/*********************************************************************
|
|
176
|
+
* ****************** Check device startdirectory ********************
|
|
177
|
+
* ******************************************************************/
|
|
178
|
+
|
|
179
|
+
if(messageType !== "up" && !await this.adapter.objectExists(`${deviceStartdirectory}`)){
|
|
180
|
+
this.adapter.log.debug(`There was a message with the topic ${topic}, but the object ${deviceStartdirectory} does not exists yet.`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
175
184
|
/*********************************************************************
|
|
176
185
|
* ************************* Infodata ********************************
|
|
177
186
|
* ******************************************************************/
|
|
@@ -294,12 +303,6 @@ class messagehandlerClass {
|
|
|
294
303
|
else if(messageType === "queued" || messageType === "sent"){ //if(message.downlink_queued || message.downlink_sent)//if(message.downlink_queued || message.downlink_sent){
|
|
295
304
|
// Check wich downlink was recieved
|
|
296
305
|
const downlinkType = `downlink_${messageType}`;
|
|
297
|
-
/*********************************************************************
|
|
298
|
-
* ************************ Main directories *************************
|
|
299
|
-
* ******************************************************************/
|
|
300
|
-
|
|
301
|
-
await this.directoryhandler.generateRekursivObjects(this.directoryhandler.directories,"",topic,message);
|
|
302
|
-
|
|
303
306
|
/*********************************************************************
|
|
304
307
|
* ************************ Rawdata json *****************************
|
|
305
308
|
* ******************************************************************/
|
|
@@ -415,9 +418,18 @@ class messagehandlerClass {
|
|
|
415
418
|
const messageType = topic.substring(topic.lastIndexOf("/") + 1 ,topic.length);
|
|
416
419
|
this.adapter.log.silly(`the messagetype ${messageType} was determined`);
|
|
417
420
|
// generate startdirectory of device
|
|
418
|
-
const deviceStartdirectory = await this.directoryhandler.getObjectDirectory(topic,message,this.directoryhandler.searchableAttributeNames.
|
|
421
|
+
const deviceStartdirectory = await this.directoryhandler.getObjectDirectory(topic,message,this.directoryhandler.searchableAttributeNames.deviceEUI);
|
|
419
422
|
this.adapter.log.silly(`the startdirectory ${deviceStartdirectory} was determined`);
|
|
420
423
|
|
|
424
|
+
/*********************************************************************
|
|
425
|
+
* ****************** Check device startdirectory ********************
|
|
426
|
+
* ******************************************************************/
|
|
427
|
+
|
|
428
|
+
if(messageType !== "up" && !await this.adapter.objectExists(`${deviceStartdirectory}`)){
|
|
429
|
+
this.adapter.log.debug(`There was a message with the topic ${topic}, but the object ${deviceStartdirectory} does not exists yet.`);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
421
433
|
/*********************************************************************
|
|
422
434
|
* ************************* Infodata ********************************
|
|
423
435
|
* ******************************************************************/
|
|
@@ -538,12 +550,6 @@ class messagehandlerClass {
|
|
|
538
550
|
// check for uplink message
|
|
539
551
|
else if(messageType === "down"){ //if(message.downlink_queued || message.downlink_sent)//if(message.downlink_queued || message.downlink_sent){
|
|
540
552
|
|
|
541
|
-
/*********************************************************************
|
|
542
|
-
* ************************ Main directories *************************
|
|
543
|
-
* ******************************************************************/
|
|
544
|
-
|
|
545
|
-
await this.directoryhandler.generateRekursivObjects(this.directoryhandler.directories,"",topic,message);
|
|
546
|
-
|
|
547
553
|
/*********************************************************************
|
|
548
554
|
* ************************ Rawdata json *****************************
|
|
549
555
|
* ******************************************************************/
|
package/main.js
CHANGED
|
@@ -24,7 +24,7 @@ class Lorawan extends utils.Adapter {
|
|
|
24
24
|
this.on("ready", this.onReady.bind(this));
|
|
25
25
|
this.on("stateChange", this.onStateChange.bind(this));
|
|
26
26
|
// this.on("objectChange", this.onObjectChange.bind(this));
|
|
27
|
-
|
|
27
|
+
this.on("message", this.onMessage.bind(this));
|
|
28
28
|
this.on("unload", this.onUnload.bind(this));
|
|
29
29
|
|
|
30
30
|
this.origin = {
|
|
@@ -82,8 +82,8 @@ class Lorawan extends utils.Adapter {
|
|
|
82
82
|
//const message = {"end_device_ids":{"device_id":"eui-lobaro-modbus","application_ids":{"application_id":"hafi-ttn-lorawan"},"dev_eui":"70B3D5E050013950","join_eui":"D55B58C0DDC074DE","dev_addr":"260B5972"},"correlation_ids":["gs:uplink:01HMQZVSCX4D7JRDNFA7GJ9D4W"],"received_at":"2024-01-22T07:06:25.260676101Z","uplink_message":{"session_key_id":"AY0v/ZirzRkpNW0Cgjdhig==","f_port":20,"f_cnt":2,"frm_payload":"AA5BAf0AxwIAAQ==","decoded_payload":{"airhumidity":50.9,"airtemperature":19.9,"port":20,"relais1":0,"relais2":1,"relais3":null,"relais5":null,"volt":3.649,"zisternenpegel":2},"rx_metadata":[{"gateway_ids":{"gateway_id":"hafenmeister-port2ttn-ng","eui":"50313953530A4750"},"time":"2024-01-22T07:06:25.013878Z","timestamp":995696116,"rssi":-37,"channel_rssi":-37,"snr":8.5,"location":{"latitude":53.5548443059465,"longitude":9.92155426743724,"altitude":10,"source":"SOURCE_REGISTRY"},"uplink_token":"CiYKJAoYaGFmZW5tZWlzdGVyLXBvcnQydHRuLW5nEghQMTlTUwpHUBD0u+TaAxoLCPGnuK0GEM3uvhkgoIL0oP24Sg==","channel_index":5,"received_at":"2024-01-22T07:06:25.032492359Z"}],"settings":{"data_rate":{"lora":{"bandwidth":125000,"spreading_factor":9,"coding_rate":"4/5"}},"frequency":"867500000","timestamp":995696116,"time":"2024-01-22T07:06:25.013878Z"},"received_at":"2024-01-22T07:06:25.054442349Z","consumed_airtime":"0.205824s","network_ids":{"net_id":"000013","ns_id":"EC656E0000000181","tenant_id":"ttn","cluster_id":"eu1","cluster_address":"eu1.cloud.thethings.network"}}};
|
|
83
83
|
|
|
84
84
|
// ACK
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
const topic = "v3/hafi-ttn-lorawan@ttn/devices/eui-a84041162183f8fb/down/ack";
|
|
86
|
+
const message = {"end_device_ids":{"device_id":"eui-a84041162183f8fb","application_ids":{"application_id":"hafi-ttn-lorawan"},"dev_eui":"A84041162183F8FB","join_eui":"A840410000000101","dev_addr":"260B141A"},"correlation_ids":["as:downlink:01HP6D18MQXJN90J5B07DC11HY","gs:uplink:01HP6D1A9X4WAA3SFMXH4ESSMV"],"received_at":"2024-02-09T07:41:41.776887672Z","downlink_ack":{"session_key_id":"AY2MUrmnuovS8DCZAfYmsA==","f_port":1,"f_cnt":21,"frm_payload":"AQAAeA==","confirmed":true,"priority":"NORMAL","correlation_ids":["as:downlink:01HP6D18MQXJN90J5B07DC11HY"],"confirmed_retry":{"attempt":1}}};
|
|
87
87
|
|
|
88
88
|
// Chipstack
|
|
89
89
|
//const topic = "application/d63c10b6-9263-4ab3-9299-4308fa19a2ad/device/a84041f621857cd2/event/up";
|
|
@@ -110,8 +110,8 @@ class Lorawan extends utils.Adapter {
|
|
|
110
110
|
//const message = {"deduplicationId":"4a91b00d-b5e1-4955-b085-ba21b9318213","time":"2024-01-26T20:18:45.299871+00:00","deviceInfo":{"tenantId":"52f14cd4-c6f1-4fbd-8f87-4025e1d49242","tenantName":"ChirpStack","applicationId":"59bcc5a7-59e2-4481-9615-fc4e58791915","applicationName":"Mclimate_Vicki","deviceProfileId":"3a9bc28f-3664-4bdf-b3be-a20d1eb32dc8","deviceProfileName":"Mclimate_Vicki","deviceName":"MClimate_Vicki_Heizkoerperventil_001","devEui":"70b3d52dd300ed31","deviceClassEnabled":"CLASS_A","tags":{}},"margin":7,"externalPowerSource":false,"batteryLevelUnavailable":false,"batteryLevel":85.826775};
|
|
111
111
|
|
|
112
112
|
// UP
|
|
113
|
-
const topic = "application/59bcc5a7-59e2-4481-9615-fc4e58791915/device/70b3d52dd300ed31/event/up";
|
|
114
|
-
const message = {"deduplicationId":"c14f77c3-cfe5-42f3-9c43-651d3ca4cf45","time":"2024-01-27T12:00:05.267780+00:00","deviceInfo":{"tenantId":"52f14cd4-c6f1-4fbd-8f87-4025e1d49242","tenantName":"ChirpStack","applicationId":"59bcc5a7-59e2-4481-9615-fc4e58791915","applicationName":"Mclimate_Vicki","deviceProfileId":"3a9bc28f-3664-4bdf-b3be-a20d1eb32dc8","deviceProfileName":"Mclimate_Vicki","deviceName":"MClimate_Vicki_Heizkoerperventil_001","devEui":"70b3d52dd300ed31","deviceClassEnabled":"CLASS_A","tags":{}},"devAddr":"01343968","adr":true,"dr":5,"fCnt":11129,"fPort":2,"confirmed":false,"data":"gQyOiP39EdAw","object":{"sensorTemperature":20.06,"targetTemperature":12.0,"motorRange":509.0,"childLock":false,"batteryVoltage":3.3,"attachedBackplate":true,"lowMotorConsumption":false,"motorPosition":509.0,"reason":81.0,"highMotorConsumption":false,"calibrationFailed":false,"relativeHumidity":53.13,"perceiveAsOnline":true,"openWindow":false,"brokenSensor":false},"rxInfo":[{"gatewayId":"50313953530a4750","uplinkId":47105,"gwTime":"2024-01-27T12:00:05.267780+00:00","nsTime":"2024-01-27T12:00:05.314616473+00:00","rssi":-68,"snr":9.25,"channel":7,"location":{"latitude":53.55485739669679,"longitude":9.921609163284304},"context":"nnL1/A==","metadata":{"region_common_name":"EU868","region_config_id":"eu868"},"crcStatus":"CRC_OK"}],"txInfo":{"frequency":867900000,"modulation":{"lora":{"bandwidth":125000,"spreadingFactor":7,"codeRate":"CR_4_5"}}}};
|
|
113
|
+
//const topic = "application/59bcc5a7-59e2-4481-9615-fc4e58791915/device/70b3d52dd300ed31/event/up";
|
|
114
|
+
//const message = {"deduplicationId":"c14f77c3-cfe5-42f3-9c43-651d3ca4cf45","time":"2024-01-27T12:00:05.267780+00:00","deviceInfo":{"tenantId":"52f14cd4-c6f1-4fbd-8f87-4025e1d49242","tenantName":"ChirpStack","applicationId":"59bcc5a7-59e2-4481-9615-fc4e58791915","applicationName":"Mclimate_Vicki","deviceProfileId":"3a9bc28f-3664-4bdf-b3be-a20d1eb32dc8","deviceProfileName":"Mclimate_Vicki","deviceName":"MClimate_Vicki_Heizkoerperventil_001","devEui":"70b3d52dd300ed31","deviceClassEnabled":"CLASS_A","tags":{}},"devAddr":"01343968","adr":true,"dr":5,"fCnt":11129,"fPort":2,"confirmed":false,"data":"gQyOiP39EdAw","object":{"sensorTemperature":20.06,"targetTemperature":12.0,"motorRange":509.0,"childLock":false,"batteryVoltage":3.3,"attachedBackplate":true,"lowMotorConsumption":false,"motorPosition":509.0,"reason":81.0,"highMotorConsumption":false,"calibrationFailed":false,"relativeHumidity":53.13,"perceiveAsOnline":true,"openWindow":false,"brokenSensor":false},"rxInfo":[{"gatewayId":"50313953530a4750","uplinkId":47105,"gwTime":"2024-01-27T12:00:05.267780+00:00","nsTime":"2024-01-27T12:00:05.314616473+00:00","rssi":-68,"snr":9.25,"channel":7,"location":{"latitude":53.55485739669679,"longitude":9.921609163284304},"context":"nnL1/A==","metadata":{"region_common_name":"EU868","region_config_id":"eu868"},"crcStatus":"CRC_OK"}],"txInfo":{"frequency":867900000,"modulation":{"lora":{"bandwidth":125000,"spreadingFactor":7,"codeRate":"CR_4_5"}}}};
|
|
115
115
|
|
|
116
116
|
// LOG
|
|
117
117
|
//const topic = "application/59bcc5a7-59e2-4481-9615-fc4e58791915/device/70b3d52dd300ed31/event/up";
|
|
@@ -317,10 +317,9 @@ class Lorawan extends utils.Adapter {
|
|
|
317
317
|
const deviceInfo = {
|
|
318
318
|
id: id,
|
|
319
319
|
applicationId : idElements[0],
|
|
320
|
-
|
|
321
|
-
device_id : idElements[3],
|
|
320
|
+
deviceEUI : idElements[2],
|
|
322
321
|
changedState : idElements[idElements.length - 1],
|
|
323
|
-
objectStartDirectory : `${idElements[0]}
|
|
322
|
+
objectStartDirectory : `${idElements[0]}.${idElements[1]}.${idElements[2]}`,
|
|
324
323
|
allElements : idElements
|
|
325
324
|
};
|
|
326
325
|
return deviceInfo;
|
|
@@ -335,20 +334,28 @@ class Lorawan extends utils.Adapter {
|
|
|
335
334
|
try{
|
|
336
335
|
this.log.silly(`changeinfo of id ${id}, will be generated.`);
|
|
337
336
|
const changeInfo = this.getBaseDeviceInfo(id);
|
|
338
|
-
const myId = `${changeInfo?.applicationId}.devices.${changeInfo?.
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
337
|
+
const myId = `${changeInfo?.applicationId}.devices.${changeInfo?.deviceEUI}.${this.messagehandler?.directoryhandler.reachableSubfolders.configuration}.devicetype`;
|
|
338
|
+
// Check for changeInfo
|
|
339
|
+
if(changeInfo){
|
|
340
|
+
// Get Obect from startdirectory
|
|
341
|
+
const startDirectoryObject = await this.getObjectAsync(changeInfo.objectStartDirectory);
|
|
342
|
+
if(startDirectoryObject){
|
|
343
|
+
changeInfo.deviceId = startDirectoryObject.common.name;
|
|
344
|
+
}
|
|
345
|
+
// Get deviceType
|
|
346
|
+
const deviceTypeIdState = await this.getStateAsync(myId);
|
|
347
|
+
if(deviceTypeIdState){
|
|
348
|
+
changeInfo.deviceType = deviceTypeIdState.val;
|
|
349
|
+
if(options && options.withBestMatch){
|
|
350
|
+
// Get best match of expert downlink
|
|
351
|
+
const bestMatchForDeviceType = this.downlinkConfighandler?.getBestMatchForDeviceType(changeInfo);
|
|
352
|
+
if(bestMatchForDeviceType){
|
|
353
|
+
changeInfo.bestMatchForDeviceType = bestMatchForDeviceType;
|
|
354
|
+
this.log.debug(`best match for expertconfig of device: ${changeInfo.deviceType? changeInfo.deviceType: "empty devicetype"} is: ${bestMatchForDeviceType}`);
|
|
355
|
+
}
|
|
356
|
+
else{
|
|
357
|
+
this.log.debug(`no match for expert downlinkconfig found: ${changeInfo.deviceType? changeInfo.deviceType: "empty devicetype"}`);
|
|
358
|
+
}
|
|
352
359
|
}
|
|
353
360
|
}
|
|
354
361
|
}
|
|
@@ -375,18 +382,93 @@ class Lorawan extends utils.Adapter {
|
|
|
375
382
|
// * Using this method requires "common.messagebox" property to be set to true in io-package.json
|
|
376
383
|
// * @param {ioBroker.Message} obj
|
|
377
384
|
// */
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
385
|
+
async onMessage(obj){
|
|
386
|
+
const activeFunction = "onMessage";
|
|
387
|
+
try{
|
|
388
|
+
if (typeof obj === "object" && obj.message){
|
|
389
|
+
let result = {};
|
|
390
|
+
if(obj.command === "getDeviceInfo"){
|
|
391
|
+
if(obj.message.deviceEUI){
|
|
392
|
+
let changeInfo = undefined;
|
|
393
|
+
const adapterObjects = await this.getAdapterObjectsAsync();
|
|
394
|
+
for(const adapterObject of Object.values(adapterObjects)){
|
|
395
|
+
if(adapterObject.type === "device"){
|
|
396
|
+
if(adapterObject._id.indexOf(obj.message.deviceEUI) !== -1){
|
|
397
|
+
changeInfo = await this.getChangeInfo(`${adapterObject._id}.${this.messagehandler?.directoryhandler.reachableSubfolders.configuration}.devicetype`);
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if(changeInfo){
|
|
403
|
+
result = {applicationId: changeInfo.applicationId, deviceEUI: changeInfo.deviceEUI, deviceId: changeInfo.deviceId};
|
|
404
|
+
}
|
|
405
|
+
else{
|
|
406
|
+
result = {error:true, message:"No device found"};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
else{
|
|
410
|
+
result = {error:true, message:"No deviceEUI found"};
|
|
411
|
+
}
|
|
412
|
+
// Send response
|
|
413
|
+
if (obj.callback) this.sendTo(obj.from, obj.command, result, obj.callback);
|
|
414
|
+
}
|
|
415
|
+
else if (obj.command === "sendDownlink"){
|
|
416
|
+
if(obj.message.deviceEUI && obj.message.downlink && (obj.message.value || obj.message.value === false)){
|
|
417
|
+
let changeInfo = undefined;
|
|
418
|
+
const adapterObjects = await this.getAdapterObjectsAsync();
|
|
419
|
+
for(const adapterObject of Object.values(adapterObjects)){
|
|
420
|
+
if(adapterObject.type === "device"){
|
|
421
|
+
if(adapterObject._id.indexOf(obj.message.deviceEUI) !== -1){
|
|
422
|
+
changeInfo = await this.getChangeInfo(`${adapterObject._id}.${this.messagehandler?.directoryhandler.reachableSubfolders.downlinkControl}.${obj.message.downlink}`);
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if(changeInfo){
|
|
428
|
+
const downlinkId = `${changeInfo.id}`;
|
|
429
|
+
if(await this.objectExists(downlinkId)){
|
|
430
|
+
const downlinkParameter = this.downlinkConfighandler?.getDownlinkParameter(changeInfo);
|
|
431
|
+
// downlinkvalue is type number
|
|
432
|
+
if(downlinkParameter.type === "number"){
|
|
433
|
+
// Check limit
|
|
434
|
+
if((!downlinkParameter.limitMin || obj.message.value >= downlinkParameter.limitMinValue) && (!downlinkParameter.limitMax || obj.message.value <= downlinkParameter.limitMaxValue)){
|
|
435
|
+
await this.setStateAsync(downlinkId,obj.message.value);
|
|
436
|
+
result = {applicationId: changeInfo.applicationId, deviceEUI: changeInfo.deviceEUI, deviceId: changeInfo.deviceId, downlink: obj.message.downlink, value: obj.message.value};
|
|
437
|
+
}
|
|
438
|
+
else{
|
|
439
|
+
result = {error:true, message:"value is not in valid range"};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// downlinkvalue not a number
|
|
443
|
+
else{
|
|
444
|
+
await this.setStateAsync(downlinkId,obj.message.value);
|
|
445
|
+
result = {applicationId: changeInfo.applicationId, deviceEUI: changeInfo.deviceEUI, deviceId: changeInfo.deviceId, downlink: obj.message.downlink, value: obj.message.value};
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else{
|
|
449
|
+
result = {error:true, message:"No downlink matches"};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
else{
|
|
453
|
+
result = {error:true, message:"No device found"};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
else{
|
|
457
|
+
result = {error:true, message:"No deviceEUI, downlink & value found"};
|
|
458
|
+
}
|
|
459
|
+
// Send response
|
|
460
|
+
if (obj.callback) this.sendTo(obj.from, obj.command, result, obj.callback);
|
|
461
|
+
}
|
|
462
|
+
else{
|
|
463
|
+
const result = {error:true, message: "No message matched"};
|
|
464
|
+
if (obj.callback) this.sendTo(obj.from, obj.command, result, obj.callback);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
catch(error){
|
|
469
|
+
this.log.error(`error at ${activeFunction}: ` + error);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
390
472
|
}
|
|
391
473
|
|
|
392
474
|
if (require.main !== module) {
|