homebridge-smartsystem 7.1.17 → 7.1.20

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.
@@ -0,0 +1 @@
1
+ {"sessionId":"29e28b48-3a89-4159-baf0-58e668c1fe0d","pid":61331,"procStart":"Wed Sep 16 12:58:32 2026","acquiredAt":1789563711220}
package/CLAUDE.md ADDED
@@ -0,0 +1,118 @@
1
+ # CLAUDE.md — handover notes for AI coding agents
2
+
3
+ Project: **homebridge-smartsystem** ("SmartSocket"), a Homebridge plugin + standalone
4
+ server that bridges **Duotecno** home-automation gateways (and a few other power/energy
5
+ sources) to HomeKit, a web UI, and a WebSocket/HTTP API. Author: Johan Coppieters.
6
+
7
+ ## What this thing actually is
8
+
9
+ - It's a Homebridge **platform plugin** (`homebridge-smartsystem` in [package.json](package.json)),
10
+ registered from [index.ts](index.ts) via `api.registerPlatform(...)`.
11
+ - It also runs stand-alone as an Express-like web/proxy server (no Homebridge) — see
12
+ `server/webapp.ts`, `server/smartapp.ts`, `server/socapp.ts`.
13
+ - Talks to one or more **Duotecno masters** (physical IP-connected controllers) over raw
14
+ TCP using a custom binary protocol, and exposes their nodes/units (switches, dimmers,
15
+ moods, up/downs, temperature sensors, locks, garage doors, window coverings...) as:
16
+ - HomeKit accessories (via Homebridge)
17
+ - a web UI (EJS views in `server/views/`, static assets in `www/`)
18
+ - a WebSocket device-control API (see [WEBSOCKET-API.md](WEBSOCKET-API.md))
19
+ - proxied raw TCP-over-WebSocket (`server/proxy.ts`)
20
+ - Also integrates power/energy sources: Smappee (MQTT), a P1 smart meter, Shelly, and
21
+ can drive Somfy screens over GPIO on a Raspberry Pi.
22
+
23
+ ## Source layout
24
+
25
+ - [index.ts](index.ts) — Homebridge plugin entry point.
26
+ - `duotecno/` — protocol layer, independent of Homebridge/HTTP:
27
+ - [protocol.ts](duotecno/protocol.ts) — binary frame encode/decode, `Unit` types.
28
+ - [master.ts](duotecno/master.ts) (885 lines) — one TCP connection to a Duotecno master; heartbeat, reconnect logic.
29
+ - [system.ts](duotecno/system.ts) — collection of masters/nodes/units, the in-memory model.
30
+ - [smartsocket.ts](duotecno/smartsocket.ts), [Q.ts](duotecno/Q.ts) — low-level socket/queue helpers.
31
+ - [config.ts](duotecno/config.ts), [types.ts](duotecno/types.ts), [logger.ts](duotecno/logger.ts), [colors.ts](duotecno/colors.ts).
32
+ - `server/` — everything HTTP/WebSocket/Homebridge-facing:
33
+ - [platform.ts](server/platform.ts) — Homebridge `Platform` class; wires `System` + `SmartApp` + `SocApp` to Homebridge accessories.
34
+ - [smartapp.ts](server/smartapp.ts) (1691 lines, **the biggest file**) — web app: routes, device WebSocket API, power/energy bindings, links, switches. See [MIXINS-GUIDE.md](MIXINS-GUIDE.md) for a *proposed but not-yet-applied* refactor plan to split this file.
35
+ - [webapp.ts](server/webapp.ts) — generic HTTP server base (Express-like) that `smartapp`/`socapp` extend.
36
+ - [socapp.ts](server/socapp.ts) — raw-socket flavored app variant.
37
+ - [proxy.ts](server/proxy.ts) — WebSocket⇄TCP proxy.
38
+ - [base.ts](server/base.ts) — tiny shared base class.
39
+ - [smappee.ts](server/smappee.ts), [p1.ts](server/p1.ts), [shelly.ts](server/shelly.ts) — power measurement sources.
40
+ - [somfy.ts](server/somfy.ts) — GPIO control for Somfy screens (Raspberry Pi only).
41
+ - [HA-API.ts](server/HA-API.ts) — Home Assistant / openHAB style API stubs.
42
+ - [HB.ts](server/HB.ts) — Homebridge-specific glue.
43
+ - [mDNS.ts](server/mDNS.ts) — Bonjour service advertisement (`*.local` names).
44
+ - [support.ts](server/support.ts) — misc helpers.
45
+ - `views/` — EJS templates for the built-in web UI.
46
+ - `accessories/` — one file per HomeKit accessory type (bulb, dimmer, switch, lock, door,
47
+ garagedoor, mood, temperature, windowcovering) — each maps a Duotecno unit type/name
48
+ convention to a HomeKit service. The naming-convention rules (`$`, `*`, `!` markers in
49
+ Duotecno unit names) are documented at the bottom of [README.md](README.md) (v5.4.0 entry) — this is important domain logic, not incidental.
50
+ - `www/` — built/bundled front-end assets (webpack output, not source you'd hand-edit).
51
+ - `data/` — sample/backup JSON dumps of real systems (groups/scenes/system), used for
52
+ local testing, not app config.
53
+ - Root `test*.ts` (`testHB.ts`, `testWS.ts`, `testProxy.ts`) — standalone manual test
54
+ scripts/harnesses, not a unit test suite. See [TESTING-GUIDE.md](TESTING-GUIDE.md).
55
+ - `config*.json` at repo root — real/sample runtime configs for different installs
56
+ (`config.gm.json`, `config.peter.json`, ...). `config.json` is the active one used when
57
+ running locally via the `start` npm script.
58
+
59
+ ## Build & run
60
+
61
+ - **Language**: TypeScript, compiled to plain `.js` sitting next to each `.ts` file
62
+ (`"outDir"` = same folder, per [tsconfig.json](tsconfig.json)). Every `.ts` file has a
63
+ matching committed `.js` + `.js.map` — **the compiled JS is checked into git** and is
64
+ what Homebridge actually loads (`main: index.js`). Always rebuild after editing `.ts`.
65
+ - Build once: `npm run build` (`tsc --build`).
66
+ - Watch mode: `npm run watch` (also runs automatically on folder open, task `npm: 1`).
67
+ - Run under Homebridge locally: `npm start` (`homebridge -D -P <this dir>`), reads
68
+ `config.json` for platform config.
69
+ - No automated test framework/CI configured — testing is manual via `testHB.ts` /
70
+ `testWS.ts` / `testProxy.ts` and the guides in [TESTING-GUIDE.md](TESTING-GUIDE.md).
71
+ - Releases: `npm run release:patch|minor|major` — bumps version, commits, tags, publishes
72
+ to npm, and **pushes to origin**. Never run these without the user's explicit go-ahead.
73
+
74
+ ## Conventions / gotchas worth knowing before editing
75
+
76
+ - Compiled `.js` files are committed — after any `.ts` change, rebuild (or rely on the
77
+ watch task) before assuming the runtime behavior changed, and commit the regenerated
78
+ `.js`/`.js.map` alongside the `.ts` change.
79
+ - Duotecno unit *display names* encode behavior via trailing marker characters
80
+ (`$`, `*`, `!`, `#`), e.g. an "updown" unit named with `$` becomes a HomeKit
81
+ `GarageDoor`, with `*` becomes a `Door`, otherwise a `WindowCovering` — same pattern for
82
+ moods (`mood`/`unlocker`) and switches (`lock`/`switch`/`lightbulb`). This logic lives
83
+ across `accessories/*.ts` and `duotecno/types.ts` — check [README.md](README.md)'s
84
+ v5.4.0 section for the authoritative rule table before changing accessory-type mapping.
85
+ - `server/smartapp.ts` is large and mixes several concerns (device WS API, links, HTTP
86
+ switches, power bindings, proxy config). [MIXINS-GUIDE.md](MIXINS-GUIDE.md) captures a
87
+ discussed refactor (module/delegate pattern) that was **never carried out** — don't
88
+ assume that structure exists; it's a plan, not current code.
89
+ - Multiple root `config*.json` files exist for different physical installs/customers
90
+ (gm, peter, homebridge). Don't assume `config.json` is representative of all of them;
91
+ check which one is relevant when debugging a specific report.
92
+ - Hex formatting convention for Duotecno addresses: always display Node/Unit addresses
93
+ as hex with `0x` prefix (e.g. `0x04`, `0xFC`) in any UI/log output you add.
94
+ - `raspberry-pi-setup.sh`, `update.sh`, `updserv.sh` are deployment scripts for the actual
95
+ Raspberry Pi gateway hardware in the field — treat changes to these as
96
+ infrastructure-affecting, confirm with the user before running them.
97
+ - Somfy/GPIO code (`server/somfy.ts`) only makes sense on real Raspberry Pi hardware.
98
+
99
+ ## Where to look for more detail
100
+
101
+ - [README.md](README.md) — full version history/changelog; the most reliable source of
102
+ "why does this weird naming rule exist" answers.
103
+ - [WEBSOCKET-API.md](WEBSOCKET-API.md) — device WebSocket protocol (client/server message
104
+ formats, device ID = `node*256 + unit`).
105
+ - [MIXINS-GUIDE.md](MIXINS-GUIDE.md) — proposed (unapplied) refactor of `smartapp.ts`.
106
+ - [TESTING-GUIDE.md](TESTING-GUIDE.md) — manual testing steps using `testHB.ts`/`testWS.ts`.
107
+ - [SETTINGS-UPDATE.md](SETTINGS-UPDATE.md) — notes on the web UI Settings page state
108
+ (some features intentionally disabled/commented out pending real implementation).
109
+
110
+ ## Working style expected on this repo
111
+
112
+ - Prefer lean, minimal diffs; avoid speculative refactors or defensive code not clearly
113
+ needed (see the large-file/mixins situation above — it's known and intentionally
114
+ deferred, don't "fix" it unprompted).
115
+ - Commit locally often once a coherent, verified chunk of work is done (this user has
116
+ lost uncommitted work before). Never push or run `release:*` scripts without explicit
117
+ confirmation.
118
+ - Split unrelated changes into separate commits with descriptive messages.
@@ -23,7 +23,8 @@ class Temperature extends accessory_1.Accessory {
23
23
  getTemperature(next) {
24
24
  if (this.unit) {
25
25
  (0, logger_1.log)("accessory", "getTemperature was called for " + this.unit.node.getName() + " - " + this.unit.getName() + " -> " + this.unit.value);
26
- next(null, this.unit.value / 10.0);
26
+ const limited = Math.max(-100, Math.min(100, this.unit.value / 10.0));
27
+ next(null, limited);
27
28
  }
28
29
  else {
29
30
  next(new Error("accessory -> getTemperature needs a unit."));
@@ -20,6 +20,7 @@ exports.logSettings = {
20
20
  "webapp": LogLevel.log,
21
21
  "p1": LogLevel.log,
22
22
  "shelly": LogLevel.log,
23
+ "overkiz": LogLevel.log,
23
24
  "power": LogLevel.log,
24
25
  "smartapp": LogLevel.log
25
26
  };
@@ -754,13 +754,13 @@ exports.Protocol = {
754
754
  let kind = "-";
755
755
  if (next.cmd === Rec.Sensor) {
756
756
  // sensor -> value
757
- unit.value = this.makeWord(next.message, 9); // 10x current temperature
757
+ unit.value = this.makeSignedWord(next.message, 9); // 10x current temperature
758
758
  unit.status = next.message[7]; // 0=idle, 1=heating, 2=cooling
759
759
  unit.preset = (next.message[6]) ? next.message[8] : -1; // 0=sun, 1=half sun, 2=moon, 3=half moon, -1 = off
760
- unit.sun = this.makeWord(next.message, 11); // 10x temperature
761
- unit.hsun = this.makeWord(next.message, 13); // 10x temperature
762
- unit.moon = this.makeWord(next.message, 15); // 10x temperature
763
- unit.hmoon = this.makeWord(next.message, 17); // 10x temperature
760
+ unit.sun = this.makeSignedWord(next.message, 11); // 10x temperature
761
+ unit.hsun = this.makeSignedWord(next.message, 13); // 10x temperature
762
+ unit.moon = this.makeSignedWord(next.message, 15); // 10x temperature
763
+ unit.hmoon = this.makeSignedWord(next.message, 17); // 10x temperature
764
764
  (0, logger_1.debug)("protocol", "received status - " + unit.getDisplayName() + ", temperature = " + unit.value / 10.0 + ", sun = " + unit.sun);
765
765
  kind = "S";
766
766
  // Dimmers, switches and moods have
@@ -815,7 +815,7 @@ exports.Protocol = {
815
815
  // only change dim value when state = 1 (ON, PIR ON, DIM STOP) for Dimmers and IRTX + for any setpoint event
816
816
  if ((((next.message[4] === 1) || (next.message[4] === 6) || (next.message[4] === 7)) && (next.message[5] === 1)) ||
817
817
  (next.message[4] === 11)) {
818
- unit.value = this.makeWord(next.message, 6);
818
+ unit.value = this.makeSignedWord(next.message, 6);
819
819
  }
820
820
  else if (next.message[4] === 32) {
821
821
  // unit_type_duoswitch -> Event = 32
package/duotecno/types.js CHANGED
@@ -214,6 +214,7 @@ exports.Sanitizers = {
214
214
  config.smappee = config.smappee || false;
215
215
  config.p1 = config.p1 || false;
216
216
  config.shelly = config.shelly || false;
217
+ config.overkiz = config.overkiz || false;
217
218
  config.smartapp = config.smartapp || null;
218
219
  config.system = exports.Sanitizers.system(config.system);
219
220
  config.proxy = exports.Sanitizers.proxy(config.proxy, config.system);
@@ -308,6 +309,17 @@ exports.Sanitizers = {
308
309
  cfg.addresses = (config === null || config === void 0 ? void 0 : config.addresses) || "";
309
310
  return cfg;
310
311
  },
312
+ overkiz: function (config) {
313
+ if (!config)
314
+ config = {};
315
+ config.debug = config.debug || false;
316
+ config.service = config.service || "local";
317
+ config.user = config.user || "";
318
+ config.password = config.password || "";
319
+ config.pollingPeriod = config.pollingPeriod || 10;
320
+ config.refreshPeriod = config.refreshPeriod || 60;
321
+ return config;
322
+ },
311
323
  linkConfig: function (aLink) {
312
324
  const link = exports.Sanitizers.unitDef(aLink);
313
325
  link.accId = aLink.accId || "";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-smartsystem",
3
3
  "displayname": "Duotecno Bridge",
4
- "version": "7.1.17",
4
+ "version": "7.1.20",
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",
@@ -44,6 +44,7 @@
44
44
  "mqtt": "^4.3.7",
45
45
  "multicast-dns": "^7.2.5",
46
46
  "node-fetch": "^3.2.4",
47
+ "overkiz-client": "^1.0.23",
47
48
  "rpi-gpio": "^2.1.7",
48
49
  "ws": "^7.4.6"
49
50
  },
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ // Direct Somfy/TaHoma (Overkiz) integration.
3
+ //
4
+ // Talks straight to the Overkiz gateway (local TaHoma Developer Mode API, or
5
+ // cloud API as a fallback) using the same client library ("overkiz-client")
6
+ // that dubocr/homebridge-tahoma itself depends on. This replaces the old
7
+ // detour of running homebridge-tahoma as a second Homebridge platform and
8
+ // then polling its HomeKit accessories back into Duotecno via server/HB.ts
9
+ // and the Link mechanism in smartapp.ts.
10
+ //
11
+ // Phase 1 (this file): connect, discover devices, send open/close/stop
12
+ // commands, and emit translated Duotecno-style states when Overkiz reports
13
+ // a change. Wiring this into the Link UI (so a Duotecno unit can be coupled
14
+ // to a specific Somfy screen) is a follow-up step once connectivity against
15
+ // real hardware is confirmed.
16
+ //
17
+ // Johan Coppieters, 2026.
18
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
19
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20
+ return new (P || (P = Promise))(function (resolve, reject) {
21
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
25
+ });
26
+ };
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.Overkiz = void 0;
29
+ const events_1 = require("events");
30
+ const overkiz_client_1 = require("overkiz-client");
31
+ const types_1 = require("../duotecno/types");
32
+ const logger_1 = require("../duotecno/logger");
33
+ const base_1 = require("./base");
34
+ // core:ClosureState / core:ClosureOrRollerShutterPositionState: 0 = fully
35
+ // open, 100 = fully closed (opposite of HomeKit's TargetPosition scale).
36
+ const kOpenThreshold = 5;
37
+ const kClosedThreshold = 95;
38
+ function closureToUnitState(closure, previousClosure) {
39
+ if (closure <= kOpenThreshold)
40
+ return types_1.UnitState.kOpen;
41
+ if (closure >= kClosedThreshold)
42
+ return types_1.UnitState.kClosed;
43
+ if (typeof previousClosure === "number") {
44
+ if (closure < previousClosure)
45
+ return types_1.UnitState.kOpening;
46
+ if (closure > previousClosure)
47
+ return types_1.UnitState.kClosing;
48
+ }
49
+ return types_1.UnitState.kStopped;
50
+ }
51
+ class Overkiz extends base_1.Base {
52
+ constructor(system) {
53
+ super("overkiz");
54
+ this.emitter = new events_1.EventEmitter();
55
+ this.devices = new Map();
56
+ this.closures = new Map();
57
+ this.system = system;
58
+ if (this.config.user && this.config.password) {
59
+ this.connect().catch(e => (0, logger_1.err)("overkiz", "connect failed: " + (e.message || e)));
60
+ }
61
+ else {
62
+ (0, logger_1.log)("overkiz", "Overkiz not configured (missing user/password) -> not starting.");
63
+ }
64
+ }
65
+ connect() {
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ const bridge = {
68
+ warn: (...args) => (0, logger_1.log)("overkiz", args.join(" ")),
69
+ error: (...args) => (0, logger_1.err)("overkiz", args.join(" ")),
70
+ debug: (...args) => (0, logger_1.debug)("overkiz", args.join(" ")),
71
+ info: (...args) => (0, logger_1.log)("overkiz", args.join(" ")),
72
+ log: (...args) => (0, logger_1.log)("overkiz", args.join(" ")),
73
+ };
74
+ this.client = new overkiz_client_1.Client(bridge, {
75
+ service: this.config.service || "local",
76
+ user: this.config.user,
77
+ password: this.config.password,
78
+ pollingPeriod: this.config.pollingPeriod || 10,
79
+ refreshPeriod: this.config.refreshPeriod || 60,
80
+ });
81
+ const setup = yield this.client.getSetup();
82
+ setup.devices.forEach(device => this.attachDevice(device));
83
+ (0, logger_1.log)("overkiz", `connected -> ${setup.devices.length} device(s) found`);
84
+ });
85
+ }
86
+ attachDevice(device) {
87
+ this.devices.set(device.deviceURL, device);
88
+ const closure = device.getNumber("core:ClosureState");
89
+ if (device.hasState("core:ClosureState"))
90
+ this.closures.set(device.deviceURL, closure);
91
+ device.on("states", (states) => this.onStates(device, states));
92
+ }
93
+ onStates(device, states) {
94
+ (0, logger_1.debug)("overkiz", `states for ${device.label} -> ${JSON.stringify(states)}`);
95
+ const closureState = states.find(s => s.name === "core:ClosureState");
96
+ if (closureState) {
97
+ const closure = Number(closureState.value);
98
+ const previous = this.closures.get(device.deviceURL);
99
+ const unitState = closureToUnitState(closure, previous);
100
+ this.closures.set(device.deviceURL, closure);
101
+ (0, logger_1.log)("overkiz", `${device.label} -> closure ${closure} -> UnitState ${types_1.UnitState[unitState]}`);
102
+ this.emitter.emit("state", device.deviceURL, unitState, closure);
103
+ }
104
+ }
105
+ listDevices() {
106
+ return Array.from(this.devices.values()).map(d => ({
107
+ deviceURL: d.deviceURL, label: d.label, widget: d.definition.widgetName,
108
+ }));
109
+ }
110
+ execute(deviceURL, commandName, parameters = []) {
111
+ return __awaiter(this, void 0, void 0, function* () {
112
+ if (!this.client)
113
+ throw new Error("Overkiz not connected");
114
+ (0, logger_1.log)("overkiz", `execute ${commandName}${parameters.length ? "(" + parameters.join(",") + ")" : ""} on ${deviceURL}`);
115
+ yield this.client.execute("apply", {
116
+ label: "smartsocket",
117
+ actions: [{ deviceURL, commands: [{ name: commandName, parameters }] }],
118
+ });
119
+ });
120
+ }
121
+ up(deviceURL) {
122
+ return __awaiter(this, void 0, void 0, function* () {
123
+ yield this.execute(deviceURL, "open");
124
+ });
125
+ }
126
+ down(deviceURL) {
127
+ return __awaiter(this, void 0, void 0, function* () {
128
+ yield this.execute(deviceURL, "close");
129
+ });
130
+ }
131
+ stop(deviceURL) {
132
+ return __awaiter(this, void 0, void 0, function* () {
133
+ yield this.execute(deviceURL, "stop");
134
+ });
135
+ }
136
+ setClosure(deviceURL, percentClosed) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ yield this.execute(deviceURL, "setClosure", [percentClosed]);
139
+ });
140
+ }
141
+ }
142
+ exports.Overkiz = Overkiz;
143
+ //# sourceMappingURL=overkiz.js.map
@@ -28,6 +28,7 @@ const door_1 = require("../accessories/door");
28
28
  const lock_1 = require("../accessories/lock");
29
29
  const p1_1 = require("./p1");
30
30
  const shelly_1 = require("./shelly");
31
+ const overkiz_1 = require("./overkiz");
31
32
  const config_1 = require("../duotecno/config");
32
33
  const fs_1 = require("fs");
33
34
  const proxy_1 = require("./proxy");
@@ -85,6 +86,7 @@ class Platform extends base_1.Base {
85
86
  this.startPower("smappee", smappee_1.Smappee);
86
87
  this.startPower("p1", p1_1.P1);
87
88
  this.startPower("shelly", shelly_1.Shelly);
89
+ this.startPower("overkiz", overkiz_1.Overkiz);
88
90
  // startup a smartApp if configured
89
91
  if (this.config.smartapp) {
90
92
  try {
@@ -108,12 +108,15 @@ class PowerBase extends base_1.Base {
108
108
  // no previous value -> always update
109
109
  if (isNaN(previous))
110
110
  return true;
111
- // less than 1000W -> update on: difference > 5% previous
112
- if (current < 1000)
113
- return Math.abs(previous - current) > (previous * 0.05);
114
- // more than 1000W -> update on: difference > 2% previous
115
- if (current >= 1000)
116
- return Math.abs(previous - current) > (previous * 0.02);
111
+ const absCurrent = Math.abs(current);
112
+ const absPrevious = Math.abs(previous);
113
+ const diff = Math.abs(previous - current);
114
+ // less than 1000W -> update on: difference > 5% of previous absolute value
115
+ if (absCurrent < 1000)
116
+ return diff > (absPrevious * 0.05);
117
+ // more than 1000W -> update on: difference > 2% of previous absolute value
118
+ if (absCurrent >= 1000)
119
+ return diff > (absPrevious * 0.02);
117
120
  return false;
118
121
  }
119
122
  applyBindings() {
@@ -138,7 +141,7 @@ class PowerBase extends base_1.Base {
138
141
  });
139
142
  }
140
143
  else {
141
- (0, logger_1.debug)("power", `[Binding ${idx}] NOT significant for channel ${b.channel} -> register ${b.register}: ${currentValue}W -> ${power}W (diff: ${Math.abs(currentValue - power)}W, threshold: ${currentValue < 1000 ? '5%=' + Math.abs(currentValue * 0.05) : '2%=' + Math.abs(currentValue * 0.02)}W)`);
144
+ (0, logger_1.debug)("power", `[Binding ${idx}] NOT significant for channel ${b.channel} -> register ${b.register}: ${currentValue}W -> ${power}W (diff: ${Math.abs(currentValue - power)}W, threshold: ${Math.abs(currentValue) < 1000 ? '5%=' + Math.abs(currentValue * 0.05) : '2%=' + Math.abs(currentValue * 0.02)}W)`);
142
145
  }
143
146
  });
144
147
  }