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 +6 -0
- package/admin/i18n/en/translations.json +9 -1
- package/admin/jsonConfig.json +243 -83
- package/io-package.json +29 -2
- package/lib/modules/directorieshandler.js +25 -79
- package/lib/modules/downlinkConfig.js +55 -0
- package/lib/modules/downlinks/downlinks.js +37 -0
- package/lib/modules/messagehandler.js +112 -24
- package/main.js +147 -91
- package/package.json +1 -1
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
|
}
|
package/admin/jsonConfig.json
CHANGED
|
@@ -1,89 +1,249 @@
|
|
|
1
1
|
{
|
|
2
2
|
"i18n": true,
|
|
3
|
-
"type": "
|
|
4
|
-
"items":
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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.
|
|
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]
|
|
158
|
-
internalObjectId = `${obj[elementName]
|
|
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]
|
|
145
|
+
type: obj[elementName].objectType? obj[elementName].objectType : "folder",
|
|
176
146
|
common: {
|
|
177
|
-
name: obj[elementName]
|
|
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
|
|
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]
|
|
197
|
-
internalObjectId = `${obj[elementName]
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
}
|
|
140
|
-
|
|
141
|
-
this.log.
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
155
|
-
|
|
196
|
+
catch(error){
|
|
197
|
+
this.log.error(`error at ${activeFunction}: ` + error);
|
|
156
198
|
}
|
|
157
199
|
}
|
|
158
200
|
|
|
159
|
-
getTtnChangeInfo(id){
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
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, ...
|