iobroker.lorawan 0.0.2 → 0.0.4

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
@@ -19,6 +19,12 @@ Adapter was created in collaboration with Joerg Froehner J-Paul0815@hafenmeister
19
19
  Placeholder for the next version (at the beginning of the line):
20
20
  ### **WORK IN PROGRESS**
21
21
  -->
22
+ ### 0.0.4 (2024-01-15)
23
+ * (BenAhrdt) implements buttons and standard downlink control ind json (push / replace)
24
+
25
+ ### 0.0.3 (2024-01-14)
26
+ * (BenAhrdt) first config for downlinks inputed
27
+
22
28
  ### 0.0.2 (2024-01-12)
23
29
  * (BenAhrdt) initial release
24
30
 
@@ -5,5 +5,13 @@
5
5
  "AuthenticationHeader": "Authenticationsettings",
6
6
  "AuthenticationInformationrmation": "Set the authentication for your server (if needed)",
7
7
  "OriginHeader": "Origensettings",
8
- "OriginInformation": "Set the origin of the expected data"
8
+ "OriginInformation": "Set the origin of the expected data",
9
+ "mainSettings": "Main Settings",
10
+ "ipUrl": "url or ip of the server you want connect to",
11
+ "port": "port",
12
+ "SSL": "SSL",
13
+ "AuthenticationInformation": "insert your logindata",
14
+ "username": "username",
15
+ "password": "password",
16
+ "downlinkConfig": "Donwlinkkonfiguration"
9
17
  }
@@ -1,89 +1,249 @@
1
1
  {
2
2
  "i18n": true,
3
- "type": "panel",
4
- "items": {
5
- "ServersettingsHeader": {
6
- "newLine": true,
7
- "type": "header",
8
- "text": "ServersettingsHeader",
9
- "size": 3
10
- },
11
- "Serverinformation":{
12
- "newLine":true,
13
- "type": "staticText",
14
- "label": "Serverinformation"
15
- },
16
- "ipUrl":{
17
- "newLine": true,
18
- "type": "text",
19
- "label": "ipUrl",
20
- "tooltip": "ipUrlTooltip",
21
- "default": "",
22
- "sm": 4
3
+ "type": "tabs",
4
+ "items":{
5
+ "mainTab":{
6
+ "type": "panel",
7
+ "label": "mainSettings",
8
+ "items": {
9
+ "ServersettingsHeader": {
10
+ "newLine": true,
11
+ "type": "header",
12
+ "text": "ServersettingsHeader",
13
+ "size": 3
14
+ },
15
+ "Serverinformation":{
16
+ "newLine":true,
17
+ "type": "staticText",
18
+ "label": "Serverinformation"
19
+ },
20
+ "ipUrl":{
21
+ "newLine": true,
22
+ "type": "text",
23
+ "label": "ipUrl",
24
+ "tooltip": "ipUrlTooltip",
25
+ "default": "",
26
+ "sm": 4
27
+ },
28
+ "port":{
29
+ "type": "number",
30
+ "label": "port",
31
+ "tooltip": "portTooltip",
32
+ "default": 8883,
33
+ "sm": 1
34
+ },
35
+ "ssl":{
36
+ "type": "checkbox",
37
+ "label": "SSL",
38
+ "tooltip": "sslTooltip",
39
+ "default": true
40
+ },
41
+ "AuthenticationHeader": {
42
+ "newLine": true,
43
+ "type": "header",
44
+ "text": "AuthenticationHeader",
45
+ "size": 3
46
+ },
47
+ "AuthenticationInformation":{
48
+ "newLine":true,
49
+ "type": "staticText",
50
+ "label": "AuthenticationInformation"
51
+ },
52
+ "username":{
53
+ "newLine":true,
54
+ "type": "text",
55
+ "label": "username",
56
+ "tooltip": "usernameTooltip",
57
+ "default": "",
58
+ "sm": 4
59
+ },
60
+ "password":{
61
+ "type": "password",
62
+ "label": "password",
63
+ "tooltip": "passwordTooltip",
64
+ "repeat": true,
65
+ "default": "",
66
+ "sm": 6
67
+ },
68
+ "OriginHeader": {
69
+ "newLine": true,
70
+ "type": "header",
71
+ "text": "OriginHeader",
72
+ "size": 3
73
+ },
74
+ "OriginInformation":{
75
+ "newLine":true,
76
+ "type": "staticText",
77
+ "label": "OriginInformation"
78
+ },
79
+ "ttn":{
80
+ "newLine":true,
81
+ "type": "checkbox",
82
+ "label": "Ttn",
83
+ "tooltip": "ttnTooltip",
84
+ "default": true
85
+ },
86
+ "chirpstack":{
87
+ "type": "checkbox",
88
+ "label": "Chirpstack",
89
+ "tooltip": "chirpstackTooltip",
90
+ "default": false
91
+ }
92
+ }
23
93
  },
24
- "port":{
25
- "type": "number",
26
- "label": "port",
27
- "tooltip": "portTooltip",
28
- "default": 8883,
29
- "sm": 1
30
- },
31
- "ssl":{
32
- "type": "checkbox",
33
- "label": "SSL",
34
- "tooltip": "sslTooltip",
35
- "default": true
36
- },
37
- "AuthenticationHeader": {
38
- "newLine": true,
39
- "type": "header",
40
- "text": "AuthenticationHeader",
41
- "size": 3
42
- },
43
- "AuthenticationInformation":{
44
- "newLine":true,
45
- "type": "staticText",
46
- "label": "AuthenticationInformation"
47
- },
48
- "username":{
49
- "newLine":true,
50
- "type": "text",
51
- "label": "username",
52
- "tooltip": "usernameTooltip",
53
- "default": "",
54
- "sm": 4
55
- },
56
- "password":{
57
- "type": "password",
58
- "label": "password",
59
- "tooltip": "passwordTooltip",
60
- "repeat": true,
61
- "default": "",
62
- "sm": 6
63
- },
64
- "OriginHeader": {
65
- "newLine": true,
66
- "type": "header",
67
- "text": "OriginHeader",
68
- "size": 3
69
- },
70
- "OriginInformation":{
71
- "newLine":true,
72
- "type": "staticText",
73
- "label": "OriginInformation"
74
- },
75
- "ttn":{
76
- "newLine":true,
77
- "type": "checkbox",
78
- "label": "Ttn",
79
- "tooltip": "ttnTooltip",
80
- "default": true
81
- },
82
- "chirpstack":{
83
- "type": "checkbox",
84
- "label": "Chirpstack",
85
- "tooltip": "chirpstackTooltip",
86
- "default": false
94
+ "downlinkConfigTab":{
95
+ "type": "panel",
96
+ "label": "downlinkConfig",
97
+ "items": {
98
+ "downlinkConfigAccordion":{
99
+ "type":"accordion",
100
+ "titleAttr": "name",
101
+ "clone": true,
102
+ "sm":12,
103
+ "items":[
104
+ {
105
+ "type": "text",
106
+ "attr": "name",
107
+ "label": "name",
108
+ "tooltip": "nameTooltip",
109
+ "default": "",
110
+ "sm":2
111
+ },
112
+ {
113
+ "type": "number",
114
+ "attr": "port",
115
+ "label": "port",
116
+ "tooltip": "portTooltip",
117
+ "default": 2,
118
+ "sm":2
119
+ },
120
+ {
121
+ "type": "text",
122
+ "attr": "priority",
123
+ "label": "priority",
124
+ "tooltip": "priorityTooltip",
125
+ "default": "NORMAL",
126
+ "sm":2
127
+ },
128
+ {
129
+ "type": "select",
130
+ "attr": "type",
131
+ "label": "type",
132
+ "tooltip": "typeTooltip",
133
+ "options": [
134
+ {"label":"button","value":"button"},
135
+ {"label":"boolean","value":"boolean"},
136
+ {"label":"number","value":"number"},
137
+ {"label":"ASCII","value":"ascii"},
138
+ {"label":"String","value":"string"}
139
+ ],
140
+ "default": "boolean",
141
+ "sm":2
142
+ },
143
+ {
144
+ "type": "checkbox",
145
+ "attr": "confirmed",
146
+ "label": "confirmed",
147
+ "tooltip": "confirmedTooltip",
148
+ "default": false,
149
+ "sm":2
150
+ },
151
+ {
152
+ "newLine": true,
153
+ "type": "text",
154
+ "attr": "front",
155
+ "label": "front",
156
+ "tooltip": "frontTooltip",
157
+ "default": "03",
158
+ "hidden": "data.type === 'boolean' || data.type === 'button'",
159
+ "sm":2
160
+ },
161
+ {
162
+ "type": "text",
163
+ "attr": "end",
164
+ "label": "end",
165
+ "tooltip": "endTooltip",
166
+ "default": "11",
167
+ "hidden": "data.type === 'boolean' || data.type === 'button'",
168
+ "sm":2
169
+ },
170
+ {
171
+ "type": "select",
172
+ "attr": "length",
173
+ "label": "length",
174
+ "tooltip": "lengthTooltip",
175
+ "options": [
176
+ {"label":"2","value":2},
177
+ {"label":"4","value":4},
178
+ {"label":"6","value":6},
179
+ {"label":"8","value":8}
180
+ ],
181
+ "default": 2,
182
+ "hidden": "data.type === 'boolean' || data.type === 'button'",
183
+ "sm":2
184
+ },
185
+ {
186
+ "newLine": true,
187
+ "type": "text",
188
+ "attr": "on",
189
+ "label": "on",
190
+ "tooltip": "onTooltip",
191
+ "default": "01",
192
+ "hidden": "data.type !== 'boolean'",
193
+ "sm":2
194
+ },
195
+ {
196
+ "type": "text",
197
+ "attr": "off",
198
+ "label": "off",
199
+ "tooltip": "offTooltip",
200
+ "default": "11",
201
+ "hidden": "data.type !== 'boolean'",
202
+ "sm":2
203
+ },
204
+ {
205
+ "newLine": true,
206
+ "type": "text",
207
+ "attr": "onClick",
208
+ "label": "onClick",
209
+ "tooltip": "onClickTooltip",
210
+ "default": "030111",
211
+ "hidden": "data.type !== 'button'",
212
+ "sm":2
213
+ },
214
+ {
215
+ "type": "number",
216
+ "attr": "multiplyfaktor",
217
+ "label": "multiplyfaktor",
218
+ "tooltip": "multiplyfaktorTooltip",
219
+ "default": "1",
220
+ "hidden": "data.type !== 'number'",
221
+ "sm":2
222
+ },
223
+ {
224
+ "type": "text",
225
+ "attr": "unit",
226
+ "label": "unit",
227
+ "tooltip": "unitTooltip",
228
+ "default": "",
229
+ "hidden": "data.type === 'boolean' || data.type === 'button' || data.type === 'string'",
230
+ "sm":2
231
+ },
232
+ {
233
+ "type": "autocomplete",
234
+ "attr": "deviceType",
235
+ "label": "deviceType",
236
+ "tooltip": "deviceTypeTooltip",
237
+ "options": [
238
+ {"label":"all","value":"all"}
239
+ ],
240
+ "default": "all",
241
+ "freeSolo": true,
242
+ "sm":2
243
+ }
244
+ ]
245
+ }
246
+ }
87
247
  }
88
248
  }
89
249
  }
package/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "lorawan",
4
- "version": "0.0.2",
4
+ "version": "0.0.4",
5
5
  "news": {
6
+ "0.0.4": {
7
+ "en": "implements buttons and standard downlink control ind json (push / replace)",
8
+ "de": "implementiert tasten und standard-downlink-steuerung ind json (push / ersetzen)",
9
+ "ru": "реализует кнопки и стандартный нисходящий контрольный джсон (push / заменить)",
10
+ "pt": "implementa botões e padrão downlink control ind json (push / substituir)",
11
+ "nl": "implementeert knoppen en standaard downlink control ind json (push / replace)",
12
+ "fr": "implémente boutons et standard de contrôle de liaison descendante json (pousser / remplacer)",
13
+ "it": "implementa pulsanti e controllo standard downlink ind json (push / sostituire)",
14
+ "es": "implementa botones y control de enlace estándar json (push / reemplazar)",
15
+ "pl": "implementuje przyciski i standardową kontrolę łącza w dół ind json (push / replacement)",
16
+ "uk": "реалізує кнопки та стандартний контроль за допомогою перемикача json (push / заміна)",
17
+ "zh-cn": "执行按钮和标准下行链路控制 ind json(推/替换)"
18
+ },
19
+ "0.0.3": {
20
+ "en": "first config for downlinks inputed",
21
+ "de": "erste config für downlinks eingegeben",
22
+ "ru": "первая настройка для входных ссылок",
23
+ "pt": "primeiro config para downlinks inseridos",
24
+ "nl": "eerste configuratie voor downlinks ingevoerd",
25
+ "fr": "première configuration pour les liaisons descendantes entrée",
26
+ "it": "prima configurazione per downlink in ingresso",
27
+ "es": "primer config para downlinks",
28
+ "pl": "pierwszy config dla downlinks nieznany",
29
+ "uk": "перший конфігурація для вхідних посилань",
30
+ "zh-cn": "输入下行链路的第一个配置"
31
+ },
6
32
  "0.0.2": {
7
33
  "en": "initial release",
8
34
  "de": "erstausstrahlung",
@@ -98,7 +124,8 @@
98
124
  "password": "",
99
125
  "ssl": true,
100
126
  "ttn": true,
101
- "chirpstack": false
127
+ "chirpstack": false,
128
+ "downlinkConfigAccordion": {}
102
129
  },
103
130
  "objects": [],
104
131
  "instanceObjects": [
@@ -3,12 +3,6 @@ class messagehandlerClass {
3
3
  this.adapter = adapter;
4
4
 
5
5
  // used dataentries in directory structurt
6
- this.elementDeclaration = {
7
- objectStateName: "objectStateName",
8
- objectCommonName: "objectCommonName",
9
- moreDirectories: "moreDirectories"
10
- };
11
-
12
6
  this.searchableAttributeNames = {
13
7
  apllicationId: "applicationId",
14
8
  deviceUid: "devEui",
@@ -16,6 +10,7 @@ class messagehandlerClass {
16
10
  };
17
11
 
18
12
  this.safeableDirectories = {
13
+ configuration: "configuration",
19
14
  uplinkDecoded: "uplinkDecoded",
20
15
  uplinkRaw: "uplinkRaw",
21
16
  uplinkRemaining: "uplinkRemaining",
@@ -37,7 +32,8 @@ class messagehandlerClass {
37
32
  "airhumidity": "%",
38
33
  "volt": "V",
39
34
  "temperatur" : "°C",
40
- "airtemperature" : "°C"
35
+ "airtemperature" : "°C",
36
+ "soiltemperature": "°C"
41
37
  };
42
38
 
43
39
  // declare the directory structre
@@ -59,6 +55,14 @@ class messagehandlerClass {
59
55
  },
60
56
  objectCommonName: "device ID",
61
57
  objectType:"channel",
58
+ configuration:{
59
+ safeDirectory: this.safeableDirectories.configuration,
60
+ devicetype:{
61
+ isState: true,
62
+ stateCommonType: "string",
63
+ stateCommonWrite: true,
64
+ }
65
+ },
62
66
  uplink:{
63
67
  raw:{
64
68
  safeDirectory: this.safeableDirectories.uplinkRaw
@@ -75,43 +79,10 @@ class messagehandlerClass {
75
79
  safeDirectory: this.safeableDirectories.downlinkRaw
76
80
  },
77
81
  control:{
78
- safeDirectory: this.safeableDirectories.downlinkControl,
79
- push: {
80
- isState: true,
81
- stateVal: {
82
- downlinks: [{
83
- f_port: 128,
84
- frm_payload: "Pw==",
85
- priority: "NORMAL"
86
- }]
87
- },
88
- stateCommonType: "json",
89
- subscribe: true,
90
- stateCommonWrite: true
91
- },
92
- replace: {
93
- isState: true,
94
- stateVal: {
95
- downlinks: [{
96
- f_port: 128,
97
- frm_payload: "Pw==",
98
- priority: "NORMAL"
99
- }]
100
- },
101
- stateCommonType: "json",
102
- subscribe: true,
103
- stateCommonWrite: true
104
- }
82
+ safeDirectory: this.safeableDirectories.downlinkControl
105
83
  },
106
84
  configuration:{
107
85
  safeDirectory: this.safeableDirectories.downlinkConfiguration,
108
- refreshRate:{
109
- isState: true,
110
- stateVal: 2,
111
- stateCommonType: "number",
112
- stateFactor: 60,
113
- stateCommonWrite: true,
114
- }
115
86
  },
116
87
  remaining:{
117
88
  safeDirectory: this.safeableDirectories.downlinkRemaining
@@ -127,7 +98,6 @@ class messagehandlerClass {
127
98
  objectCommonName: "objectCommonName",
128
99
  objectType: "objectType",
129
100
  stateVal: "stateVal",
130
- stateFactor: "stateFactor",
131
101
  stateCommonType: "stateCommonType",
132
102
  stateCommonWrite: "stateCommonWrite",
133
103
  stateCommonUnit: "stateCommonUnit",
@@ -154,8 +124,8 @@ class messagehandlerClass {
154
124
  // if there is an declared ObjectStateName (must be a function)=> take it
155
125
  let objectId = `${startDirectory}.${elementName}`;
156
126
  let internalObjectId = elementName;
157
- if(obj[elementName][this.elementDeclaration.objectStateName]){
158
- internalObjectId = `${obj[elementName][this.elementDeclaration.objectStateName](message)}`;
127
+ if(obj[elementName].objectStateName){
128
+ internalObjectId = `${obj[elementName].objectStateName(message)}`;
159
129
  objectId = `${startDirectory}.${internalObjectId}`;
160
130
  }
161
131
  if(objectId.indexOf(".") === 0){
@@ -172,9 +142,9 @@ class messagehandlerClass {
172
142
  }
173
143
  await this.adapter.setObjectNotExistsAsync(objectId,{
174
144
  // @ts-ignore
175
- type: obj[elementName][this.elementDeclaration.objectType]? obj[elementName][this.elementDeclaration.objectType] : "folder",
145
+ type: obj[elementName].objectType? obj[elementName].objectType : "folder",
176
146
  common: {
177
- name: obj[elementName][this.elementDeclaration.objectCommonName]? obj[elementName][this.elementDeclaration.objectCommonName] : ""
147
+ name: obj[elementName].objectCommonName? obj[elementName].objectCommonName : ""
178
148
  },
179
149
  native : {},
180
150
  });
@@ -186,15 +156,15 @@ class messagehandlerClass {
186
156
  let stateCommonWrite = false;
187
157
  let stateVal = obj[elementName];
188
158
  if(obj[elementName].isState){
189
- stateVal = obj[elementName].stateVal? obj[elementName].stateVal : undefined;
159
+ stateVal = obj[elementName].stateVal !== undefined? obj[elementName].stateVal: undefined;
190
160
  stateCommonType = obj[elementName].stateCommonType? obj[elementName].stateCommonType : typeof stateVal;
191
161
  stateCommonName = obj[elementName].stateCommonName ? obj[elementName].stateCommonName : stateCommonName;
192
162
  stateCommonWrite = obj[elementName].stateCommonWrite ? obj[elementName].stateCommonWrite : stateCommonWrite;
193
163
  }
194
164
  let objectId = `${startDirectory}.${elementName}`;
195
165
  let internalObjectId = elementName;
196
- if(obj[elementName][this.elementDeclaration.objectStateName]){
197
- internalObjectId = `${obj[elementName][this.elementDeclaration.objectStateName](message)}`;
166
+ if(obj[elementName].objectStateName){
167
+ internalObjectId = `${obj[elementName].objectStateName(message)}`;
198
168
  objectId = `${startDirectory}.${internalObjectId}`;
199
169
  }
200
170
  if(objectId.indexOf(".") === 0){
@@ -208,6 +178,7 @@ class messagehandlerClass {
208
178
  role: "value",
209
179
  read: true,
210
180
  unit: obj[elementName].CommonStateUnit? obj[elementName].CommonStateUnit : this.units[internalObjectId]? this.units[internalObjectId] : "",
181
+ def: stateCommonType === "boolean"? false : stateCommonType === "number"? 0: "",
211
182
  write: stateCommonWrite
212
183
  },
213
184
  native: {},
@@ -215,7 +186,9 @@ class messagehandlerClass {
215
186
  if(typeof stateVal === "object"){
216
187
  stateVal = JSON.stringify(stateVal);
217
188
  }
218
- await this.adapter.setStateAsync(`${objectId}`,stateVal,true);
189
+ if(stateVal !== undefined){
190
+ await this.adapter.setStateAsync(`${objectId}`,stateVal,true);
191
+ }
219
192
  if(obj[elementName].subscribe){
220
193
  this.adapter.subscribeStatesAsync(objectId);
221
194
  }
@@ -226,36 +199,9 @@ class messagehandlerClass {
226
199
  }
227
200
 
228
201
  /*********************************************************************
229
- * ************************ Objectstring *****************************
202
+ * ************************** Attribute ******************************
230
203
  * ******************************************************************/
231
204
 
232
- attributPresentInMessage(message,resolvetype){
233
- // Select search in case of origin
234
- if(this.adapter.config.ttn){
235
- return this.attributPresentInTtnMessage(message,resolvetype);
236
- }
237
- else if(this.adapter.config.chirpstack){
238
- // this.handleChirpstack(topic,message);
239
- }
240
- }
241
-
242
- attributPresentInTtnMessage(message,resolvetype){
243
- switch(resolvetype){
244
- case this.searchableAttributeNames.apllicationId:
245
- return message.end_device_ids.application_ids.application_id? true: false;
246
-
247
- case this.searchableAttributeNames.deviceUid:
248
- return message.end_device_ids.dev_eui? true: false;
249
-
250
- case this.searchableAttributeNames.deviceId:
251
- return message.end_device_ids.device_id? true: false;
252
-
253
- default:
254
- this.adapter.log.warn(`No attribute with the name ${resolvetype} found.`);
255
- return "";
256
- }
257
- }
258
-
259
205
  getAttributValue(message,resolvetype){
260
206
  // Select search in case of origin
261
207
  if(this.adapter.config.ttn){
@@ -297,7 +243,7 @@ class messagehandlerClass {
297
243
  if(typeof message !== "string"){
298
244
  switch(resolvetype){
299
245
  case this.searchableAttributeNames.deviceId:
300
- return `${message.end_device_ids.application_ids.application_id}.${message.end_device_ids.dev_eui}.${message.end_device_ids.device_id}`;
246
+ return `${message.end_device_ids.application_ids.application_id}.devices.${message.end_device_ids.dev_eui}.${message.end_device_ids.device_id}`;
301
247
 
302
248
  default:
303
249
  return message;
@@ -0,0 +1,55 @@
1
+ const downlinkClass = require("./downlinks/downlinks");
2
+
3
+ class downlinkConfigClass {
4
+ constructor(adapter) {
5
+ this.adapter = adapter;
6
+ //this.presentConfigs = {};
7
+
8
+ this.downlinks = new downlinkClass();
9
+ this.activeDownlinkConfigs = {};
10
+ }
11
+
12
+ addAndMergeDownlinks(){
13
+ // @ts-ignore
14
+ for(const downlinkConfig of Object.values(this.downlinks.internalDownlinks)){
15
+ this.addDownlinkConfigByType(downlinkConfig);
16
+ }
17
+ for(const downlinkConfig of Object.values(this.adapter.config.downlinkConfigAccordion)){
18
+ this.addDownlinkConfigByType(downlinkConfig);
19
+ }
20
+ }
21
+
22
+ addDownlinkConfigByType(downlinkConfig){
23
+ if(!this.activeDownlinkConfigs[downlinkConfig.deviceType]){
24
+ this.activeDownlinkConfigs[downlinkConfig.deviceType] = {};
25
+ }
26
+ if(!this.activeDownlinkConfigs[downlinkConfig.deviceType][downlinkConfig.name]){
27
+ this.activeDownlinkConfigs[downlinkConfig.deviceType][downlinkConfig.name] = downlinkConfig;
28
+ }
29
+ }
30
+
31
+ getDownlinkConfig(changeInfo){
32
+ const activeFunction = "getDownlinkConfig";
33
+ try{
34
+ if(this.activeDownlinkConfigs[changeInfo.deviceType] && this.activeDownlinkConfigs[changeInfo.deviceType][changeInfo.changedState])
35
+ {
36
+ return this.activeDownlinkConfigs[changeInfo.deviceType][changeInfo.changedState];
37
+ }
38
+ else{
39
+ changeInfo.deviceType = "all";
40
+ if(this.activeDownlinkConfigs[changeInfo.deviceType] && this.activeDownlinkConfigs[changeInfo.deviceType][changeInfo.changedState])
41
+ {
42
+ return this.activeDownlinkConfigs[changeInfo.deviceType][changeInfo.changedState];
43
+ }
44
+ else{
45
+ this.adapter.log.warn(`${activeFunction}: no downlinkConfig found: deviceType: ${changeInfo.deviceType} - changed state: ${changeInfo.changedState}`);
46
+ }
47
+ }
48
+ }
49
+ catch(error){
50
+ this.adapter.log.error(`error at ${activeFunction}: ` + error);
51
+ }
52
+ }
53
+ }
54
+
55
+ module.exports = downlinkConfigClass;
@@ -0,0 +1,37 @@
1
+
2
+ class downlinksClass {
3
+ constructor() {
4
+ this.internalDownlinks = [
5
+ {
6
+ name: "push",
7
+ type: "json",
8
+ deviceType: "all"
9
+ },
10
+ {
11
+ name: "replace",
12
+ type: "json",
13
+ deviceType: "all"
14
+ },
15
+ {
16
+ name: "Intervall",
17
+ port: 1,
18
+ priority: "NORMAL",
19
+ type: "number",
20
+ confirmed: false,
21
+ front: "01",
22
+ end: "",
23
+ length: 8,
24
+ on: "11",
25
+ off: "11",
26
+ multiplyfaktor: 60,
27
+ nit: "min",
28
+ deviceType: "Dragino",
29
+ isDownlinkConfiguration: true
30
+ }
31
+ ];
32
+ }
33
+
34
+
35
+ }
36
+
37
+ module.exports = downlinksClass;
@@ -1,4 +1,3 @@
1
-
2
1
  const directorieshandlerClass = require("./directorieshandler");
3
2
 
4
3
  class messagehandlerClass {
@@ -7,6 +6,61 @@ class messagehandlerClass {
7
6
  this.directoryhandler = new directorieshandlerClass(this.adapter);
8
7
  }
9
8
 
9
+ getDownlink(downlinkConfig,state){
10
+ // Select datahandling in case of origin
11
+ if(this.adapter.config.ttn){
12
+ return this.getTtnDownlink(downlinkConfig,state);
13
+ }
14
+ else if(this.adapter.config.chirpstack){
15
+ // this.handleChirpstack(topic,message);
16
+ }
17
+ }
18
+
19
+ getTtnDownlink(downlinkConfig,state){
20
+ // declare pyaload variable
21
+ let payloadInHex = "";
22
+ let multipliedVal = 0;
23
+ //Check type
24
+ if(downlinkConfig.type === "button"){
25
+ payloadInHex = downlinkConfig.onClick;
26
+ }
27
+ else if(downlinkConfig.type === "boolean"){
28
+ if(state.val){
29
+ payloadInHex = downlinkConfig.on;
30
+ }
31
+ else{
32
+ payloadInHex = downlinkConfig.off;
33
+ }
34
+ }
35
+ else{
36
+ switch(downlinkConfig.type){
37
+ case "number":
38
+ multipliedVal = state.val * downlinkConfig.multiplyfaktor;
39
+ payloadInHex = multipliedVal.toString(16).toUpperCase();
40
+ break;
41
+
42
+ case "ascii":
43
+ case "string":
44
+ payloadInHex = Buffer.from(state.val).toString("hex");
45
+ break;
46
+ }
47
+ const numberOfDiggits = downlinkConfig.length - downlinkConfig.front.length + downlinkConfig.end.length;
48
+ let zeroDiggits = "";
49
+
50
+ for(let index = 1; index <= numberOfDiggits; index++){
51
+ zeroDiggits += "0";
52
+ }
53
+ payloadInHex = (zeroDiggits + payloadInHex).slice(-numberOfDiggits);
54
+ payloadInHex = downlinkConfig.front + payloadInHex + downlinkConfig.end;
55
+ }
56
+
57
+ //convert hex in base64
58
+ const payloadInBase64 = Buffer.from(payloadInHex, "hex").toString("base64");
59
+
60
+ // retun the whole downlink
61
+ return {downlinks:[{f_port:downlinkConfig.port,frm_payload:payloadInBase64,priority:downlinkConfig.priority,confirmed:downlinkConfig.confirmed}]};
62
+ }
63
+
10
64
  async handleMessage(topic,message){
11
65
  // Select datahandling in case of origin
12
66
  if(this.adapter.config.ttn){
@@ -18,38 +72,17 @@ class messagehandlerClass {
18
72
  }
19
73
 
20
74
  async handleTtnMessage(topic,message){
21
-
22
75
  // generate startdorectory of device
23
76
  const deviceStartdirectory = this.directoryhandler.getTtnObjectDirectory(message,this.directoryhandler.searchableAttributeNames.deviceId);
24
77
  /*********************************************************************
25
78
  * ************************ Main directories *************************
26
79
  * ******************************************************************/
27
- /* if(message.end_device_ids.device_id !== "eui-lobaro-modbus"){
28
- return;
29
- }*/
30
80
  try{
31
81
  await this.directoryhandler.generateRekursivObjects(this.directoryhandler.directories,"",message);
32
82
  /*********************************************************************
33
83
  * ************************* Infodata ********************************
34
84
  * ******************************************************************/
35
- /*
36
- if(message[this.ttn.dataEntries.uplink_message]){
37
- if(message[this.ttn.dataEntries.uplink_message][this.ttn.dataEntries.f_port] === this.ttn.writeableData.firmware.port){
38
- const stringdata = Buffer.from(message[this.ttn.dataEntries.uplink_message][this.ttn.dataEntries.frm_payload], "base64").toString();
39
- await this.adapter.setStateAsync(`${stateId}.${this.ttn.writeableData.firmware.name}`,stringdata,true);
40
- }
41
- }
42
- else if(message[this.ttn.dataEntries.downlink_queued]){
43
- if(message[this.ttn.dataEntries.downlink_queued][this.ttn.dataEntries.f_port] === this.ttn.writeableData.firmware.port){
44
- await this.adapter.setStateAsync(`${stateId}.${this.ttn.writeableData.firmware.name}`,this.ttn.writeableData.firmware.downlink_queued,true);
45
- }
46
- }
47
- else if(message[this.ttn.dataEntries.downlink_sent]){
48
- if(message[this.ttn.dataEntries.downlink_sent][this.ttn.dataEntries.f_port] === this.ttn.writeableData.firmware.port){
49
- await this.adapter.setStateAsync(`${stateId}.${this.ttn.writeableData.firmware.name}`,this.ttn.writeableData.firmware.downlink_sent,true);
50
- }
51
- }
52
- */
85
+
53
86
  /*********************************************************************
54
87
  * ********************** Uplink data ********************************
55
88
  * ******************************************************************/
@@ -244,6 +277,12 @@ class messagehandlerClass {
244
277
  await this.directoryhandler.generateRekursivObjects(message[downlinkType],startDirectory,message,{ignoredElementNames:{frm_payload:{}}});
245
278
  }
246
279
  }
280
+
281
+ /*********************************************************************
282
+ * ************************* downlinks *******************************
283
+ * ******************************************************************/
284
+
285
+ this.fillWithDownloadconfig(deviceStartdirectory);
247
286
  }
248
287
  catch(error){
249
288
  this.adapter.log.warn("check: " + error);
@@ -251,6 +290,55 @@ class messagehandlerClass {
251
290
  }
252
291
  }
253
292
 
293
+ // Startup
294
+ async generateDownlinkstatesAtStatup(){
295
+ const adapterObjectsAtStart = await this.adapter.getAdapterObjectsAsync();
296
+ for(const adapterObject of Object.values(adapterObjectsAtStart)){
297
+ if(adapterObject.type === "channel" && adapterObject.common.name === "device ID"){
298
+ adapterObject._id = this.adapter.removeNamespace(adapterObject._id);
299
+ await this.fillWithDownloadconfig(adapterObject._id);
300
+ }
301
+ }
302
+ }
303
+
304
+ async fillWithDownloadconfig(deviceStartdirectory){
305
+ for(const downlinkDevices of Object.values(this.adapter.downlinkConfig.activeDownlinkConfigs)){
306
+ for(const downlinkConfig of Object.values(downlinkDevices)){
307
+ let startDirectory = "";
308
+ if(!downlinkConfig.isDownlinkConfiguration){
309
+ startDirectory = `${deviceStartdirectory}.downlink.control`;
310
+ }
311
+ else{
312
+ startDirectory = `${deviceStartdirectory}.downlink.configuration`;
313
+ }
314
+ const changeInfo = await this.adapter.getChangeInfo(`${startDirectory}.${downlinkConfig.name}`);
315
+ if(downlinkConfig.deviceType === "all" || downlinkConfig.deviceType === changeInfo.deviceType || changeInfo.deviceType.indexOf(downlinkConfig.deviceType) === 0){
316
+ let commonStateRole = "value";
317
+ let commonStateType = downlinkConfig.type;
318
+ if(commonStateType === "button"){
319
+ commonStateType = "boolean";
320
+ commonStateRole = "button";
321
+ }
322
+ else if(commonStateType === "ascii"){
323
+ commonStateType = "string";
324
+ }
325
+ await this.adapter.setObjectNotExistsAsync(`${startDirectory}.${downlinkConfig.name}`,{
326
+ type: "state",
327
+ common: {
328
+ name: "",
329
+ type: commonStateType,
330
+ role: commonStateRole,
331
+ read: true,
332
+ write: true,
333
+ unit: downlinkConfig.unit? downlinkConfig.unit:"",
334
+ def: commonStateType === "boolean"? false : commonStateType === "number"? 0: "",
335
+ },
336
+ native: {},
337
+ });
338
+ }
339
+ }
340
+ }
341
+ }
254
342
 
255
343
  /*********************************************************************
256
344
  * *********************** Downlinktopic *****************************
@@ -272,7 +360,7 @@ class messagehandlerClass {
272
360
  applicationId : `/${changeInfo.applicationId}`,
273
361
  applicationFrom : "@ttn",
274
362
  devices : `/devices`,
275
- dev_uid : `/${changeInfo.dev_uid}`,
363
+ device_id : `/${changeInfo.device_id}`,
276
364
  suffix : suffix
277
365
  };
278
366
  let downlink = "";
package/main.js CHANGED
@@ -1,6 +1,4 @@
1
1
  "use strict";
2
-
3
-
4
2
  /*
5
3
  * Created with @iobroker/create-adapter v2.6.0
6
4
  */
@@ -10,9 +8,7 @@
10
8
  const utils = require("@iobroker/adapter-core");
11
9
  const mqttClientClass = require("./lib/modules/mqttclient");
12
10
  const messagehandlerClass = require("./lib/modules/messagehandler");
13
-
14
- // Load your modules here, e.g.:
15
- // const fs = require("fs");
11
+ const downlinkConfigClass = require("./lib/modules/downlinkConfig");
16
12
 
17
13
  class Lorawan extends utils.Adapter {
18
14
 
@@ -29,46 +25,67 @@ class Lorawan extends utils.Adapter {
29
25
  // this.on("objectChange", this.onObjectChange.bind(this));
30
26
  // this.on("message", this.onMessage.bind(this));
31
27
  this.on("unload", this.onUnload.bind(this));
32
- this.mqttClient = {};
33
- this.changeInfo = {};
34
28
  }
35
29
 
36
30
  /**
37
31
  * Is called when databases are connected and adapter received configuration.
38
32
  */
39
33
  async onReady() {
40
- /*
41
- let a = {b:"",c:"2"};
42
- a.val = JSON.parse(JSON.stringify(a));
43
- this.log.debug(JSON.stringify(a));
44
- delete a.val;
45
- this.log.debug(JSON.stringify(a));
46
- return;*/
47
- // create new messagehandler
48
- this.messagehandler = new messagehandlerClass(this);
49
-
50
- // Set all mqtt clients
51
- this.mqttClient = new mqttClientClass(this,this.config);
34
+ const activeFunction = "onReady";
35
+ try{
52
36
  /*
53
- // Subscribe all States (given from messagehandler)
54
- this.subscibeableStates = this.messagehandler.getSubscribeableStates(undefined);
55
- if(this.subscibeableStates){
56
- for(const subscibeableState of Object.values(this.subscibeableStates)){
57
- this.subscribeStatesAsync(`*.${subscibeableState}`);
58
- }
37
+ Definitionen der Umrechnungen:
38
+ dec to hex:
39
+ const decdata = 33;
40
+ const decdatastring = decdata.toString(16);
41
+
42
+ base64 to hex:
43
+ return(Buffer.from(base_64, 'base64').toString("hex"));
44
+
45
+ ascii to hex:
46
+ return (Buffer.from(ascii).toString('hex'));
47
+
48
+ ascii to base64:
49
+ return (Buffer.from(ascii).toString('base64'));
50
+
51
+ base64 to string:
52
+ return(Buffer.from(base_64, 'base64').toString());
53
+
54
+ hex 2 base64:
55
+ return Buffer.from(hex, 'hex').toString('base64')
56
+
57
+ hex 2 number:
58
+ parseInt(hexdata,16);
59
+
60
+ return Math.abs(dec).toString(16);
61
+ // force 4 Digits
62
+ //return ('0000' + dec.toString(16).toUpperCase()).slice(-4);
63
+ // force 2 Digits
64
+ // return ('00' + dec.toString(16).toUpperCase()).slice(-2);
65
+
66
+ */
67
+ // create downlinkConfigs
68
+ this.downlinkConfig = new downlinkConfigClass(this);
69
+
70
+ // create new messagehandler
71
+ this.messagehandler = new messagehandlerClass(this);
72
+
73
+ // Set all mqtt clients
74
+ this.mqttClient = new mqttClientClass(this,this.config);
75
+
76
+ // Merge the configed and standard profile of downlinks
77
+ this.downlinkConfig.addAndMergeDownlinks();
78
+
79
+ // generate new configed downlinkstates on allready existing devices at adapter startup
80
+ await this.messagehandler.generateDownlinkstatesAtStatup();
81
+
82
+ //Subscribe all configuration and control states
83
+ this.subscribeStatesAsync("*.configuration.*");
84
+ this.subscribeStatesAsync("*downlink.control.*");
85
+ }
86
+ catch(error){
87
+ this.log.error(`error at ${activeFunction}: ` + error.stack);
59
88
  }
60
- */
61
- this.subscribeStatesAsync(`*.push`);
62
- this.subscribeStatesAsync(`*.replace`);
63
- /*
64
- setTimeout(() => {
65
- this.mqttClient[1]?.publish("R/c0619ab24727/keepalive",null);
66
- }, 1000);*/
67
- // Reset the connection indicator during startup
68
- /*setTimeout(() => {
69
- this.mqttClient?.publish("v3/hafi-ttn-lorawan@ttn/devices/eui-lobaro-modbus/down/push",JSON.stringify({"downlinks":[{"f_port": 128,"frm_payload":"Pw==","priority": "NORMAL"}]}));
70
- }, 5000);
71
- this.setState("info.connection", false, true);*/
72
89
  }
73
90
 
74
91
  /**
@@ -77,12 +94,7 @@ class Lorawan extends utils.Adapter {
77
94
  */
78
95
  onUnload(callback) {
79
96
  try {
80
- // Here you must clear all timeouts or intervals that may still be active
81
- // clearTimeout(timeout1);
82
- // clearTimeout(timeout2);
83
- // ...
84
- // clearInterval(interval1);
85
-
97
+ this.mqttClient?.destroy();
86
98
  callback();
87
99
  } catch (e) {
88
100
  callback();
@@ -112,33 +124,57 @@ class Lorawan extends utils.Adapter {
112
124
  * @param {ioBroker.State | null | undefined} state
113
125
  */
114
126
  async onStateChange(id, state) {
115
- if (state) {
116
- // this.log.debug(`state ${id} changed: val: ${state.val} - ack: ${state.ack}`);
117
- // The state was changed => only states with ack = false will be processed, others will be ignored
118
- if(!state.ack){
119
- // get information of the changing state
120
- // @ts-ignore
121
- this.changeInfo = this.getChangeInfo(id);
122
- let appending = "";
123
- if(this.changeInfo.changedState === "push"){
124
- // @ts-ignore
125
- appending = "push";
126
- const downlinkTopic = this.messagehandler?.getDownlinkTopic(this.changeInfo,`/down/${appending}`);
127
- //this.sendDownlink(downlinkTopic,JSON.stringify(state.val));
128
- this.sendDownlink(downlinkTopic,state.val);
129
- this.setStateAsync(id,state.val,true);
130
- }
131
- else if(this.changeInfo.changedState === "replace"){
132
- // @ts-ignore
133
- appending = "replace";
134
- const downlinkTopic = this.messagehandler?.getDownlinkTopic(this.changeInfo,`/down/${appending}`);
135
- this.sendDownlink(downlinkTopic,state.val);
136
- this.setStateAsync(id,state.val,true);
127
+ const activeFunction = "onStateChange";
128
+ try{
129
+ if (state) {
130
+ //this.log.debug(`state ${id} changed: val: ${state.val} - ack: ${state.ack}`);
131
+ // The state was changed => only states with ack = false will be processed, others will be ignored
132
+ if(!state.ack){
133
+ // Check for downlink in id
134
+ if(id.indexOf("downlink") !== -1){
135
+ // get information of the changing state
136
+ // @ts-ignore
137
+ const changeInfo = await this.getChangeInfo(id);
138
+ let appending = "push";
139
+ // @ts-ignore
140
+ if(changeInfo.changedState === "push"){
141
+ const downlinkTopic = this.messagehandler?.getDownlinkTopic(changeInfo,`/down/${appending}`);
142
+ //this.sendDownlink(downlinkTopic,JSON.stringify(state.val));
143
+ this.sendDownlink(downlinkTopic,state.val);
144
+ this.setStateAsync(id,state.val,true);
145
+ }
146
+ // @ts-ignore
147
+ else if(changeInfo.changedState === "replace"){
148
+ appending = "replace";
149
+ const downlinkTopic = this.messagehandler?.getDownlinkTopic(changeInfo,`/down/${appending}`);
150
+ this.sendDownlink(downlinkTopic,state.val);
151
+ this.setStateAsync(id,state.val,true);
152
+ }
153
+ else{
154
+ const downlinkTopic = this.messagehandler?.getDownlinkTopic(changeInfo,`/down/${appending}`);
155
+ // @ts-ignore
156
+ const downlinkConfig = this.downlinkConfig?.getDownlinkConfig(changeInfo);
157
+ if(downlinkConfig !== undefined){
158
+ const downlink = this.messagehandler?.getDownlink(downlinkConfig,state);
159
+ if(downlink !== undefined){
160
+ this.sendDownlink(downlinkTopic,JSON.stringify(downlink));
161
+ }
162
+ this.setStateAsync(id,state.val,true);
163
+ }
164
+ }
165
+ }
166
+ // State is from configuration path
167
+ else{
168
+ this.setStateAsync(id,state.val,true);
169
+ }
137
170
  }
171
+ } else {
172
+ // The state was deleted
173
+ this.log.info(`state ${id} deleted`);
138
174
  }
139
- } else {
140
- // The state was deleted
141
- this.log.info(`state ${id} deleted`);
175
+ }
176
+ catch(error){
177
+ this.log.error(`error at ${activeFunction}: ` + error);
142
178
  }
143
179
  }
144
180
 
@@ -146,36 +182,56 @@ class Lorawan extends utils.Adapter {
146
182
  this.mqttClient?.publish(topic,message);
147
183
  }
148
184
 
149
- getChangeInfo(id){
150
- // Select datahandling in case of origin
151
- if(this.config.ttn){
152
- return this.getTtnChangeInfo(id);
185
+ async getChangeInfo(id){
186
+ const activeFunction = "getChangeInfo";
187
+ try{
188
+ // Select datahandling in case of origin
189
+ if(this.config.ttn){
190
+ return await this.getTtnChangeInfo(id);
191
+ }
192
+ else if(this.config.chirpstack){
193
+ // this.handleChirpstack(topic,message);
194
+ }
153
195
  }
154
- else if(this.config.chirpstack){
155
- // this.handleChirpstack(topic,message);
196
+ catch(error){
197
+ this.log.error(`error at ${activeFunction}: ` + error);
156
198
  }
157
199
  }
158
200
 
159
- getTtnChangeInfo(id){
160
- id = id.substring(this.namespace.length + 1,id.length);
161
- const idElements = id.split(".");
162
- const changeInfo = {
163
- applicationId : idElements[0],
164
- dev_uid : idElements[3],
165
- device_id : idElements[2],
166
- changedState : idElements[idElements.length - 1],
167
- allElements : idElements
168
- };
169
- return changeInfo;
201
+ async getTtnChangeInfo(id){
202
+ const activeFunction = "getTtnChangeInfo";
203
+ try{
204
+ id = this.removeNamespace(id);
205
+ const idElements = id.split(".");
206
+ const changeInfo = {
207
+ applicationId : idElements[0],
208
+ dev_uid : idElements[2],
209
+ device_id : idElements[3],
210
+ changedState : idElements[idElements.length - 1],
211
+ deviceType : "",
212
+ allElements : idElements
213
+ };
214
+ const myId = `${changeInfo.applicationId}.devices.${changeInfo.dev_uid}.${changeInfo.device_id}.configuration.devicetype`;
215
+ const deviceTypeIdState = await this.getStateAsync(myId);
216
+ if(deviceTypeIdState){
217
+ // @ts-ignore
218
+ changeInfo.deviceType = deviceTypeIdState.val;
219
+ }
220
+ return changeInfo;
221
+ }
222
+ catch(error){
223
+ this.log.error(`error at ${activeFunction}: ` + error);
224
+ }
170
225
  }
171
226
 
172
- /* getStringprefix(source,searchstring,times){
173
- let position = 0;
174
- for(let index = 0; index < times ; index++){
175
- position = source.indexOf(searchstring,position) + 1;
227
+ removeNamespace(id){
228
+ if(id.indexOf(this.namespace) !== -1){
229
+ id = id.substring(this.namespace.length + 1,id.length);
176
230
  }
177
- return source.substring(0,position-1);
178
- }*/
231
+ return id;
232
+ }
233
+
234
+
179
235
  // If you need to accept messages in your adapter, uncomment the following block and the corresponding line in the constructor.
180
236
  // /**
181
237
  // * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ...
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.lorawan",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "converts the desired lora gateway data to a ioBroker structure",
5
5
  "author": {
6
6
  "name": "BenAhrdt",