homebridge-smartsystem 6.7.3 → 6.8.0

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 CHANGED
@@ -298,6 +298,11 @@ Homebridge UI version
298
298
  - 1: updated the readme file
299
299
  - 2: use HB Accessory characteristics in set/get, ignore status for the moment
300
300
  - 2: remember/show username in login screen + set focus to password
301
+ - 3: do dimmers/up-downs
302
+ - 4: better up-down values to and from HB
303
+
304
+ ### v6.8.0 - 08/07/2025
305
+ - 0: first version including the proxy
301
306
 
302
307
  ## Hardware
303
308
  A Raspberry Pi that connects to a Duotecno IP Node
package/config.json CHANGED
@@ -270,22 +270,6 @@
270
270
  "ObstructionDetected"
271
271
  ],
272
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
- ],
288
- "type": "accessory"
289
273
  }
290
274
  ]
291
275
  },
@@ -373,6 +357,18 @@
373
357
  "used": "Y",
374
358
  "group": 0
375
359
  },
360
+ {
361
+ "logicalNodeAddress": 3,
362
+ "logicalAddress": 28,
363
+ "masterAddress": "192.168.0.97",
364
+ "masterPort": 5001,
365
+ "name": "Garage $",
366
+ "displayName": "Garage ",
367
+ "extendedType": 8,
368
+ "active": "N",
369
+ "used": "Y",
370
+ "group": 0
371
+ },
376
372
  {
377
373
  "logicalNodeAddress": 3,
378
374
  "logicalAddress": 31,
@@ -484,5 +480,14 @@
484
480
  "gateway2": ""
485
481
  }
486
482
  },
487
- "p1": false
483
+ "p1": false,
484
+ "proxy": {
485
+ "cloudServer": "ws.duotecno.eu",
486
+ "cloudPort": 5098,
487
+ "masterAddress": "192.168.0.97",
488
+ "masterPort": 5001,
489
+ "uniqueId": "",
490
+ "debug": false,
491
+ "kind": "solo"
492
+ }
488
493
  }
@@ -15,7 +15,7 @@ var LogLevel;
15
15
  exports.logSettings = {
16
16
  "*": LogLevel.log,
17
17
  "master": LogLevel.log,
18
- "protocol": LogLevel.noLog,
18
+ "protocol": LogLevel.debug,
19
19
  "webapp": LogLevel.log,
20
20
  "p1": LogLevel.log,
21
21
  "shelly": LogLevel.log,
@@ -10,7 +10,7 @@ export enum LogLevel { noLog = 0, log = 1, debug = 2 };
10
10
  export const logSettings = {
11
11
  "*": LogLevel.log,
12
12
  "master": LogLevel.log,
13
- "protocol": LogLevel.noLog,
13
+ "protocol": LogLevel.debug,
14
14
  "webapp": LogLevel.log,
15
15
  "p1": LogLevel.log,
16
16
  "shelly": LogLevel.log,
package/duotecno/types.js CHANGED
@@ -15,6 +15,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
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;
18
+ const proxy_1 = require("../server/proxy");
18
19
  // Node types
19
20
  var NodeType;
20
21
  (function (NodeType) {
@@ -204,6 +205,7 @@ exports.Sanitizers = {
204
205
  config.shelly = config.shelly || false;
205
206
  config.smartapp = config.smartapp || null;
206
207
  config.system = exports.Sanitizers.system(config.system);
208
+ config.proxy = exports.Sanitizers.proxy(config.proxy, config.system);
207
209
  return config;
208
210
  },
209
211
  homebridge: function (config) {
@@ -358,6 +360,19 @@ exports.Sanitizers = {
358
360
  b.channel = binding.channel || "0";
359
361
  return b;
360
362
  },
363
+ ///////////
364
+ // Proxy //
365
+ ///////////
366
+ proxy: function (config, system) {
367
+ var _a, _b;
368
+ const c = proxy_1.kEmptyProxy;
369
+ c.cloudServer = (config === null || config === void 0 ? void 0 : config.cloudServer) || proxy_1.kEmptyProxy.cloudServer;
370
+ c.cloudPort = (config === null || config === void 0 ? void 0 : config.cloudPort) || proxy_1.kEmptyProxy.cloudPort;
371
+ c.masterAddress = (config === null || config === void 0 ? void 0 : config.masterAddress) || ((_a = system === null || system === void 0 ? void 0 : system.cmasters[0]) === null || _a === void 0 ? void 0 : _a.address) || "";
372
+ c.masterPort = (config === null || config === void 0 ? void 0 : config.masterPort) || ((_b = system === null || system === void 0 ? void 0 : system.cmasters[0]) === null || _b === void 0 ? void 0 : _b.port) || 5001;
373
+ c.uniqueId = (config === null || config === void 0 ? void 0 : config.uniqueId) || proxy_1.kEmptyProxy.uniqueId;
374
+ return c;
375
+ },
361
376
  //////////////
362
377
  // Duotecno //
363
378
  //////////////
package/duotecno/types.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  // v3 - smart server version, Mar 2020
5
5
  // v3.1 - added scenes from app version, May 2020
6
6
 
7
+ import { kEmptyProxy, ProxyConfig } from "../server/proxy";
7
8
  import { Unit } from "./protocol";
8
9
 
9
10
  // for Active=Y/N or New=X
@@ -269,8 +270,8 @@ export interface Link extends UnitDef{
269
270
  accService: Array<string>; // e.g. ["On", "Brightness"], ["TargetPosition", "CurrentPosition", ...]
270
271
  type: LinkType;
271
272
  unit?: Unit;
272
- status?: number | boolean;
273
- value?: number | boolean | string
273
+ dtValue?: number | boolean | string;
274
+ hbValue?: number | boolean | string
274
275
  }
275
276
 
276
277
  export const kEmptyLink: Link = {
@@ -412,6 +413,7 @@ export const Sanitizers = {
412
413
  config.shelly = config.shelly || false;
413
414
  config.smartapp = config.smartapp || null;
414
415
  config.system = Sanitizers.system(config.system);
416
+ config.proxy = Sanitizers.proxy(config.proxy, config.system);
415
417
 
416
418
  return config;
417
419
  },
@@ -581,6 +583,19 @@ export const Sanitizers = {
581
583
  return b;
582
584
  },
583
585
 
586
+ ///////////
587
+ // Proxy //
588
+ ///////////
589
+ proxy: function(config?: ProxyConfig, system?: SystemConfig): ProxyConfig {
590
+ const c = kEmptyProxy as ProxyConfig;
591
+ c.cloudServer = config?.cloudServer || kEmptyProxy.cloudServer;
592
+ c.cloudPort = config?.cloudPort || kEmptyProxy.cloudPort;
593
+ c.masterAddress = config?.masterAddress || system?.cmasters[0]?.address || "";
594
+ c.masterPort = config?.masterPort || system?.cmasters[0]?.port || 5001;
595
+ c.uniqueId = config?.uniqueId || kEmptyProxy.uniqueId;
596
+ return c;
597
+ },
598
+
584
599
  //////////////
585
600
  // Duotecno //
586
601
  //////////////
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-smartsystem",
3
3
  "displayname": "Duotecno Bridge",
4
- "version": "6.7.3",
4
+ "version": "6.8.0",
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.hbService = exports.hbName = void 0;
12
+ exports.hbUnsubscribe = exports.hbSubscribe = exports.hbGetValue = exports.hbSetState = exports.dt2hbvalue = exports.hb2dtvalue = exports.hbLogin = exports.hbAccessories = exports.hbService = exports.hbName = void 0;
13
13
  const logger_1 = require("../duotecno/logger");
14
14
  const HA_API_1 = require("./HA-API");
15
15
  let gAccessories = [];
@@ -200,21 +200,49 @@ function hbLoad(value, service) {
200
200
  ;
201
201
  return data;
202
202
  }
203
+ function hb2dtvalue(unit, value) {
204
+ let dtTarget;
205
+ let dtValue;
206
+ if (typeof value === "boolean")
207
+ value = (value) ? 99 : 1;
208
+ value = Math.round(value);
209
+ if (unit.isUpDown()) {
210
+ dtTarget = (value >= 50) ? 4 : 5; // 4 = up, 5 = down
211
+ dtValue = (value >= 50) ? 2 : 1; // 2 = idle-up, 1 = idle-down
212
+ }
213
+ if (unit.isDimmer())
214
+ dtTarget = dtValue = (value <= 1) ? 1 : (value >= 99) ? 100 : value;
215
+ if (unit.isSwitch() || unit.isMood())
216
+ dtTarget = dtValue = (value > 50) ? 1 : 0;
217
+ return { dtTarget, dtValue };
218
+ }
219
+ exports.hb2dtvalue = hb2dtvalue;
220
+ function dt2hbvalue(unit, dontChangeValue) {
221
+ let value = (typeof unit.value === "boolean") ? (unit.value ? 100 : 0) : Math.round(unit.value);
222
+ // status Motor: 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
223
+ if (unit.isUpDown())
224
+ return ((value === 1) || (value === 3)) ? 0 : ((value === 2) || (value === 4)) ? 100 : dontChangeValue;
225
+ if (unit.isDimmer())
226
+ return (value <= 2) ? 0 : (value >= 99) ? 100 : value;
227
+ if (unit.isSwitch() || unit.isMood())
228
+ return (value) ? 1 : 0;
229
+ return value;
230
+ }
231
+ exports.dt2hbvalue = dt2hbvalue;
203
232
  function hbValue(data, service) {
204
- const values = {};
205
233
  if (service.includes("Brightness")) {
206
- values["value"] = parseInt(data["Brightness"]);
234
+ return parseInt(data["Brightness"]);
207
235
  }
208
236
  if (service.includes("On")) {
209
- values["state"] = parseInt(data["On"]);
237
+ return parseInt(data["On"]);
210
238
  }
211
239
  if (service.includes("TargetPosition")) {
212
- values["value"] = parseInt(data["TargetPosition"]);
240
+ return parseInt(data["TargetPosition"]);
213
241
  }
214
- return values;
242
+ return undefined;
215
243
  }
216
244
  // send a status change to HB Accessory
217
- function hbSetState(server, id, service, value, state, token) {
245
+ function hbSetState(server, id, service, value, token) {
218
246
  return __awaiter(this, void 0, void 0, function* () {
219
247
  const data = hbLoad(value, service);
220
248
  (0, logger_1.log)("HB", "hbSetState: " + id + " -> " + JSON.stringify(data));
@@ -228,26 +256,26 @@ function hbSetState(server, id, service, value, state, token) {
228
256
  }
229
257
  exports.hbSetState = hbSetState;
230
258
  // send a status change to HB Accessory
231
- function hbGetState(server, id, service, token) {
259
+ function hbGetValue(server, id, service, token) {
232
260
  return __awaiter(this, void 0, void 0, function* () {
233
261
  const respBody = yield (0, HA_API_1.httpRequest)(server + "/api/accessories/" + id, 'GET', "", HA_API_1.ContentType.json, {
234
262
  Authorization: 'Bearer ' + token
235
263
  });
236
264
  if (respBody && respBody.error) {
237
265
  (0, logger_1.log)("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
238
- return {};
266
+ return undefined;
239
267
  }
240
268
  else if (respBody && respBody.values) {
241
269
  return hbValue(respBody.values, service);
242
270
  }
243
271
  else {
244
272
  (0, logger_1.log)("HB", "hbGetState: " + id + " -> no values?? : " + JSON.stringify(respBody));
245
- return {};
273
+ return undefined;
246
274
  }
247
275
  ;
248
276
  });
249
277
  }
250
- exports.hbGetState = hbGetState;
278
+ exports.hbGetValue = hbGetValue;
251
279
  function hbSubscribe(callback) {
252
280
  if (timer) {
253
281
  clearInterval(timer);
package/server/HB.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { err, log } from "../duotecno/logger";
2
+ import { Unit } from "../duotecno/protocol";
2
3
  import { ContentType, httpRequest } from "./HA-API";
3
4
 
4
5
  export interface HBAccessory {
@@ -205,24 +206,52 @@ function hbLoad(value: number | boolean, service: Array<string>): Record<string,
205
206
  return data;
206
207
  }
207
208
 
208
- function hbValue(data: Record<string, any>, service: Array<string>): {value?: number, state?: number} {
209
- const values = {};
209
+ export function hb2dtvalue(unit: Unit, value: number | boolean): {dtValue: number, dtTarget: number} {
210
+ let dtTarget;
211
+ let dtValue;
212
+
213
+ if (typeof value === "boolean") value = (value) ? 99 : 1;
214
+ value = Math.round(value);
215
+
216
+ if (unit.isUpDown()) {
217
+ dtTarget = (value >= 50) ? 4 : 5; // 4 = up, 5 = down
218
+ dtValue = (value >= 50) ? 2 : 1; // 2 = idle-up, 1 = idle-down
219
+ }
220
+ if (unit.isDimmer()) dtTarget = dtValue = (value <= 1) ? 1 : (value >= 99) ? 100 : value;
221
+ if (unit.isSwitch() || unit.isMood()) dtTarget = dtValue = (value > 50) ? 1 : 0;
222
+
223
+ return {dtTarget, dtValue};
224
+ }
225
+
226
+ export function dt2hbvalue(unit: Unit, dontChangeValue: number): number {
227
+ let value = (typeof unit.value === "boolean") ? (unit.value ? 100 : 0) : Math.round(unit.value);
228
+
229
+ // status Motor: 0 = stopped, 1 stopped/down, 2 = stopped/up, 3 = busy/down, 4 = busy/up
230
+ if (unit.isUpDown()) return ((value === 1) || (value === 3)) ? 0 : ((value === 2) || (value === 4)) ? 100 : dontChangeValue;
231
+
232
+ if (unit.isDimmer()) return (value <= 2) ? 0 : (value >= 99) ? 100 : value;
233
+ if (unit.isSwitch() || unit.isMood()) return (value) ? 1 : 0;
234
+
235
+ return value;
236
+ }
237
+
238
+ function hbValue(data: Record<string, any>, service: Array<string>): number {
210
239
  if (service.includes("Brightness")) {
211
- values["value"] = parseInt(data["Brightness"]);
240
+ return parseInt(data["Brightness"]);
212
241
  }
213
242
 
214
243
  if (service.includes("On")) {
215
- values["state"] = parseInt(data["On"])
244
+ return parseInt(data["On"])
216
245
  }
217
246
 
218
247
  if (service.includes("TargetPosition")) {
219
- values["value"] = parseInt(data["TargetPosition"]);
248
+ return parseInt(data["TargetPosition"]);
220
249
  }
221
- return values
250
+ return undefined
222
251
  }
223
252
 
224
253
  // 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> {
254
+ export async function hbSetState(server: string, id: string, service: Array<string>, value: number | boolean, token: string) : Promise<void> {
226
255
  const data = hbLoad(value, service);
227
256
 
228
257
  log("HB", "hbSetState: " + id + " -> " + JSON.stringify(data));
@@ -236,20 +265,20 @@ export async function hbSetState(server: string, id: string, service: Array<stri
236
265
  }
237
266
 
238
267
  // send a status change to HB Accessory
239
- export async function hbGetState(server: string, id: string, service: Array<string>, token: string) : Promise<{value?: number, state?: number}> {
268
+ export async function hbGetValue(server: string, id: string, service: Array<string>, token: string) : Promise<number> {
240
269
  const respBody = await httpRequest(server + "/api/accessories/"+id, 'GET', "", ContentType.json, {
241
270
  Authorization: 'Bearer ' + token
242
271
  });
243
272
  if (respBody && respBody.error) {
244
273
  log("HB", "hbGetState: " + id + " -> error: " + JSON.stringify(respBody));
245
- return {};
274
+ return undefined;
246
275
 
247
276
  } else if (respBody && respBody.values) {
248
277
  return hbValue(respBody.values, service);
249
278
 
250
279
  } else {
251
280
  log("HB", "hbGetState: " + id + " -> no values?? : " + JSON.stringify(respBody));
252
- return {};
281
+ return undefined;
253
282
  };
254
283
  }
255
284
 
@@ -30,8 +30,10 @@ const p1_1 = require("./p1");
30
30
  const shelly_1 = require("./shelly");
31
31
  const config_1 = require("../duotecno/config");
32
32
  const fs_1 = require("fs");
33
+ const proxy_1 = require("./proxy");
33
34
  class Platform extends base_1.Base {
34
35
  constructor(logger, pConfig, api) {
36
+ var _a;
35
37
  super("homebridge");
36
38
  this.accessoryList = [];
37
39
  this.ready = false;
@@ -109,6 +111,15 @@ class Platform extends base_1.Base {
109
111
  else {
110
112
  (0, logger_1.log)("homebridge", "platform - No SocApp configured.");
111
113
  }
114
+ if (this.config.proxy && ((_a = this.config.proxy) === null || _a === void 0 ? void 0 : _a.uniqueId)) {
115
+ this.config.proxy.kind = "gw";
116
+ (0, proxy_1.setProxyConfig)(this.config.proxy);
117
+ (0, proxy_1.cleanStart)(true);
118
+ (0, logger_1.log)("homebridge", "platform - Proxy configured: " + JSON.stringify(this.config.proxy, null, 2));
119
+ }
120
+ else {
121
+ (0, logger_1.log)("homebridge", "platform - No Proxy configured.");
122
+ }
112
123
  }
113
124
  startPower(type, PowerMgr) {
114
125
  // startup a power handler, if configured
@@ -20,7 +20,8 @@ import { Lock } from "../accessories/lock";
20
20
  import { P1 } from "./p1";
21
21
  import { Shelly } from "./shelly";
22
22
  import { kConfigFiles, setConfig } from "../duotecno/config";
23
- import { fstat, readFileSync } from "fs";
23
+ import { readFileSync } from "fs";
24
+ import { cleanStart, setProxyConfig } from "./proxy";
24
25
 
25
26
 
26
27
  export class Platform extends Base {
@@ -115,6 +116,15 @@ export class Platform extends Base {
115
116
  } else {
116
117
  log("homebridge", "platform - No SocApp configured.");
117
118
  }
119
+
120
+ if (this.config.proxy && this.config.proxy?.uniqueId) {
121
+ this.config.proxy.kind = "gw";
122
+ setProxyConfig(this.config.proxy);
123
+ cleanStart(true);
124
+ log("homebridge", "platform - Proxy configured: " + JSON.stringify(this.config.proxy, null, 2));
125
+ } else {
126
+ log("homebridge", "platform - No Proxy configured.");
127
+ }
118
128
  }
119
129
 
120
130
  startPower(type: string, PowerMgr: any) {