homebridge-smartsystem 6.7.1 → 6.7.2
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/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 +143 -68
- package/server/HB.ts +161 -73
- package/server/smartapp.js +15 -9
- package/server/smartapp.ts +17 -10
- 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/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.2",
|
|
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
|
+
]
|
|
81
98
|
},
|
|
82
|
-
{
|
|
83
|
-
"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
"
|
|
96
|
-
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
+
]
|
|
119
|
+
},
|
|
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,61 @@ function hbLogin(server, username, password) {
|
|
|
120
167
|
});
|
|
121
168
|
}
|
|
122
169
|
exports.hbLogin = hbLogin;
|
|
170
|
+
function hbLoad(value, service) {
|
|
171
|
+
const data = {};
|
|
172
|
+
if (service.includes("On")) {
|
|
173
|
+
data["value"] = value;
|
|
174
|
+
data["characteristicType"] = "On";
|
|
175
|
+
}
|
|
176
|
+
if (service.includes("Brightness")) {
|
|
177
|
+
data["value"] = value;
|
|
178
|
+
data["characteristicType"] = "Brightness";
|
|
179
|
+
}
|
|
180
|
+
if (service.includes("TargetPosition")) {
|
|
181
|
+
data["value"] = value;
|
|
182
|
+
data["characteristicType"] = "TargetPosition";
|
|
183
|
+
}
|
|
184
|
+
;
|
|
185
|
+
return data;
|
|
186
|
+
}
|
|
187
|
+
function hbValue(data, service) {
|
|
188
|
+
const values = {};
|
|
189
|
+
if (service.includes("Brightness")) {
|
|
190
|
+
values["value"] = parseInt(data["Brightness"]);
|
|
191
|
+
}
|
|
192
|
+
if (service.includes("On")) {
|
|
193
|
+
values["state"] = parseInt(data["On"]);
|
|
194
|
+
}
|
|
195
|
+
if (service.includes("TargetPosition")) {
|
|
196
|
+
values["value"] = parseInt(data["TargetPosition"]);
|
|
197
|
+
}
|
|
198
|
+
return values;
|
|
199
|
+
}
|
|
123
200
|
// send a status change to HB Accessory
|
|
124
|
-
function hbSetState(server, id, value, state, token) {
|
|
201
|
+
function hbSetState(server, id, service, value, state, token) {
|
|
125
202
|
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);
|
|
203
|
+
const data = hbLoad(value, service);
|
|
204
|
+
(0, logger_1.log)("HB", "hbSetState: " + id + " -> " + JSON.stringify(data));
|
|
131
205
|
const respBody = yield (0, HA_API_1.httpRequest)(server + "/api/accessories/" + id, 'PUT', JSON.stringify(data), HA_API_1.ContentType.json, {
|
|
132
206
|
Authorization: 'Bearer ' + token
|
|
133
207
|
});
|
|
208
|
+
if (respBody && respBody.error) {
|
|
209
|
+
(0, logger_1.log)("HB", "hbSetState: " + id + " -> error: " + JSON.stringify(respBody));
|
|
210
|
+
}
|
|
134
211
|
});
|
|
135
212
|
}
|
|
136
213
|
exports.hbSetState = hbSetState;
|
|
137
214
|
// send a status change to HB Accessory
|
|
138
|
-
function hbGetState(server, id, token) {
|
|
215
|
+
function hbGetState(server, id, service, token) {
|
|
139
216
|
return __awaiter(this, void 0, void 0, function* () {
|
|
140
|
-
const data = {
|
|
141
|
-
characteristicType: "TargetPosition",
|
|
142
|
-
};
|
|
143
217
|
const respBody = yield (0, HA_API_1.httpRequest)(server + "/api/accessories/" + id, 'GET', "", HA_API_1.ContentType.json, {
|
|
144
218
|
Authorization: 'Bearer ' + token
|
|
145
219
|
});
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
220
|
+
if (respBody && respBody.error) {
|
|
221
|
+
(0, logger_1.log)("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
|
|
222
|
+
return {};
|
|
223
|
+
}
|
|
224
|
+
return hbValue(respBody.values, service);
|
|
150
225
|
});
|
|
151
226
|
}
|
|
152
227
|
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
|
|
|
@@ -115,32 +170,65 @@ export async function hbLogin(server: string, username: string, password: string
|
|
|
115
170
|
}
|
|
116
171
|
}
|
|
117
172
|
|
|
173
|
+
function hbLoad(value: number | boolean, service: Array<string>): Record<string, number | boolean> {
|
|
174
|
+
const data = {};
|
|
175
|
+
|
|
176
|
+
if (service.includes("On")) {
|
|
177
|
+
data["value"] = value;
|
|
178
|
+
data["characteristicType"] = "On";
|
|
179
|
+
}
|
|
180
|
+
if (service.includes("Brightness")) {
|
|
181
|
+
data["value"] = value;
|
|
182
|
+
data["characteristicType"] = "Brightness";
|
|
183
|
+
}
|
|
184
|
+
if (service.includes("TargetPosition")) {
|
|
185
|
+
data["value"] = value;
|
|
186
|
+
data["characteristicType"] = "TargetPosition";
|
|
187
|
+
};
|
|
188
|
+
return data;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function hbValue(data: Record<string, any>, service: Array<string>): {value?: number, state?: number} {
|
|
192
|
+
const values = {};
|
|
193
|
+
if (service.includes("Brightness")) {
|
|
194
|
+
values["value"] = parseInt(data["Brightness"]);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (service.includes("On")) {
|
|
198
|
+
values["state"] = parseInt(data["On"])
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (service.includes("TargetPosition")) {
|
|
202
|
+
values["value"] = parseInt(data["TargetPosition"]);
|
|
203
|
+
}
|
|
204
|
+
return values
|
|
205
|
+
}
|
|
118
206
|
|
|
119
207
|
// send a status change to HB Accessory
|
|
120
|
-
export async function hbSetState(server: string, id: string, value: number | boolean, state: number, token: string) : Promise<void> {
|
|
121
|
-
const data =
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
208
|
+
export async function hbSetState(server: string, id: string, service: Array<string>, value: number | boolean, state: number, token: string) : Promise<void> {
|
|
209
|
+
const data = hbLoad(value, service);
|
|
210
|
+
|
|
211
|
+
log("HB", "hbSetState: " + id + " -> " + JSON.stringify(data));
|
|
212
|
+
const respBody = await httpRequest(server + "/api/accessories/"+id, 'PUT', JSON.stringify(data), ContentType.json, {
|
|
213
|
+
Authorization: 'Bearer ' + token
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (respBody && respBody.error) {
|
|
217
|
+
log("HB", "hbSetState: " + id + " -> error: " + JSON.stringify(respBody))
|
|
218
|
+
}
|
|
129
219
|
}
|
|
130
220
|
|
|
131
221
|
// 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
|
-
};
|
|
222
|
+
export async function hbGetState(server: string, id: string, service: Array<string>, token: string) : Promise<{value?: number, state?: number}> {
|
|
136
223
|
const respBody = await httpRequest(server + "/api/accessories/"+id, 'GET', "", ContentType.json, {
|
|
137
224
|
Authorization: 'Bearer ' + token
|
|
138
225
|
});
|
|
226
|
+
if (respBody && respBody.error) {
|
|
227
|
+
log("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
|
|
228
|
+
return {};
|
|
229
|
+
}
|
|
139
230
|
|
|
140
|
-
|
|
141
|
-
const value = parseInt(respBody.values.TargetPosition);
|
|
142
|
-
const state = parseInt(respBody.values.PositionState);
|
|
143
|
-
return {value, state};
|
|
231
|
+
return hbValue(respBody.values, service);
|
|
144
232
|
}
|
|
145
233
|
|
|
146
234
|
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,12 +408,12 @@ 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
|
-
const { value, state } = yield (0, HB_1.hbGetState)(this.config.apiHost + ":" + this.config.apiPort, link.accId, this.token);
|
|
411
|
+
const { 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 !== link.value) { // || state !== link.status
|
|
413
|
+
if ((typeof value != "undefined") && (value !== link.value)) { // || state !== link.status
|
|
409
414
|
(0, logger_1.log)("smartapp", " -> Setting value of Duotecno unit: " + link.unit.getDisplayName() + " to " + value + " of HB accessory: " + link.accName);
|
|
410
415
|
link.value = value;
|
|
411
|
-
link.status = state;
|
|
416
|
+
// link.status = state;
|
|
412
417
|
link.unit.setState(value);
|
|
413
418
|
}
|
|
414
419
|
else {
|
|
@@ -428,7 +433,7 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
428
433
|
(0, logger_1.log)("smartapp", " -> Setting HB value for accessory: " + link.accName + " to " + u.value + " / " + u.status + " of Duotecno unit: " + u.getDisplayName());
|
|
429
434
|
link.value = u.value;
|
|
430
435
|
link.status = u.status;
|
|
431
|
-
yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.accId, u.value, u.status, this.token);
|
|
436
|
+
yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.accId, link.accService, u.value, u.status, this.token);
|
|
432
437
|
}
|
|
433
438
|
else {
|
|
434
439
|
(0, logger_1.debug)("smartapp", " -> No change for accessory: " + link.accName + " to " + u.value + " / " + u.status);
|
|
@@ -503,11 +508,12 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
503
508
|
// uniqiue id for the accessory
|
|
504
509
|
const accId = context.getParam({ name: "accId", type: "string", default: "" });
|
|
505
510
|
const accName = (0, HB_1.hbName)(accId);
|
|
506
|
-
|
|
511
|
+
const accService = (0, HB_1.hbService)(accId);
|
|
512
|
+
return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype };
|
|
507
513
|
}
|
|
508
|
-
updateLink(inx,
|
|
514
|
+
updateLink(inx, link) {
|
|
509
515
|
if ((inx >= 0) && (inx < this.links.length)) {
|
|
510
|
-
this.links[inx] =
|
|
516
|
+
this.links[inx] = link;
|
|
511
517
|
this.initLinks();
|
|
512
518
|
this.writeConfig();
|
|
513
519
|
}
|
|
@@ -548,7 +554,7 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
548
554
|
else {
|
|
549
555
|
if (link.type == types_1.LinkType.kAccessory) {
|
|
550
556
|
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);
|
|
557
|
+
yield (0, HB_1.hbSetState)(this.config.apiHost + ":" + this.config.apiPort, link.id, link.service, link.unit.value, link.unit.status, this.token);
|
|
552
558
|
}
|
|
553
559
|
else {
|
|
554
560
|
(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,12 +459,12 @@ 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
|
-
const {value, state} = await hbGetState(this.config.apiHost +":"+ this.config.apiPort, link.accId, this.token);
|
|
462
|
+
const {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 !== link.value) { // || state !== link.status
|
|
464
|
+
if ((typeof value != "undefined") && (value !== link.value)) { // || state !== link.status
|
|
459
465
|
log("smartapp", " -> Setting value of Duotecno unit: " + link.unit.getDisplayName() + " to " + value + " of HB accessory: " + link.accName);
|
|
460
466
|
link.value = value;
|
|
461
|
-
link.status = state;
|
|
467
|
+
// link.status = state;
|
|
462
468
|
link.unit.setState(value);
|
|
463
469
|
} else {
|
|
464
470
|
debug("smartapp", " -> No change for unit: " + link.unit.getDisplayName() + " to " + value);
|
|
@@ -478,7 +484,7 @@ export class SmartApp extends WebApp {
|
|
|
478
484
|
log("smartapp", " -> Setting HB value for accessory: " + link.accName + " to " + u.value + " / " + u.status + " of Duotecno unit: " + u.getDisplayName());
|
|
479
485
|
link.value = u.value;
|
|
480
486
|
link.status = u.status;
|
|
481
|
-
await hbSetState(this.config.apiHost +":"+ this.config.apiPort, link.accId, u.value, u.status, this.token);
|
|
487
|
+
await hbSetState(this.config.apiHost +":"+ this.config.apiPort, link.accId, link.accService, u.value, u.status, this.token);
|
|
482
488
|
|
|
483
489
|
} else {
|
|
484
490
|
debug("smartapp", " -> No change for accessory: " + link.accName + " to " + u.value + " / " + u.status);
|
|
@@ -555,12 +561,13 @@ export class SmartApp extends WebApp {
|
|
|
555
561
|
// uniqiue id for the accessory
|
|
556
562
|
const accId = context.getParam({name: "accId", type: "string", default: "" });
|
|
557
563
|
const accName = hbName(accId);
|
|
558
|
-
|
|
564
|
+
const accService = hbService(accId);
|
|
565
|
+
return { accId, accName, accService, name, masterAddress, masterPort, logicalAddress, logicalNodeAddress, type: stype };
|
|
559
566
|
}
|
|
560
567
|
|
|
561
|
-
updateLink(inx: number,
|
|
568
|
+
updateLink(inx: number, link: Link) {
|
|
562
569
|
if ((inx >= 0) && (inx < this.links.length)) {
|
|
563
|
-
this.links[inx] =
|
|
570
|
+
this.links[inx] = link;
|
|
564
571
|
this.initLinks();
|
|
565
572
|
this.writeConfig();
|
|
566
573
|
}
|
|
@@ -608,7 +615,7 @@ export class SmartApp extends WebApp {
|
|
|
608
615
|
} else {
|
|
609
616
|
if (link.type == LinkType.kAccessory) {
|
|
610
617
|
if (await this.checkToken())
|
|
611
|
-
await hbSetState(this.config.apiHost +":"+ this.config.apiPort, link.id, link.unit.value, link.unit.status, this.token)
|
|
618
|
+
await hbSetState(this.config.apiHost +":"+ this.config.apiPort, link.id, link.service, link.unit.value, link.unit.status, this.token)
|
|
612
619
|
|
|
613
620
|
} else {
|
|
614
621
|
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> {
|