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.
Files changed (75) hide show
  1. package/.vscode/launch.json +15 -4
  2. package/MIXINS-GUIDE.md +210 -0
  3. package/README.md +32 -0
  4. package/SETTINGS-UPDATE.md +195 -0
  5. package/TESTING-GUIDE.md +220 -0
  6. package/WEBSOCKET-API.md +303 -0
  7. package/accessories/accessory.js +20 -4
  8. package/accessories/accessory.ts +19 -4
  9. package/accessories/bulb.js +0 -5
  10. package/accessories/bulb.ts +1 -5
  11. package/accessories/dimmer.js +11 -3
  12. package/accessories/dimmer.ts +11 -4
  13. package/accessories/garagedoor.js +19 -9
  14. package/accessories/garagedoor.ts +21 -9
  15. package/accessories/lock.js +11 -3
  16. package/accessories/lock.ts +10 -3
  17. package/accessories/temperature.js +10 -2
  18. package/accessories/temperature.ts +9 -2
  19. package/accessories/windowcovering.js +30 -14
  20. package/accessories/windowcovering.ts +29 -15
  21. package/config.json +57 -19
  22. package/duotecno/types.js +48 -2
  23. package/duotecno/types.ts +75 -1
  24. package/package.json +12 -4
  25. package/raspberry-pi-setup.sh +80 -0
  26. package/server/HA-API.js +4 -1
  27. package/server/HA-API.ts +4 -1
  28. package/server/HB.js +6 -1
  29. package/server/HB.ts +5 -1
  30. package/server/mDNS.js +168 -0
  31. package/server/mDNS.ts +198 -0
  32. package/server/platform.js +7 -0
  33. package/server/platform.ts +8 -0
  34. package/server/proxy.js +58 -19
  35. package/server/proxy.ts +31 -3
  36. package/server/smartapp.js +397 -18
  37. package/server/smartapp.ts +432 -20
  38. package/server/socapp.js +2 -1
  39. package/server/socapp.ts +2 -1
  40. package/server/views/assets/Duotecno_logo_black.svg +10 -0
  41. package/server/views/assets/Duotecno_logo_white.svg +10 -0
  42. package/server/views/device-details.ejs +139 -0
  43. package/server/views/device-list.ejs +44 -0
  44. package/server/views/footer.ejs +0 -1
  45. package/server/views/head.ejs +9 -0
  46. package/server/views/homekit.ejs +2 -9
  47. package/server/views/link-details.ejs +2 -15
  48. package/server/views/link-list.ejs +2 -9
  49. package/server/views/login.ejs +8 -9
  50. package/server/views/master-detail.ejs +3 -9
  51. package/server/views/master-list.ejs +3 -10
  52. package/server/views/nav.ejs +44 -11
  53. package/server/views/node-details.ejs +1 -8
  54. package/server/views/power-binding.ejs +3 -10
  55. package/server/views/power-rule.ejs +3 -10
  56. package/server/views/power.ejs +8 -15
  57. package/server/views/proxy.ejs +1 -8
  58. package/server/views/scripts.ejs +10 -0
  59. package/server/views/service-list.ejs +2 -9
  60. package/server/views/settings.ejs +61 -22
  61. package/server/views/style.ejs +49 -5
  62. package/server/views/switch-details.ejs +2 -9
  63. package/server/views/switch-list.ejs +2 -9
  64. package/server/webapp.js +150 -11
  65. package/server/webapp.ts +166 -14
  66. package/{index.spec.js → testHB.js} +1 -1
  67. package/{run.js → testProxy.js} +2 -2
  68. package/{run.ts → testProxy.ts} +1 -1
  69. package/testWS.js +137 -0
  70. package/testWS.ts +174 -0
  71. package/run9998.js +0 -7
  72. package/run9998.ts +0 -6
  73. package/run9999.js +0 -7
  74. package/run9999.ts +0 -6
  75. /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
- if (unit.status == types_1.UnitState.kOpen)
97
- next(null, 100);
98
- else if (unit.status == types_1.UnitState.kClosed)
99
- next(null, 0);
100
- else
101
- next(null, 50); // ????
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
- if (this.unit.status == types_1.UnitState.kOpen)
127
- next(null, 100);
128
- else if (this.unit.status == types_1.UnitState.kClosed)
129
- next(null, 0);
130
- else
131
- next(null, 50); // ????
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
- if (unit.status == UnitState.kOpen)
116
- next(null, 100);
117
- else if (unit.status == UnitState.kClosed)
118
- next(null, 0);
119
- else
120
- next(null, 50); // ????
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
- if (this.unit.status == UnitState.kOpen)
151
- next(null, 100);
152
- else if (this.unit.status == UnitState.kClosed)
153
- next(null, 0);
154
- else
155
- next(null, 50); // ????
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
- "socapp": {
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": 252,
222
- "logicalAddress": 104,
240
+ "logicalNodeAddress": 3,
241
+ "logicalAddress": 34,
223
242
  "masterAddress": "192.168.0.97",
224
243
  "masterPort": 5001,
225
- "name": "unit",
226
- "displayName": "unit",
244
+ "name": "Test Lamp",
245
+ "displayName": "Test Lamp",
227
246
  "extendedType": 0,
228
- "accId": "986b901a30d92cb7bd0205be90f65786c2d87c9a95d8a0a60cb4c048deda5ce6",
229
- "accName": "Tim Klein",
230
- "accService": [
231
- "CurrentPosition",
232
- "PositionState",
233
- "TargetPosition",
234
- "ObstructionDetected"
235
- ],
236
- "type": "accessory",
237
- "max": 100,
238
- "min": 0
247
+ "id": 802,
248
+ "power": 0,
249
+ "energy": 0,
250
+ "voltage": 230,
251
+ "current": 0,
252
+ "relay": false,
253
+ "measured": true,
254
+ "powerSource": "shelly",
255
+ "powerChannel": "1+2",
256
+ "estimatedPower": 100
257
+ },
258
+ {
259
+ "logicalNodeAddress": 3,
260
+ "logicalAddress": 30,
261
+ "masterAddress": "192.168.0.97",
262
+ "masterPort": 5001,
263
+ "name": "Blue LED",
264
+ "displayName": "Blue LED",
265
+ "extendedType": 0,
266
+ "id": 798,
267
+ "power": 0,
268
+ "energy": 0,
269
+ "voltage": 230,
270
+ "current": 0,
271
+ "relay": false,
272
+ "measured": false,
273
+ "powerSource": "none",
274
+ "powerChannel": "",
275
+ "estimatedPower": 50
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": "gw"
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": "6.8.13",
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
- "publish": "npx tsc --build && npm i && npm publish"
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
- "typescript": "^4.7.4",
47
- "homebridge": "^1.3.9"
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 = http.request(url, options, res => {
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 = http.request(url, options, res => {
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
- callback();
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
@@ -327,7 +327,11 @@ export function hbSubscribe(callback) {
327
327
  }
328
328
 
329
329
  timer = setInterval(() => {
330
- callback()
330
+ try {
331
+ callback();
332
+ } catch (error) {
333
+ log("HB", "hbSubscribe: error: " + JSON.stringify(error));
334
+ }
331
335
  }, 10000);
332
336
  }
333
337