homebridge-smartsystem 6.7.1 → 6.7.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 +24 -2
- package/duotecno/protocol.js +7 -8
- package/duotecno/protocol.ts +7 -9
- package/duotecno/types.js +3 -2
- package/duotecno/types.ts +5 -3
- package/index.spec.ts +1 -1
- package/package.json +1 -1
- package/server/HB.js +166 -68
- package/server/HB.ts +183 -73
- package/server/smartapp.js +39 -18
- package/server/smartapp.ts +36 -18
- package/server/views/login.ejs +13 -1
- package/server/webapp.js +1 -1
- package/server/webapp.ts +1 -1
package/README.md
CHANGED
|
@@ -296,11 +296,15 @@ Homebridge UI version
|
|
|
296
296
|
### v6.7.0 - 19/06/2025
|
|
297
297
|
- 0: implemented "links" between HB accessories and Duotecno units.
|
|
298
298
|
- 1: updated the readme file
|
|
299
|
+
- 2: use HB Accessory characteristics in set/get, ignore status for the moment
|
|
300
|
+
- 2: remember/show username in login screen + set focus to password
|
|
299
301
|
|
|
300
302
|
## Hardware
|
|
301
303
|
A Raspberry Pi that connects to a Duotecno IP Node
|
|
302
304
|
and (if configured) to a Smappee Infinity (power and plugs)
|
|
303
305
|
and to http switches (bi-directional)
|
|
306
|
+
and to openHAB http (from v5.1)
|
|
307
|
+
and linked to HB accessories (from v6.7)
|
|
304
308
|
|
|
305
309
|
|
|
306
310
|
## Desired functions
|
package/config.json
CHANGED
|
@@ -258,11 +258,33 @@
|
|
|
258
258
|
"logicalAddress": 30,
|
|
259
259
|
"masterAddress": "192.168.0.97",
|
|
260
260
|
"masterPort": 5001,
|
|
261
|
-
"name": "
|
|
262
|
-
"displayName": "
|
|
261
|
+
"name": "unit",
|
|
262
|
+
"displayName": "unit",
|
|
263
263
|
"extendedType": 0,
|
|
264
264
|
"accId": "986b901a30d92cb7bd0205be90f65786c2d87c9a95d8a0a60cb4c048deda5ce6",
|
|
265
265
|
"accName": "Tim Klein",
|
|
266
|
+
"accService": [
|
|
267
|
+
"CurrentPosition",
|
|
268
|
+
"PositionState",
|
|
269
|
+
"TargetPosition",
|
|
270
|
+
"ObstructionDetected"
|
|
271
|
+
],
|
|
272
|
+
"type": "accessory"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"logicalNodeAddress": 3,
|
|
276
|
+
"logicalAddress": 30,
|
|
277
|
+
"masterAddress": "192.168.0.97",
|
|
278
|
+
"masterPort": 5001,
|
|
279
|
+
"name": "dimmers",
|
|
280
|
+
"displayName": "dimmers",
|
|
281
|
+
"extendedType": 0,
|
|
282
|
+
"accId": "da32bb657ad68d3fd63004015406e0cbde28355fc950204cec2fcd90d835e044",
|
|
283
|
+
"accName": "Somfy Dimmer",
|
|
284
|
+
"accService": [
|
|
285
|
+
"On",
|
|
286
|
+
"Brightness"
|
|
287
|
+
],
|
|
266
288
|
"type": "accessory"
|
|
267
289
|
}
|
|
268
290
|
]
|
package/duotecno/protocol.js
CHANGED
|
@@ -41,13 +41,15 @@ var Cmd;
|
|
|
41
41
|
Cmd[Cmd["Heartbeat"] = 215] = "Heartbeat";
|
|
42
42
|
Cmd[Cmd["DatabaseInfo"] = 209] = "DatabaseInfo";
|
|
43
43
|
Cmd[Cmd["SetSchedule"] = 217] = "SetSchedule";
|
|
44
|
+
Cmd[Cmd["reqSchedule"] = 218] = "reqSchedule";
|
|
45
|
+
Cmd[Cmd["reqNodeManagement"] = 220] = "reqNodeManagement";
|
|
44
46
|
Cmd[Cmd["Register"] = 224] = "Register";
|
|
45
47
|
})(Cmd || (Cmd = {}));
|
|
46
|
-
// for Set Switch/Dimmer/Control
|
|
48
|
+
// for Set Switch/Dimmer/Control/Motor/Sensor = 162, 163, 168, 182, 136
|
|
47
49
|
const reqDim = 3;
|
|
48
50
|
const reqOff = 9;
|
|
49
51
|
const reqOn = 10;
|
|
50
|
-
// for Login
|
|
52
|
+
// for Login = 214
|
|
51
53
|
const reqDisconnect = 0;
|
|
52
54
|
const reqConnect = 3;
|
|
53
55
|
// for DatabaseInfo = 209;
|
|
@@ -55,10 +57,7 @@ const reqDBInfo = 0;
|
|
|
55
57
|
const reqNodeInfo = 1;
|
|
56
58
|
const reqUnitInfo = 2;
|
|
57
59
|
const reqUnitStatus = 3;
|
|
58
|
-
// for
|
|
59
|
-
const reqSchedule = 218;
|
|
60
|
-
// for versions et al
|
|
61
|
-
const reqNodeManagement = 220;
|
|
60
|
+
// for reqNodeMgt, versions et al
|
|
62
61
|
var reqNodeAttributes;
|
|
63
62
|
(function (reqNodeAttributes) {
|
|
64
63
|
reqNodeAttributes[reqNodeAttributes["masterSupported"] = 0] = "masterSupported";
|
|
@@ -671,10 +670,10 @@ exports.Protocol = {
|
|
|
671
670
|
return [Cmd.DatabaseInfo, reqUnitStatus, node.logicalAddress, unit.logicalAddress, unit.type];
|
|
672
671
|
},
|
|
673
672
|
buildRequestSchedule() {
|
|
674
|
-
return [reqSchedule, 0];
|
|
673
|
+
return [Cmd.reqSchedule, 0];
|
|
675
674
|
},
|
|
676
675
|
buildRequestNodeMgt(method = reqNodeAttributes.nodeProtocol) {
|
|
677
|
-
return [reqNodeManagement, method];
|
|
676
|
+
return [Cmd.reqNodeManagement, method];
|
|
678
677
|
},
|
|
679
678
|
getCmdAndMethod: function (unit, value) {
|
|
680
679
|
switch (unit.type) {
|
package/duotecno/protocol.ts
CHANGED
|
@@ -38,15 +38,17 @@ enum Cmd {
|
|
|
38
38
|
Heartbeat = 215,
|
|
39
39
|
DatabaseInfo = 209,
|
|
40
40
|
SetSchedule = 217,
|
|
41
|
+
reqSchedule = 218,
|
|
42
|
+
reqNodeManagement = 220,
|
|
41
43
|
Register = 224
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
// for Set Switch/Dimmer/Control
|
|
46
|
+
// for Set Switch/Dimmer/Control/Motor/Sensor = 162, 163, 168, 182, 136
|
|
45
47
|
const reqDim = 3;
|
|
46
48
|
const reqOff = 9;
|
|
47
49
|
const reqOn = 10;
|
|
48
50
|
|
|
49
|
-
// for Login
|
|
51
|
+
// for Login = 214
|
|
50
52
|
const reqDisconnect = 0;
|
|
51
53
|
const reqConnect = 3;
|
|
52
54
|
|
|
@@ -56,11 +58,7 @@ const reqNodeInfo = 1;
|
|
|
56
58
|
const reqUnitInfo = 2;
|
|
57
59
|
const reqUnitStatus = 3;
|
|
58
60
|
|
|
59
|
-
// for
|
|
60
|
-
const reqSchedule = 218;
|
|
61
|
-
|
|
62
|
-
// for versions et al
|
|
63
|
-
const reqNodeManagement = 220;
|
|
61
|
+
// for reqNodeMgt, versions et al
|
|
64
62
|
export enum reqNodeAttributes {
|
|
65
63
|
masterSupported = 0,
|
|
66
64
|
isMaster = 4,
|
|
@@ -782,10 +780,10 @@ export const Protocol = {
|
|
|
782
780
|
},
|
|
783
781
|
|
|
784
782
|
buildRequestSchedule(): Message {
|
|
785
|
-
return [reqSchedule, 0];
|
|
783
|
+
return [Cmd.reqSchedule, 0];
|
|
786
784
|
},
|
|
787
785
|
buildRequestNodeMgt(method = reqNodeAttributes.nodeProtocol): Message {
|
|
788
|
-
return [reqNodeManagement, method];
|
|
786
|
+
return [Cmd.reqNodeManagement, method];
|
|
789
787
|
},
|
|
790
788
|
|
|
791
789
|
getCmdAndMethod: function(unit: Unit, value: number | boolean): CmdSet {
|
package/duotecno/types.js
CHANGED
|
@@ -153,7 +153,7 @@ var LinkType;
|
|
|
153
153
|
LinkType["kNoType"] = "";
|
|
154
154
|
LinkType["kAccessory"] = "accessory";
|
|
155
155
|
})(LinkType = exports.LinkType || (exports.LinkType = {}));
|
|
156
|
-
exports.kEmptyLink = Object.assign(Object.assign({}, exports.kEmptyUnit), { type: LinkType.kAccessory, accName: "acc", accId: "" });
|
|
156
|
+
exports.kEmptyLink = Object.assign(Object.assign({}, exports.kEmptyUnit), { type: LinkType.kAccessory, accName: "acc", accId: "", accService: [] });
|
|
157
157
|
exports.kEmptyBinding = {
|
|
158
158
|
channel: "0", masterAddress: "0.0.0.0", masterPort: 5001, register: 0
|
|
159
159
|
};
|
|
@@ -298,6 +298,7 @@ exports.Sanitizers = {
|
|
|
298
298
|
const link = exports.Sanitizers.unitDef(aLink);
|
|
299
299
|
link.accId = aLink.accId || "";
|
|
300
300
|
link.accName = aLink.accName || "";
|
|
301
|
+
link.accService = aLink.accService || [];
|
|
301
302
|
link.type = aLink.type || LinkType.kNoType;
|
|
302
303
|
return link;
|
|
303
304
|
},
|
|
@@ -329,7 +330,7 @@ exports.Sanitizers = {
|
|
|
329
330
|
return exports.Sanitizers.linkConfig({ name: aLink.name, unitName: aLink.unitName,
|
|
330
331
|
masterAddress: aLink.masterAddress, masterPort: aLink.masterPort,
|
|
331
332
|
logicalAddress: aLink.logicalAddress, logicalNodeAddress: aLink.logicalNodeAddress,
|
|
332
|
-
accName: aLink.accName, accId: aLink.accId, type: aLink.type });
|
|
333
|
+
accName: aLink.accName, accService: aLink.accService, accId: aLink.accId, type: aLink.type });
|
|
333
334
|
},
|
|
334
335
|
ruleConfig(rule) {
|
|
335
336
|
rule = Object.assign({}, rule);
|
package/duotecno/types.ts
CHANGED
|
@@ -266,6 +266,7 @@ export enum LinkType {
|
|
|
266
266
|
export interface Link extends UnitDef{
|
|
267
267
|
accName: string;
|
|
268
268
|
accId: string;
|
|
269
|
+
accService: Array<string>; // e.g. ["On", "Brightness"], ["TargetPosition", "CurrentPosition", ...]
|
|
269
270
|
type: LinkType;
|
|
270
271
|
unit?: Unit;
|
|
271
272
|
status?: number | boolean;
|
|
@@ -273,8 +274,8 @@ export interface Link extends UnitDef{
|
|
|
273
274
|
}
|
|
274
275
|
|
|
275
276
|
export const kEmptyLink: Link = {
|
|
276
|
-
...kEmptyUnit, type: LinkType.kAccessory, accName: "acc", accId: ""
|
|
277
|
-
}
|
|
277
|
+
...kEmptyUnit, type: LinkType.kAccessory, accName: "acc", accId: "", accService: []
|
|
278
|
+
};
|
|
278
279
|
|
|
279
280
|
|
|
280
281
|
///////////
|
|
@@ -515,6 +516,7 @@ export const Sanitizers = {
|
|
|
515
516
|
const link = <Link>Sanitizers.unitDef(aLink);
|
|
516
517
|
link.accId = aLink.accId || "";
|
|
517
518
|
link.accName = aLink.accName || "";
|
|
519
|
+
link.accService = aLink.accService || [];
|
|
518
520
|
link.type = aLink.type || LinkType.kNoType;
|
|
519
521
|
return link;
|
|
520
522
|
},
|
|
@@ -550,7 +552,7 @@ export const Sanitizers = {
|
|
|
550
552
|
return Sanitizers.linkConfig({ name: aLink.name, unitName: aLink.unitName,
|
|
551
553
|
masterAddress: aLink.masterAddress, masterPort: aLink.masterPort,
|
|
552
554
|
logicalAddress: aLink.logicalAddress, logicalNodeAddress: aLink.logicalNodeAddress,
|
|
553
|
-
accName: aLink.accName, accId: aLink.accId, type: aLink.type });
|
|
555
|
+
accName: aLink.accName, accService: aLink.accService, accId: aLink.accId, type: aLink.type });
|
|
554
556
|
},
|
|
555
557
|
|
|
556
558
|
ruleConfig(rule): Rule {
|
package/index.spec.ts
CHANGED
|
@@ -35,7 +35,7 @@ try {
|
|
|
35
35
|
debug("spec", config);
|
|
36
36
|
config.debug = true;
|
|
37
37
|
|
|
38
|
-
const platform = new Platform(console, config, <API>{registerPlatformAccessories: adder});
|
|
38
|
+
const platform = new Platform(console as any, config, <API>{registerPlatformAccessories: adder});
|
|
39
39
|
|
|
40
40
|
} catch(err) {
|
|
41
41
|
log("spec", err);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-smartsystem",
|
|
3
3
|
"displayname": "Duotecno Bridge",
|
|
4
|
-
"version": "6.7.
|
|
4
|
+
"version": "6.7.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.hbGetState = exports.hbSetState = exports.hbLogin = exports.hbAccessories = exports.hbName = void 0;
|
|
12
|
+
exports.hbUnsubscribe = exports.hbSubscribe = exports.hbGetState = exports.hbSetState = 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 = [];
|
|
@@ -19,13 +19,17 @@ function hbName(id) {
|
|
|
19
19
|
return ((_a = gAccessories.find(item => item.id === id)) === null || _a === void 0 ? void 0 : _a.name) || "Unknown";
|
|
20
20
|
}
|
|
21
21
|
exports.hbName = hbName;
|
|
22
|
+
function hbService(id) {
|
|
23
|
+
var _a;
|
|
24
|
+
return ((_a = gAccessories.find(item => item.id === id)) === null || _a === void 0 ? void 0 : _a.service) || [];
|
|
25
|
+
}
|
|
26
|
+
exports.hbService = hbService;
|
|
22
27
|
function hbAccessories(server, token) {
|
|
23
28
|
return __awaiter(this, void 0, void 0, function* () {
|
|
24
29
|
if (gAccessories.length === 0) {
|
|
25
30
|
const list = yield (0, HA_API_1.httpRequest)(server + '/api/accessories', 'GET', "", HA_API_1.ContentType.json, {
|
|
26
31
|
Authorization: 'Bearer ' + token
|
|
27
32
|
});
|
|
28
|
-
// console.log("getAccessories: " + JSON.stringify(list));
|
|
29
33
|
gAccessories = list
|
|
30
34
|
.filter(item => item.accessoryInformation.Manufacturer != "homebridge.io")
|
|
31
35
|
.map(item => ({
|
|
@@ -33,7 +37,7 @@ function hbAccessories(server, token) {
|
|
|
33
37
|
manufacturer: item.accessoryInformation.Manufacturer,
|
|
34
38
|
id: item.uniqueId,
|
|
35
39
|
type: item.type,
|
|
36
|
-
service: ["On", "Brightness"] // TargetPosition
|
|
40
|
+
service: Object.keys(item.values) // ["On", "Brightness"] // TargetPosition
|
|
37
41
|
}));
|
|
38
42
|
}
|
|
39
43
|
return gAccessories;
|
|
@@ -47,58 +51,101 @@ function hbAccessories(server, token) {
|
|
|
47
51
|
}
|
|
48
52
|
exports.hbAccessories = hbAccessories;
|
|
49
53
|
/*
|
|
50
|
-
const
|
|
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
|
-
|
|
54
|
+
const getAccessories: [
|
|
55
|
+
{
|
|
56
|
+
"name": "Living",
|
|
57
|
+
"manufacturer": "Somfy",
|
|
58
|
+
"id": "06e5b66f78fe0136ea075e742a7fb388587329a4717393b21aaa180083305704",
|
|
59
|
+
"type": "WindowCovering",
|
|
60
|
+
"service": [
|
|
61
|
+
"CurrentPosition",
|
|
62
|
+
"PositionState",
|
|
63
|
+
"TargetPosition"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "Tim Klein",
|
|
68
|
+
"manufacturer": "Somfy",
|
|
69
|
+
"id": "986b901a30d92cb7bd0205be90f65786c2d87c9a95d8a0a60cb4c048deda5ce6",
|
|
70
|
+
"type": "WindowCovering",
|
|
71
|
+
"service": [
|
|
72
|
+
"CurrentPosition",
|
|
73
|
+
"PositionState",
|
|
74
|
+
"TargetPosition",
|
|
75
|
+
"ObstructionDetected"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "Keuken",
|
|
80
|
+
"manufacturer": "Somfy",
|
|
81
|
+
"id": "f18e175bdc561a8e2318662ebd225c21338623668152b23bf383849b085f340b",
|
|
82
|
+
"type": "WindowCovering",
|
|
83
|
+
"service": [
|
|
84
|
+
"CurrentPosition",
|
|
85
|
+
"PositionState",
|
|
86
|
+
"TargetPosition"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "Connectivity kit-ledBox",
|
|
91
|
+
"manufacturer": "Somfy",
|
|
92
|
+
"id": "04b9348dda8ef417605a8661c2850721975cfcbd1398c6425ab9824ec238bc96",
|
|
93
|
+
"type": "Lightbulb",
|
|
94
|
+
"service": [
|
|
95
|
+
"On",
|
|
96
|
+
"Brightness"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "Tim Groot",
|
|
101
|
+
"manufacturer": "Somfy",
|
|
102
|
+
"id": "81892c3728bb9e401c915ec4be9cb6cff2de98a27b0573f8396b94d386db99b0",
|
|
103
|
+
"type": "WindowCovering",
|
|
104
|
+
"service": [
|
|
105
|
+
"CurrentPosition",
|
|
106
|
+
"PositionState",
|
|
107
|
+
"TargetPosition",
|
|
108
|
+
"ObstructionDetected"
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "Bottom-Left",
|
|
113
|
+
"manufacturer": "Duotecno-Coppieters",
|
|
114
|
+
"id": "5f7746ab9fdcab159ee4806750bfcac5539df233e9dafe7ed1a568deecceb8ca",
|
|
115
|
+
"type": "Switch",
|
|
116
|
+
"service": [
|
|
117
|
+
"On"
|
|
118
|
+
]
|
|
81
119
|
},
|
|
82
|
-
{
|
|
83
|
-
"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
{
|
|
93
|
-
"
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
120
|
+
{
|
|
121
|
+
"name": "Somfy Dimmer",
|
|
122
|
+
"manufacturer": "Duotecno-Coppieters",
|
|
123
|
+
"id": "da32bb657ad68d3fd63004015406e0cbde28355fc950204cec2fcd90d835e044",
|
|
124
|
+
"type": "Lightbulb",
|
|
125
|
+
"service": [
|
|
126
|
+
"On",
|
|
127
|
+
"Brightness"
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "Alles uitje",
|
|
132
|
+
"manufacturer": "Duotecno-Coppieters",
|
|
133
|
+
"id": "f0645ea91ad7730f9c9115364ad8c3a2bd58d3fdce7bfcf00e84a7cc1017c217",
|
|
134
|
+
"type": "Switch",
|
|
135
|
+
"service": [
|
|
136
|
+
"On"
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "Alles aantje",
|
|
141
|
+
"manufacturer": "Duotecno-Coppieters",
|
|
142
|
+
"id": "cdcab0c257fcd190e31d3cc736419f9fc32c4ff6e406e2d4d2cc51727d323fe9",
|
|
143
|
+
"type": "Switch",
|
|
144
|
+
"service": [
|
|
145
|
+
"On"
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
]
|
|
102
149
|
*/
|
|
103
150
|
//
|
|
104
151
|
// Get a token for the Homebridge API
|
|
@@ -120,33 +167,84 @@ function hbLogin(server, username, password) {
|
|
|
120
167
|
});
|
|
121
168
|
}
|
|
122
169
|
exports.hbLogin = hbLogin;
|
|
170
|
+
function hbLoad(value, service) {
|
|
171
|
+
function hbvalue(value) {
|
|
172
|
+
if (typeof value === "boolean") {
|
|
173
|
+
return (value) ? 100 : 0;
|
|
174
|
+
}
|
|
175
|
+
else if (typeof value === "number") {
|
|
176
|
+
if (value <= 2) {
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
else if (value >= 98) {
|
|
180
|
+
return 100;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
return Math.round(value);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const data = {};
|
|
188
|
+
if (service.includes("On")) {
|
|
189
|
+
data["value"] = value;
|
|
190
|
+
data["characteristicType"] = "On";
|
|
191
|
+
}
|
|
192
|
+
if (service.includes("Brightness")) {
|
|
193
|
+
data["value"] = hbvalue(value);
|
|
194
|
+
data["characteristicType"] = "Brightness";
|
|
195
|
+
}
|
|
196
|
+
if (service.includes("TargetPosition")) {
|
|
197
|
+
data["value"] = hbvalue(value);
|
|
198
|
+
data["characteristicType"] = "TargetPosition";
|
|
199
|
+
}
|
|
200
|
+
;
|
|
201
|
+
return data;
|
|
202
|
+
}
|
|
203
|
+
function hbValue(data, service) {
|
|
204
|
+
const values = {};
|
|
205
|
+
if (service.includes("Brightness")) {
|
|
206
|
+
values["value"] = parseInt(data["Brightness"]);
|
|
207
|
+
}
|
|
208
|
+
if (service.includes("On")) {
|
|
209
|
+
values["state"] = parseInt(data["On"]);
|
|
210
|
+
}
|
|
211
|
+
if (service.includes("TargetPosition")) {
|
|
212
|
+
values["value"] = parseInt(data["TargetPosition"]);
|
|
213
|
+
}
|
|
214
|
+
return values;
|
|
215
|
+
}
|
|
123
216
|
// send a status change to HB Accessory
|
|
124
|
-
function hbSetState(server, id, value, state, token) {
|
|
217
|
+
function hbSetState(server, id, service, value, state, token) {
|
|
125
218
|
return __awaiter(this, void 0, void 0, function* () {
|
|
126
|
-
const data =
|
|
127
|
-
|
|
128
|
-
value
|
|
129
|
-
};
|
|
130
|
-
(0, logger_1.log)("HB", "hbSetState: " + id + " -> " + value + ", state: " + state);
|
|
219
|
+
const data = hbLoad(value, service);
|
|
220
|
+
(0, logger_1.log)("HB", "hbSetState: " + id + " -> " + JSON.stringify(data));
|
|
131
221
|
const respBody = yield (0, HA_API_1.httpRequest)(server + "/api/accessories/" + id, 'PUT', JSON.stringify(data), HA_API_1.ContentType.json, {
|
|
132
222
|
Authorization: 'Bearer ' + token
|
|
133
223
|
});
|
|
224
|
+
if (respBody && respBody.error) {
|
|
225
|
+
(0, logger_1.log)("HB", "hbSetState: " + id + " -> error: " + JSON.stringify(respBody));
|
|
226
|
+
}
|
|
134
227
|
});
|
|
135
228
|
}
|
|
136
229
|
exports.hbSetState = hbSetState;
|
|
137
230
|
// send a status change to HB Accessory
|
|
138
|
-
function hbGetState(server, id, token) {
|
|
231
|
+
function hbGetState(server, id, service, token) {
|
|
139
232
|
return __awaiter(this, void 0, void 0, function* () {
|
|
140
|
-
const data = {
|
|
141
|
-
characteristicType: "TargetPosition",
|
|
142
|
-
};
|
|
143
233
|
const respBody = yield (0, HA_API_1.httpRequest)(server + "/api/accessories/" + id, 'GET', "", HA_API_1.ContentType.json, {
|
|
144
234
|
Authorization: 'Bearer ' + token
|
|
145
235
|
});
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
236
|
+
if (respBody && respBody.error) {
|
|
237
|
+
(0, logger_1.log)("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
|
|
238
|
+
return {};
|
|
239
|
+
}
|
|
240
|
+
else if (respBody && respBody.values) {
|
|
241
|
+
return hbValue(respBody.values, service);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
(0, logger_1.log)("HB", "hbGetState: " + id + " -> no values?? : " + JSON.stringify(respBody));
|
|
245
|
+
return {};
|
|
246
|
+
}
|
|
247
|
+
;
|
|
150
248
|
});
|
|
151
249
|
}
|
|
152
250
|
exports.hbGetState = hbGetState;
|
package/server/HB.ts
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
import { err, log } from "../duotecno/logger";
|
|
2
2
|
import { ContentType, httpRequest } from "./HA-API";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
export interface HBAccessory {
|
|
5
|
+
name: string,
|
|
6
|
+
manufacturer: string,
|
|
7
|
+
id: string,
|
|
8
|
+
type: string,
|
|
9
|
+
service: Array<string>
|
|
10
|
+
// Dimmers: [On, Brightness],
|
|
11
|
+
// Switch: [On],
|
|
12
|
+
// WindowCovering: [TargetPosition, PositionState, ObstructionDetected, CurrentPosition]
|
|
13
|
+
}
|
|
14
|
+
let gAccessories: Array<HBAccessory> = [];
|
|
5
15
|
let timer = null;
|
|
6
16
|
|
|
7
17
|
export function hbName(id: string): string {
|
|
8
18
|
return gAccessories.find(item => item.id === id)?.name || "Unknown";
|
|
9
19
|
}
|
|
20
|
+
export function hbService(id: string): Array<string> {
|
|
21
|
+
return gAccessories.find(item => item.id === id)?.service || [];
|
|
22
|
+
}
|
|
10
23
|
|
|
11
|
-
export async function hbAccessories(server: string, token: string): Promise<Array<
|
|
24
|
+
export async function hbAccessories(server: string, token: string): Promise<Array<HBAccessory>> {
|
|
12
25
|
if (gAccessories.length === 0) {
|
|
13
26
|
|
|
14
27
|
const list = await httpRequest(server+'/api/accessories', 'GET', "", ContentType.json, {
|
|
15
28
|
Authorization: 'Bearer ' + token
|
|
16
29
|
});
|
|
17
|
-
// console.log("getAccessories: " + JSON.stringify(list));
|
|
18
30
|
|
|
19
31
|
gAccessories = list
|
|
20
32
|
.filter(item =>
|
|
@@ -25,7 +37,7 @@ export async function hbAccessories(server: string, token: string): Promise<Arra
|
|
|
25
37
|
manufacturer: item.accessoryInformation.Manufacturer,
|
|
26
38
|
id: item.uniqueId,
|
|
27
39
|
type: item.type,
|
|
28
|
-
service: ["On", "Brightness"] // TargetPosition
|
|
40
|
+
service: Object.keys(item.values) // ["On", "Brightness"] // TargetPosition
|
|
29
41
|
})
|
|
30
42
|
);
|
|
31
43
|
}
|
|
@@ -41,58 +53,101 @@ export async function hbAccessories(server: string, token: string): Promise<Arra
|
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
/*
|
|
44
|
-
const
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
|
|
94
|
-
"
|
|
95
|
-
|
|
56
|
+
const getAccessories: [
|
|
57
|
+
{
|
|
58
|
+
"name": "Living",
|
|
59
|
+
"manufacturer": "Somfy",
|
|
60
|
+
"id": "06e5b66f78fe0136ea075e742a7fb388587329a4717393b21aaa180083305704",
|
|
61
|
+
"type": "WindowCovering",
|
|
62
|
+
"service": [
|
|
63
|
+
"CurrentPosition",
|
|
64
|
+
"PositionState",
|
|
65
|
+
"TargetPosition"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "Tim Klein",
|
|
70
|
+
"manufacturer": "Somfy",
|
|
71
|
+
"id": "986b901a30d92cb7bd0205be90f65786c2d87c9a95d8a0a60cb4c048deda5ce6",
|
|
72
|
+
"type": "WindowCovering",
|
|
73
|
+
"service": [
|
|
74
|
+
"CurrentPosition",
|
|
75
|
+
"PositionState",
|
|
76
|
+
"TargetPosition",
|
|
77
|
+
"ObstructionDetected"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "Keuken",
|
|
82
|
+
"manufacturer": "Somfy",
|
|
83
|
+
"id": "f18e175bdc561a8e2318662ebd225c21338623668152b23bf383849b085f340b",
|
|
84
|
+
"type": "WindowCovering",
|
|
85
|
+
"service": [
|
|
86
|
+
"CurrentPosition",
|
|
87
|
+
"PositionState",
|
|
88
|
+
"TargetPosition"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "Connectivity kit-ledBox",
|
|
93
|
+
"manufacturer": "Somfy",
|
|
94
|
+
"id": "04b9348dda8ef417605a8661c2850721975cfcbd1398c6425ab9824ec238bc96",
|
|
95
|
+
"type": "Lightbulb",
|
|
96
|
+
"service": [
|
|
97
|
+
"On",
|
|
98
|
+
"Brightness"
|
|
99
|
+
]
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "Tim Groot",
|
|
103
|
+
"manufacturer": "Somfy",
|
|
104
|
+
"id": "81892c3728bb9e401c915ec4be9cb6cff2de98a27b0573f8396b94d386db99b0",
|
|
105
|
+
"type": "WindowCovering",
|
|
106
|
+
"service": [
|
|
107
|
+
"CurrentPosition",
|
|
108
|
+
"PositionState",
|
|
109
|
+
"TargetPosition",
|
|
110
|
+
"ObstructionDetected"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "Bottom-Left",
|
|
115
|
+
"manufacturer": "Duotecno-Coppieters",
|
|
116
|
+
"id": "5f7746ab9fdcab159ee4806750bfcac5539df233e9dafe7ed1a568deecceb8ca",
|
|
117
|
+
"type": "Switch",
|
|
118
|
+
"service": [
|
|
119
|
+
"On"
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "Somfy Dimmer",
|
|
124
|
+
"manufacturer": "Duotecno-Coppieters",
|
|
125
|
+
"id": "da32bb657ad68d3fd63004015406e0cbde28355fc950204cec2fcd90d835e044",
|
|
126
|
+
"type": "Lightbulb",
|
|
127
|
+
"service": [
|
|
128
|
+
"On",
|
|
129
|
+
"Brightness"
|
|
130
|
+
]
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "Alles uitje",
|
|
134
|
+
"manufacturer": "Duotecno-Coppieters",
|
|
135
|
+
"id": "f0645ea91ad7730f9c9115364ad8c3a2bd58d3fdce7bfcf00e84a7cc1017c217",
|
|
136
|
+
"type": "Switch",
|
|
137
|
+
"service": [
|
|
138
|
+
"On"
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"name": "Alles aantje",
|
|
143
|
+
"manufacturer": "Duotecno-Coppieters",
|
|
144
|
+
"id": "cdcab0c257fcd190e31d3cc736419f9fc32c4ff6e406e2d4d2cc51727d323fe9",
|
|
145
|
+
"type": "Switch",
|
|
146
|
+
"service": [
|
|
147
|
+
"On"
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
]
|
|
96
151
|
*/
|
|
97
152
|
|
|
98
153
|
|
|
@@ -116,31 +171,86 @@ export async function hbLogin(server: string, username: string, password: string
|
|
|
116
171
|
}
|
|
117
172
|
|
|
118
173
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
value
|
|
174
|
+
function hbLoad(value: number | boolean, service: Array<string>): Record<string, number | boolean> {
|
|
175
|
+
function hbvalue(value: number | boolean): number {
|
|
176
|
+
if (typeof value === "boolean") {
|
|
177
|
+
return (value) ? 100 : 0;
|
|
178
|
+
} else if (typeof value === "number") {
|
|
179
|
+
if (value <= 2) {
|
|
180
|
+
return 0;
|
|
181
|
+
} else if (value >= 98) {
|
|
182
|
+
return 100;
|
|
183
|
+
} else {
|
|
184
|
+
return Math.round(value);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const data = {};
|
|
190
|
+
|
|
191
|
+
if (service.includes("On")) {
|
|
192
|
+
data["value"] = value;
|
|
193
|
+
data["characteristicType"] = "On";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (service.includes("Brightness")) {
|
|
197
|
+
data["value"] = hbvalue(value);
|
|
198
|
+
data["characteristicType"] = "Brightness";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (service.includes("TargetPosition")) {
|
|
202
|
+
data["value"] = hbvalue(value);
|
|
203
|
+
data["characteristicType"] = "TargetPosition";
|
|
124
204
|
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
205
|
+
return data;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function hbValue(data: Record<string, any>, service: Array<string>): {value?: number, state?: number} {
|
|
209
|
+
const values = {};
|
|
210
|
+
if (service.includes("Brightness")) {
|
|
211
|
+
values["value"] = parseInt(data["Brightness"]);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (service.includes("On")) {
|
|
215
|
+
values["state"] = parseInt(data["On"])
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (service.includes("TargetPosition")) {
|
|
219
|
+
values["value"] = parseInt(data["TargetPosition"]);
|
|
220
|
+
}
|
|
221
|
+
return values
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// send a status change to HB Accessory
|
|
225
|
+
export async function hbSetState(server: string, id: string, service: Array<string>, value: number | boolean, state: number, token: string) : Promise<void> {
|
|
226
|
+
const data = hbLoad(value, service);
|
|
227
|
+
|
|
228
|
+
log("HB", "hbSetState: " + id + " -> " + JSON.stringify(data));
|
|
229
|
+
const respBody = await httpRequest(server + "/api/accessories/"+id, 'PUT', JSON.stringify(data), ContentType.json, {
|
|
230
|
+
Authorization: 'Bearer ' + token
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
if (respBody && respBody.error) {
|
|
234
|
+
log("HB", "hbSetState: " + id + " -> error: " + JSON.stringify(respBody))
|
|
235
|
+
}
|
|
129
236
|
}
|
|
130
237
|
|
|
131
238
|
// send a status change to HB Accessory
|
|
132
|
-
export async function hbGetState(server: string, id: string, token: string) : Promise<{value
|
|
133
|
-
const data = {
|
|
134
|
-
characteristicType: "TargetPosition",
|
|
135
|
-
};
|
|
239
|
+
export async function hbGetState(server: string, id: string, service: Array<string>, token: string) : Promise<{value?: number, state?: number}> {
|
|
136
240
|
const respBody = await httpRequest(server + "/api/accessories/"+id, 'GET', "", ContentType.json, {
|
|
137
241
|
Authorization: 'Bearer ' + token
|
|
138
242
|
});
|
|
243
|
+
if (respBody && respBody.error) {
|
|
244
|
+
log("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
|
|
245
|
+
return {};
|
|
246
|
+
|
|
247
|
+
} else if (respBody && respBody.values) {
|
|
248
|
+
return hbValue(respBody.values, service);
|
|
139
249
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
250
|
+
} else {
|
|
251
|
+
log("HB", "hbGetState: " + id + " -> no values?? : " + JSON.stringify(respBody));
|
|
252
|
+
return {};
|
|
253
|
+
};
|
|
144
254
|
}
|
|
145
255
|
|
|
146
256
|
export function hbSubscribe(callback) {
|
package/server/smartapp.js
CHANGED
|
@@ -56,7 +56,8 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
56
56
|
this.system.emitter.on('plug', this.alertSwitch.bind(this));
|
|
57
57
|
// get some configurated params
|
|
58
58
|
this.port = this.config.port || this.port || 80;
|
|
59
|
-
this.password = platform.config.password;
|
|
59
|
+
this.password = platform.config.password || this.config.password || "";
|
|
60
|
+
this.user = platform.config.user || this.config.user || "pi";
|
|
60
61
|
this.system = system;
|
|
61
62
|
this.power = power;
|
|
62
63
|
this.platform = platform;
|
|
@@ -118,6 +119,10 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
118
119
|
try {
|
|
119
120
|
const access_token = yield this.checkLogin(this.config.apiHost + ":" + this.config.apiPort, user, pw);
|
|
120
121
|
if (access_token) {
|
|
122
|
+
// save username / password in config
|
|
123
|
+
this.config.username = user;
|
|
124
|
+
this.config.password = pw;
|
|
125
|
+
this.writeConfig();
|
|
121
126
|
this.token = access_token;
|
|
122
127
|
(0, config_1.setToken)(this.token);
|
|
123
128
|
return true;
|
|
@@ -403,16 +408,24 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
403
408
|
if (this.links.length && (yield this.checkToken())) {
|
|
404
409
|
for (const link of this.links) {
|
|
405
410
|
if ((link.type === types_1.LinkType.kAccessory) && link.unit) {
|
|
406
|
-
|
|
411
|
+
let { value, state } = yield (0, HB_1.hbGetState)(this.config.apiHost + ":" + this.config.apiPort, link.accId, link.accService, this.token);
|
|
407
412
|
(0, logger_1.debug)("smartapp", "Received HB value for accessory: " + link.accName + " -> " + value + " / " + state + " from accessory");
|
|
408
|
-
if (value
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
link.
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
413
|
+
if (typeof value != "undefined") {
|
|
414
|
+
if (link.unit.isUpDown())
|
|
415
|
+
value = (value >= 50) ? 4 : 5;
|
|
416
|
+
if (link.unit.isDimmer)
|
|
417
|
+
value = (value <= 1) ? 1 : (value >= 99) ? 100 : value;
|
|
418
|
+
if (link.unit.isSwitch() || link.unit.isMood())
|
|
419
|
+
value = (value > 50) ? 1 : 0;
|
|
420
|
+
if (value !== link.value) { // || state !== link.status
|
|
421
|
+
(0, logger_1.log)("smartapp", " -> Setting value of Duotecno unit: " + link.unit.getDisplayName() + " to " + value + " of HB accessory: " + link.accName);
|
|
422
|
+
link.value = value;
|
|
423
|
+
// link.status = state;
|
|
424
|
+
link.unit.setState(value);
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
(0, logger_1.debug)("smartapp", " -> No change for unit: " + link.unit.getDisplayName() + " to " + value);
|
|
428
|
+
}
|
|
416
429
|
}
|
|
417
430
|
}
|
|
418
431
|
}
|
|
@@ -424,11 +437,18 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
424
437
|
(0, logger_1.debug)("smartapp", "Received DT value from unit: " + u.getDisplayName() + " -> " + u.value + " / " + u.status + " from unit");
|
|
425
438
|
for (const link of this.links) {
|
|
426
439
|
if (u.isUnit(link.masterAddress, link.masterPort, link.logicalNodeAddress, link.logicalAddress)) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
440
|
+
let value = (typeof u.value === "boolean") ? (u.value ? 100 : 0) : u.value;
|
|
441
|
+
if (u.isUpDown())
|
|
442
|
+
value = ((value === 1) || (value === 3)) ? 100 : 0;
|
|
443
|
+
if (u.isDimmer)
|
|
444
|
+
value = (value <= 2) ? 0 : (value >= 99) ? 100 : value;
|
|
445
|
+
if (u.isSwitch() || link.unit.isMood())
|
|
446
|
+
value = (value) ? 1 : 0;
|
|
447
|
+
if (link.value !== value) { // || link.status !== u.status
|
|
448
|
+
(0, logger_1.log)("smartapp", " -> Setting HB value for accessory: " + link.accName + " to " + value + " / " + u.status + " of Duotecno unit: " + u.getDisplayName());
|
|
449
|
+
link.value = value;
|
|
430
450
|
link.status = u.status;
|
|
431
|
-
yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.accId, u.value, u.status, this.token);
|
|
451
|
+
yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.accId, link.accService, u.value, u.status, this.token);
|
|
432
452
|
}
|
|
433
453
|
else {
|
|
434
454
|
(0, logger_1.debug)("smartapp", " -> No change for accessory: " + link.accName + " to " + u.value + " / " + u.status);
|
|
@@ -503,11 +523,12 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
503
523
|
// uniqiue id for the accessory
|
|
504
524
|
const accId = context.getParam({ name: "accId", type: "string", default: "" });
|
|
505
525
|
const accName = (0, HB_1.hbName)(accId);
|
|
506
|
-
|
|
526
|
+
const accService = (0, HB_1.hbService)(accId);
|
|
527
|
+
return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype };
|
|
507
528
|
}
|
|
508
|
-
updateLink(inx,
|
|
529
|
+
updateLink(inx, link) {
|
|
509
530
|
if ((inx >= 0) && (inx < this.links.length)) {
|
|
510
|
-
this.links[inx] =
|
|
531
|
+
this.links[inx] = link;
|
|
511
532
|
this.initLinks();
|
|
512
533
|
this.writeConfig();
|
|
513
534
|
}
|
|
@@ -548,7 +569,7 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
548
569
|
else {
|
|
549
570
|
if (link.type == types_1.LinkType.kAccessory) {
|
|
550
571
|
if (yield this.checkToken())
|
|
551
|
-
yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.id, link.unit.value, link.unit.status, this.token);
|
|
572
|
+
yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.id, link.service, link.unit.value, link.unit.status, this.token);
|
|
552
573
|
}
|
|
553
574
|
else {
|
|
554
575
|
(0, logger_1.err)("smartapp", "Don't know how to set a link of type " + link.type);
|
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 { hbAccessories, hbGetState, hbLogin, hbName, hbSetState, hbSubscribe } from "./HB";
|
|
25
|
+
import { hbAccessories, hbGetState, hbLogin, hbName, hbService, hbSetState, hbSubscribe } from "./HB";
|
|
26
26
|
import { httpRequest, ContentType } from "./HA-API";
|
|
27
27
|
|
|
28
28
|
const kMaster = {name: "master", type: "string", default: "0.0.0.0:5001"} as const;
|
|
@@ -72,7 +72,8 @@ export class SmartApp extends WebApp {
|
|
|
72
72
|
|
|
73
73
|
// get some configurated params
|
|
74
74
|
this.port = this.config.port || this.port || 80;
|
|
75
|
-
this.password = platform.config.password;
|
|
75
|
+
this.password = platform.config.password || this.config.password || "";
|
|
76
|
+
this.user = platform.config.user || this.config.user || "pi";
|
|
76
77
|
|
|
77
78
|
this.system = system;
|
|
78
79
|
this.power = power;
|
|
@@ -145,6 +146,11 @@ export class SmartApp extends WebApp {
|
|
|
145
146
|
try {
|
|
146
147
|
const access_token = await this.checkLogin(this.config.apiHost +":"+ this.config.apiPort, user, pw);
|
|
147
148
|
if (access_token) {
|
|
149
|
+
// save username / password in config
|
|
150
|
+
this.config.username = user;
|
|
151
|
+
this.config.password = pw;
|
|
152
|
+
this.writeConfig();
|
|
153
|
+
|
|
148
154
|
this.token = access_token;
|
|
149
155
|
setToken(this.token);
|
|
150
156
|
return true;
|
|
@@ -453,15 +459,21 @@ export class SmartApp extends WebApp {
|
|
|
453
459
|
if (this.links.length && await this.checkToken()) {
|
|
454
460
|
for (const link of this.links) {
|
|
455
461
|
if ((link.type === LinkType.kAccessory) && link.unit) {
|
|
456
|
-
|
|
462
|
+
let {value, state} = await hbGetState(this.config.apiHost +":"+ this.config.apiPort, link.accId, link.accService, this.token);
|
|
457
463
|
debug("smartapp", "Received HB value for accessory: " + link.accName + " -> " + value + " / " + state + " from accessory");
|
|
458
|
-
if (value
|
|
459
|
-
|
|
460
|
-
link.value = value;
|
|
461
|
-
link.
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
464
|
+
if (typeof value != "undefined") {
|
|
465
|
+
if (link.unit.isUpDown()) value = (value >= 50) ? 4 : 5;
|
|
466
|
+
if (link.unit.isDimmer) value = (value <= 1) ? 1 : (value >= 99) ? 100 : value;
|
|
467
|
+
if (link.unit.isSwitch() || link.unit.isMood()) value = (value > 50) ? 1 : 0;
|
|
468
|
+
|
|
469
|
+
if (value !== link.value) { // || state !== link.status
|
|
470
|
+
log("smartapp", " -> Setting value of Duotecno unit: " + link.unit.getDisplayName() + " to " + value + " of HB accessory: " + link.accName);
|
|
471
|
+
link.value = value;
|
|
472
|
+
// link.status = state;
|
|
473
|
+
link.unit.setState(value);
|
|
474
|
+
} else {
|
|
475
|
+
debug("smartapp", " -> No change for unit: " + link.unit.getDisplayName() + " to " + value);
|
|
476
|
+
}
|
|
465
477
|
}
|
|
466
478
|
}
|
|
467
479
|
}
|
|
@@ -474,11 +486,16 @@ export class SmartApp extends WebApp {
|
|
|
474
486
|
debug("smartapp", "Received DT value from unit: " + u.getDisplayName() + " -> " + u.value + " / " + u.status + " from unit");
|
|
475
487
|
for (const link of this.links) {
|
|
476
488
|
if (u.isUnit(link.masterAddress, link.masterPort, link.logicalNodeAddress, link.logicalAddress)) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
489
|
+
let value = (typeof u.value === "boolean") ? (u.value ? 100 : 0) : u.value;
|
|
490
|
+
if (u.isUpDown()) value = ((value === 1) || (value === 3)) ? 100 : 0;
|
|
491
|
+
if (u.isDimmer) value = (value <= 2) ? 0 : (value >= 99) ? 100 : value;
|
|
492
|
+
if (u.isSwitch() || link.unit.isMood()) value = (value) ? 1 : 0;
|
|
493
|
+
|
|
494
|
+
if (link.value !== value) { // || link.status !== u.status
|
|
495
|
+
log("smartapp", " -> Setting HB value for accessory: " + link.accName + " to " + value + " / " + u.status + " of Duotecno unit: " + u.getDisplayName());
|
|
496
|
+
link.value = value;
|
|
480
497
|
link.status = u.status;
|
|
481
|
-
await hbSetState(this.config.apiHost +":"+ this.config.apiPort, link.accId, u.value, u.status, this.token);
|
|
498
|
+
await hbSetState(this.config.apiHost +":"+ this.config.apiPort, link.accId, link.accService, u.value, u.status, this.token);
|
|
482
499
|
|
|
483
500
|
} else {
|
|
484
501
|
debug("smartapp", " -> No change for accessory: " + link.accName + " to " + u.value + " / " + u.status);
|
|
@@ -555,12 +572,13 @@ export class SmartApp extends WebApp {
|
|
|
555
572
|
// uniqiue id for the accessory
|
|
556
573
|
const accId = context.getParam({name: "accId", type: "string", default: "" });
|
|
557
574
|
const accName = hbName(accId);
|
|
558
|
-
|
|
575
|
+
const accService = hbService(accId);
|
|
576
|
+
return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype };
|
|
559
577
|
}
|
|
560
578
|
|
|
561
|
-
updateLink(inx: number,
|
|
579
|
+
updateLink(inx: number, link: Link) {
|
|
562
580
|
if ((inx >= 0) && (inx < this.links.length)) {
|
|
563
|
-
this.links[inx] =
|
|
581
|
+
this.links[inx] = link;
|
|
564
582
|
this.initLinks();
|
|
565
583
|
this.writeConfig();
|
|
566
584
|
}
|
|
@@ -608,7 +626,7 @@ export class SmartApp extends WebApp {
|
|
|
608
626
|
} else {
|
|
609
627
|
if (link.type == LinkType.kAccessory) {
|
|
610
628
|
if (await this.checkToken())
|
|
611
|
-
await hbSetState(this.config.apiHost +":"+ this.config.apiPort, link.id, link.unit.value, link.unit.status, this.token)
|
|
629
|
+
await hbSetState(this.config.apiHost +":"+ this.config.apiPort, link.id, link.service, link.unit.value, link.unit.status, this.token)
|
|
612
630
|
|
|
613
631
|
} else {
|
|
614
632
|
err("smartapp", "Don't know how to set a link of type " + link.type);
|
package/server/views/login.ejs
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
<div class="row">
|
|
35
35
|
<div class="input-field col s12">
|
|
36
|
-
<input type="text" name="user" id="user" value="" />
|
|
36
|
+
<input type="text" name="user" id="user" value="<%= user %>" />
|
|
37
37
|
<label for="user">Username</label>
|
|
38
38
|
</div>
|
|
39
39
|
</div>
|
|
@@ -54,4 +54,16 @@
|
|
|
54
54
|
|
|
55
55
|
</body>
|
|
56
56
|
<%- include('/views/scripts'); -%>
|
|
57
|
+
<script>
|
|
58
|
+
// select password field if username is filled
|
|
59
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
60
|
+
var user = document.getElementById('user');
|
|
61
|
+
var password = document.getElementById('password');
|
|
62
|
+
if (user.value) {
|
|
63
|
+
setTimeout(password.focus.bind(password), 100);
|
|
64
|
+
} else {
|
|
65
|
+
user.focus();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
</script>
|
|
57
69
|
</html>
|
package/server/webapp.js
CHANGED
package/server/webapp.ts
CHANGED
|
@@ -444,7 +444,7 @@ export class WebApp extends Base {
|
|
|
444
444
|
return this.doTryLogin(context);
|
|
445
445
|
|
|
446
446
|
} else {
|
|
447
|
-
return this.ejs("login", context, {});
|
|
447
|
+
return this.ejs("login", context, {user: this.user});
|
|
448
448
|
}
|
|
449
449
|
}
|
|
450
450
|
async doTryLogin(context: Context): Promise<HttpResponse> {
|