homebridge-smartsystem 7.0.1 → 7.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 +55 -1
- package/config.json +44 -504
- package/duotecno/protocol.ts +7 -7
- package/duotecno/types.js +1 -0
- package/duotecno/types.ts +5 -0
- package/package.json +1 -1
- package/server/mDNS.js +44 -15
- package/server/mDNS.ts +41 -16
- package/server/powerbase.js +11 -8
- package/server/powerbase.ts +9 -7
- package/server/proxy.js +23 -7
- package/server/proxy.ts +24 -8
- package/server/shelly.js +1 -1
- package/server/shelly.ts +1 -1
- package/server/smartapp.js +15 -0
- package/server/smartapp.ts +19 -1
- package/server/webapp.js +9 -1
- package/server/webapp.ts +11 -1
package/README.md
CHANGED
|
@@ -319,6 +319,7 @@ Homebridge UI version
|
|
|
319
319
|
|
|
320
320
|
### v7.0.0 - 12/11/2025
|
|
321
321
|
- 0: added mDNS/Bonjour + always port 80 + don't preload EJS files if system.debug == true
|
|
322
|
+
- 4: fixed a bug when shelly has empty list of ip addresses
|
|
322
323
|
|
|
323
324
|
## Hardware
|
|
324
325
|
A Raspberry Pi that connects to a Duotecno IP Node
|
|
@@ -630,4 +631,57 @@ Accessory.Categories = {
|
|
|
630
631
|
SHOWER_HEAD: 30,
|
|
631
632
|
TELEVISION: 31,
|
|
632
633
|
TARGET_CONTROLLER: 32
|
|
633
|
-
};
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
Small example of config.
|
|
638
|
+
|
|
639
|
+
{
|
|
640
|
+
"manufacturer": "Duotecno-Coppieters",
|
|
641
|
+
"platform": "DuotecnoPlatform",
|
|
642
|
+
"debug": false,
|
|
643
|
+
"smappee": {
|
|
644
|
+
"rules": [],
|
|
645
|
+
"address": "",
|
|
646
|
+
"uid": "57e3e0d8-bb05-4b04-8662-1a9871998f3f",
|
|
647
|
+
"debug": false,
|
|
648
|
+
"bindings": []
|
|
649
|
+
},
|
|
650
|
+
"smartapp": {
|
|
651
|
+
"port": 5002,
|
|
652
|
+
"switches": [],
|
|
653
|
+
"debug": true,
|
|
654
|
+
"links": [],
|
|
655
|
+
"apiHost": "localhost",
|
|
656
|
+
"apiPort": 8581,
|
|
657
|
+
"username": "duotecno",
|
|
658
|
+
"password": "#Duotecno@1",
|
|
659
|
+
"devices": []
|
|
660
|
+
},
|
|
661
|
+
"system": {
|
|
662
|
+
"language": "EN",
|
|
663
|
+
"stores": false,
|
|
664
|
+
"multiple": false,
|
|
665
|
+
"socketserver": "gateway.duotecno.com",
|
|
666
|
+
"socketport": 9999,
|
|
667
|
+
"mood": "sfeer",
|
|
668
|
+
"cmasters": [],
|
|
669
|
+
"cunits": [],
|
|
670
|
+
"debug": false
|
|
671
|
+
},
|
|
672
|
+
"socapp": false,
|
|
673
|
+
"p1": {
|
|
674
|
+
"debug": false,
|
|
675
|
+
"rules": [],
|
|
676
|
+
"bindings": [],
|
|
677
|
+
"address": ""
|
|
678
|
+
},
|
|
679
|
+
"shelly": {
|
|
680
|
+
"debug": false,
|
|
681
|
+
"address": "",
|
|
682
|
+
"addresses": "",
|
|
683
|
+
"rules": [],
|
|
684
|
+
"bindings": []
|
|
685
|
+
},
|
|
686
|
+
"proxy": false
|
|
687
|
+
}
|
package/config.json
CHANGED
|
@@ -1,509 +1,49 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
"smappee": {
|
|
2
|
+
"manufacturer": "Duotecno-Coppieters",
|
|
3
|
+
"platform": "DuotecnoPlatform",
|
|
5
4
|
"debug": false,
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"masterPort": 5001,
|
|
34
|
-
"logicalAddress": 34,
|
|
35
|
-
"logicalNodeAddress": 3
|
|
36
|
-
}
|
|
37
|
-
],
|
|
38
|
-
"power": -758,
|
|
39
|
-
"current": 0
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"type": "power",
|
|
43
|
-
"channel": "7+8+9",
|
|
44
|
-
"low": 500,
|
|
45
|
-
"high": 10000,
|
|
46
|
-
"actions": [
|
|
47
|
-
{
|
|
48
|
-
"name": "Blue LED",
|
|
49
|
-
"value": false,
|
|
50
|
-
"masterAddress": "192.168.0.97",
|
|
51
|
-
"masterPort": 5001,
|
|
52
|
-
"logicalAddress": 30,
|
|
53
|
-
"logicalNodeAddress": 3
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"name": "Blue LED",
|
|
57
|
-
"value": true,
|
|
58
|
-
"masterAddress": "192.168.0.97",
|
|
59
|
-
"masterPort": 5001,
|
|
60
|
-
"logicalAddress": 30,
|
|
61
|
-
"logicalNodeAddress": 3
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
"name": "--",
|
|
65
|
-
"value": true,
|
|
66
|
-
"masterAddress": "192.168.0.97",
|
|
67
|
-
"masterPort": 5001,
|
|
68
|
-
"logicalAddress": 0,
|
|
69
|
-
"logicalNodeAddress": 0
|
|
70
|
-
}
|
|
71
|
-
]
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
"type": "power",
|
|
75
|
-
"channel": "7+8+9",
|
|
76
|
-
"low": 400,
|
|
77
|
-
"high": 900,
|
|
78
|
-
"actions": [
|
|
79
|
-
{
|
|
80
|
-
"name": "Lamp-Top",
|
|
81
|
-
"value": false,
|
|
82
|
-
"masterAddress": "192.168.0.97",
|
|
83
|
-
"masterPort": 5001,
|
|
84
|
-
"logicalAddress": 33,
|
|
85
|
-
"logicalNodeAddress": 3
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"name": "Lamp-Top",
|
|
89
|
-
"value": true,
|
|
90
|
-
"masterAddress": "192.168.0.97",
|
|
91
|
-
"masterPort": 5001,
|
|
92
|
-
"logicalAddress": 33,
|
|
93
|
-
"logicalNodeAddress": 3
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
"name": "--",
|
|
97
|
-
"value": true,
|
|
98
|
-
"masterAddress": "192.168.0.97",
|
|
99
|
-
"masterPort": 5001,
|
|
100
|
-
"logicalAddress": 0,
|
|
101
|
-
"logicalNodeAddress": 0
|
|
102
|
-
}
|
|
103
|
-
],
|
|
104
|
-
"power": 266,
|
|
105
|
-
"current": 0
|
|
106
|
-
}
|
|
107
|
-
],
|
|
108
|
-
"address": "192.168.0.183",
|
|
109
|
-
"uid": "57e3e0d8-bb05-4b04-8662-1a9871998f3f",
|
|
110
|
-
"bindings": [
|
|
111
|
-
{
|
|
112
|
-
"channel": "0+1",
|
|
113
|
-
"masterAddress": "192.168.0.97",
|
|
114
|
-
"masterPort": 5001,
|
|
115
|
-
"register": 1
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
"channel": "2+3",
|
|
119
|
-
"masterAddress": "192.168.0.97",
|
|
120
|
-
"masterPort": 5001,
|
|
121
|
-
"register": 2
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
"channel": "4+5",
|
|
125
|
-
"masterAddress": "192.168.0.97",
|
|
126
|
-
"masterPort": 5001,
|
|
127
|
-
"register": 7
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
"channel": "8",
|
|
131
|
-
"masterAddress": "192.168.0.97",
|
|
132
|
-
"masterPort": 5001,
|
|
133
|
-
"register": 8
|
|
134
|
-
}
|
|
135
|
-
]
|
|
136
|
-
},
|
|
137
|
-
"p11": {
|
|
138
|
-
"debug": false,
|
|
139
|
-
"rules": [],
|
|
140
|
-
"bindings": [
|
|
141
|
-
{
|
|
142
|
-
"channel": "0+1+2",
|
|
143
|
-
"masterAddress": "192.168.0.97",
|
|
144
|
-
"masterPort": 5001,
|
|
145
|
-
"register": 11
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
"channel": "3+4+5",
|
|
149
|
-
"masterAddress": "192.168.0.97",
|
|
150
|
-
"masterPort": 5001,
|
|
151
|
-
"register": 12
|
|
152
|
-
}
|
|
153
|
-
],
|
|
154
|
-
"address": "192.168.0.206"
|
|
155
|
-
},
|
|
156
|
-
"shelly": {
|
|
157
|
-
"debug": false,
|
|
158
|
-
"address": "192.168.0.95",
|
|
159
|
-
"addresses": "192.168.0.95,192.168.0.95",
|
|
160
|
-
"rules": [],
|
|
161
|
-
"bindings": [
|
|
162
|
-
{
|
|
163
|
-
"channel": "0+1+2+3+4+5+6+7+8",
|
|
164
|
-
"masterAddress": "192.168.0.97",
|
|
165
|
-
"masterPort": 5001,
|
|
166
|
-
"register": 5
|
|
167
|
-
}
|
|
168
|
-
]
|
|
169
|
-
},
|
|
170
|
-
"xsocapp": {
|
|
171
|
-
"debug": true,
|
|
172
|
-
"port": 9999
|
|
173
|
-
},
|
|
174
|
-
"smartapp": {
|
|
175
|
-
"apiPort": 8581,
|
|
176
|
-
"apiHost": "192.168.0.99",
|
|
177
|
-
"port": 5002,
|
|
178
|
-
"switches": [
|
|
179
|
-
{
|
|
180
|
-
"logicalNodeAddress": 252,
|
|
181
|
-
"logicalAddress": 107,
|
|
182
|
-
"masterAddress": "192.168.0.97",
|
|
183
|
-
"masterPort": 5001,
|
|
184
|
-
"name": "Shelly Valies",
|
|
185
|
-
"displayName": "Shelly Valies",
|
|
186
|
-
"extendedType": 0,
|
|
187
|
-
"nomacro": "Y",
|
|
188
|
-
"nostop": "Y",
|
|
189
|
-
"unitName": "Red",
|
|
190
|
-
"value": 0,
|
|
191
|
-
"data": "",
|
|
192
|
-
"header": "",
|
|
193
|
-
"method": "GET",
|
|
194
|
-
"type": "httpdim",
|
|
195
|
-
"plug": "http://192.168.0.96/light/0?brightness=$&turn=#"
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
"logicalNodeAddress": 16,
|
|
199
|
-
"logicalAddress": 35,
|
|
200
|
-
"masterAddress": "gm.coppieters.be",
|
|
201
|
-
"masterPort": 5005,
|
|
202
|
-
"name": "Test Plug",
|
|
203
|
-
"displayName": "Test Plug",
|
|
204
|
-
"extendedType": 0,
|
|
205
|
-
"nomacro": "N",
|
|
206
|
-
"nostop": "N",
|
|
207
|
-
"unitName": "Plug",
|
|
208
|
-
"value": 0,
|
|
209
|
-
"data": "",
|
|
210
|
-
"header": "",
|
|
211
|
-
"method": "GET",
|
|
212
|
-
"type": "smappee",
|
|
213
|
-
"plug": "1"
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
"logicalNodeAddress": 3,
|
|
217
|
-
"logicalAddress": 34,
|
|
218
|
-
"masterAddress": "192.168.0.97",
|
|
219
|
-
"masterPort": 5001,
|
|
220
|
-
"name": "Notification Sender",
|
|
221
|
-
"displayName": "Notification Sender",
|
|
222
|
-
"extendedType": 0,
|
|
223
|
-
"nomacro": "N",
|
|
224
|
-
"nostop": "N",
|
|
225
|
-
"unitName": "Lamp-Bottom",
|
|
226
|
-
"value": 0,
|
|
227
|
-
"data": "",
|
|
228
|
-
"header": "",
|
|
229
|
-
"method": "GET",
|
|
230
|
-
"type": "http",
|
|
231
|
-
"plug": "https://duotecno.eu/nl/notification/send?title=Lamp+changed&body=Turned+#&proof=MwJDIgEUOgYNFQsmHEMjC15yXllXXmZVKgw6ASkBBw=="
|
|
232
|
-
}
|
|
233
|
-
],
|
|
234
|
-
"debug": false,
|
|
235
|
-
"username": "pi",
|
|
236
|
-
"password": "GMTecno",
|
|
237
|
-
"links": [],
|
|
238
|
-
"devices": [
|
|
239
|
-
{
|
|
240
|
-
"logicalNodeAddress": 3,
|
|
241
|
-
"logicalAddress": 34,
|
|
242
|
-
"masterAddress": "192.168.0.97",
|
|
243
|
-
"masterPort": 5001,
|
|
244
|
-
"name": "Test Lamp",
|
|
245
|
-
"displayName": "Test Lamp",
|
|
246
|
-
"extendedType": 0,
|
|
247
|
-
"id": 802,
|
|
248
|
-
"power": 0,
|
|
249
|
-
"energy": 0,
|
|
250
|
-
"voltage": 230,
|
|
251
|
-
"current": 0,
|
|
252
|
-
"relay": false,
|
|
253
|
-
"measured": true,
|
|
254
|
-
"powerSource": "shelly",
|
|
255
|
-
"powerChannel": "1+2",
|
|
256
|
-
"estimatedPower": 100
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
"logicalNodeAddress": 3,
|
|
260
|
-
"logicalAddress": 30,
|
|
261
|
-
"masterAddress": "192.168.0.97",
|
|
262
|
-
"masterPort": 5001,
|
|
263
|
-
"name": "Blue LED",
|
|
264
|
-
"displayName": "Blue LED",
|
|
265
|
-
"extendedType": 0,
|
|
266
|
-
"id": 798,
|
|
267
|
-
"power": 0,
|
|
268
|
-
"energy": 0,
|
|
269
|
-
"voltage": 230,
|
|
270
|
-
"current": 0,
|
|
271
|
-
"relay": false,
|
|
272
|
-
"measured": false,
|
|
273
|
-
"powerSource": "none",
|
|
274
|
-
"powerChannel": "",
|
|
275
|
-
"estimatedPower": 50
|
|
276
|
-
}
|
|
277
|
-
]
|
|
278
|
-
},
|
|
279
|
-
"system": {
|
|
280
|
-
"language": "EN",
|
|
281
|
-
"stores": false,
|
|
282
|
-
"multiple": false,
|
|
283
|
-
"socketserver": "ws.duotecno.eu",
|
|
284
|
-
"socketport": 9999,
|
|
285
|
-
"mood": "sfeer",
|
|
286
|
-
"debug": false,
|
|
287
|
-
"cmasters": [
|
|
288
|
-
{
|
|
289
|
-
"address": "192.168.0.97",
|
|
290
|
-
"port": 5001,
|
|
291
|
-
"password": "GMTecno",
|
|
292
|
-
"name": "Valies",
|
|
293
|
-
"active": true,
|
|
294
|
-
"nodenames": {
|
|
295
|
-
"3": "SB 1",
|
|
296
|
-
"4": "oled",
|
|
297
|
-
"252": " 20211003"
|
|
298
|
-
},
|
|
5
|
+
"smappee": {
|
|
6
|
+
"rules": [],
|
|
7
|
+
"address": "",
|
|
8
|
+
"uid": "57e3e0d8-bb05-4b04-8662-1a9871998f3f",
|
|
9
|
+
"debug": false,
|
|
10
|
+
"bindings": []
|
|
11
|
+
},
|
|
12
|
+
"smartapp": {
|
|
13
|
+
"port": 5002,
|
|
14
|
+
"switches": [],
|
|
15
|
+
"debug": true,
|
|
16
|
+
"links": [],
|
|
17
|
+
"apiHost": "localhost",
|
|
18
|
+
"apiPort": 8581,
|
|
19
|
+
"username": "duotecno",
|
|
20
|
+
"password": "#Duotecno@1",
|
|
21
|
+
"devices": []
|
|
22
|
+
},
|
|
23
|
+
"system": {
|
|
24
|
+
"language": "EN",
|
|
25
|
+
"stores": false,
|
|
26
|
+
"multiple": false,
|
|
27
|
+
"socketserver": "gateway.duotecno.com",
|
|
28
|
+
"socketport": 9999,
|
|
29
|
+
"mood": "sfeer",
|
|
30
|
+
"cmasters": [],
|
|
31
|
+
"cunits": [],
|
|
299
32
|
"debug": false
|
|
300
|
-
}
|
|
301
|
-
],
|
|
302
|
-
"cunits": [
|
|
303
|
-
{
|
|
304
|
-
"logicalNodeAddress": 252,
|
|
305
|
-
"logicalAddress": 102,
|
|
306
|
-
"masterAddress": "192.168.0.97",
|
|
307
|
-
"masterPort": 5001,
|
|
308
|
-
"name": "<Solar Panels",
|
|
309
|
-
"displayName": "<Solar Panels",
|
|
310
|
-
"extendedType": 0,
|
|
311
|
-
"active": "N",
|
|
312
|
-
"used": "Y",
|
|
313
|
-
"group": 0
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
"logicalNodeAddress": 252,
|
|
317
|
-
"logicalAddress": 103,
|
|
318
|
-
"masterAddress": "192.168.0.97",
|
|
319
|
-
"masterPort": 5001,
|
|
320
|
-
"name": ">Cars",
|
|
321
|
-
"displayName": ">Cars",
|
|
322
|
-
"extendedType": 0,
|
|
323
|
-
"active": "N",
|
|
324
|
-
"used": "Y",
|
|
325
|
-
"group": 0
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
"logicalNodeAddress": 252,
|
|
329
|
-
"logicalAddress": 100,
|
|
330
|
-
"masterAddress": "192.168.0.97",
|
|
331
|
-
"masterPort": 5001,
|
|
332
|
-
"name": ">Grid",
|
|
333
|
-
"displayName": ">Grid",
|
|
334
|
-
"extendedType": 0,
|
|
335
|
-
"active": "N",
|
|
336
|
-
"used": "Y",
|
|
337
|
-
"group": 0
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
"logicalNodeAddress": 252,
|
|
341
|
-
"logicalAddress": 101,
|
|
342
|
-
"masterAddress": "192.168.0.97",
|
|
343
|
-
"masterPort": 5001,
|
|
344
|
-
"name": ">Swimmingpool",
|
|
345
|
-
"displayName": ">Swimmingpool",
|
|
346
|
-
"extendedType": 0,
|
|
347
|
-
"active": "N",
|
|
348
|
-
"used": "Y",
|
|
349
|
-
"group": 0
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
"logicalNodeAddress": 252,
|
|
353
|
-
"logicalAddress": 104,
|
|
354
|
-
"masterAddress": "192.168.0.97",
|
|
355
|
-
"masterPort": 5001,
|
|
356
|
-
"name": "Tim Small",
|
|
357
|
-
"displayName": "Tim Small",
|
|
358
|
-
"extendedType": 8,
|
|
359
|
-
"active": "Y",
|
|
360
|
-
"used": "Y",
|
|
361
|
-
"group": 0
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
"logicalNodeAddress": 252,
|
|
365
|
-
"logicalAddress": 7,
|
|
366
|
-
"masterAddress": "192.168.0.97",
|
|
367
|
-
"masterPort": 5001,
|
|
368
|
-
"name": "Bottom-Left",
|
|
369
|
-
"displayName": "Bottom-Left",
|
|
370
|
-
"extendedType": 102,
|
|
371
|
-
"active": "Y",
|
|
372
|
-
"used": "Y",
|
|
373
|
-
"group": 0
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
"logicalNodeAddress": 3,
|
|
377
|
-
"logicalAddress": 28,
|
|
378
|
-
"masterAddress": "192.168.0.97",
|
|
379
|
-
"masterPort": 5001,
|
|
380
|
-
"name": "Garage $",
|
|
381
|
-
"displayName": "Garage ",
|
|
382
|
-
"extendedType": 8,
|
|
383
|
-
"active": "Y",
|
|
384
|
-
"used": "Y",
|
|
385
|
-
"group": 0
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
"logicalNodeAddress": 3,
|
|
389
|
-
"logicalAddress": 31,
|
|
390
|
-
"masterAddress": "192.168.0.97",
|
|
391
|
-
"masterPort": 5001,
|
|
392
|
-
"name": "2|Dimmer",
|
|
393
|
-
"displayName": "Somfy Dimmer",
|
|
394
|
-
"extendedType": 1,
|
|
395
|
-
"active": "Y",
|
|
396
|
-
"used": "Y",
|
|
397
|
-
"group": 0
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
"logicalNodeAddress": 3,
|
|
401
|
-
"logicalAddress": 30,
|
|
402
|
-
"masterAddress": "192.168.0.97",
|
|
403
|
-
"masterPort": 5001,
|
|
404
|
-
"name": "Blue LED",
|
|
405
|
-
"displayName": "Blue LED",
|
|
406
|
-
"extendedType": 1,
|
|
407
|
-
"active": "N",
|
|
408
|
-
"used": "Y",
|
|
409
|
-
"group": 0
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
"logicalNodeAddress": 3,
|
|
413
|
-
"logicalAddress": 29,
|
|
414
|
-
"masterAddress": "192.168.0.97",
|
|
415
|
-
"masterPort": 5001,
|
|
416
|
-
"name": "Red LED",
|
|
417
|
-
"displayName": "Red LED",
|
|
418
|
-
"extendedType": 1,
|
|
419
|
-
"active": "N",
|
|
420
|
-
"used": "Y",
|
|
421
|
-
"group": 0
|
|
422
|
-
},
|
|
423
|
-
{
|
|
424
|
-
"logicalNodeAddress": 3,
|
|
425
|
-
"logicalAddress": 34,
|
|
426
|
-
"masterAddress": "192.168.0.97",
|
|
427
|
-
"masterPort": 5001,
|
|
428
|
-
"name": "Lamp-Bottom",
|
|
429
|
-
"displayName": "Lamp-Bottom",
|
|
430
|
-
"extendedType": 2,
|
|
431
|
-
"active": "N",
|
|
432
|
-
"used": "Y",
|
|
433
|
-
"group": 0
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
"logicalNodeAddress": 3,
|
|
437
|
-
"logicalAddress": 33,
|
|
438
|
-
"masterAddress": "192.168.0.97",
|
|
439
|
-
"masterPort": 5001,
|
|
440
|
-
"name": "Lamp-Top",
|
|
441
|
-
"displayName": "Lamp-Top",
|
|
442
|
-
"extendedType": 2,
|
|
443
|
-
"active": "N",
|
|
444
|
-
"used": "Y",
|
|
445
|
-
"group": 0
|
|
446
|
-
},
|
|
447
|
-
{
|
|
448
|
-
"logicalNodeAddress": 3,
|
|
449
|
-
"logicalAddress": 4,
|
|
450
|
-
"masterAddress": "192.168.0.97",
|
|
451
|
-
"masterPort": 5001,
|
|
452
|
-
"name": "All off",
|
|
453
|
-
"displayName": "Alles uitje",
|
|
454
|
-
"extendedType": 7,
|
|
455
|
-
"active": "Y",
|
|
456
|
-
"used": "Y",
|
|
457
|
-
"group": 0
|
|
458
|
-
},
|
|
459
|
-
{
|
|
460
|
-
"logicalNodeAddress": 3,
|
|
461
|
-
"logicalAddress": 5,
|
|
462
|
-
"masterAddress": "192.168.0.97",
|
|
463
|
-
"masterPort": 5001,
|
|
464
|
-
"name": "All on",
|
|
465
|
-
"displayName": "Alles aantje",
|
|
466
|
-
"extendedType": 7,
|
|
467
|
-
"active": "Y",
|
|
468
|
-
"used": "Y",
|
|
469
|
-
"group": 0
|
|
470
|
-
}
|
|
471
|
-
]
|
|
472
|
-
},
|
|
473
|
-
"scenes": [
|
|
474
|
-
{
|
|
475
|
-
"name": "Scene",
|
|
476
|
-
"trigger": {
|
|
477
|
-
"masterAddress": "0.0.0.0",
|
|
478
|
-
"masterPort": 5001,
|
|
479
|
-
"name": "unit",
|
|
480
|
-
"logicalAddress": 0,
|
|
481
|
-
"logicalNodeAddress": 0,
|
|
482
|
-
"value": true
|
|
483
|
-
},
|
|
484
|
-
"order": 0,
|
|
485
|
-
"units": []
|
|
486
|
-
}
|
|
487
|
-
],
|
|
488
|
-
"debug": true,
|
|
489
|
-
"settings": {
|
|
490
|
-
"network": {
|
|
491
|
-
"ip1": "1.2.3.4",
|
|
492
|
-
"gateway1": "1.2.3.1",
|
|
493
|
-
"nameservers": "8.8.8.8",
|
|
494
|
-
"ip2": "",
|
|
495
|
-
"gateway2": ""
|
|
496
33
|
},
|
|
497
|
-
"
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
"
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
34
|
+
"socapp": false,
|
|
35
|
+
"p1": {
|
|
36
|
+
"debug": false,
|
|
37
|
+
"rules": [],
|
|
38
|
+
"bindings": [],
|
|
39
|
+
"address": ""
|
|
40
|
+
},
|
|
41
|
+
"shelly": {
|
|
42
|
+
"debug": false,
|
|
43
|
+
"address": "",
|
|
44
|
+
"addresses": "",
|
|
45
|
+
"rules": [],
|
|
46
|
+
"bindings": []
|
|
47
|
+
},
|
|
48
|
+
"proxy": false
|
|
509
49
|
}
|
package/duotecno/protocol.ts
CHANGED
|
@@ -576,23 +576,23 @@ export const Protocol = {
|
|
|
576
576
|
.join("");
|
|
577
577
|
},
|
|
578
578
|
|
|
579
|
-
makeWord: function(arr: Array<number>, at: number) {
|
|
579
|
+
makeWord: function(arr: Array<number>, at: number): number {
|
|
580
580
|
return arr[at+0] * 256 + arr[at+1];
|
|
581
581
|
},
|
|
582
|
-
makeSignedWord: function(arr: Array<number>, at: number) {
|
|
582
|
+
makeSignedWord: function(arr: Array<number>, at: number): number {
|
|
583
583
|
if (arr[at+0] > 127)
|
|
584
584
|
return (arr[at+0]-255) * 256 +
|
|
585
585
|
(arr[at+1]-255) - 1;
|
|
586
586
|
else
|
|
587
587
|
return arr[at+0] * 256 + arr[at+1];
|
|
588
588
|
},
|
|
589
|
-
makeLong: function(arr: Array<number>, at: number) {
|
|
589
|
+
makeLong: function(arr: Array<number>, at: number): number {
|
|
590
590
|
return arr[at+0] * 256 * 256 * 256 +
|
|
591
591
|
arr[at+1] * 256 * 256 +
|
|
592
592
|
arr[at+2] * 256 +
|
|
593
593
|
arr[at+3];
|
|
594
594
|
},
|
|
595
|
-
makeSigned: function(arr: Array<number>, at: number) {
|
|
595
|
+
makeSigned: function(arr: Array<number>, at: number): number {
|
|
596
596
|
if (arr[at+0] > 127)
|
|
597
597
|
return (arr[at+0]-255) * 256 * 256 * 256 +
|
|
598
598
|
(arr[at+1]-255) * 256 * 256 +
|
|
@@ -604,7 +604,7 @@ export const Protocol = {
|
|
|
604
604
|
arr[at+2] * 256 +
|
|
605
605
|
arr[at+3];
|
|
606
606
|
},
|
|
607
|
-
signedToArray: function(value: number) {
|
|
607
|
+
signedToArray: function(value: number): Array<number> {
|
|
608
608
|
if (value < 0)
|
|
609
609
|
return [256 + Math.floor(value / 256 / 256 / 256),
|
|
610
610
|
256 + Math.floor(value / 256 / 256) % 256,
|
|
@@ -616,13 +616,13 @@ export const Protocol = {
|
|
|
616
616
|
Math.floor(value / 256) % 256,
|
|
617
617
|
Math.floor(value) % 256];
|
|
618
618
|
},
|
|
619
|
-
signedWordToArray: function(value: number) {
|
|
619
|
+
signedWordToArray: function(value: number): Array<number> {
|
|
620
620
|
if (value < 0)
|
|
621
621
|
return [256 + Math.floor(value / 256) % 256, 256 + Math.floor(value) % 256];
|
|
622
622
|
else
|
|
623
623
|
return [Math.floor(value / 256) % 256, Math.floor(value) % 256];
|
|
624
624
|
},
|
|
625
|
-
wordToArray: function(value: number) {
|
|
625
|
+
wordToArray: function(value: number): Array<number> {
|
|
626
626
|
return [Math.floor(value / 256) % 256, Math.floor(value) % 256];
|
|
627
627
|
},
|
|
628
628
|
|
package/duotecno/types.js
CHANGED
|
@@ -413,6 +413,7 @@ exports.Sanitizers = {
|
|
|
413
413
|
c.masterAddress = (config === null || config === void 0 ? void 0 : config.masterAddress) || ((_a = system === null || system === void 0 ? void 0 : system.cmasters[0]) === null || _a === void 0 ? void 0 : _a.address) || "";
|
|
414
414
|
c.masterPort = (config === null || config === void 0 ? void 0 : config.masterPort) || ((_b = system === null || system === void 0 ? void 0 : system.cmasters[0]) === null || _b === void 0 ? void 0 : _b.port) || 5001;
|
|
415
415
|
c.uniqueId = (config === null || config === void 0 ? void 0 : config.uniqueId) || proxy_1.kEmptyProxy.uniqueId;
|
|
416
|
+
c.kind = "gw";
|
|
416
417
|
return c;
|
|
417
418
|
},
|
|
418
419
|
//////////////
|
package/duotecno/types.ts
CHANGED
|
@@ -251,6 +251,10 @@ export interface Switch extends UnitDef {
|
|
|
251
251
|
value?: number | boolean | string;
|
|
252
252
|
nomacro: string;
|
|
253
253
|
nostop: string;
|
|
254
|
+
|
|
255
|
+
// Track last sent status to prevent duplicate action to be triggered
|
|
256
|
+
lastSentStatus?: number | boolean;
|
|
257
|
+
lastSentValue?: number | boolean | string;
|
|
254
258
|
}
|
|
255
259
|
|
|
256
260
|
export const kEmptySwitch: Switch = {
|
|
@@ -667,6 +671,7 @@ export const Sanitizers = {
|
|
|
667
671
|
c.masterAddress = config?.masterAddress || system?.cmasters[0]?.address || "";
|
|
668
672
|
c.masterPort = config?.masterPort || system?.cmasters[0]?.port || 5001;
|
|
669
673
|
c.uniqueId = config?.uniqueId || kEmptyProxy.uniqueId;
|
|
674
|
+
c.kind = "gw";
|
|
670
675
|
return c;
|
|
671
676
|
},
|
|
672
677
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-smartsystem",
|
|
3
3
|
"displayname": "Duotecno Bridge",
|
|
4
|
-
"version": "7.0.
|
|
4
|
+
"version": "7.0.4",
|
|
5
5
|
"description": "SmartServer (Proxy TCP sockets to the cloud, Smappee MQTT, Duotecno IP Nodes, Homekit interface)",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"author": "Johan Coppieters",
|
package/server/mDNS.js
CHANGED
|
@@ -40,26 +40,55 @@ class MDNSService {
|
|
|
40
40
|
const ports = Array.isArray(port) ? port : [port];
|
|
41
41
|
const primaryPort = ports[0];
|
|
42
42
|
// Register the HTTP service using bonjour
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
// Wrap in try-catch because bonjour can throw synchronously on conflicts
|
|
44
|
+
let service;
|
|
45
|
+
try {
|
|
46
|
+
service = this.bonjour.publish({
|
|
47
|
+
name,
|
|
48
|
+
type: 'http',
|
|
49
|
+
port: primaryPort,
|
|
50
|
+
txt: {
|
|
51
|
+
version: version || '1.0.0',
|
|
52
|
+
ports: ports.join(','),
|
|
53
|
+
path: '/'
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
(0, logger_1.log)("mDNS", `HTTP service registered: ${name}.local on port(s) ${ports.join(', ')}`);
|
|
57
|
+
}
|
|
58
|
+
catch (publishError) {
|
|
59
|
+
if (publishError.message && publishError.message.includes('already in use')) {
|
|
60
|
+
(0, logger_1.err)("mDNS", `Service name "${name}" is already in use. This is usually harmless - continuing anyway.`);
|
|
61
|
+
(0, logger_1.err)("mDNS", "The old service registration will expire in ~30 seconds.");
|
|
51
62
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
else {
|
|
64
|
+
(0, logger_1.err)("mDNS", `Service publish error: ${publishError.message}`);
|
|
65
|
+
}
|
|
66
|
+
// Continue anyway - service will work once the old registration expires
|
|
67
|
+
}
|
|
68
|
+
// Handle service errors - especially "already in use" errors
|
|
69
|
+
if (service) {
|
|
70
|
+
service.on('error', (error) => {
|
|
71
|
+
if (error.message.includes('already in use')) {
|
|
72
|
+
(0, logger_1.err)("mDNS", `Service name "${name}" is already in use. Another instance may be running.`);
|
|
73
|
+
(0, logger_1.err)("mDNS", "To fix: 1) Stop other instances, or 2) Change the mdnsName in settings, or 3) Wait 30s for old service to expire");
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
(0, logger_1.err)("mDNS", `Service error: ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
58
80
|
// Publish the hostname A record (like ESP32 MDNS.begin())
|
|
59
81
|
this.publishHostname(name);
|
|
60
82
|
}
|
|
61
83
|
catch (e) {
|
|
62
|
-
(
|
|
84
|
+
if (e.message.includes('already in use')) {
|
|
85
|
+
(0, logger_1.err)("mDNS", `Service name "${this.config.name}" is already in use on the network`);
|
|
86
|
+
(0, logger_1.err)("mDNS", "Possible causes: 1) Another instance is running, 2) Previous instance didn't clean up properly");
|
|
87
|
+
(0, logger_1.err)("mDNS", "Solutions: Stop other instances, or restart with a different service name in settings");
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
(0, logger_1.err)("mDNS", `Failed to register service: ${e.message.split('\n')[0]}`);
|
|
91
|
+
}
|
|
63
92
|
}
|
|
64
93
|
}
|
|
65
94
|
/**
|
package/server/mDNS.ts
CHANGED
|
@@ -52,29 +52,54 @@ export class MDNSService {
|
|
|
52
52
|
const primaryPort = ports[0];
|
|
53
53
|
|
|
54
54
|
// Register the HTTP service using bonjour
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
// Wrap in try-catch because bonjour can throw synchronously on conflicts
|
|
56
|
+
let service;
|
|
57
|
+
try {
|
|
58
|
+
service = this.bonjour.publish({
|
|
59
|
+
name,
|
|
60
|
+
type: 'http',
|
|
61
|
+
port: primaryPort,
|
|
62
|
+
txt: {
|
|
63
|
+
version: version || '1.0.0',
|
|
64
|
+
ports: ports.join(','),
|
|
65
|
+
path: '/'
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
log("mDNS", `HTTP service registered: ${name}.local on port(s) ${ports.join(', ')}`);
|
|
70
|
+
} catch (publishError: any) {
|
|
71
|
+
if (publishError.message && publishError.message.includes('already in use')) {
|
|
72
|
+
err("mDNS", `Service name "${name}" is already in use. This is usually harmless - continuing anyway.`);
|
|
73
|
+
err("mDNS", "The old service registration will expire in ~30 seconds.");
|
|
74
|
+
} else {
|
|
75
|
+
err("mDNS", `Service publish error: ${publishError.message}`);
|
|
63
76
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
log("mDNS", `HTTP service registered: ${name}.local on port(s) ${ports.join(', ')}`);
|
|
77
|
+
// Continue anyway - service will work once the old registration expires
|
|
78
|
+
}
|
|
67
79
|
|
|
68
|
-
// Handle service errors
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
// Handle service errors - especially "already in use" errors
|
|
81
|
+
if (service) {
|
|
82
|
+
service.on('error', (error: Error) => {
|
|
83
|
+
if (error.message.includes('already in use')) {
|
|
84
|
+
err("mDNS", `Service name "${name}" is already in use. Another instance may be running.`);
|
|
85
|
+
err("mDNS", "To fix: 1) Stop other instances, or 2) Change the mdnsName in settings, or 3) Wait 30s for old service to expire");
|
|
86
|
+
} else {
|
|
87
|
+
err("mDNS", `Service error: ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
72
91
|
|
|
73
92
|
// Publish the hostname A record (like ESP32 MDNS.begin())
|
|
74
93
|
this.publishHostname(name);
|
|
75
94
|
|
|
76
95
|
} catch (e) {
|
|
77
|
-
|
|
96
|
+
if (e.message.includes('already in use')) {
|
|
97
|
+
err("mDNS", `Service name "${this.config.name}" is already in use on the network`);
|
|
98
|
+
err("mDNS", "Possible causes: 1) Another instance is running, 2) Previous instance didn't clean up properly");
|
|
99
|
+
err("mDNS", "Solutions: Stop other instances, or restart with a different service name in settings");
|
|
100
|
+
} else {
|
|
101
|
+
err("mDNS", `Failed to register service: ${e.message.split('\n')[0]}`);
|
|
102
|
+
}
|
|
78
103
|
}
|
|
79
104
|
}
|
|
80
105
|
|
package/server/powerbase.js
CHANGED
|
@@ -79,16 +79,17 @@ class PowerBase extends base_1.Base {
|
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
evalPower(exp) {
|
|
82
|
-
var _a, _b, _c, _d
|
|
82
|
+
var _a, _b, _c, _d;
|
|
83
83
|
let parsed = exp.split(/(\+|-)/);
|
|
84
84
|
let result = (_b = (_a = this.channels[parseInt(parsed[0]) || 0]) === null || _a === void 0 ? void 0 : _a.power) !== null && _b !== void 0 ? _b : 0;
|
|
85
85
|
for (let i = 1; i < parsed.length; i = i + 2) {
|
|
86
|
+
const channelIndex = parseInt(parsed[i + 1]) || 0;
|
|
87
|
+
const channelPower = (_d = (_c = this.channels[channelIndex]) === null || _c === void 0 ? void 0 : _c.power) !== null && _d !== void 0 ? _d : 0;
|
|
86
88
|
if (parsed[i] === "-")
|
|
87
|
-
result -=
|
|
89
|
+
result -= channelPower;
|
|
88
90
|
else if (parsed[i] === "+")
|
|
89
|
-
result +=
|
|
91
|
+
result += channelPower;
|
|
90
92
|
}
|
|
91
|
-
(0, logger_1.debug)("power", "evalPower: " + exp + " -> " + parsed + " = " + result);
|
|
92
93
|
return result;
|
|
93
94
|
}
|
|
94
95
|
//////////////////////////////////////////////
|
|
@@ -114,6 +115,7 @@ class PowerBase extends base_1.Base {
|
|
|
114
115
|
// for all bindings
|
|
115
116
|
this.bindings.forEach(b => {
|
|
116
117
|
const power = this.evalPower(b.channel);
|
|
118
|
+
(0, logger_1.debug)("power", `Binding channel=${b.channel}, register=${b.register}, current_value=${b.value}, calculated_power=${power}`);
|
|
117
119
|
if (this.significant(power, b.value)) {
|
|
118
120
|
(0, logger_1.debug)("power", "APPLY to " + b.channel + " = " + b.value + " -> " + power + " for " + b.register +
|
|
119
121
|
"): difference: " + Math.abs(b.value - power) +
|
|
@@ -124,10 +126,11 @@ class PowerBase extends base_1.Base {
|
|
|
124
126
|
(0, logger_1.debug)("power", "set register " + b.register + " to " + power);
|
|
125
127
|
})
|
|
126
128
|
.catch((e) => (0, logger_1.err)(this.type, e.message || e));
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
(0, logger_1.debug)("power", "NOT significant for " + b.channel + " = " + b.value + " -> " + power + " for register " + b.register +
|
|
132
|
+
": difference: " + Math.abs(b.value - power) +
|
|
133
|
+
" <= threshold: 5% =" + Math.abs(b.value * 0.05) + " or 2% =" + Math.abs(b.value * 0.02));
|
|
131
134
|
}
|
|
132
135
|
});
|
|
133
136
|
}
|
package/server/powerbase.ts
CHANGED
|
@@ -99,12 +99,13 @@ export class PowerBase extends Base {
|
|
|
99
99
|
let parsed = exp.split(/(\+|-)/);
|
|
100
100
|
let result = this.channels[parseInt(parsed[0])||0]?.power ?? 0;
|
|
101
101
|
for (let i=1; i < parsed.length; i=i+2) {
|
|
102
|
+
const channelIndex = parseInt(parsed[i+1])||0;
|
|
103
|
+
const channelPower = this.channels[channelIndex]?.power ?? 0;
|
|
102
104
|
if (parsed[i] === "-")
|
|
103
|
-
result -=
|
|
105
|
+
result -= channelPower;
|
|
104
106
|
else if (parsed[i] === "+")
|
|
105
|
-
result +=
|
|
107
|
+
result += channelPower;
|
|
106
108
|
}
|
|
107
|
-
debug("power", "evalPower: " + exp + " -> " + parsed + " = " + result);
|
|
108
109
|
return result;
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -136,6 +137,7 @@ export class PowerBase extends Base {
|
|
|
136
137
|
// for all bindings
|
|
137
138
|
this.bindings.forEach( b => {
|
|
138
139
|
const power = this.evalPower(b.channel);
|
|
140
|
+
debug("power", `Binding channel=${b.channel}, register=${b.register}, current_value=${b.value}, calculated_power=${power}`);
|
|
139
141
|
if (this.significant(power, b.value)) {
|
|
140
142
|
debug("power", "APPLY to " + b.channel + " = " + b.value + " -> " + power + " for " + b.register +
|
|
141
143
|
"): difference: " + Math.abs(b.value - power) +
|
|
@@ -147,10 +149,10 @@ export class PowerBase extends Base {
|
|
|
147
149
|
})
|
|
148
150
|
.catch((e) => err(this.type, e.message || e));
|
|
149
151
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
} else {
|
|
153
|
+
debug("power", "NOT significant for " + b.channel + " = " + b.value + " -> " + power + " for register " + b.register +
|
|
154
|
+
": difference: " + Math.abs(b.value - power) +
|
|
155
|
+
" <= threshold: 5% =" + Math.abs(b.value * 0.05) + " or 2% =" + Math.abs(b.value * 0.02));
|
|
154
156
|
}
|
|
155
157
|
});
|
|
156
158
|
}
|
package/server/proxy.js
CHANGED
|
@@ -20,7 +20,7 @@ exports.kEmptyProxy = {
|
|
|
20
20
|
masterPort: 5001,
|
|
21
21
|
uniqueId: "",
|
|
22
22
|
debug: false,
|
|
23
|
-
kind: "
|
|
23
|
+
kind: "gw" // default running under PM2 or other process manager -> don't restart yourself
|
|
24
24
|
};
|
|
25
25
|
const config = Object.assign({}, exports.kEmptyProxy);
|
|
26
26
|
// Store reference to the webapp so we can close servers before restarting
|
|
@@ -117,7 +117,7 @@ exports.makeNewCloudConnection = makeNewCloudConnection;
|
|
|
117
117
|
// Restarting //
|
|
118
118
|
////////////////
|
|
119
119
|
function cleanStart(restart = false) {
|
|
120
|
-
if (gCloudConnections.length) {
|
|
120
|
+
if (gCloudConnections === null || gCloudConnections === void 0 ? void 0 : gCloudConnections.length) {
|
|
121
121
|
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
122
122
|
log(`[PROXY] → [CLOUD] Cleaned up all ${gCloudConnections.length} cloud connections.`);
|
|
123
123
|
gCloudConnections.splice(0, gCloudConnections.length); // clear the array
|
|
@@ -127,6 +127,7 @@ function cleanStart(restart = false) {
|
|
|
127
127
|
connectionChecker = null;
|
|
128
128
|
}
|
|
129
129
|
if (restart) {
|
|
130
|
+
log(`[PROXY] → [CLOUD] Restarting proxy... for ${config.uniqueId}.`);
|
|
130
131
|
if (config.uniqueId) {
|
|
131
132
|
log(`[PROXY] Starting proxy with config: ${JSON.stringify(config, null, 2)}`);
|
|
132
133
|
makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
|
|
@@ -284,7 +285,7 @@ class Context {
|
|
|
284
285
|
gCloudConnections.splice(index, 1);
|
|
285
286
|
this.cleanupSockets();
|
|
286
287
|
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
287
|
-
if (gCloudConnections.length === 0) {
|
|
288
|
+
if (gCloudConnections && gCloudConnections.length === 0) {
|
|
288
289
|
log(`[PROXY] → [CLOUD] No more cloud connections, restarting proxy.`);
|
|
289
290
|
// just to be sure: restart...
|
|
290
291
|
if (config.kind === "pm2") {
|
|
@@ -345,8 +346,10 @@ function handleShutdown(signal) {
|
|
|
345
346
|
connectionChecker = null;
|
|
346
347
|
}
|
|
347
348
|
// Close all cloud connections
|
|
348
|
-
gCloudConnections.
|
|
349
|
-
|
|
349
|
+
if (gCloudConnections && gCloudConnections.length > 0) {
|
|
350
|
+
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
351
|
+
gCloudConnections.splice(0, gCloudConnections.length);
|
|
352
|
+
}
|
|
350
353
|
// Close HTTP servers if available
|
|
351
354
|
if (gWebApp && typeof gWebApp.closeServers === 'function') {
|
|
352
355
|
log(`[PROXY] → [SYSTEM] Closing HTTP servers...`);
|
|
@@ -381,11 +384,24 @@ process.on('SIGTERM', (err) => {
|
|
|
381
384
|
handleShutdown('SIGTERM');
|
|
382
385
|
});
|
|
383
386
|
process.on('uncaughtException', (err) => {
|
|
384
|
-
error(`[SYSTEM] Uncaught Exception: ${err.message}`);
|
|
387
|
+
error(`[SYSTEM] Uncaught Exception: ${err.message || err}`);
|
|
385
388
|
handleShutdown('uncaughtException');
|
|
386
389
|
});
|
|
387
390
|
process.on('unhandledRejection', (reason) => {
|
|
388
|
-
|
|
391
|
+
// Handle different types of rejection reasons
|
|
392
|
+
try {
|
|
393
|
+
const message = reason instanceof Error ? reason.message :
|
|
394
|
+
typeof reason === 'object' ? JSON.stringify(reason) :
|
|
395
|
+
String(reason);
|
|
396
|
+
error(`[SYSTEM] Unhandled Rejection: ${message}`);
|
|
397
|
+
// Also log the stack trace if available
|
|
398
|
+
if (reason instanceof Error && reason.stack) {
|
|
399
|
+
console.error('Stack trace:', reason.stack);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch (e) {
|
|
403
|
+
error(`[SYSTEM] Unhandled Rejection: [Error converting reason to string]`);
|
|
404
|
+
}
|
|
389
405
|
handleShutdown('unhandledRejection');
|
|
390
406
|
});
|
|
391
407
|
//# sourceMappingURL=proxy.js.map
|
package/server/proxy.ts
CHANGED
|
@@ -23,7 +23,7 @@ export const kEmptyProxy: ProxyConfig = {
|
|
|
23
23
|
masterPort: 5001,
|
|
24
24
|
uniqueId: "", // no unique ID -> don't start the proxy
|
|
25
25
|
debug: false, // default nodebug mode
|
|
26
|
-
kind: "
|
|
26
|
+
kind: "gw" // default running under PM2 or other process manager -> don't restart yourself
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const config = {...kEmptyProxy};
|
|
@@ -137,7 +137,7 @@ export function makeNewCloudConnection(master: string, masterPort: number, serve
|
|
|
137
137
|
// Restarting //
|
|
138
138
|
////////////////
|
|
139
139
|
export function cleanStart(restart: boolean = false) {
|
|
140
|
-
if (gCloudConnections
|
|
140
|
+
if (gCloudConnections?.length) {
|
|
141
141
|
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
142
142
|
log(`[PROXY] → [CLOUD] Cleaned up all ${gCloudConnections.length} cloud connections.`);
|
|
143
143
|
|
|
@@ -150,6 +150,7 @@ export function cleanStart(restart: boolean = false) {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
if (restart) {
|
|
153
|
+
log(`[PROXY] → [CLOUD] Restarting proxy... for ${config.uniqueId}.`);
|
|
153
154
|
if (config.uniqueId) {
|
|
154
155
|
log(`[PROXY] Starting proxy with config: ${JSON.stringify(config, null, 2)}`);
|
|
155
156
|
makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
|
|
@@ -344,7 +345,7 @@ export class Context {
|
|
|
344
345
|
this.cleanupSockets();
|
|
345
346
|
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
346
347
|
|
|
347
|
-
if (gCloudConnections.length === 0) {
|
|
348
|
+
if (gCloudConnections && gCloudConnections.length === 0) {
|
|
348
349
|
log(`[PROXY] → [CLOUD] No more cloud connections, restarting proxy.`);
|
|
349
350
|
// just to be sure: restart...
|
|
350
351
|
if (config.kind === "pm2") {
|
|
@@ -415,8 +416,10 @@ async function handleShutdown(signal: string) {
|
|
|
415
416
|
}
|
|
416
417
|
|
|
417
418
|
// Close all cloud connections
|
|
418
|
-
gCloudConnections.
|
|
419
|
-
|
|
419
|
+
if (gCloudConnections && gCloudConnections.length > 0) {
|
|
420
|
+
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
421
|
+
gCloudConnections.splice(0, gCloudConnections.length);
|
|
422
|
+
}
|
|
420
423
|
|
|
421
424
|
// Close HTTP servers if available
|
|
422
425
|
if (gWebApp && typeof gWebApp.closeServers === 'function') {
|
|
@@ -456,11 +459,24 @@ process.on('SIGTERM', (err) => {
|
|
|
456
459
|
});
|
|
457
460
|
|
|
458
461
|
process.on('uncaughtException', (err) => {
|
|
459
|
-
error(`[SYSTEM] Uncaught Exception: ${err.message}`);
|
|
462
|
+
error(`[SYSTEM] Uncaught Exception: ${err.message || err}`);
|
|
460
463
|
handleShutdown('uncaughtException');
|
|
461
464
|
});
|
|
462
465
|
|
|
463
|
-
process.on('unhandledRejection', (reason) => {
|
|
464
|
-
|
|
466
|
+
process.on('unhandledRejection', (reason: any) => {
|
|
467
|
+
// Handle different types of rejection reasons
|
|
468
|
+
try {
|
|
469
|
+
const message = reason instanceof Error ? reason.message :
|
|
470
|
+
typeof reason === 'object' ? JSON.stringify(reason) :
|
|
471
|
+
String(reason);
|
|
472
|
+
error(`[SYSTEM] Unhandled Rejection: ${message}`);
|
|
473
|
+
|
|
474
|
+
// Also log the stack trace if available
|
|
475
|
+
if (reason instanceof Error && reason.stack) {
|
|
476
|
+
console.error('Stack trace:', reason.stack);
|
|
477
|
+
}
|
|
478
|
+
} catch (e) {
|
|
479
|
+
error(`[SYSTEM] Unhandled Rejection: [Error converting reason to string]`);
|
|
480
|
+
}
|
|
465
481
|
handleShutdown('unhandledRejection');
|
|
466
482
|
});
|
package/server/shelly.js
CHANGED
|
@@ -102,7 +102,7 @@ class Shelly extends powerbase_1.PowerBase {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
(0, logger_1.debug)("shelly", "meters: " + JSON.stringify(this.meters));
|
|
105
|
-
if (this.meters.length) {
|
|
105
|
+
if (this.meters && this.meters.length) {
|
|
106
106
|
this.timer = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
107
107
|
this.realtimeCounter += kInterval;
|
|
108
108
|
// fetch data
|
package/server/shelly.ts
CHANGED
|
@@ -119,7 +119,7 @@ export class Shelly extends PowerBase {
|
|
|
119
119
|
}
|
|
120
120
|
debug("shelly", "meters: " + JSON.stringify(this.meters));
|
|
121
121
|
|
|
122
|
-
if (this.meters.length) {
|
|
122
|
+
if (this.meters && this.meters.length) {
|
|
123
123
|
this.timer = setInterval(async () => {
|
|
124
124
|
this.realtimeCounter += kInterval;
|
|
125
125
|
|
package/server/smartapp.js
CHANGED
|
@@ -1125,6 +1125,18 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
1125
1125
|
(0, logger_1.err)("smartapp", "Don't have unit for switch: " + swtch.unitname);
|
|
1126
1126
|
}
|
|
1127
1127
|
else {
|
|
1128
|
+
// Check if state has actually changed (to prevent duplicate notifications/actions)
|
|
1129
|
+
// Exception: Moods should always execute even if state hasn't changed (they are actions, not states)
|
|
1130
|
+
const currentStatus = !!swtch.unit.status;
|
|
1131
|
+
const currentValue = +swtch.unit.value;
|
|
1132
|
+
const isMood = (swtch.unit.type === types_1.UnitType.kMood);
|
|
1133
|
+
if (!isMood &&
|
|
1134
|
+
swtch.lastSentStatus === currentStatus &&
|
|
1135
|
+
swtch.lastSentValue === currentValue) {
|
|
1136
|
+
(0, logger_1.debug)("smartapp", "Switch state unchanged (status=" + currentStatus + ", value=" + currentValue + "), skipping action for " + swtch.name);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
// Dispatch to appropriate handler
|
|
1128
1140
|
if ((swtch.type === types_1.SwitchType.kSmappee) && (this.power.smappee)) {
|
|
1129
1141
|
this.power.smappee.setPlug(parseInt(swtch.plug), swtch.unit.value);
|
|
1130
1142
|
}
|
|
@@ -1152,6 +1164,9 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
1152
1164
|
else {
|
|
1153
1165
|
(0, logger_1.err)("smartapp", "Don't know how to set a switch of type " + swtch.type);
|
|
1154
1166
|
}
|
|
1167
|
+
// Remember the state we just sent (for all switch types, including moods)
|
|
1168
|
+
swtch.lastSentStatus = currentStatus;
|
|
1169
|
+
swtch.lastSentValue = currentValue;
|
|
1155
1170
|
}
|
|
1156
1171
|
}
|
|
1157
1172
|
///////////
|
package/server/smartapp.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// v2.0: mar/apr 2020
|
|
10
10
|
|
|
11
11
|
import { Context, HttpResponse, WebApp } from "./webapp";
|
|
12
|
-
import { Sanitizers, Rule, kEmptyRule, actionValue, Action, kEmptySwitch, Switch, SwitchType, hex, Binding, NetworkConfig, SettingsConfig, kEmptySettings, Link, LinkType, kEmptyLink, Device, kEmptyDevice, PowerSource } from "../duotecno/types";
|
|
12
|
+
import { Sanitizers, Rule, kEmptyRule, actionValue, Action, kEmptySwitch, Switch, SwitchType, hex, Binding, NetworkConfig, SettingsConfig, kEmptySettings, Link, LinkType, kEmptyLink, Device, kEmptyDevice, PowerSource, UnitType } from "../duotecno/types";
|
|
13
13
|
import { log, debug, err } from "../duotecno/logger";
|
|
14
14
|
import { System } from "../duotecno/system";
|
|
15
15
|
import { Node, Unit } from "../duotecno/protocol";
|
|
@@ -1237,6 +1237,20 @@ export class SmartApp extends WebApp {
|
|
|
1237
1237
|
err("smartapp", "Don't have unit for switch: "+swtch.unitname);
|
|
1238
1238
|
|
|
1239
1239
|
} else {
|
|
1240
|
+
// Check if state has actually changed (to prevent duplicate notifications/actions)
|
|
1241
|
+
// Exception: Moods should always execute even if state hasn't changed (they are actions, not states)
|
|
1242
|
+
const currentStatus = !!swtch.unit.status;
|
|
1243
|
+
const currentValue = +swtch.unit.value;
|
|
1244
|
+
const isMood = (swtch.unit.type === UnitType.kMood);
|
|
1245
|
+
|
|
1246
|
+
if (!isMood &&
|
|
1247
|
+
swtch.lastSentStatus === currentStatus &&
|
|
1248
|
+
swtch.lastSentValue === currentValue) {
|
|
1249
|
+
debug("smartapp", "Switch state unchanged (status=" + currentStatus + ", value=" + currentValue + "), skipping action for " + swtch.name);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// Dispatch to appropriate handler
|
|
1240
1254
|
if ((swtch.type === SwitchType.kSmappee) && (this.power.smappee)) {
|
|
1241
1255
|
this.power.smappee.setPlug(parseInt(swtch.plug), swtch.unit.value);
|
|
1242
1256
|
|
|
@@ -1264,6 +1278,10 @@ export class SmartApp extends WebApp {
|
|
|
1264
1278
|
} else {
|
|
1265
1279
|
err("smartapp", "Don't know how to set a switch of type " + swtch.type);
|
|
1266
1280
|
}
|
|
1281
|
+
|
|
1282
|
+
// Remember the state we just sent (for all switch types, including moods)
|
|
1283
|
+
swtch.lastSentStatus = currentStatus;
|
|
1284
|
+
swtch.lastSentValue = currentValue;
|
|
1267
1285
|
}
|
|
1268
1286
|
}
|
|
1269
1287
|
|
package/server/webapp.js
CHANGED
|
@@ -246,6 +246,10 @@ class WebApp extends base_1.Base {
|
|
|
246
246
|
return false; // Default: use cached templates for performance
|
|
247
247
|
}
|
|
248
248
|
serve(onConnected) {
|
|
249
|
+
// Ensure servers array is initialized (defensive coding)
|
|
250
|
+
if (!this.servers) {
|
|
251
|
+
this.servers = [];
|
|
252
|
+
}
|
|
249
253
|
// Handle both single port and multiple ports
|
|
250
254
|
const ports = Array.isArray(this.port) ? this.port : [this.port];
|
|
251
255
|
(0, logger_1.log)("server", "WebApp - Start serving http on port(s): " + ports.join(", "));
|
|
@@ -297,8 +301,12 @@ class WebApp extends base_1.Base {
|
|
|
297
301
|
*/
|
|
298
302
|
closeServers() {
|
|
299
303
|
return __awaiter(this, void 0, void 0, function* () {
|
|
304
|
+
// First, cleanup mDNS to avoid "already in use" errors on restart
|
|
305
|
+
this.destroyMDNS();
|
|
300
306
|
return new Promise((resolve) => {
|
|
301
|
-
|
|
307
|
+
// Safety check: servers might be undefined if never initialized
|
|
308
|
+
if (!this.servers || this.servers.length === 0) {
|
|
309
|
+
(0, logger_1.log)("server", "No HTTP servers to close");
|
|
302
310
|
resolve();
|
|
303
311
|
return;
|
|
304
312
|
}
|
package/server/webapp.ts
CHANGED
|
@@ -301,6 +301,11 @@ export class WebApp extends Base {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
serve(onConnected?: () => void) {
|
|
304
|
+
// Ensure servers array is initialized (defensive coding)
|
|
305
|
+
if (!this.servers) {
|
|
306
|
+
this.servers = [];
|
|
307
|
+
}
|
|
308
|
+
|
|
304
309
|
// Handle both single port and multiple ports
|
|
305
310
|
const ports = Array.isArray(this.port) ? this.port : [this.port];
|
|
306
311
|
|
|
@@ -365,8 +370,13 @@ export class WebApp extends Base {
|
|
|
365
370
|
* Call this before restarting or shutting down
|
|
366
371
|
*/
|
|
367
372
|
async closeServers(): Promise<void> {
|
|
373
|
+
// First, cleanup mDNS to avoid "already in use" errors on restart
|
|
374
|
+
this.destroyMDNS();
|
|
375
|
+
|
|
368
376
|
return new Promise((resolve) => {
|
|
369
|
-
|
|
377
|
+
// Safety check: servers might be undefined if never initialized
|
|
378
|
+
if (!this.servers || this.servers.length === 0) {
|
|
379
|
+
log("server", "No HTTP servers to close");
|
|
370
380
|
resolve();
|
|
371
381
|
return;
|
|
372
382
|
}
|