homebridge-smartsystem 6.8.13 → 7.0.1
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/.vscode/launch.json +15 -4
- package/MIXINS-GUIDE.md +210 -0
- package/README.md +32 -0
- package/SETTINGS-UPDATE.md +195 -0
- package/TESTING-GUIDE.md +220 -0
- package/WEBSOCKET-API.md +303 -0
- package/accessories/accessory.js +20 -4
- package/accessories/accessory.ts +19 -4
- package/accessories/bulb.js +0 -5
- package/accessories/bulb.ts +1 -5
- package/accessories/dimmer.js +11 -3
- package/accessories/dimmer.ts +11 -4
- package/accessories/garagedoor.js +19 -9
- package/accessories/garagedoor.ts +21 -9
- package/accessories/lock.js +11 -3
- package/accessories/lock.ts +10 -3
- package/accessories/temperature.js +10 -2
- package/accessories/temperature.ts +9 -2
- package/accessories/windowcovering.js +30 -14
- package/accessories/windowcovering.ts +29 -15
- package/config.json +57 -19
- package/duotecno/types.js +48 -2
- package/duotecno/types.ts +75 -1
- package/package.json +12 -4
- package/raspberry-pi-setup.sh +80 -0
- package/server/HA-API.js +4 -1
- package/server/HA-API.ts +4 -1
- package/server/HB.js +6 -1
- package/server/HB.ts +5 -1
- package/server/mDNS.js +168 -0
- package/server/mDNS.ts +198 -0
- package/server/platform.js +7 -0
- package/server/platform.ts +8 -0
- package/server/proxy.js +58 -19
- package/server/proxy.ts +31 -3
- package/server/smartapp.js +397 -18
- package/server/smartapp.ts +432 -20
- package/server/socapp.js +2 -1
- package/server/socapp.ts +2 -1
- package/server/views/assets/Duotecno_logo_black.svg +10 -0
- package/server/views/assets/Duotecno_logo_white.svg +10 -0
- package/server/views/device-details.ejs +139 -0
- package/server/views/device-list.ejs +44 -0
- package/server/views/footer.ejs +0 -1
- package/server/views/head.ejs +9 -0
- package/server/views/homekit.ejs +2 -9
- package/server/views/link-details.ejs +2 -15
- package/server/views/link-list.ejs +2 -9
- package/server/views/login.ejs +8 -9
- package/server/views/master-detail.ejs +3 -9
- package/server/views/master-list.ejs +3 -10
- package/server/views/nav.ejs +44 -11
- package/server/views/node-details.ejs +1 -8
- package/server/views/power-binding.ejs +3 -10
- package/server/views/power-rule.ejs +3 -10
- package/server/views/power.ejs +8 -15
- package/server/views/proxy.ejs +1 -8
- package/server/views/scripts.ejs +10 -0
- package/server/views/service-list.ejs +2 -9
- package/server/views/settings.ejs +61 -22
- package/server/views/style.ejs +49 -5
- package/server/views/switch-details.ejs +2 -9
- package/server/views/switch-list.ejs +2 -9
- package/server/webapp.js +150 -11
- package/server/webapp.ts +166 -14
- package/{index.spec.js → testHB.js} +1 -1
- package/{run.js → testProxy.js} +2 -2
- package/{run.ts → testProxy.ts} +1 -1
- package/testWS.js +137 -0
- package/testWS.ts +174 -0
- package/run9998.js +0 -7
- package/run9998.ts +0 -6
- package/run9999.js +0 -7
- package/run9999.ts +0 -6
- /package/{index.spec.ts → testHB.ts} +0 -0
|
@@ -93,13 +93,21 @@ class WindowCovering extends accessory_1.Accessory {
|
|
|
93
93
|
getCurrentPosition(next) {
|
|
94
94
|
(0, logger_1.log)("accessory", "get CurrentPosition was called of " + this.name);
|
|
95
95
|
this.unit.reqState(unit => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
try {
|
|
97
|
+
if (!unit) {
|
|
98
|
+
return next(new Error("Unit state not available"));
|
|
99
|
+
}
|
|
100
|
+
if (unit.status == types_1.UnitState.kOpen)
|
|
101
|
+
next(null, 100);
|
|
102
|
+
else if (unit.status == types_1.UnitState.kClosed)
|
|
103
|
+
next(null, 0);
|
|
104
|
+
else
|
|
105
|
+
next(null, 50);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
next(err);
|
|
109
|
+
}
|
|
110
|
+
}).catch(err => next(err));
|
|
103
111
|
}
|
|
104
112
|
setTargetPosition(value, next) {
|
|
105
113
|
// homekit is giving 0-100 for windows
|
|
@@ -123,13 +131,21 @@ class WindowCovering extends accessory_1.Accessory {
|
|
|
123
131
|
getTargetPosition(next) {
|
|
124
132
|
(0, logger_1.log)("accessory", "get Characteristic.TargetPosition was called");
|
|
125
133
|
this.unit.reqState(unit => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
try {
|
|
135
|
+
if (!unit) {
|
|
136
|
+
return next(new Error("Unit state not available"));
|
|
137
|
+
}
|
|
138
|
+
if (this.unit.status == types_1.UnitState.kOpen)
|
|
139
|
+
next(null, 100);
|
|
140
|
+
else if (this.unit.status == types_1.UnitState.kClosed)
|
|
141
|
+
next(null, 0);
|
|
142
|
+
else
|
|
143
|
+
next(null, 50);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
next(err);
|
|
147
|
+
}
|
|
148
|
+
}).catch(err => next(err));
|
|
133
149
|
}
|
|
134
150
|
// called in response to Duotecno status update
|
|
135
151
|
updateState() {
|
|
@@ -110,15 +110,22 @@ export class WindowCovering extends Accessory {
|
|
|
110
110
|
|
|
111
111
|
getCurrentPosition(next) {
|
|
112
112
|
log("accessory", "get CurrentPosition was called of " + this.name);
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
this.unit.reqState(unit => {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
try {
|
|
116
|
+
if (!unit) {
|
|
117
|
+
return next(new Error("Unit state not available"));
|
|
118
|
+
}
|
|
119
|
+
if (unit.status == UnitState.kOpen)
|
|
120
|
+
next(null, 100);
|
|
121
|
+
else if (unit.status == UnitState.kClosed)
|
|
122
|
+
next(null, 0);
|
|
123
|
+
else
|
|
124
|
+
next(null, 50);
|
|
125
|
+
} catch(err) {
|
|
126
|
+
next(err);
|
|
127
|
+
}
|
|
128
|
+
}).catch(err => next(err));
|
|
122
129
|
}
|
|
123
130
|
|
|
124
131
|
|
|
@@ -147,13 +154,20 @@ export class WindowCovering extends Accessory {
|
|
|
147
154
|
log("accessory", "get Characteristic.TargetPosition was called");
|
|
148
155
|
|
|
149
156
|
this.unit.reqState(unit => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
try {
|
|
158
|
+
if (!unit) {
|
|
159
|
+
return next(new Error("Unit state not available"));
|
|
160
|
+
}
|
|
161
|
+
if (this.unit.status == UnitState.kOpen)
|
|
162
|
+
next(null, 100);
|
|
163
|
+
else if (this.unit.status == UnitState.kClosed)
|
|
164
|
+
next(null, 0);
|
|
165
|
+
else
|
|
166
|
+
next(null, 50);
|
|
167
|
+
} catch(err) {
|
|
168
|
+
next(err);
|
|
169
|
+
}
|
|
170
|
+
}).catch(err => next(err));
|
|
157
171
|
}
|
|
158
172
|
|
|
159
173
|
// called in response to Duotecno status update
|
package/config.json
CHANGED
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
}
|
|
168
168
|
]
|
|
169
169
|
},
|
|
170
|
-
"
|
|
170
|
+
"xsocapp": {
|
|
171
171
|
"debug": true,
|
|
172
172
|
"port": 9999
|
|
173
173
|
},
|
|
@@ -211,31 +211,68 @@
|
|
|
211
211
|
"method": "GET",
|
|
212
212
|
"type": "smappee",
|
|
213
213
|
"plug": "1"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"logicalNodeAddress": 3,
|
|
217
|
+
"logicalAddress": 34,
|
|
218
|
+
"masterAddress": "192.168.0.97",
|
|
219
|
+
"masterPort": 5001,
|
|
220
|
+
"name": "Notification Sender",
|
|
221
|
+
"displayName": "Notification Sender",
|
|
222
|
+
"extendedType": 0,
|
|
223
|
+
"nomacro": "N",
|
|
224
|
+
"nostop": "N",
|
|
225
|
+
"unitName": "Lamp-Bottom",
|
|
226
|
+
"value": 0,
|
|
227
|
+
"data": "",
|
|
228
|
+
"header": "",
|
|
229
|
+
"method": "GET",
|
|
230
|
+
"type": "http",
|
|
231
|
+
"plug": "https://duotecno.eu/nl/notification/send?title=Lamp+changed&body=Turned+#&proof=MwJDIgEUOgYNFQsmHEMjC15yXllXXmZVKgw6ASkBBw=="
|
|
214
232
|
}
|
|
215
233
|
],
|
|
216
234
|
"debug": false,
|
|
217
235
|
"username": "pi",
|
|
218
236
|
"password": "GMTecno",
|
|
219
|
-
"links": [
|
|
237
|
+
"links": [],
|
|
238
|
+
"devices": [
|
|
220
239
|
{
|
|
221
|
-
"logicalNodeAddress":
|
|
222
|
-
"logicalAddress":
|
|
240
|
+
"logicalNodeAddress": 3,
|
|
241
|
+
"logicalAddress": 34,
|
|
223
242
|
"masterAddress": "192.168.0.97",
|
|
224
243
|
"masterPort": 5001,
|
|
225
|
-
"name": "
|
|
226
|
-
"displayName": "
|
|
244
|
+
"name": "Test Lamp",
|
|
245
|
+
"displayName": "Test Lamp",
|
|
227
246
|
"extendedType": 0,
|
|
228
|
-
"
|
|
229
|
-
"
|
|
230
|
-
"
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
"
|
|
237
|
-
"
|
|
238
|
-
|
|
247
|
+
"id": 802,
|
|
248
|
+
"power": 0,
|
|
249
|
+
"energy": 0,
|
|
250
|
+
"voltage": 230,
|
|
251
|
+
"current": 0,
|
|
252
|
+
"relay": false,
|
|
253
|
+
"measured": true,
|
|
254
|
+
"powerSource": "shelly",
|
|
255
|
+
"powerChannel": "1+2",
|
|
256
|
+
"estimatedPower": 100
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"logicalNodeAddress": 3,
|
|
260
|
+
"logicalAddress": 30,
|
|
261
|
+
"masterAddress": "192.168.0.97",
|
|
262
|
+
"masterPort": 5001,
|
|
263
|
+
"name": "Blue LED",
|
|
264
|
+
"displayName": "Blue LED",
|
|
265
|
+
"extendedType": 0,
|
|
266
|
+
"id": 798,
|
|
267
|
+
"power": 0,
|
|
268
|
+
"energy": 0,
|
|
269
|
+
"voltage": 230,
|
|
270
|
+
"current": 0,
|
|
271
|
+
"relay": false,
|
|
272
|
+
"measured": false,
|
|
273
|
+
"powerSource": "none",
|
|
274
|
+
"powerChannel": "",
|
|
275
|
+
"estimatedPower": 50
|
|
239
276
|
}
|
|
240
277
|
]
|
|
241
278
|
},
|
|
@@ -456,7 +493,8 @@
|
|
|
456
493
|
"nameservers": "8.8.8.8",
|
|
457
494
|
"ip2": "",
|
|
458
495
|
"gateway2": ""
|
|
459
|
-
}
|
|
496
|
+
},
|
|
497
|
+
"mdnsName": "duotecno-gateway"
|
|
460
498
|
},
|
|
461
499
|
"p1": false,
|
|
462
500
|
"proxy": {
|
|
@@ -466,6 +504,6 @@
|
|
|
466
504
|
"masterPort": 5001,
|
|
467
505
|
"uniqueId": "",
|
|
468
506
|
"debug": false,
|
|
469
|
-
"kind": "
|
|
507
|
+
"kind": "solo"
|
|
470
508
|
}
|
|
471
509
|
}
|
package/duotecno/types.js
CHANGED
|
@@ -14,7 +14,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.makeInt = exports.now = exports.datetime = exports.time = exports.date = exports.watt = exports.hex = exports.two = exports.char = exports.ascii = exports.wait = exports.Sanitizers = exports.actionValueStr = exports.actionValue = exports.kEmptyBinding = exports.kEmptyLink = exports.LinkType = exports.kEmptySwitch = exports.SwitchType = exports.kEmptyRule = exports.kEmptyAction = exports.RuleType = exports.Boundaries = exports.kEmptyCommRecord = exports.WriteError = exports.kEmptySettings = exports.kEmptyNetworkConfig = exports.kEmptyGroup = exports.kEmptyScene = exports.kEmptyUnitScene = exports.kEmptyUnit = exports.UnitExtendedType = exports.UnitType = exports.UnitMotorCmd = exports.UnitState = exports.NodeType = void 0;
|
|
17
|
+
exports.makeInt = exports.now = exports.datetime = exports.time = exports.date = exports.watt = exports.hex = exports.two = exports.cap = exports.char = exports.ascii = exports.wait = exports.Sanitizers = exports.actionValueStr = exports.actionValue = exports.kEmptyBinding = exports.kEmptyDevice = exports.PowerSource = exports.kEmptyLink = exports.LinkType = exports.kEmptySwitch = exports.SwitchType = exports.kEmptyRule = exports.kEmptyAction = exports.RuleType = exports.Boundaries = exports.kEmptyCommRecord = exports.WriteError = exports.kEmptySettings = exports.kEmptyNetworkConfig = exports.kEmptyGroup = exports.kEmptyScene = exports.kEmptyUnitScene = exports.kEmptyUnit = exports.UnitExtendedType = exports.UnitType = exports.UnitMotorCmd = exports.UnitState = exports.NodeType = void 0;
|
|
18
18
|
const proxy_1 = require("../server/proxy");
|
|
19
19
|
// Node types
|
|
20
20
|
var NodeType;
|
|
@@ -99,7 +99,7 @@ exports.kEmptyGroup = { name: "Home", id: 0, order: 0, visible: true };
|
|
|
99
99
|
;
|
|
100
100
|
exports.kEmptyNetworkConfig = { ip1: "", ip2: "", gateway1: "", gateway2: "", nameservers: "" };
|
|
101
101
|
;
|
|
102
|
-
exports.kEmptySettings = { network: exports.kEmptyNetworkConfig };
|
|
102
|
+
exports.kEmptySettings = { network: exports.kEmptyNetworkConfig, mdnsName: "duotecno-gateway" };
|
|
103
103
|
;
|
|
104
104
|
;
|
|
105
105
|
;
|
|
@@ -155,6 +155,17 @@ var LinkType;
|
|
|
155
155
|
LinkType["kAccessory"] = "accessory";
|
|
156
156
|
})(LinkType = exports.LinkType || (exports.LinkType = {}));
|
|
157
157
|
exports.kEmptyLink = Object.assign(Object.assign({}, exports.kEmptyUnit), { type: LinkType.kNoType, max: 100, min: 0, accName: "acc", accId: "", accService: [] });
|
|
158
|
+
/////////////
|
|
159
|
+
// Devices //
|
|
160
|
+
/////////////
|
|
161
|
+
var PowerSource;
|
|
162
|
+
(function (PowerSource) {
|
|
163
|
+
PowerSource["kNone"] = "none";
|
|
164
|
+
PowerSource["kP1"] = "p1";
|
|
165
|
+
PowerSource["kShelly"] = "shelly";
|
|
166
|
+
PowerSource["kSmappee"] = "smappee";
|
|
167
|
+
})(PowerSource = exports.PowerSource || (exports.PowerSource = {}));
|
|
168
|
+
exports.kEmptyDevice = Object.assign(Object.assign({}, exports.kEmptyUnit), { id: 0, power: 0, energy: 0, voltage: 230.0, current: 0, relay: false, measured: false, powerSource: PowerSource.kNone, powerChannel: "", estimatedPower: 0 });
|
|
158
169
|
exports.kEmptyBinding = {
|
|
159
170
|
channel: "0", masterAddress: "0.0.0.0", masterPort: 5001, register: 0
|
|
160
171
|
};
|
|
@@ -228,6 +239,7 @@ exports.Sanitizers = {
|
|
|
228
239
|
if (!config)
|
|
229
240
|
config = {};
|
|
230
241
|
config.network = exports.Sanitizers.networkConfig(config.network);
|
|
242
|
+
config.mdnsName = config.mdnsName || exports.kEmptySettings.mdnsName;
|
|
231
243
|
return config;
|
|
232
244
|
},
|
|
233
245
|
networkConfig: function (config) {
|
|
@@ -306,6 +318,34 @@ exports.Sanitizers = {
|
|
|
306
318
|
link.min = aLink.min || exports.kEmptyLink.min;
|
|
307
319
|
return link;
|
|
308
320
|
},
|
|
321
|
+
deviceConfig: function (aDevice) {
|
|
322
|
+
const device = exports.Sanitizers.unitDef(aDevice);
|
|
323
|
+
// Calculate ID from node/unit if not provided
|
|
324
|
+
device.id = aDevice.id || (device.logicalNodeAddress * 256 + device.logicalAddress);
|
|
325
|
+
device.power = aDevice.power || 0;
|
|
326
|
+
device.energy = aDevice.energy || 0;
|
|
327
|
+
device.voltage = aDevice.voltage || 230.0;
|
|
328
|
+
device.current = aDevice.current || 0;
|
|
329
|
+
device.relay = aDevice.relay || false;
|
|
330
|
+
device.measured = aDevice.measured || false;
|
|
331
|
+
device.powerSource = aDevice.powerSource || PowerSource.kNone;
|
|
332
|
+
device.powerChannel = aDevice.powerChannel || "";
|
|
333
|
+
device.estimatedPower = aDevice.estimatedPower || 0;
|
|
334
|
+
return device;
|
|
335
|
+
},
|
|
336
|
+
makeDeviceConfig: function (aDevice) {
|
|
337
|
+
return exports.Sanitizers.deviceConfig({
|
|
338
|
+
name: aDevice.name,
|
|
339
|
+
masterAddress: aDevice.masterAddress,
|
|
340
|
+
masterPort: aDevice.masterPort,
|
|
341
|
+
logicalAddress: aDevice.logicalAddress,
|
|
342
|
+
logicalNodeAddress: aDevice.logicalNodeAddress,
|
|
343
|
+
measured: aDevice.measured || false,
|
|
344
|
+
powerSource: aDevice.powerSource,
|
|
345
|
+
powerChannel: aDevice.powerChannel,
|
|
346
|
+
estimatedPower: aDevice.estimatedPower
|
|
347
|
+
});
|
|
348
|
+
},
|
|
309
349
|
switchConfig: function (aSwitch) {
|
|
310
350
|
const sw = exports.Sanitizers.unitDef(aSwitch);
|
|
311
351
|
sw.nomacro = aSwitch.nomacro || "N";
|
|
@@ -536,6 +576,12 @@ function char(ascii) {
|
|
|
536
576
|
return String.fromCharCode(ascii);
|
|
537
577
|
}
|
|
538
578
|
exports.char = char;
|
|
579
|
+
function cap(s) {
|
|
580
|
+
if (s.length === 0)
|
|
581
|
+
return s;
|
|
582
|
+
return s[0].toUpperCase() + s.substr(1, s.length - 1).toLowerCase();
|
|
583
|
+
}
|
|
584
|
+
exports.cap = cap;
|
|
539
585
|
function two(n) {
|
|
540
586
|
return (+n < 10) ? ("0" + n) : n.toString();
|
|
541
587
|
}
|
package/duotecno/types.ts
CHANGED
|
@@ -133,8 +133,9 @@ export const kEmptyNetworkConfig = { ip1: "", ip2: "", gateway1: "", gateway2: "
|
|
|
133
133
|
|
|
134
134
|
export interface SettingsConfig {
|
|
135
135
|
network: NetworkConfig;
|
|
136
|
+
mdnsName?: string; // mDNS service name (default: duotecno-gateway)
|
|
136
137
|
};
|
|
137
|
-
export const kEmptySettings: SettingsConfig = { network: kEmptyNetworkConfig } as const;
|
|
138
|
+
export const kEmptySettings: SettingsConfig = { network: kEmptyNetworkConfig, mdnsName: "duotecno-gateway" } as const;
|
|
138
139
|
|
|
139
140
|
|
|
140
141
|
// from Protocol//Hardware to Nodes and Units
|
|
@@ -280,6 +281,43 @@ export const kEmptyLink: Link = {
|
|
|
280
281
|
...kEmptyUnit, type: LinkType.kNoType, max: 100, min: 0, accName: "acc", accId: "", accService: []
|
|
281
282
|
};
|
|
282
283
|
|
|
284
|
+
/////////////
|
|
285
|
+
// Devices //
|
|
286
|
+
/////////////
|
|
287
|
+
export enum PowerSource {
|
|
288
|
+
kNone = "none",
|
|
289
|
+
kP1 = "p1",
|
|
290
|
+
kShelly = "shelly",
|
|
291
|
+
kSmappee = "smappee"
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface Device extends UnitDef {
|
|
295
|
+
id: number; // node * 256 + unit
|
|
296
|
+
power?: number; // Current power in watts
|
|
297
|
+
energy?: number; // Energy consumption in kWh
|
|
298
|
+
voltage?: number; // Voltage (usually 230V)
|
|
299
|
+
current?: number; // Current in amps
|
|
300
|
+
relay?: boolean; // Current relay state
|
|
301
|
+
measured: boolean; // True if values are measured, false if estimated
|
|
302
|
+
powerSource?: PowerSource; // Where to get power measurements from
|
|
303
|
+
powerChannel?: string; // Channel/register for power source
|
|
304
|
+
estimatedPower?: number; // Estimated power when not measured
|
|
305
|
+
unit?: Unit; // Link to actual Duotecno unit
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export const kEmptyDevice: Device = {
|
|
309
|
+
...kEmptyUnit,
|
|
310
|
+
id: 0,
|
|
311
|
+
power: 0,
|
|
312
|
+
energy: 0,
|
|
313
|
+
voltage: 230.0,
|
|
314
|
+
current: 0,
|
|
315
|
+
relay: false,
|
|
316
|
+
measured: false,
|
|
317
|
+
powerSource: PowerSource.kNone,
|
|
318
|
+
powerChannel: "",
|
|
319
|
+
estimatedPower: 0
|
|
320
|
+
};
|
|
283
321
|
|
|
284
322
|
///////////
|
|
285
323
|
// Power //
|
|
@@ -324,6 +362,7 @@ export interface SmartAppConfig extends BaseConfig {
|
|
|
324
362
|
username: string; password: string;
|
|
325
363
|
switches: Array<Switch>;
|
|
326
364
|
links: Array<Link>;
|
|
365
|
+
devices?: Array<Device>;
|
|
327
366
|
}
|
|
328
367
|
|
|
329
368
|
|
|
@@ -443,6 +482,7 @@ export const Sanitizers = {
|
|
|
443
482
|
settings: function(config: SettingsConfig): SettingsConfig {
|
|
444
483
|
if (!config) config = <SettingsConfig>{};
|
|
445
484
|
config.network = Sanitizers.networkConfig(config.network);
|
|
485
|
+
config.mdnsName = config.mdnsName || kEmptySettings.mdnsName;
|
|
446
486
|
return config;
|
|
447
487
|
},
|
|
448
488
|
|
|
@@ -527,6 +567,36 @@ export const Sanitizers = {
|
|
|
527
567
|
return link;
|
|
528
568
|
},
|
|
529
569
|
|
|
570
|
+
deviceConfig: function(aDevice): Device {
|
|
571
|
+
const device = <Device>Sanitizers.unitDef(aDevice);
|
|
572
|
+
// Calculate ID from node/unit if not provided
|
|
573
|
+
device.id = aDevice.id || (device.logicalNodeAddress * 256 + device.logicalAddress);
|
|
574
|
+
device.power = aDevice.power || 0;
|
|
575
|
+
device.energy = aDevice.energy || 0;
|
|
576
|
+
device.voltage = aDevice.voltage || 230.0;
|
|
577
|
+
device.current = aDevice.current || 0;
|
|
578
|
+
device.relay = aDevice.relay || false;
|
|
579
|
+
device.measured = aDevice.measured || false;
|
|
580
|
+
device.powerSource = aDevice.powerSource || PowerSource.kNone;
|
|
581
|
+
device.powerChannel = aDevice.powerChannel || "";
|
|
582
|
+
device.estimatedPower = aDevice.estimatedPower || 0;
|
|
583
|
+
return device;
|
|
584
|
+
},
|
|
585
|
+
|
|
586
|
+
makeDeviceConfig: function(aDevice): Device {
|
|
587
|
+
return Sanitizers.deviceConfig({
|
|
588
|
+
name: aDevice.name,
|
|
589
|
+
masterAddress: aDevice.masterAddress,
|
|
590
|
+
masterPort: aDevice.masterPort,
|
|
591
|
+
logicalAddress: aDevice.logicalAddress,
|
|
592
|
+
logicalNodeAddress: aDevice.logicalNodeAddress,
|
|
593
|
+
measured: aDevice.measured || false,
|
|
594
|
+
powerSource: aDevice.powerSource,
|
|
595
|
+
powerChannel: aDevice.powerChannel,
|
|
596
|
+
estimatedPower: aDevice.estimatedPower
|
|
597
|
+
});
|
|
598
|
+
},
|
|
599
|
+
|
|
530
600
|
switchConfig: function(aSwitch): Switch {
|
|
531
601
|
const sw = <Switch>Sanitizers.unitDef(aSwitch);
|
|
532
602
|
sw.nomacro = aSwitch.nomacro || "N";
|
|
@@ -772,6 +842,10 @@ export function ascii(char: string): number {
|
|
|
772
842
|
export function char(ascii: number): string {
|
|
773
843
|
return String.fromCharCode(ascii)
|
|
774
844
|
}
|
|
845
|
+
export function cap(s: string): string {
|
|
846
|
+
if (s.length === 0) return s;
|
|
847
|
+
return s[0].toUpperCase() + s.substr(1, s.length - 1).toLowerCase();
|
|
848
|
+
}
|
|
775
849
|
|
|
776
850
|
|
|
777
851
|
export function two(n: number | string) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-smartsystem",
|
|
3
3
|
"displayname": "Duotecno Bridge",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "7.0.1",
|
|
5
5
|
"description": "SmartServer (Proxy TCP sockets to the cloud, Smappee MQTT, Duotecno IP Nodes, Homekit interface)",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"author": "Johan Coppieters",
|
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "DEBUG=* homebridge -D -P '/Users/johan/Development/duotecno/smartsocket'",
|
|
12
12
|
"watch": "npx tsc --build . --watch",
|
|
13
|
-
"
|
|
13
|
+
"build": "npx tsc --build",
|
|
14
|
+
"unpublish": "npm unpublish --force homebridge-smartsystem@6.8.17",
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
16
|
+
"publish": "npm publish",
|
|
17
|
+
"version:patch": "npm version patch && npm publish",
|
|
18
|
+
"version:minor": "npm version minor && npm publish"
|
|
14
19
|
},
|
|
15
20
|
"engines": {
|
|
16
21
|
"node": "^18.20.4 || ^20.15.1 || ^22",
|
|
@@ -32,18 +37,21 @@
|
|
|
32
37
|
"url": "https://github.com/jcoppieters/smartsocket.git"
|
|
33
38
|
},
|
|
34
39
|
"dependencies": {
|
|
40
|
+
"bonjour-service": "^1.3.0",
|
|
35
41
|
"ejs": "^3.1.7",
|
|
36
42
|
"mime-types": "^2.1.25",
|
|
37
43
|
"mqtt": "^4.3.7",
|
|
44
|
+
"multicast-dns": "^7.2.5",
|
|
38
45
|
"node-fetch": "^3.2.4",
|
|
39
46
|
"rpi-gpio": "^2.1.7",
|
|
40
47
|
"ws": "^7.4.6"
|
|
41
48
|
},
|
|
42
49
|
"devDependencies": {
|
|
50
|
+
"@types/bonjour": "^3.5.13",
|
|
43
51
|
"@types/mime-types": "^2.1.0",
|
|
44
52
|
"@types/node": "^16.10.9",
|
|
45
53
|
"@types/ws": "^6.0.4",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
54
|
+
"homebridge": "^1.3.9",
|
|
55
|
+
"typescript": "^4.7.4"
|
|
48
56
|
}
|
|
49
57
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Raspberry Pi Setup Script for Duotecno Gateway
|
|
3
|
+
# This script configures the Raspberry Pi to be accessible via duotecno-gateway.local
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
echo "========================================"
|
|
8
|
+
echo " Duotecno Gateway - Raspberry Pi Setup"
|
|
9
|
+
echo "========================================"
|
|
10
|
+
echo ""
|
|
11
|
+
|
|
12
|
+
# Check if running as root
|
|
13
|
+
if [ "$EUID" -ne 0 ]; then
|
|
14
|
+
echo "Please run as root (use sudo)"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# 1. Install Avahi (mDNS/Bonjour for Linux)
|
|
19
|
+
echo "📦 Installing Avahi mDNS daemon..."
|
|
20
|
+
apt-get update -qq
|
|
21
|
+
apt-get install -y avahi-daemon avahi-utils libnss-mdns
|
|
22
|
+
|
|
23
|
+
# 2. Set the hostname to duotecno-gateway
|
|
24
|
+
echo "🏷️ Setting hostname to duotecno-gateway..."
|
|
25
|
+
hostnamectl set-hostname duotecno-gateway
|
|
26
|
+
|
|
27
|
+
# Update /etc/hosts
|
|
28
|
+
sed -i '/127.0.1.1/d' /etc/hosts
|
|
29
|
+
echo "127.0.1.1 duotecno-gateway" >> /etc/hosts
|
|
30
|
+
|
|
31
|
+
# 3. Configure Avahi
|
|
32
|
+
echo "⚙️ Configuring Avahi..."
|
|
33
|
+
cat > /etc/avahi/avahi-daemon.conf << 'EOF'
|
|
34
|
+
[server]
|
|
35
|
+
host-name=duotecno-gateway
|
|
36
|
+
domain-name=local
|
|
37
|
+
use-ipv4=yes
|
|
38
|
+
use-ipv6=yes
|
|
39
|
+
allow-interfaces=eth0,wlan0
|
|
40
|
+
deny-interfaces=docker0,veth*
|
|
41
|
+
ratelimit-interval-usec=1000000
|
|
42
|
+
ratelimit-burst=1000
|
|
43
|
+
|
|
44
|
+
[wide-area]
|
|
45
|
+
enable-wide-area=yes
|
|
46
|
+
|
|
47
|
+
[publish]
|
|
48
|
+
publish-addresses=yes
|
|
49
|
+
publish-hinfo=yes
|
|
50
|
+
publish-workstation=yes
|
|
51
|
+
publish-domain=yes
|
|
52
|
+
EOF
|
|
53
|
+
|
|
54
|
+
# 4. Restart Avahi
|
|
55
|
+
echo "🔄 Restarting Avahi daemon..."
|
|
56
|
+
systemctl enable avahi-daemon
|
|
57
|
+
systemctl restart avahi-daemon
|
|
58
|
+
|
|
59
|
+
# 5. Wait a moment for Avahi to start
|
|
60
|
+
sleep 2
|
|
61
|
+
|
|
62
|
+
# 6. Test the configuration
|
|
63
|
+
echo ""
|
|
64
|
+
echo "✅ Setup complete!"
|
|
65
|
+
echo ""
|
|
66
|
+
echo "Testing mDNS resolution..."
|
|
67
|
+
if avahi-resolve -n duotecno-gateway.local > /dev/null 2>&1; then
|
|
68
|
+
echo "✅ duotecno-gateway.local is resolvable"
|
|
69
|
+
avahi-resolve -n duotecno-gateway.local
|
|
70
|
+
else
|
|
71
|
+
echo "⚠️ Warning: duotecno-gateway.local not yet resolvable (may need reboot)"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
echo ""
|
|
75
|
+
echo "📋 Next steps:"
|
|
76
|
+
echo " 1. Reboot the Raspberry Pi: sudo reboot"
|
|
77
|
+
echo " 2. After reboot, access the gateway at:"
|
|
78
|
+
echo " http://duotecno-gateway.local"
|
|
79
|
+
echo " (or http://duotecno-gateway.local:5002 if not using port 80)"
|
|
80
|
+
echo ""
|
package/server/HA-API.js
CHANGED
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.httpRequest = exports.ContentType = void 0;
|
|
13
13
|
const logger_1 = require("../duotecno/logger");
|
|
14
14
|
const http = require("http");
|
|
15
|
+
const https = require("https");
|
|
15
16
|
//////////////////////////////////
|
|
16
17
|
// Home Assistant API //
|
|
17
18
|
// and other http based APIs //
|
|
@@ -28,6 +29,8 @@ function httpRequest(url, method = "GET", formdata = "", contentType = ContentTy
|
|
|
28
29
|
return __awaiter(this, void 0, void 0, function* () {
|
|
29
30
|
if (url.indexOf("://") === -1)
|
|
30
31
|
url = "http://" + url;
|
|
32
|
+
const isHttps = url.startsWith("https://");
|
|
33
|
+
const httpModule = isHttps ? https : http;
|
|
31
34
|
return new Promise((resolve, reject) => {
|
|
32
35
|
try {
|
|
33
36
|
// const data = querystring.stringify(formdata);
|
|
@@ -77,7 +80,7 @@ function httpRequest(url, method = "GET", formdata = "", contentType = ContentTy
|
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
(0, logger_1.debug)("smartapp", "Sending to " + url + " with options: " + JSON.stringify(options));
|
|
80
|
-
const req =
|
|
83
|
+
const req = httpModule.request(url, options, res => {
|
|
81
84
|
let resp = "";
|
|
82
85
|
// res.setEncoding('utf8');
|
|
83
86
|
res.on('error', error => {
|
package/server/HA-API.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { debug, err, log } from "../duotecno/logger";
|
|
2
2
|
import * as http from "http";
|
|
3
|
+
import * as https from "https";
|
|
3
4
|
|
|
4
5
|
//////////////////////////////////
|
|
5
6
|
// Home Assistant API //
|
|
@@ -11,6 +12,8 @@ export enum ContentType {form, plain, none, json};
|
|
|
11
12
|
export async function httpRequest(url: string, method = "GET", formdata = "",
|
|
12
13
|
contentType = ContentType.form, header: string | object = ""): Promise<Record<string, any>> {
|
|
13
14
|
if (url.indexOf("://") === -1) url = "http://" + url;
|
|
15
|
+
const isHttps = url.startsWith("https://");
|
|
16
|
+
const httpModule = isHttps ? https : http;
|
|
14
17
|
|
|
15
18
|
return new Promise((resolve, reject) => {
|
|
16
19
|
try {
|
|
@@ -57,7 +60,7 @@ export async function httpRequest(url: string, method = "GET", formdata = "",
|
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
debug("smartapp", "Sending to " + url + " with options: " + JSON.stringify(options));
|
|
60
|
-
const req =
|
|
63
|
+
const req = httpModule.request(url, options, res => {
|
|
61
64
|
let resp = "";
|
|
62
65
|
// res.setEncoding('utf8');
|
|
63
66
|
res.on('error', error => {
|
package/server/HB.js
CHANGED
|
@@ -319,7 +319,12 @@ function hbSubscribe(callback) {
|
|
|
319
319
|
clearInterval(timer);
|
|
320
320
|
}
|
|
321
321
|
timer = setInterval(() => {
|
|
322
|
-
|
|
322
|
+
try {
|
|
323
|
+
callback();
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
(0, logger_1.log)("HB", "hbSubscribe: error: " + JSON.stringify(error));
|
|
327
|
+
}
|
|
323
328
|
}, 10000);
|
|
324
329
|
}
|
|
325
330
|
exports.hbSubscribe = hbSubscribe;
|
package/server/HB.ts
CHANGED