homebridge-smartsystem 6.8.0 → 6.8.3
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 +4 -0
- package/config.json +25 -47
- package/duotecno/logger.js +1 -1
- package/duotecno/logger.ts +1 -1
- package/duotecno/protocol.js +10 -1
- package/duotecno/protocol.ts +10 -1
- package/duotecno/types.js +4 -2
- package/duotecno/types.ts +6 -2
- package/package.json +1 -1
- package/server/HB.js +52 -14
- package/server/HB.ts +50 -11
- package/server/smartapp.js +22 -10
- package/server/smartapp.ts +24 -13
- package/server/views/link-details.ejs +12 -2
- package/server/webapp.js +10 -4
- package/server/webapp.ts +10 -4
package/README.md
CHANGED
|
@@ -301,8 +301,12 @@ Homebridge UI version
|
|
|
301
301
|
- 3: do dimmers/up-downs
|
|
302
302
|
- 4: better up-down values to and from HB
|
|
303
303
|
|
|
304
|
+
|
|
304
305
|
### v6.8.0 - 08/07/2025
|
|
305
306
|
- 0: first version including the proxy
|
|
307
|
+
- 1: max/min values for hbValues
|
|
308
|
+
- 2: HB up/down -> don't respond to motor unit's, only macro
|
|
309
|
+
- 3: remember request before the login kicked in (and redirect to that one, once the login was succesful)
|
|
306
310
|
|
|
307
311
|
## Hardware
|
|
308
312
|
A Raspberry Pi that connects to a Duotecno IP Node
|
package/config.json
CHANGED
|
@@ -194,42 +194,6 @@
|
|
|
194
194
|
"type": "httpdim",
|
|
195
195
|
"plug": "http://192.168.0.96/light/0?brightness=$&turn=#"
|
|
196
196
|
},
|
|
197
|
-
{
|
|
198
|
-
"logicalNodeAddress": 252,
|
|
199
|
-
"logicalAddress": 105,
|
|
200
|
-
"masterAddress": "192.168.0.97",
|
|
201
|
-
"masterPort": 5001,
|
|
202
|
-
"name": "Screen Salon Top",
|
|
203
|
-
"displayName": "Screen Salon Top",
|
|
204
|
-
"extendedType": 0,
|
|
205
|
-
"nomacro": "N",
|
|
206
|
-
"nostop": "N",
|
|
207
|
-
"unitName": "Salon Top",
|
|
208
|
-
"value": 0,
|
|
209
|
-
"data": "",
|
|
210
|
-
"header": "",
|
|
211
|
-
"method": "GET",
|
|
212
|
-
"type": "somfy",
|
|
213
|
-
"plug": "1"
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
"logicalNodeAddress": 252,
|
|
217
|
-
"logicalAddress": 104,
|
|
218
|
-
"masterAddress": "192.168.0.97",
|
|
219
|
-
"masterPort": 5001,
|
|
220
|
-
"name": "Screen Tim small",
|
|
221
|
-
"displayName": "Screen Tim small",
|
|
222
|
-
"extendedType": 0,
|
|
223
|
-
"nomacro": "N",
|
|
224
|
-
"nostop": "Y",
|
|
225
|
-
"unitName": "Tim Small",
|
|
226
|
-
"value": 0,
|
|
227
|
-
"data": "",
|
|
228
|
-
"header": "",
|
|
229
|
-
"method": "GET",
|
|
230
|
-
"type": "httpupdown",
|
|
231
|
-
"plug": "2"
|
|
232
|
-
},
|
|
233
197
|
{
|
|
234
198
|
"logicalNodeAddress": 16,
|
|
235
199
|
"logicalAddress": 35,
|
|
@@ -254,8 +218,8 @@
|
|
|
254
218
|
"password": "GMTecno",
|
|
255
219
|
"links": [
|
|
256
220
|
{
|
|
257
|
-
"logicalNodeAddress":
|
|
258
|
-
"logicalAddress":
|
|
221
|
+
"logicalNodeAddress": 252,
|
|
222
|
+
"logicalAddress": 104,
|
|
259
223
|
"masterAddress": "192.168.0.97",
|
|
260
224
|
"masterPort": 5001,
|
|
261
225
|
"name": "unit",
|
|
@@ -269,7 +233,9 @@
|
|
|
269
233
|
"TargetPosition",
|
|
270
234
|
"ObstructionDetected"
|
|
271
235
|
],
|
|
272
|
-
"type": "accessory"
|
|
236
|
+
"type": "accessory",
|
|
237
|
+
"max": 100,
|
|
238
|
+
"min": 0
|
|
273
239
|
}
|
|
274
240
|
]
|
|
275
241
|
},
|
|
@@ -304,7 +270,7 @@
|
|
|
304
270
|
"masterPort": 5001,
|
|
305
271
|
"name": "<Solar Panels",
|
|
306
272
|
"displayName": "<Solar Panels",
|
|
307
|
-
"extendedType":
|
|
273
|
+
"extendedType": 0,
|
|
308
274
|
"active": "N",
|
|
309
275
|
"used": "Y",
|
|
310
276
|
"group": 0
|
|
@@ -316,7 +282,7 @@
|
|
|
316
282
|
"masterPort": 5001,
|
|
317
283
|
"name": ">Cars",
|
|
318
284
|
"displayName": ">Cars",
|
|
319
|
-
"extendedType":
|
|
285
|
+
"extendedType": 0,
|
|
320
286
|
"active": "N",
|
|
321
287
|
"used": "Y",
|
|
322
288
|
"group": 0
|
|
@@ -328,7 +294,7 @@
|
|
|
328
294
|
"masterPort": 5001,
|
|
329
295
|
"name": ">Grid",
|
|
330
296
|
"displayName": ">Grid",
|
|
331
|
-
"extendedType":
|
|
297
|
+
"extendedType": 0,
|
|
332
298
|
"active": "N",
|
|
333
299
|
"used": "Y",
|
|
334
300
|
"group": 0
|
|
@@ -340,11 +306,23 @@
|
|
|
340
306
|
"masterPort": 5001,
|
|
341
307
|
"name": ">Swimmingpool",
|
|
342
308
|
"displayName": ">Swimmingpool",
|
|
343
|
-
"extendedType":
|
|
309
|
+
"extendedType": 0,
|
|
344
310
|
"active": "N",
|
|
345
311
|
"used": "Y",
|
|
346
312
|
"group": 0
|
|
347
313
|
},
|
|
314
|
+
{
|
|
315
|
+
"logicalNodeAddress": 252,
|
|
316
|
+
"logicalAddress": 104,
|
|
317
|
+
"masterAddress": "192.168.0.97",
|
|
318
|
+
"masterPort": 5001,
|
|
319
|
+
"name": "Tim Small",
|
|
320
|
+
"displayName": "Tim Small",
|
|
321
|
+
"extendedType": 8,
|
|
322
|
+
"active": "Y",
|
|
323
|
+
"used": "Y",
|
|
324
|
+
"group": 0
|
|
325
|
+
},
|
|
348
326
|
{
|
|
349
327
|
"logicalNodeAddress": 252,
|
|
350
328
|
"logicalAddress": 7,
|
|
@@ -365,7 +343,7 @@
|
|
|
365
343
|
"name": "Garage $",
|
|
366
344
|
"displayName": "Garage ",
|
|
367
345
|
"extendedType": 8,
|
|
368
|
-
"active": "
|
|
346
|
+
"active": "Y",
|
|
369
347
|
"used": "Y",
|
|
370
348
|
"group": 0
|
|
371
349
|
},
|
|
@@ -376,7 +354,7 @@
|
|
|
376
354
|
"masterPort": 5001,
|
|
377
355
|
"name": "2|Dimmer",
|
|
378
356
|
"displayName": "Somfy Dimmer",
|
|
379
|
-
"extendedType":
|
|
357
|
+
"extendedType": 1,
|
|
380
358
|
"active": "Y",
|
|
381
359
|
"used": "Y",
|
|
382
360
|
"group": 0
|
|
@@ -388,7 +366,7 @@
|
|
|
388
366
|
"masterPort": 5001,
|
|
389
367
|
"name": "Blue LED",
|
|
390
368
|
"displayName": "Blue LED",
|
|
391
|
-
"extendedType":
|
|
369
|
+
"extendedType": 1,
|
|
392
370
|
"active": "N",
|
|
393
371
|
"used": "Y",
|
|
394
372
|
"group": 0
|
|
@@ -400,7 +378,7 @@
|
|
|
400
378
|
"masterPort": 5001,
|
|
401
379
|
"name": "Red LED",
|
|
402
380
|
"displayName": "Red LED",
|
|
403
|
-
"extendedType":
|
|
381
|
+
"extendedType": 1,
|
|
404
382
|
"active": "N",
|
|
405
383
|
"used": "Y",
|
|
406
384
|
"group": 0
|
package/duotecno/logger.js
CHANGED
package/duotecno/logger.ts
CHANGED
|
@@ -10,7 +10,7 @@ export enum LogLevel { noLog = 0, log = 1, debug = 2 };
|
|
|
10
10
|
export const logSettings = {
|
|
11
11
|
"*": LogLevel.log,
|
|
12
12
|
"master": LogLevel.log,
|
|
13
|
-
"protocol": LogLevel.
|
|
13
|
+
"protocol": LogLevel.noLog,
|
|
14
14
|
"webapp": LogLevel.log,
|
|
15
15
|
"p1": LogLevel.log,
|
|
16
16
|
"shelly": LogLevel.log,
|
package/duotecno/protocol.js
CHANGED
|
@@ -790,6 +790,7 @@ exports.Protocol = {
|
|
|
790
790
|
}
|
|
791
791
|
else if (next.cmd === Rec.Motor) {
|
|
792
792
|
// motor -> boolean/status
|
|
793
|
+
// [38,0,252,104,8,0,0]
|
|
793
794
|
// 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
|
|
794
795
|
unit.status = next.message[6];
|
|
795
796
|
unit.value = next.message[6];
|
|
@@ -800,7 +801,8 @@ exports.Protocol = {
|
|
|
800
801
|
// = EV_UNITMACROCOMMANDO
|
|
801
802
|
// examples: On 50%: [69,0,NodeAddress,UnitAddress,6,1,0,50]
|
|
802
803
|
// Off: [69,0,NodeAddress,UnitAddress,6,0,0,0] -> don't touch dimmer value
|
|
803
|
-
//
|
|
804
|
+
// unit_type_duoswitch -> Event = 32, state 0, 1, 2
|
|
805
|
+
// STATUS (Event: 7 = dimmer, 5 = switch, on = 1, off = 0)
|
|
804
806
|
if (((next.message[4] == 7) || (next.message[4] == 5)) && (next.message[5] == 1)) {
|
|
805
807
|
// PIR ON -- override status value with "2" (our PIR ON)
|
|
806
808
|
unit.status = 2;
|
|
@@ -815,6 +817,13 @@ exports.Protocol = {
|
|
|
815
817
|
(next.message[4] === 11)) {
|
|
816
818
|
unit.value = this.makeWord(next.message, 6);
|
|
817
819
|
}
|
|
820
|
+
else if (next.message[4] === 32) {
|
|
821
|
+
// unit_type_duoswitch -> Event = 32
|
|
822
|
+
// [69,0,252,104,32,2,0,0]
|
|
823
|
+
// state = 0 -> stop, state = 1 -> up, state = 2 -> down
|
|
824
|
+
unit.value = 10 + next.message[5];
|
|
825
|
+
unit.status = next.message[5];
|
|
826
|
+
}
|
|
818
827
|
(0, logger_1.debug)("protocol", "received status - macro -> value=" + unit.value + " / status=" + unit.status);
|
|
819
828
|
kind = "M";
|
|
820
829
|
}
|
package/duotecno/protocol.ts
CHANGED
|
@@ -913,6 +913,7 @@ export const Protocol = {
|
|
|
913
913
|
|
|
914
914
|
} else if (next.cmd === Rec.Motor) {
|
|
915
915
|
// motor -> boolean/status
|
|
916
|
+
// [38,0,252,104,8,0,0]
|
|
916
917
|
// 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
|
|
917
918
|
unit.status = next.message[6];
|
|
918
919
|
unit.value = next.message[6];
|
|
@@ -923,8 +924,9 @@ export const Protocol = {
|
|
|
923
924
|
// = EV_UNITMACROCOMMANDO
|
|
924
925
|
// examples: On 50%: [69,0,NodeAddress,UnitAddress,6,1,0,50]
|
|
925
926
|
// Off: [69,0,NodeAddress,UnitAddress,6,0,0,0] -> don't touch dimmer value
|
|
927
|
+
// unit_type_duoswitch -> Event = 32, state 0, 1, 2
|
|
926
928
|
|
|
927
|
-
// STATUS
|
|
929
|
+
// STATUS (Event: 7 = dimmer, 5 = switch, on = 1, off = 0)
|
|
928
930
|
if (((next.message[4] == 7) || (next.message[4] == 5)) && (next.message[5] == 1)) {
|
|
929
931
|
// PIR ON -- override status value with "2" (our PIR ON)
|
|
930
932
|
unit.status = 2;
|
|
@@ -939,6 +941,13 @@ export const Protocol = {
|
|
|
939
941
|
if ((((next.message[4] === 1) || (next.message[4] === 6) || (next.message[4] === 7)) && (next.message[5] === 1)) ||
|
|
940
942
|
(next.message[4] === 11)) {
|
|
941
943
|
unit.value = this.makeWord(next.message, 6);
|
|
944
|
+
|
|
945
|
+
} else if (next.message[4] === 32) {
|
|
946
|
+
// unit_type_duoswitch -> Event = 32
|
|
947
|
+
// [69,0,252,104,32,2,0,0]
|
|
948
|
+
// state = 0 -> stop, state = 1 -> up, state = 2 -> down
|
|
949
|
+
unit.value = 10 + next.message[5];
|
|
950
|
+
unit.status = next.message[5];
|
|
942
951
|
}
|
|
943
952
|
|
|
944
953
|
debug("protocol", "received status - macro -> value=" + unit.value + " / status=" + unit.status);
|
package/duotecno/types.js
CHANGED
|
@@ -154,7 +154,7 @@ var LinkType;
|
|
|
154
154
|
LinkType["kNoType"] = "";
|
|
155
155
|
LinkType["kAccessory"] = "accessory";
|
|
156
156
|
})(LinkType = exports.LinkType || (exports.LinkType = {}));
|
|
157
|
-
exports.kEmptyLink = Object.assign(Object.assign({}, exports.kEmptyUnit), { type: LinkType.
|
|
157
|
+
exports.kEmptyLink = Object.assign(Object.assign({}, exports.kEmptyUnit), { type: LinkType.kNoType, max: 100, min: 0, accName: "acc", accId: "", accService: [] });
|
|
158
158
|
exports.kEmptyBinding = {
|
|
159
159
|
channel: "0", masterAddress: "0.0.0.0", masterPort: 5001, register: 0
|
|
160
160
|
};
|
|
@@ -301,7 +301,9 @@ exports.Sanitizers = {
|
|
|
301
301
|
link.accId = aLink.accId || "";
|
|
302
302
|
link.accName = aLink.accName || "";
|
|
303
303
|
link.accService = aLink.accService || [];
|
|
304
|
-
link.type = aLink.type ||
|
|
304
|
+
link.type = aLink.type || exports.kEmptyLink.type;
|
|
305
|
+
link.max = aLink.max || exports.kEmptyLink.max;
|
|
306
|
+
link.min = aLink.min || exports.kEmptyLink.min;
|
|
305
307
|
return link;
|
|
306
308
|
},
|
|
307
309
|
switchConfig: function (aSwitch) {
|
package/duotecno/types.ts
CHANGED
|
@@ -269,13 +269,15 @@ export interface Link extends UnitDef{
|
|
|
269
269
|
accId: string;
|
|
270
270
|
accService: Array<string>; // e.g. ["On", "Brightness"], ["TargetPosition", "CurrentPosition", ...]
|
|
271
271
|
type: LinkType;
|
|
272
|
+
max?: number;
|
|
273
|
+
min?: number;
|
|
272
274
|
unit?: Unit;
|
|
273
275
|
dtValue?: number | boolean | string;
|
|
274
276
|
hbValue?: number | boolean | string
|
|
275
277
|
}
|
|
276
278
|
|
|
277
279
|
export const kEmptyLink: Link = {
|
|
278
|
-
...kEmptyUnit, type: LinkType.
|
|
280
|
+
...kEmptyUnit, type: LinkType.kNoType, max: 100, min: 0, accName: "acc", accId: "", accService: []
|
|
279
281
|
};
|
|
280
282
|
|
|
281
283
|
|
|
@@ -519,7 +521,9 @@ export const Sanitizers = {
|
|
|
519
521
|
link.accId = aLink.accId || "";
|
|
520
522
|
link.accName = aLink.accName || "";
|
|
521
523
|
link.accService = aLink.accService || [];
|
|
522
|
-
link.type = aLink.type ||
|
|
524
|
+
link.type = aLink.type || kEmptyLink.type;
|
|
525
|
+
link.max = aLink.max || kEmptyLink.max;
|
|
526
|
+
link.min = aLink.min || kEmptyLink.min;
|
|
523
527
|
return link;
|
|
524
528
|
},
|
|
525
529
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-smartsystem",
|
|
3
3
|
"displayname": "Duotecno Bridge",
|
|
4
|
-
"version": "6.8.
|
|
4
|
+
"version": "6.8.3",
|
|
5
5
|
"description": "SmartServer (Proxy Websockets to TCP sockets, Smappee MQTT, Duotecno IP Nodes, Homekit interface)",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"author": "Johan Coppieters",
|
package/server/HB.js
CHANGED
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.hbUnsubscribe = exports.hbSubscribe = exports.hbGetValue = exports.hbSetState = exports.dt2hbvalue = exports.hb2dtvalue = exports.hbLogin = exports.hbAccessories = exports.hbService = exports.hbName = void 0;
|
|
12
|
+
exports.hbUnsubscribe = exports.hbSubscribe = exports.hbStop = exports.hbGetValue = exports.hbSetState = exports.dt2hbvalue = exports.hb2dtvalue = exports.hbLogin = exports.hbAccessories = exports.hbService = exports.hbName = void 0;
|
|
13
13
|
const logger_1 = require("../duotecno/logger");
|
|
14
14
|
const HA_API_1 = require("./HA-API");
|
|
15
15
|
let gAccessories = [];
|
|
@@ -173,14 +173,15 @@ function hbLoad(value, service) {
|
|
|
173
173
|
return (value) ? 100 : 0;
|
|
174
174
|
}
|
|
175
175
|
else if (typeof value === "number") {
|
|
176
|
-
|
|
176
|
+
value = Math.round(value);
|
|
177
|
+
if (value <= 1) {
|
|
177
178
|
return 0;
|
|
178
179
|
}
|
|
179
|
-
else if (value >=
|
|
180
|
+
else if (value >= 99) {
|
|
180
181
|
return 100;
|
|
181
182
|
}
|
|
182
183
|
else {
|
|
183
|
-
return
|
|
184
|
+
return value;
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
}
|
|
@@ -217,16 +218,31 @@ function hb2dtvalue(unit, value) {
|
|
|
217
218
|
return { dtTarget, dtValue };
|
|
218
219
|
}
|
|
219
220
|
exports.hb2dtvalue = hb2dtvalue;
|
|
220
|
-
function dt2hbvalue(unit, dontChangeValue) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (unit.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
221
|
+
function dt2hbvalue(unit, dontChangeValue, min, max) {
|
|
222
|
+
// tunr into (rounded) number
|
|
223
|
+
let value = (typeof unit.value === "boolean") ? (unit.value ? max : min) : Math.round(unit.value);
|
|
224
|
+
// Virtual motor: state = 0 -> stop, state = 1 -> up, state = 2 -> down
|
|
225
|
+
// Real motor: 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
|
|
226
|
+
if (unit.isUpDown()) {
|
|
227
|
+
// don't send status of real motor
|
|
228
|
+
return (value == 10) ? -1 : // stop [ virtual ]
|
|
229
|
+
(value == 11) ? max : // up [ virtual ]
|
|
230
|
+
(value == 12) ? min : undefined; // down [ virtual ]
|
|
231
|
+
// return ((value === 1) || (value === 3)) ? min : // down [ motor ]
|
|
232
|
+
// ((value === 2) || (value === 4)) ? max : // up [ motor ]
|
|
233
|
+
// (value == 0) ? dontChangeValue : // no change [ motor ]
|
|
234
|
+
// (value == 10) ? -1 : // stop [ virtual ]
|
|
235
|
+
// (value == 11) ? max : min; // up/down [ virtual ]
|
|
236
|
+
}
|
|
237
|
+
else if (unit.isDimmer()) {
|
|
238
|
+
return (value <= 1) ? 0 : (value >= 99) ? 100 : value;
|
|
239
|
+
}
|
|
240
|
+
else if (unit.isSwitch() || unit.isMood()) {
|
|
241
|
+
return (value) ? max : min;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
230
246
|
}
|
|
231
247
|
exports.dt2hbvalue = dt2hbvalue;
|
|
232
248
|
function hbValue(data, service) {
|
|
@@ -276,6 +292,28 @@ function hbGetValue(server, id, service, token) {
|
|
|
276
292
|
});
|
|
277
293
|
}
|
|
278
294
|
exports.hbGetValue = hbGetValue;
|
|
295
|
+
function hbStop(server, id, service, token) {
|
|
296
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
297
|
+
// does not exist, but we'll do a getCurrentPosition + setTargetPosition to this value
|
|
298
|
+
const respBody = yield (0, HA_API_1.httpRequest)(server + "/api/accessories/" + id, 'GET', "", HA_API_1.ContentType.json, {
|
|
299
|
+
Authorization: 'Bearer ' + token
|
|
300
|
+
});
|
|
301
|
+
if (respBody && respBody.error) {
|
|
302
|
+
(0, logger_1.log)("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
else if (respBody && respBody.values) {
|
|
306
|
+
(0, logger_1.log)("HB", "hbStop GetState: " + id + " -> " + JSON.stringify(respBody.values));
|
|
307
|
+
const data = { value: respBody.values.CurrentPosition, characteristicType: "TargetPosition" };
|
|
308
|
+
(0, logger_1.log)("HB", "hbStop SetState: " + id + " -> " + JSON.stringify(data));
|
|
309
|
+
yield (0, HA_API_1.httpRequest)(server + "/api/accessories/" + id, 'PUT', JSON.stringify(data), HA_API_1.ContentType.json, {
|
|
310
|
+
Authorization: 'Bearer ' + token
|
|
311
|
+
});
|
|
312
|
+
return hbValue(respBody.values, service);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
exports.hbStop = hbStop;
|
|
279
317
|
function hbSubscribe(callback) {
|
|
280
318
|
if (timer) {
|
|
281
319
|
clearInterval(timer);
|
package/server/HB.ts
CHANGED
|
@@ -177,12 +177,13 @@ function hbLoad(value: number | boolean, service: Array<string>): Record<string,
|
|
|
177
177
|
if (typeof value === "boolean") {
|
|
178
178
|
return (value) ? 100 : 0;
|
|
179
179
|
} else if (typeof value === "number") {
|
|
180
|
-
|
|
180
|
+
value = Math.round(value);
|
|
181
|
+
if (value <= 1) {
|
|
181
182
|
return 0;
|
|
182
|
-
} else if (value >=
|
|
183
|
+
} else if (value >= 99) {
|
|
183
184
|
return 100;
|
|
184
185
|
} else {
|
|
185
|
-
return
|
|
186
|
+
return value;
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
}
|
|
@@ -223,16 +224,32 @@ export function hb2dtvalue(unit: Unit, value: number | boolean): {dtValue: numbe
|
|
|
223
224
|
return {dtTarget, dtValue};
|
|
224
225
|
}
|
|
225
226
|
|
|
226
|
-
export function dt2hbvalue(unit: Unit, dontChangeValue: number): number {
|
|
227
|
-
|
|
227
|
+
export function dt2hbvalue(unit: Unit, dontChangeValue: number, min: number, max: number): number {
|
|
228
|
+
// tunr into (rounded) number
|
|
229
|
+
let value = (typeof unit.value === "boolean") ? (unit.value ? max : min) : Math.round(unit.value);
|
|
228
230
|
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
// Virtual motor: state = 0 -> stop, state = 1 -> up, state = 2 -> down
|
|
232
|
+
// Real motor: 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
|
|
233
|
+
if (unit.isUpDown()) {
|
|
234
|
+
// don't send status of real motor
|
|
235
|
+
return (value == 10) ? -1 : // stop [ virtual ]
|
|
236
|
+
(value == 11) ? max : // up [ virtual ]
|
|
237
|
+
(value == 12) ? min : undefined; // down [ virtual ]
|
|
238
|
+
// return ((value === 1) || (value === 3)) ? min : // down [ motor ]
|
|
239
|
+
// ((value === 2) || (value === 4)) ? max : // up [ motor ]
|
|
240
|
+
// (value == 0) ? dontChangeValue : // no change [ motor ]
|
|
241
|
+
// (value == 10) ? -1 : // stop [ virtual ]
|
|
242
|
+
// (value == 11) ? max : min; // up/down [ virtual ]
|
|
243
|
+
|
|
244
|
+
} else if (unit.isDimmer()) {
|
|
245
|
+
return (value <= 1) ? 0 : (value >= 99) ? 100 : value;
|
|
246
|
+
|
|
247
|
+
} else if (unit.isSwitch() || unit.isMood()) {
|
|
248
|
+
return (value) ? max : min;
|
|
234
249
|
|
|
235
|
-
|
|
250
|
+
} else {
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
236
253
|
}
|
|
237
254
|
|
|
238
255
|
function hbValue(data: Record<string, any>, service: Array<string>): number {
|
|
@@ -282,6 +299,28 @@ export async function hbGetValue(server: string, id: string, service: Array<stri
|
|
|
282
299
|
};
|
|
283
300
|
}
|
|
284
301
|
|
|
302
|
+
export async function hbStop(server: string, id: string, service: Array<string>, token: string) : Promise<number | undefined> {
|
|
303
|
+
// does not exist, but we'll do a getCurrentPosition + setTargetPosition to this value
|
|
304
|
+
const respBody = await httpRequest(server + "/api/accessories/"+id, 'GET', "", ContentType.json, {
|
|
305
|
+
Authorization: 'Bearer ' + token
|
|
306
|
+
});
|
|
307
|
+
if (respBody && respBody.error) {
|
|
308
|
+
log("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
|
|
309
|
+
return undefined;
|
|
310
|
+
|
|
311
|
+
} else if (respBody && respBody.values) {
|
|
312
|
+
log("HB", "hbStop GetState: " + id + " -> " + JSON.stringify(respBody.values));
|
|
313
|
+
|
|
314
|
+
const data = {value: respBody.values.CurrentPosition, characteristicType: "TargetPosition"};
|
|
315
|
+
log("HB", "hbStop SetState: " + id + " -> " + JSON.stringify(data));
|
|
316
|
+
|
|
317
|
+
await httpRequest(server + "/api/accessories/"+id, 'PUT', JSON.stringify(data), ContentType.json, {
|
|
318
|
+
Authorization: 'Bearer ' + token
|
|
319
|
+
});
|
|
320
|
+
return hbValue(respBody.values, service);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
285
324
|
export function hbSubscribe(callback) {
|
|
286
325
|
if (timer) {
|
|
287
326
|
clearInterval(timer);
|
package/server/smartapp.js
CHANGED
|
@@ -460,14 +460,22 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
460
460
|
(0, logger_1.debug)("smartapp", "Received DT value from unit: " + u.getDisplayName() + " -> " + u.value + " / " + u.status + " from unit");
|
|
461
461
|
for (const link of this.links) {
|
|
462
462
|
if (u.isUnit(link.masterAddress, link.masterPort, link.logicalNodeAddress, link.logicalAddress)) {
|
|
463
|
-
const hbValue = (0, HB_1.dt2hbvalue)(u, link.hbValue);
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
463
|
+
const hbValue = (0, HB_1.dt2hbvalue)(u, link.hbValue, link.min, link.max);
|
|
464
|
+
if (typeof hbValue != "undefined") {
|
|
465
|
+
if (hbValue === -1) {
|
|
466
|
+
(0, logger_1.log)("smartapp", "-> Virtual unit send 'stop' for accessory: " + link.accName + " of Duotecno unit: " + u.getDisplayName());
|
|
467
|
+
const val = yield (0, HB_1.hbStop)(this.config.apiHost + ":" + this.config.apiPort, link.accId, link.accService, this.token);
|
|
468
|
+
if (typeof val != "undefined")
|
|
469
|
+
link.hbValue = val;
|
|
470
|
+
}
|
|
471
|
+
else if (hbValue !== link.hbValue) {
|
|
472
|
+
(0, logger_1.log)("smartapp", "-> Setting HB value for accessory: " + link.accName + " to " + hbValue + " of Duotecno unit: " + u.getDisplayName() + " with value " + u.value);
|
|
473
|
+
link.hbValue = hbValue;
|
|
474
|
+
yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.accId, link.accService, hbValue, this.token);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
(0, logger_1.debug)("smartapp", "-> No change for accessory: " + link.accName + " to " + u.value + " / " + u.status);
|
|
478
|
+
}
|
|
471
479
|
}
|
|
472
480
|
}
|
|
473
481
|
}
|
|
@@ -504,7 +512,7 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
504
512
|
let message;
|
|
505
513
|
try {
|
|
506
514
|
if (context.action === "add") {
|
|
507
|
-
this.links.push(types_1.kEmptyLink);
|
|
515
|
+
this.links.push(Object.assign(Object.assign({}, types_1.kEmptyLink), { type: types_1.LinkType.kAccessory }));
|
|
508
516
|
inx = this.links.length - 1;
|
|
509
517
|
return this.ejs("linkDetail", context, { link: this.links[inx], id: inx,
|
|
510
518
|
masters: this.system.masters, accessories, units });
|
|
@@ -536,11 +544,15 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
536
544
|
// for now only accessory links.
|
|
537
545
|
const stype = context.getParam({ name: "type", type: "string", default: types_1.LinkType.kAccessory });
|
|
538
546
|
const name = context.getParam({ name: "name", type: "string", default: "--" });
|
|
547
|
+
// max & min values
|
|
548
|
+
const min = context.getParam({ name: "min", type: "integer", default: 0 });
|
|
549
|
+
const max = context.getParam({ name: "max", type: "integer", default: 100 });
|
|
539
550
|
// uniqiue id for the accessory
|
|
540
551
|
const accId = context.getParam({ name: "accId", type: "string", default: "" });
|
|
541
552
|
const accName = (0, HB_1.hbName)(accId);
|
|
542
553
|
const accService = (0, HB_1.hbService)(accId);
|
|
543
|
-
return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype };
|
|
554
|
+
return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype, min, max };
|
|
555
|
+
;
|
|
544
556
|
}
|
|
545
557
|
updateLink(inx, link) {
|
|
546
558
|
if ((inx >= 0) && (inx < this.links.length)) {
|
package/server/smartapp.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { Shelly } from './shelly';
|
|
|
22
22
|
import { P1 } from './p1';
|
|
23
23
|
import { setToken } from "../duotecno/config";
|
|
24
24
|
import { PowerBase } from "./powerbase";
|
|
25
|
-
import { dt2hbvalue, hb2dtvalue, hbAccessories, hbGetValue, hbLogin, hbName, hbService, hbSetState, hbSubscribe } from "./HB";
|
|
25
|
+
import { dt2hbvalue, hb2dtvalue, hbAccessories, hbGetValue, hbLogin, hbName, hbService, hbSetState, hbStop, hbSubscribe } from "./HB";
|
|
26
26
|
import { httpRequest, ContentType } from "./HA-API";
|
|
27
27
|
import { cleanStart, kEmptyProxy, ProxyConfig, setProxyConfig } from "./proxy";
|
|
28
28
|
|
|
@@ -279,7 +279,7 @@ export class SmartApp extends WebApp {
|
|
|
279
279
|
|
|
280
280
|
} else if (context.action === "restart") {
|
|
281
281
|
cleanStart(true);
|
|
282
|
-
|
|
282
|
+
|
|
283
283
|
} else {
|
|
284
284
|
// just show the proxy config
|
|
285
285
|
}
|
|
@@ -515,15 +515,22 @@ export class SmartApp extends WebApp {
|
|
|
515
515
|
debug("smartapp", "Received DT value from unit: " + u.getDisplayName() + " -> " + u.value + " / " + u.status + " from unit");
|
|
516
516
|
for (const link of this.links) {
|
|
517
517
|
if (u.isUnit(link.masterAddress, link.masterPort, link.logicalNodeAddress, link.logicalAddress)) {
|
|
518
|
-
const hbValue = dt2hbvalue(u, link.hbValue as number);
|
|
519
|
-
|
|
520
|
-
if (
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
518
|
+
const hbValue = dt2hbvalue(u, link.hbValue as number, link.min, link.max);
|
|
519
|
+
|
|
520
|
+
if (typeof hbValue != "undefined") {
|
|
521
|
+
if (hbValue === -1) {
|
|
522
|
+
log("smartapp", "-> Virtual unit send 'stop' for accessory: " + link.accName + " of Duotecno unit: " + u.getDisplayName());
|
|
523
|
+
const val = await hbStop(this.config.apiHost +":"+ this.config.apiPort, link.accId, link.accService, this.token);
|
|
524
|
+
if (typeof val != "undefined") link.hbValue = val;
|
|
525
|
+
|
|
526
|
+
} else if (hbValue !== link.hbValue) {
|
|
527
|
+
log("smartapp", "-> Setting HB value for accessory: " + link.accName + " to " + hbValue + " of Duotecno unit: " + u.getDisplayName() + " with value " + u.value);
|
|
528
|
+
link.hbValue = hbValue;
|
|
529
|
+
await hbSetState(this.config.apiHost +":"+ this.config.apiPort, link.accId, link.accService, hbValue, this.token);
|
|
530
|
+
|
|
531
|
+
} else {
|
|
532
|
+
debug("smartapp", "-> No change for accessory: " + link.accName + " to " + u.value + " / " + u.status);
|
|
533
|
+
}
|
|
527
534
|
}
|
|
528
535
|
}
|
|
529
536
|
};
|
|
@@ -559,7 +566,7 @@ export class SmartApp extends WebApp {
|
|
|
559
566
|
let message: string;
|
|
560
567
|
try {
|
|
561
568
|
if (context.action === "add") {
|
|
562
|
-
this.links.push(kEmptyLink);
|
|
569
|
+
this.links.push({...kEmptyLink, type: LinkType.kAccessory});
|
|
563
570
|
inx = this.links.length-1;
|
|
564
571
|
return this.ejs("linkDetail", context, { link: this.links[inx], id: inx,
|
|
565
572
|
masters: this.system.masters, accessories, units });
|
|
@@ -594,11 +601,15 @@ export class SmartApp extends WebApp {
|
|
|
594
601
|
const stype = context.getParam({name: "type", type: "string", default: LinkType.kAccessory });
|
|
595
602
|
const name = context.getParam({name: "name", type: "string", default: "--" });
|
|
596
603
|
|
|
604
|
+
// max & min values
|
|
605
|
+
const min = context.getParam({name: "min", type: "integer", default: 0 });
|
|
606
|
+
const max = context.getParam({name: "max", type: "integer", default: 100 });
|
|
607
|
+
|
|
597
608
|
// uniqiue id for the accessory
|
|
598
609
|
const accId = context.getParam({name: "accId", type: "string", default: "" });
|
|
599
610
|
const accName = hbName(accId);
|
|
600
611
|
const accService = hbService(accId);
|
|
601
|
-
return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype }
|
|
612
|
+
return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype, min, max};;
|
|
602
613
|
}
|
|
603
614
|
|
|
604
615
|
updateLink(inx: number, link: Link) {
|
|
@@ -66,8 +66,6 @@
|
|
|
66
66
|
</select>
|
|
67
67
|
<label for="unitSelector">or Pick a unit</label>
|
|
68
68
|
</div>
|
|
69
|
-
|
|
70
|
-
</div>
|
|
71
69
|
</div>
|
|
72
70
|
|
|
73
71
|
<div class="row">
|
|
@@ -89,6 +87,18 @@
|
|
|
89
87
|
</div>
|
|
90
88
|
</div>
|
|
91
89
|
|
|
90
|
+
<div class="row">
|
|
91
|
+
<div class="input-field col s4">
|
|
92
|
+
<label for="min">Off value</label>
|
|
93
|
+
<input placeholder="0" id="min" name="min" type="text" value="<%= link.min %>">
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="input-field col s4">
|
|
97
|
+
<label for="max">On value</label>
|
|
98
|
+
<input placeholder="0" id="mx" name="max" type="text" value="<%= link.max %>">
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
92
102
|
</form>
|
|
93
103
|
<%- include('/views/footer'); -%>
|
|
94
104
|
|
package/server/webapp.js
CHANGED
|
@@ -20,7 +20,7 @@ const url = require("url");
|
|
|
20
20
|
const qs = require("querystring");
|
|
21
21
|
const base_1 = require("./base");
|
|
22
22
|
const types = require("../duotecno/types");
|
|
23
|
-
const
|
|
23
|
+
const logger = require("../duotecno/logger");
|
|
24
24
|
const logger_1 = require("../duotecno/logger");
|
|
25
25
|
const child_process_1 = require("child_process");
|
|
26
26
|
;
|
|
@@ -310,9 +310,10 @@ class WebApp extends base_1.Base {
|
|
|
310
310
|
doServe(req, res) {
|
|
311
311
|
return __awaiter(this, void 0, void 0, function* () {
|
|
312
312
|
const context = yield this.parseRequest(req, res);
|
|
313
|
+
let prevRequest = "";
|
|
313
314
|
try {
|
|
314
315
|
if (this.needsLogin(context)) {
|
|
315
|
-
|
|
316
|
+
logger.debug("webapp", "checking ticket: " + context.cookies["DTicket"] + " = " + this.token + " for " + context.request + "/" + context.action + " => " + (context.cookies["DTicket"] == this.token));
|
|
316
317
|
// if a password is set, check if the tickets is valid, if not: redirect to login page
|
|
317
318
|
if (!this.token || (context.cookies["DTicket"] != this.token)) {
|
|
318
319
|
context.request = "login";
|
|
@@ -320,6 +321,10 @@ class WebApp extends base_1.Base {
|
|
|
320
321
|
}
|
|
321
322
|
}
|
|
322
323
|
const httpResult = yield this.doRequest(context);
|
|
324
|
+
// see if we can go to the original request...
|
|
325
|
+
if ((context.request === "login") && (context.path != "/login")) {
|
|
326
|
+
httpResult.header = Object.assign(Object.assign({}, httpResult.header), { "Set-Cookie": "DPath=" + context.path });
|
|
327
|
+
}
|
|
323
328
|
return httpResult;
|
|
324
329
|
}
|
|
325
330
|
catch (e) {
|
|
@@ -393,10 +398,11 @@ class WebApp extends base_1.Base {
|
|
|
393
398
|
const pw = context.getParam({ name: "password", type: "string" });
|
|
394
399
|
const user = context.getParam({ name: "user", type: "string" });
|
|
395
400
|
if (yield this.pwOK(context, user, pw)) {
|
|
396
|
-
|
|
401
|
+
const path = context.getCookie("DPath") || "/";
|
|
402
|
+
return this.redirect(path, { "Set-Cookie": "DTicket=" + this.token });
|
|
397
403
|
}
|
|
398
404
|
else {
|
|
399
|
-
return this.ejs("login", context, { message: "authentication failed" }, { "Set-Cookie": "DTicket=x" });
|
|
405
|
+
return this.ejs("login", context, { message: "authentication failed", user }, { "Set-Cookie": "DTicket=x" });
|
|
400
406
|
}
|
|
401
407
|
});
|
|
402
408
|
}
|
package/server/webapp.ts
CHANGED
|
@@ -9,7 +9,7 @@ import * as url from "url";
|
|
|
9
9
|
import * as qs from "querystring";
|
|
10
10
|
import { Base } from './base';
|
|
11
11
|
import * as types from "../duotecno/types";
|
|
12
|
-
import * as
|
|
12
|
+
import * as logger from "../duotecno/logger";
|
|
13
13
|
import { log } from "../duotecno/logger";
|
|
14
14
|
import { exec } from "child_process";
|
|
15
15
|
|
|
@@ -372,15 +372,20 @@ export class WebApp extends Base {
|
|
|
372
372
|
|
|
373
373
|
async doServe(req: http.IncomingMessage, res: http.ServerResponse): Promise<HttpResponse> {
|
|
374
374
|
const context = await this.parseRequest(req, res);
|
|
375
|
+
let prevRequest = "";
|
|
375
376
|
try {
|
|
376
377
|
if (this.needsLogin(context)) {
|
|
377
|
-
|
|
378
|
+
logger.debug("webapp", "checking ticket: " + context.cookies["DTicket"] + " = " + this.token + " for " + context.request+"/"+context.action + " => " + (context.cookies["DTicket"] == this.token));
|
|
378
379
|
// if a password is set, check if the tickets is valid, if not: redirect to login page
|
|
379
380
|
if (!this.token || (context.cookies["DTicket"] != this.token)) {
|
|
380
381
|
context.request = "login"; context.action = "";
|
|
381
382
|
}
|
|
382
383
|
}
|
|
383
384
|
const httpResult = await this.doRequest(context);
|
|
385
|
+
// see if we can go to the original request...
|
|
386
|
+
if ((context.request === "login") && (context.path != "/login")) {
|
|
387
|
+
httpResult.header = {...httpResult.header, "Set-Cookie": "DPath="+context.path};
|
|
388
|
+
}
|
|
384
389
|
return httpResult;
|
|
385
390
|
|
|
386
391
|
} catch (e) {
|
|
@@ -452,10 +457,11 @@ export class WebApp extends Base {
|
|
|
452
457
|
const user = context.getParam({name: "user", type: "string"});
|
|
453
458
|
|
|
454
459
|
if (await this.pwOK(context, user, pw)) {
|
|
455
|
-
|
|
460
|
+
const path = context.getCookie("DPath") || "/"
|
|
461
|
+
return this.redirect(path, {"Set-Cookie": "DTicket="+this.token});
|
|
456
462
|
|
|
457
463
|
} else {
|
|
458
|
-
return this.ejs("login", context, {message: "authentication failed"}, {"Set-Cookie": "DTicket=x"});
|
|
464
|
+
return this.ejs("login", context, {message: "authentication failed", user}, {"Set-Cookie": "DTicket=x"});
|
|
459
465
|
}
|
|
460
466
|
}
|
|
461
467
|
async doLogout(context: Context): Promise<HttpResponse> {
|