homebridge-shelly-blu-trv 1.0.10 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,86 +3,86 @@
3
3
  [![CI](https://github.com/agiplv/homebridge-shelly-blu-trv/actions/workflows/ci.yml/badge.svg)](https://github.com/agiplv/homebridge-shelly-blu-trv/actions)
4
4
 
5
5
  Homebridge plugin for Shelly BLU Thermostatic Radiator Valve (TRV) devices
6
- via the Shelly BLU Gateway Gen3.
7
6
 
8
- This plugin exposes BLU TRV devices to Apple HomeKit using a local Shelly BLU
9
- Gateway Gen3. It supports current temperature, target temperature control,
10
- valve position, battery level and offline detection.
7
+ # Homebridge Shelly BLU TRV Platform Plugin
8
+
9
+ Homebridge plugin for controlling Shelly BLU TRV (Thermostatic Radiator Valve) devices via a Shelly Plus/Pro Gateway. **Only manual device configuration is supported.**
11
10
 
12
11
  ## Features
13
12
 
14
- - BLU TRV discovery via Shelly BLU Gateway Gen3
15
- - Current temperature
16
- - Target temperature control
17
- - Valve position (percent)
18
- - Battery level
19
- - Offline detection
20
- - Multiple gateways
13
+ - Control Shelly BLU TRV devices from HomeKit
14
+ - Direct communication with TRVs (no cloud)
15
+ - Battery, temperature, and valve state reporting
16
+ - Fast polling and state updates
21
17
 
22
18
  ## Requirements
23
19
 
24
- - Node.js 18+
25
- - Homebridge 1.6+
26
- - Shelly BLU Gateway Gen3
27
- - Shelly BLU TRV
20
+ - Homebridge v1.6+
21
+ - Node.js v18+
22
+ - Shelly Plus/Pro Gateway (with local network access)
23
+ - Shelly BLU TRV devices
28
24
 
29
25
  ## Installation
30
26
 
31
- Install via npm:
27
+ Install via Homebridge UI or npm:
32
28
 
33
- ```bash
29
+ ```sh
34
30
  npm install -g homebridge-shelly-blu-trv
35
31
  ```
36
32
 
37
- ## Configuration (Homebridge UI)
33
+ ## Configuration
38
34
 
39
- Add the platform under `platforms` in Homebridge configuration. Example:
35
+ Add the platform to your Homebridge `config.json`. You **must** specify each gateway and the list of TRVs to control. **Automatic discovery is not supported.**
40
36
 
41
37
  ```json
42
38
  {
43
- "platforms": [
39
+ "platform": "ShellyBluPlatform",
40
+ "gateways": [
44
41
  {
45
- "platform": "ShellyBluTRV",
46
- "gateways": [
47
- {
48
- "host": "192.168.1.50",
49
- "token": "<optional-auth-token>",
50
- "pollInterval": 60
51
- }
42
+ "host": "192.168.1.10",
43
+ "token": "your-gateway-token",
44
+ "pollInterval": 60,
45
+ "devices": [
46
+ { "id": 123, "name": "Living Room" },
47
+ { "id": 456, "name": "Bedroom" }
52
48
  ]
53
49
  }
54
50
  ]
55
51
  }
56
52
  ```
57
53
 
58
- Notes:
54
+ ### Config Options
59
55
 
60
- - Auth token is optional.
61
- - Valve position is reported as 0–100% (read-only).
62
- - Offline TRVs are shown as “Not Responding” in HomeKit.
56
+ - `host` (string, required): IP or hostname of your Shelly Plus/Pro Gateway
57
+ - `token` (string, required): Gateway authentication token
58
+ - `pollInterval` (number, optional): Polling interval in seconds (default: 60)
59
+ - `devices` (array, required): List of TRVs to control. Each device must have:
60
+ - `id` (number, required): TRV device ID (as shown in the Shelly app or web UI)
61
+ - `name` (string, required): Name for HomeKit
63
62
 
64
- ---
63
+ **Note:** You must manually add each TRV you want to control. The plugin will not scan or discover devices automatically.
65
64
 
66
- ## Development
65
+ ## Usage
67
66
 
68
- - Build: `npm run build`
69
- - Lint: `npm run lint`
70
- - Test: `npm test`
67
+ Once configured, your Shelly BLU TRVs will appear in HomeKit as Thermostat accessories. You can control target temperature, view current temperature, battery, and valve state.
71
68
 
72
- Contributions welcome — open an issue or a pull request.
69
+ ## Troubleshooting
73
70
 
74
- ---
71
+ - Ensure your Homebridge server can reach the Shelly Gateway and TRVs on the local network.
72
+ - Double-check the device IDs and gateway token.
73
+ - Check Homebridge logs for errors.
75
74
 
76
- ## Local testing
75
+ ## Development & Testing
77
76
 
78
- To test the plugin locally in a running Homebridge instance:
77
+ Run tests with:
79
78
 
80
- - Build a tarball: `npm pack`
81
- - Install into Homebridge (on same machine): `npm i -g ./homebridge-shelly-blu-trv-<version>.tgz` or upload the tarball in Homebridge UI as a plugin file.
79
+ ```sh
80
+ npm test
81
+ ```
82
82
 
83
- To test in a development Homebridge container, you can mount the packed tarball or install from the working directory with `npm i -g` after `npm run build`.
83
+ ## License
84
84
 
85
- ### E2E (fake gateway) tests
85
+ MIT
86
86
 
87
87
  We include a simple fake Shelly BLU gateway and an E2E runner to validate discovery and polling locally:
88
88
 
@@ -27,13 +27,15 @@
27
27
  },
28
28
  "devices": {
29
29
  "type": "array",
30
- "title": "Manual TRV devices (optional)",
30
+ "title": "TRV devices (required)",
31
+ "minItems": 1,
31
32
  "items": {
32
33
  "type": "object",
33
34
  "properties": {
34
35
  "id": { "type": "number", "title": "TRV ID", "required": true },
35
36
  "name": { "type": "string", "title": "Display name" }
36
- }
37
+ },
38
+ "required": ["id"]
37
39
  }
38
40
  }
39
41
  }
package/dist/platform.js CHANGED
@@ -31,32 +31,15 @@ class ShellyBluPlatform {
31
31
  this.log.warn("[ShellyBluPlatform] No gateways configured in config.json");
32
32
  return;
33
33
  }
34
- this.log.info(`[ShellyBluPlatform] Starting device discovery for ${gateways.length} gateway(s)`);
34
+ this.log.info(`[ShellyBluPlatform] Registering manually configured TRVs for ${gateways.length} gateway(s)`);
35
35
  for (const gw of gateways) {
36
- this.log.debug(`[ShellyBluPlatform] Discovering devices on gateway: ${gw.host}`);
36
+ if (!gw.devices || gw.devices.length === 0) {
37
+ this.log.warn(`[ShellyBluPlatform] No TRVs configured for gateway ${gw.host}`);
38
+ continue;
39
+ }
37
40
  const api = new shellyApi_1.ShellyApi(gw, this.log);
38
41
  const pollMs = (gw.pollInterval ?? 60) * 1000;
39
- let trvs;
40
- try {
41
- trvs = await api.discoverTrvs();
42
- this.log.info(`[ShellyBluPlatform] Discovered ${trvs.length} TRV(s) on gateway ${gw.host}`);
43
- // If discovery returned nothing but user provided manual devices, use them
44
- if ((!trvs || trvs.length === 0) && gw.devices && gw.devices.length > 0) {
45
- this.log.warn(`[ShellyBluPlatform] Discovery returned no devices; using manual device list for gateway ${gw.host}`);
46
- trvs = gw.devices.map((d) => ({ id: d.id, name: d.name || `BLU TRV ${d.id}` }));
47
- }
48
- }
49
- catch (error) {
50
- if (gw.devices && gw.devices.length > 0) {
51
- this.log.warn(`[ShellyBluPlatform] Discovery failed for gateway ${gw.host}, using manual device list: ${error instanceof Error ? error.message : String(error)}`);
52
- trvs = gw.devices.map((d) => ({ id: d.id, name: d.name || `BLU TRV ${d.id}` }));
53
- }
54
- else {
55
- this.log.error(`[ShellyBluPlatform] Failed to discover devices on gateway ${gw.host}: ${error instanceof Error ? error.message : String(error)}`);
56
- continue;
57
- }
58
- }
59
- for (const trv of trvs) {
42
+ for (const trv of gw.devices) {
60
43
  const uuid = this.api.hap.uuid.generate(`${gw.host}-${trv.id}`);
61
44
  let acc = this.accessories.get(uuid);
62
45
  if (!acc) {
package/dist/shellyApi.js CHANGED
@@ -45,6 +45,7 @@ class ShellyApi {
45
45
  }
46
46
  async rpcCall(id, method, params) {
47
47
  // Try several RPC variants for wider compatibility with firmware differences
48
+ const quotedMethod = `"${method}"`;
48
49
  const paramsStr = params ? `&params=${encodeURIComponent(JSON.stringify(params))}` : '';
49
50
  // Try direct GetStatus endpoints first (some firmwares expose dedicated GET endpoints)
50
51
  const candidates = [];
@@ -53,10 +54,10 @@ class ShellyApi {
53
54
  candidates.push(`/rpc/BluTrv.GetStatus&id=${id}`);
54
55
  }
55
56
  // Common CALL variants (different firmware use call vs Call and different query separators)
56
- candidates.push(`/rpc/BluTrv.Call?id=${id}&method=${method}${paramsStr}`);
57
- candidates.push(`/rpc/BluTrv.call?id=${id}&method=${method}${paramsStr}`);
58
- candidates.push(`/rpc/BluTrv.Call&id=${id}&method=${method}${paramsStr}`);
59
- candidates.push(`/rpc/BluTrv.call&id=${id}&method=${method}${paramsStr}`);
57
+ candidates.push(`/rpc/BluTrv.Call?id=${id}&method=${quotedMethod}${paramsStr}`);
58
+ candidates.push(`/rpc/BluTrv.call?id=${id}&method=${quotedMethod}${paramsStr}`);
59
+ candidates.push(`/rpc/BluTrv.Call&id=${id}&method=${quotedMethod}${paramsStr}`);
60
+ candidates.push(`/rpc/BluTrv.call&id=${id}&method=${quotedMethod}${paramsStr}`);
60
61
  for (const path of candidates) {
61
62
  try {
62
63
  return await this.get(path);
@@ -97,29 +98,10 @@ class ShellyApi {
97
98
  throw err;
98
99
  }
99
100
  }
100
- async discoverTrvs() {
101
- this.log.debug(`[ShellyApi] Discovering TRVs from gateway ${this.gw.host}`);
102
- try {
103
- const status = await this.get("/status");
104
- const devices = status.ble?.devices ?? [];
105
- const trvs = devices
106
- .filter((d) => d.type === "trv")
107
- .map((d) => ({
108
- id: d.id ?? 0,
109
- name: d.name || `BLU TRV ${d.id ?? 'unknown'}`
110
- }));
111
- this.log.debug(`[ShellyApi] Found ${trvs.length} TRV(s) on gateway ${this.gw.host}`);
112
- return trvs;
113
- }
114
- catch (error) {
115
- this.log.error(`[ShellyApi] Failed to discover TRVs: ${error instanceof Error ? error.message : String(error)}`);
116
- throw error;
117
- }
118
- }
119
101
  async getTrvState(id) {
120
102
  this.log.debug(`[ShellyApi] Fetching state for TRV ${id}`);
121
103
  try {
122
- // RPC variant may return battery/paired status directly (BluTrv.GetStatus). Use RPC result first.
104
+ // Only use direct RPC response; no /status fallback
123
105
  const rpcAny = await this.rpcCall(id, 'TRV.GetStatus');
124
106
  const rpc = rpcAny;
125
107
  // Validate required fields
@@ -128,28 +110,16 @@ class ShellyApi {
128
110
  typeof rpc.pos !== 'number') {
129
111
  throw new Error(`Missing required TRV state fields in RPC response: ${JSON.stringify(rpc)}`);
130
112
  }
131
- // Prefer battery/online information from RPC response; if missing, try /status as a graceful fallback
132
- let battery = typeof rpc.battery === 'number' ? rpc.battery : undefined;
133
- let online = typeof rpc.paired === 'boolean' ? rpc.paired : undefined;
134
- if (battery === undefined || online === undefined) {
135
- try {
136
- const status = await this.get("/status");
137
- const dev = status.ble?.devices?.find((d) => d.id === id);
138
- battery = battery ?? dev?.battery ?? 0;
139
- online = online ?? !!dev?.online;
140
- }
141
- catch (err) {
142
- this.log.debug(`[ShellyApi] /status fallback failed for TRV ${id}: ${err instanceof Error ? err.message : String(err)}`);
143
- battery = battery ?? 0;
144
- online = online ?? true;
145
- }
146
- }
113
+ // Use battery/online from RPC response if present, else default
114
+ const battery = typeof rpc.battery === 'number' ? rpc.battery : 0;
115
+ // Accept either paired or online as online indicator
116
+ const online = typeof rpc.online === 'boolean' ? rpc.online : (typeof rpc.paired === 'boolean' ? rpc.paired : true);
147
117
  const state = {
148
118
  currentTemp: rpc.current_C,
149
119
  targetTemp: rpc.target_C,
150
120
  valve: rpc.pos,
151
- battery: battery ?? 0,
152
- online: !!online
121
+ battery,
122
+ online
153
123
  };
154
124
  this.log.debug(`[ShellyApi] TRV ${id} state: temp=${state.currentTemp}°C, target=${state.targetTemp}°C, valve=${state.valve}%, battery=${state.battery}%, online=${state.online}`);
155
125
  return state;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-shelly-blu-trv",
3
3
  "displayName": "Shelly BLU TRV",
4
- "version": "1.0.10",
4
+ "version": "1.1.2",
5
5
  "description": "Homebridge plugin for Shelly BLU TRV thermostats via Shelly BLU Gateway Gen3",
6
6
  "license": "MIT",
7
7
  "author": "agiplv",
@@ -39,7 +39,6 @@
39
39
  "test": "vitest run --coverage",
40
40
  "test:watch": "vitest",
41
41
  "lint": "eslint . --ext .ts",
42
- "e2e": "node scripts/run-e2e.js",
43
42
  "check-coverage": "node scripts/check-coverage.js",
44
43
  "release:patch": "npm version patch -m \"chore(release): %s\" && git push --follow-tags",
45
44
  "semantic-release": "semantic-release"