homebridge-shelly-blu-trv 1.0.0 → 1.0.7

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
@@ -82,24 +82,55 @@ To test the plugin locally in a running Homebridge instance:
82
82
 
83
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`.
84
84
 
85
+ ### E2E (fake gateway) tests
86
+
87
+ We include a simple fake Shelly BLU gateway and an E2E runner to validate discovery and polling locally:
88
+
89
+ - Start the fake gateway + headless Homebridge locally: `npm run e2e` (the script builds the project and runs the fake gateway + Homebridge, it times out after ~60s if discovery/polling isn't observed).
90
+ - The repository also includes a GitHub Actions workflow `.github/workflows/e2e.yml` that runs the same test in CI.
91
+
92
+ ### Coverage check
93
+
94
+ To ensure coverage is sufficient:
95
+
96
+ - Run tests with coverage: `npm test`
97
+ - Check coverage threshold (lines): `npm run check-coverage` (configured to require at least 80% lines by default, set `COVERAGE_THRESHOLD` env to override)
98
+
99
+
85
100
  ## Publishing
86
101
 
87
- Automatic publishing is configured to run on new GitHub releases (see `.github/workflows/publish.yml`).
88
- To publish manually:
102
+ - The repository contains two publishing workflows:
103
+ - **Publish** (`.github/workflows/publish.yml`) — runs when a GitHub Release is *created* and performs an `npm publish` step.
104
+ - **Semantic Release** (`.github/workflows/semantic-release.yml`) — when enabled, automatically creates releases, changelogs and publishes to npm on `main` via `semantic-release`.
89
105
 
90
- 1. Ensure you're logged in to npm (`npm login`).
106
+ **NOTE:** The automatic semantic-release runs are currently **paused** (workflow trigger switched to `workflow_dispatch`). To run it manually: go to the Actions tab → **Semantic Release** → **Run workflow** (choose `main`).
107
+
108
+ Manual publish steps
109
+
110
+ 1. Ensure you're logged in to npm: `npm login`.
91
111
  2. Build the package: `npm run build`.
92
- 3. Bump the version and push tags, or run `npm version <patch|minor|major>` and push tags.
93
- 4. Publish: `npm publish --access public`.
112
+ 3. Bump the package version (e.g. `npm version patch`) and push the tag.
113
+ 4. Create a GitHub Release (or run `npm publish --access public` manually).
114
+
115
+ Developer notes about `NPM_TOKEN` and automation
94
116
 
95
- Automated semantic releases are configured with `semantic-release`. The workflow runs on pushes to `main` and publishes a GitHub Release + npm package when a new release is performed.
117
+ - For CI-based publishing you must set the repository secret `NPM_TOKEN` (Settings Secrets Actions) to a valid **npm automation token** with publish rights.
118
+ - Create it on npm: Settings → Access Tokens → **Create New Token** → choose **Automation** and ensure it has publish rights.
119
+ - If your npm account uses Two-Factor Authentication (2FA), pick a token that is usable for CI (set 2FA to **Authorization only** for automation tokens). See https://docs.npmjs.com/getting-started/working_with_tokens for details.
120
+ - If semantic-release reports `EINVALIDNPMTOKEN` or `401 Unauthorized`, try re-creating the token and updating `NPM_TOKEN` in repo secrets.
121
+ - The repository also includes a Publish workflow (triggered on Release creation) which can publish even if semantic-release is paused — this is useful for one-off releases.
96
122
 
97
- Note: You must add the following repository secrets for automatic publishing to work:
123
+ E2E and local verification (developer)
98
124
 
99
- - `NPM_TOKEN` — npm token with publish permissions
100
- - `GITHUB_TOKEN` typically provided automatically by GitHub Actions (`secrets.GITHUB_TOKEN`) but keep it available if you customize tokens
125
+ - Run the E2E runner locally: `npm run e2e` — this will install deps, build the project, install a local shim, start the fake gateway and a headless Homebridge instance and look for discovery/polling logs.
126
+ - Run unit tests + coverage: `npm test` (creates `coverage/lcov.info` and an `lcov-report` directory).
127
+ - Check coverage threshold: `npm run check-coverage` (defaults to 80% lines; use `COVERAGE_THRESHOLD` to override).
128
+
129
+ If you'd like, I can add a small diagnostic Action that runs `npm whoami` using `NPM_TOKEN` (safely) so you can quickly confirm which npm account the token maps to before re-enabling semantic-release.
130
+
131
+ ---
101
132
 
102
- If you'd like me to open a PR with this setup and add a draft release, say so and I will create the branch and PR now.
133
+ If you want this documentation expanded into a `DEVELOPING.md` file or want me to add the diagnostic Action, tell me which and I'll add it in a PR.
103
134
 
104
135
  ## License
105
136
 
package/dist/accessory.js CHANGED
@@ -52,7 +52,7 @@ class ShellyTrvAccessory {
52
52
  this.log.warn(`[${this.accessory.displayName}] Device offline, unable to retrieve ${key}`);
53
53
  throw new this.platform.api.hap.HapStatusError(-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
54
54
  }
55
- // accessing dynamic state keys
55
+ // accessing typed state keys
56
56
  const value = s[key];
57
57
  this.log.debug(`[${this.accessory.displayName}] Retrieved ${key}: ${value}`);
58
58
  return value;
package/dist/shellyApi.js CHANGED
@@ -21,8 +21,16 @@ class ShellyApi {
21
21
  signal: AbortSignal.timeout(this.requestTimeout)
22
22
  });
23
23
  if (!res.ok) {
24
- this.log.error(`[ShellyApi] HTTP ${res.status} from gateway ${this.gw.host}`);
25
- throw new Error(`HTTP ${res.status}`);
24
+ // Try to extract body text for better diagnostics
25
+ let bodyText = '';
26
+ try {
27
+ bodyText = await res.text();
28
+ }
29
+ catch {
30
+ bodyText = '<no body>';
31
+ }
32
+ this.log.error(`[ShellyApi] HTTP ${res.status} from gateway ${this.gw.host}${path} - ${bodyText}`);
33
+ throw new Error(`HTTP ${res.status}: ${bodyText}`);
26
34
  }
27
35
  const data = await res.json();
28
36
  this.log.debug(`[ShellyApi] Successfully fetched from ${this.gw.host}${path}`);
@@ -30,11 +38,58 @@ class ShellyApi {
30
38
  }
31
39
  catch (error) {
32
40
  if (error instanceof Error) {
33
- this.log.error(`[ShellyApi] Request failed to ${this.gw.host}: ${error.message}`);
41
+ this.log.error(`[ShellyApi] Request failed to ${this.gw.host}${path}: ${error.message}`);
34
42
  }
35
43
  throw error;
36
44
  }
37
45
  }
46
+ async rpcCall(id, method, params) {
47
+ // Try several RPC variants for wider compatibility with firmware differences
48
+ const paramsStr = params ? `&params=${encodeURIComponent(JSON.stringify(params))}` : '';
49
+ const candidates = [
50
+ `/rpc/BluTrv.call?id=${id}&method=${method}${paramsStr}`,
51
+ `/rpc/BluTrv.call&id=${id}&method=${method}${paramsStr}`
52
+ ];
53
+ for (const path of candidates) {
54
+ try {
55
+ return await this.get(path);
56
+ }
57
+ catch (err) {
58
+ // If 404, try next candidate; otherwise propagate
59
+ if (err instanceof Error && err.message.includes('HTTP 404')) {
60
+ this.log.debug(`[ShellyApi] RPC variant failed (404), trying next: ${path}`);
61
+ continue;
62
+ }
63
+ throw err;
64
+ }
65
+ }
66
+ // Fallback: try POST to /rpc/BluTrv.call with JSON body
67
+ try {
68
+ const url = buildUrl(this.gw.host, '/rpc/BluTrv.call', this.gw.token);
69
+ const res = await fetch(url, {
70
+ method: 'POST',
71
+ body: JSON.stringify({ id, method, params }),
72
+ headers: { 'Content-Type': 'application/json' },
73
+ signal: AbortSignal.timeout(this.requestTimeout)
74
+ });
75
+ if (!res.ok) {
76
+ let bodyText = '';
77
+ try {
78
+ bodyText = await res.text();
79
+ }
80
+ catch {
81
+ bodyText = '<no body>';
82
+ }
83
+ this.log.error(`[ShellyApi] HTTP ${res.status} from gateway ${this.gw.host} (POST /rpc/BluTrv.call): ${bodyText}`);
84
+ throw new Error(`HTTP ${res.status}: ${bodyText}`);
85
+ }
86
+ return res.json();
87
+ }
88
+ catch (err) {
89
+ this.log.error(`[ShellyApi] RPC call failed for TRV ${id} method ${method}: ${err instanceof Error ? err.message : String(err)}`);
90
+ throw err;
91
+ }
92
+ }
38
93
  async discoverTrvs() {
39
94
  this.log.debug(`[ShellyApi] Discovering TRVs from gateway ${this.gw.host}`);
40
95
  try {
@@ -57,7 +112,7 @@ class ShellyApi {
57
112
  async getTrvState(id) {
58
113
  this.log.debug(`[ShellyApi] Fetching state for TRV ${id}`);
59
114
  try {
60
- const rpc = await this.get(`/rpc/BluTrv.call&id=${id}&method=TRV.GetStatus`);
115
+ const rpc = await this.rpcCall(id, 'TRV.GetStatus');
61
116
  const status = await this.get("/status");
62
117
  const dev = status.ble?.devices?.find((d) => d.id === id);
63
118
  const state = {
@@ -78,8 +133,7 @@ class ShellyApi {
78
133
  async setTargetTemp(id, value) {
79
134
  this.log.debug(`[ShellyApi] Setting target temperature for TRV ${id} to ${value}°C`);
80
135
  try {
81
- await this.get(`/rpc/BluTrv.call&id=${id}&method=TRV.SetTarget&params=` +
82
- encodeURIComponent(JSON.stringify({ target_C: value })));
136
+ await this.rpcCall(id, 'TRV.SetTarget', { target_C: value });
83
137
  this.log.debug(`[ShellyApi] Successfully set target temperature for TRV ${id}`);
84
138
  }
85
139
  catch (error) {
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.0",
4
+ "version": "1.0.7",
5
5
  "description": "Homebridge plugin for Shelly BLU TRV thermostats via Shelly BLU Gateway Gen3",
6
6
  "license": "MIT",
7
7
  "author": "agiplv",
@@ -39,6 +39,8 @@
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
+ "check-coverage": "node scripts/check-coverage.js",
42
44
  "release:patch": "npm version patch -m \"chore(release): %s\" && git push --follow-tags",
43
45
  "semantic-release": "semantic-release"
44
46
  },