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 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.12",
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
- deviceUid: "devEui",
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
- objectStateName : async (topic,message) =>{
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
- deviceUid:{
41
- objectStateName : async (topic,message) =>{
42
- return await this.getAttributValue(topic,message,this.searchableAttributeNames.deviceUid);
40
+ deviceEUI:{
41
+ objectStateId : async (topic,message) =>{
42
+ return await this.getAttributValue(topic,message,this.searchableAttributeNames.deviceEUI);
43
43
  },
44
- objectCommonName: "device UID",
45
- deviceId:{
46
- objectStateName : async (topic,message) =>{
47
- return await this.getAttributValue(topic,message,this.searchableAttributeNames.deviceId);
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
- objectCommonName: "device ID",
50
- objectType:"channel",
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
- downlink:{
68
- raw:{
69
- },
70
- control:{
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
- objectStateName: "objectStateName",
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].objectStateName){
140
- internalObjectId = `${await obj[elementName].objectStateName(topic,message)}`;
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
- await this.adapter.setObjectNotExistsAsync(objectId,{
147
- type: obj[elementName].objectType? obj[elementName].objectType : "folder",
148
- common: {
149
- name: obj[elementName].objectCommonName? obj[elementName].objectCommonName : ""
150
- },
151
- native : {},
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].objectStateName){
172
- internalObjectId = `${await obj[elementName].objectStateName(topic,message)}`;
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.deviceUid:
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.deviceId:
316
- return `${topicResolved?.applicationId}.devices.${message.end_device_ids.dev_eui}.${topicResolved?.deviceId}`;
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.deviceUid:
378
- return topicResolved?.deviceUid;
389
+ case this.searchableAttributeNames.deviceEUI:
390
+ return topicResolved?.deviceEUI;
379
391
 
380
392
  case this.searchableAttributeNames.deviceId:
381
- if(topicResolved?.messageType === "up"){
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 === "up"){
415
+ if(topicResolved?.messageType !== "down"){
421
416
  devUid = message.deviceInfo.devEui;
422
417
  }
423
- else if(topicResolved?.messageType === "down")
424
- {
418
+ else{
425
419
  devUid = message.devEui;
426
420
  }
427
- if(devUid !== undefined){
428
- if(typeof message !== "string"){
429
- switch(resolvetype){
430
- case this.searchableAttributeNames.deviceId:
431
- return `${topicResolved?.applicationId}.devices.${devUid}.${await this.getChirpstackAttributValue(topic,message,this.searchableAttributeNames.deviceId)}`; // Hier muss die device id rein. (mussnoch mit async geholt werden)
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
- deviceUid: topicElements[3],
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.getTtnDownlinkTopicFromDirektory(changeInfo,suffix);
173
+ return this.getTtnDownlinkTopicFromDirectory(changeInfo,suffix);
174
174
  case this.adapter.origin.chirpstack:
175
- return this.getChirpstackDownlinkTopicFromDirektory(changeInfo,suffix);
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
- getTtnDownlinkTopicFromDirektory(changeInfo,suffix){
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
- device_id : `/${changeInfo.device_id}`,
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
- getChirpstackDownlinkTopicFromDirektory(changeInfo,suffix){
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
- device_uid : `/${changeInfo.dev_uid}`,
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.dev_uid,confirmed:downlinkConfig.confirmed,fPort:downlinkConfig.port,data:payloadInBase64};
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 === "channel" && adapterObject.common.name !== "Information"){
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.deviceUid.deviceId.downlink.nextSend,`${startDirectory}.${this.directoryhandler.reachableSubfolders.downlinkNextSend}`,"","");
63
- await this.directoryhandler.generateRekursivObjects(this.directoryhandler.directories.application.devices.deviceUid.deviceId.downlink.lastSend,`${startDirectory}.${this.directoryhandler.reachableSubfolders.downlinkLastSend}`,"","");
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.deviceId);
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.deviceId);
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
- // this.on("message", this.onMessage.bind(this));
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
- //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}}};
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
- dev_uid : idElements[2],
321
- device_id : idElements[3],
320
+ deviceEUI : idElements[2],
322
321
  changedState : idElements[idElements.length - 1],
323
- objectStartDirectory : `${idElements[0]}.devices.${idElements[2]}.${idElements[3]}`,
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?.dev_uid}.${changeInfo?.device_id}.configuration.devicetype`;
339
- // Get deviceType
340
- const deviceTypeIdState = await this.getStateAsync(myId);
341
- if(changeInfo && deviceTypeIdState){
342
- changeInfo.deviceType = deviceTypeIdState.val;
343
- if(options && options.withBestMatch){
344
- // Get best match of expert downlink
345
- const bestMatchForDeviceType = this.downlinkConfighandler?.getBestMatchForDeviceType(changeInfo);
346
- if(bestMatchForDeviceType){
347
- changeInfo.bestMatchForDeviceType = bestMatchForDeviceType;
348
- this.log.debug(`best match for expertconfig of device: ${changeInfo.deviceType? changeInfo.deviceType: "empty devicetype"} is: ${bestMatchForDeviceType}`);
349
- }
350
- else{
351
- this.log.debug(`no match for expert downlinkconfig found: ${changeInfo.deviceType? changeInfo.deviceType: "empty devicetype"}`);
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
- // onMessage(obj) {
379
- // if (typeof obj === "object" && obj.message) {
380
- // if (obj.command === "send") {
381
- // // e.g. send email or pushover or whatever
382
- // this.log.info("send command");
383
-
384
- // // Send response in callback if required
385
- // if (obj.callback) this.sendTo(obj.from, obj.command, "Message received", obj.callback);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.lorawan",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "converts the desired lora gateway data to a ioBroker structure",
5
5
  "author": {
6
6
  "name": "BenAhrdt",