homebridge-nuheat2 1.2.12 → 1.2.13

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/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to this project should be documented in this file
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.2.13] - 2026-04-24
8
+
9
+ ### Changed
10
+
11
+ - Subscribe to Nuheat schedule notifications alongside thermostat and group notifications
12
+ - Remove an unused Homebridge UI utility dependency from the published package
13
+ - Improve Homebridge Config UI labels, defaults, grouping, and sensitive-field handling
14
+ - Add a custom Homebridge admin UI aligned with the shared Roborock design system
15
+ - Document the upcoming PKCE public-client direction for Nuheat OAuth credentials
16
+ - Enforce a 30-second minimum polling interval at runtime to reduce accidental API load
17
+
18
+ ### Fixed
19
+
20
+ - Correct refresh-token rotation debug logging so it compares against the previous token
21
+ - Keep notification de-duplication bookkeeping consistent across mixed notification batches
22
+
7
23
  ## [1.2.12] - 2026-04-21
8
24
 
9
25
  ### Fixed
package/README.md CHANGED
@@ -44,7 +44,13 @@ The published package name for this maintained fork is `homebridge-nuheat2`. The
44
44
 
45
45
  ## Configuration
46
46
 
47
- Most users should configure the plugin through Homebridge Config UI X, but the equivalent JSON looks like this:
47
+ Most users should configure the plugin through the custom Homebridge admin UI. It is organized into Account, Accessories, Behavior, Advanced OAuth, and Diagnostics panels and writes the same config keys shown below.
48
+
49
+ Sensitive values are handled deliberately: saved passwords and legacy client secrets are not redisplayed in the UI. Leave those fields blank to keep the saved value, enter a new value to replace it, or use the Clear Overrides button in Advanced OAuth to remove OAuth overrides.
50
+
51
+ The Diagnostics panel summarizes the saved configuration and exposure strategy before restart. It does not make live Nuheat API calls.
52
+
53
+ The equivalent JSON looks like this:
48
54
 
49
55
  ```json
50
56
  {
@@ -52,9 +58,14 @@ Most users should configure the plugin through Homebridge Config UI X, but the e
52
58
  "name": "NuHeat",
53
59
  "email": "email@address.com",
54
60
  "password": "password123",
55
- "devices": [{ "serialNumber": "1111111" }, { "serialNumber": "2222222" }],
61
+ "devices": [
62
+ { "serialNumber": "1111111", "disabled": false },
63
+ { "serialNumber": "2222222", "disabled": false }
64
+ ],
56
65
  "autoPopulateAwayModeSwitches": true,
66
+ "groups": [{ "groupName": "Main Floor", "disabled": false }],
57
67
  "exposeScheduleSwitches": false,
68
+ "enableNotifications": true,
58
69
  "holdLength": 1440,
59
70
  "refresh": 60
60
71
  }
@@ -69,15 +80,17 @@ Most users should configure the plugin through Homebridge Config UI X, but the e
69
80
  - `password`: MyNuheat account password
70
81
  - `devices`: Optional list of thermostats to expose. If omitted or empty, every thermostat on the account will be discovered automatically. Blank rows in the UI are ignored
71
82
  - `serialNumber`: Thermostat serial number from MyNuheat
83
+ - `disabled`: Available on `devices` and `groups` rows. Keeps the row saved while preventing that thermostat or group from being exposed
72
84
  - `autoPopulateAwayModeSwitches`: Automatically expose away-mode switches for all groups on the account
73
85
  - `exposeScheduleSwitches`: Optionally expose a switch per thermostat that reflects whether the thermostat is following its schedule and can be turned on to resume the schedule
74
86
  - `groups`: Optional allow-list of groups to expose as away-mode switches. This only affects group/away-mode accessories. Blank rows in the UI are ignored
75
87
  - `groupName`: Group name as shown in MyNuheat
76
- - `holdLength`: Hold duration in minutes
77
- - `refresh`: Poll interval in seconds, default `60`
78
- - `debug`: Enables verbose logging
79
- - `clientId`: Optional advanced override for the Nuheat OAuth client ID. This is recommended once you have official Nuheat API credentials
80
- - `clientSecret`: Optional advanced override for the Nuheat OAuth client secret
88
+ - `holdLength`: Hold duration in minutes, default `1440`. Values are clamped from `0` to `1440`
89
+ - `refresh`: Poll interval in seconds, default `60`. Values lower than `30` are raised to `30` to reduce API traffic
90
+ - `enableNotifications`: Enables Nuheat SignalR notifications for faster updates. Defaults to `true`; set to `false` only while troubleshooting
91
+ - `debug`: Enables verbose Nuheat API, notification, and accessory logging. Defaults to `false`
92
+ - `clientId`: Optional advanced override for the Nuheat OAuth client ID. Current releases require this to be paired with `clientSecret`; PKCE public-client support is planned but is not active yet
93
+ - `clientSecret`: Optional legacy OAuth client secret override for confidential-client credentials. Required with a custom `clientId` until PKCE support ships. Do not publish or share this value
81
94
  - `redirectUri`: Optional advanced override for the Nuheat OAuth redirect URI, default `http://localhost`
82
95
 
83
96
  ### Hold Length Behavior
@@ -101,7 +114,7 @@ Nuheat's public OpenAPI documentation indicates that third-party developers shou
101
114
  - [Nuheat OpenAPI docs](https://api.mynuheat.com/)
102
115
  - [Nuheat API access request page](https://www.nuheat.com/openapi)
103
116
 
104
- This fork still supports the legacy built-in OAuth client settings as a fallback, but using your own `clientId` and `clientSecret` is the recommended long-term path once Nuheat issues them for your integration.
117
+ This fork still supports the legacy built-in OAuth client settings as a fallback. Nuheat is expected to issue a PKCE-based public client for this integration so the plugin can eventually ship with a public `clientId` without distributing a client secret. Until that migration is complete, keep any issued `clientSecret` out of GitHub, npm, screenshots, and shared logs.
105
118
 
106
119
  ## What's New In This Fork
107
120
 
@@ -161,5 +174,6 @@ After that, bump the version in `package.json`, push to `master`, and GitHub Act
161
174
  ## Future Work
162
175
 
163
176
  - Validate the plugin against the official Nuheat API credentials requested for this integration.
177
+ - Move the normal OAuth path to Nuheat's PKCE-based public client once the new client details are issued.
164
178
  - Verify group and away-mode behavior against current live API responses.
165
179
  - Evaluate whether SignalR notifications can reduce polling further in real-world deployments.
@@ -2,6 +2,10 @@
2
2
  "pluginAlias": "NuHeat",
3
3
  "pluginType": "platform",
4
4
  "singular": false,
5
+ "customUi": true,
6
+ "customUiPath": "./homebridge-ui",
7
+ "headerDisplay": "Connect Nuheat Signature floor-heating thermostats to HomeKit. Most users only need their MyNuheat account email and password.",
8
+ "footerDisplay": "The npm package is homebridge-nuheat2, but the Homebridge platform remains NuHeat for backward compatibility.",
5
9
  "schema": {
6
10
  "type": "object",
7
11
  "required": [
@@ -10,104 +14,177 @@
10
14
  "password"
11
15
  ],
12
16
  "properties": {
17
+ "platform": {
18
+ "title": "Platform",
19
+ "type": "string",
20
+ "default": "NuHeat",
21
+ "const": "NuHeat",
22
+ "description": "Homebridge platform identifier. Keep this set to NuHeat."
23
+ },
13
24
  "name": {
14
- "title": "Name",
25
+ "title": "Platform Name",
15
26
  "type": "string",
16
- "default": "NuHeat"
27
+ "default": "NuHeat",
28
+ "description": "Display name used for this platform instance in Homebridge logs."
17
29
  },
18
30
  "email": {
19
- "title": "email",
31
+ "title": "MyNuheat Email",
20
32
  "type": "string",
21
- "format": "email"
33
+ "format": "email",
34
+ "description": "Email address for the MyNuheat account that owns the thermostats."
22
35
  },
23
36
  "password": {
24
- "title": "password",
25
- "type": "string"
26
- },
27
- "clientId": {
28
- "title": "Nuheat Client ID",
29
- "type": "string",
30
- "description": "Optional advanced override. Leave blank to use the built-in default client ID."
31
- },
32
- "clientSecret": {
33
- "title": "Nuheat Client Secret",
34
- "type": "string",
35
- "description": "Optional advanced override. Leave blank to use the built-in default client secret."
36
- },
37
- "redirectUri": {
38
- "title": "Nuheat Redirect URI",
37
+ "title": "MyNuheat Password",
39
38
  "type": "string",
40
- "description": "Optional advanced override for OAuth redirect URI.",
41
- "placeholder": "http://localhost"
39
+ "format": "password",
40
+ "description": "Password for the MyNuheat account."
42
41
  },
43
42
  "devices": {
44
- "title": "Devices",
43
+ "title": "Thermostat Allow-List",
45
44
  "type": "array",
46
- "description": "Optional allow-list of thermostats to expose. Leave empty to auto-discover all thermostats on the account.",
45
+ "default": [],
46
+ "description": "Optional. Leave empty to expose every thermostat on the account. Add rows only if you want to limit HomeKit to specific thermostat serial numbers.",
47
47
  "items": {
48
48
  "type": "object",
49
49
  "properties": {
50
50
  "serialNumber": {
51
51
  "title": "Serial Number",
52
- "type": "string"
52
+ "type": "string",
53
+ "description": "Thermostat serial number from MyNuheat."
53
54
  },
54
55
  "disabled": {
55
56
  "title": "Disabled",
56
- "type": "boolean"
57
+ "type": "boolean",
58
+ "default": false,
59
+ "description": "Keep this row in the config but do not expose this thermostat."
57
60
  }
58
61
  }
59
62
  }
60
63
  },
61
- "autoPopulateAwayModeSwitches": {
62
- "title": "Auto populate Away Mode switches for all available groups",
63
- "type": "boolean"
64
- },
65
- "exposeScheduleSwitches": {
66
- "title": "Expose per-thermostat schedule switches",
67
- "type": "boolean",
68
- "description": "Creates a switch for each thermostat that reflects whether its schedule is active and can be turned on to resume the configured schedule."
69
- },
70
64
  "groups": {
71
- "title": "Groups",
65
+ "title": "Away Mode Group Allow-List",
72
66
  "type": "array",
73
- "description": "Optional allow-list of groups to expose as away-mode switches. Leave empty unless you want to add specific groups manually.",
67
+ "default": [],
68
+ "description": "Optional. Leave empty unless you want specific Nuheat groups exposed as away-mode switches.",
74
69
  "items": {
75
70
  "type": "object",
76
71
  "properties": {
77
72
  "groupName": {
78
73
  "title": "Group Name",
79
- "type": "string"
74
+ "type": "string",
75
+ "description": "Group name exactly as shown in MyNuheat."
80
76
  },
81
77
  "disabled": {
82
78
  "title": "Disabled",
83
- "type": "boolean"
79
+ "type": "boolean",
80
+ "default": false,
81
+ "description": "Keep this row in the config but do not expose this group."
84
82
  }
85
83
  }
86
84
  }
87
85
  },
86
+ "autoPopulateAwayModeSwitches": {
87
+ "title": "Expose Away Mode Switches For All Groups",
88
+ "type": "boolean",
89
+ "default": false,
90
+ "description": "Automatically create HomeKit away-mode switches for every Nuheat group on the account."
91
+ },
92
+ "exposeScheduleSwitches": {
93
+ "title": "Expose Schedule Switches",
94
+ "type": "boolean",
95
+ "default": false,
96
+ "description": "Create one switch per thermostat. The switch is on when the thermostat follows its Nuheat schedule and can be turned on to resume the schedule."
97
+ },
88
98
  "holdLength": {
89
- "title": "Hold Length (in minutes)",
99
+ "title": "Hold Length (minutes)",
90
100
  "type": "integer",
91
- "description": "If set to 0, set point changes will hold until the next scheduled event. If set to 1440 (default), set point changes will be permanent. Set to any value in between for a timed hold.",
92
- "placeholder": 1440
101
+ "default": 1440,
102
+ "minimum": 0,
103
+ "maximum": 1440,
104
+ "description": "Duration for HomeKit setpoint changes. Use 0 to hold until the next scheduled event, 1-1439 for a timed hold, or 1440 for a permanent hold."
93
105
  },
94
106
  "refresh": {
95
- "title": "Refresh Interval (in seconds)",
107
+ "title": "Polling Interval (seconds)",
96
108
  "type": "integer",
97
- "placeholder": 60,
98
- "minimum": 1
99
- },
100
- "debug": {
101
- "title": "Enable debug logs",
102
- "type": "boolean"
109
+ "default": 60,
110
+ "minimum": 30,
111
+ "description": "How often to poll Nuheat as a safety net. Notifications update faster when enabled; avoid very low values to reduce API traffic."
103
112
  },
104
113
  "enableNotifications": {
105
- "title": "Enable Nuheat notifications (SignalR)",
114
+ "title": "Enable Nuheat Notifications",
106
115
  "type": "boolean",
107
- "description": "Disable this to use REST polling only while troubleshooting notification or API issues."
116
+ "default": true,
117
+ "description": "Use Nuheat SignalR notifications for faster updates. Disable only while troubleshooting, which leaves polling as the fallback."
118
+ },
119
+ "debug": {
120
+ "title": "Enable Debug Logs",
121
+ "type": "boolean",
122
+ "default": false,
123
+ "description": "Write detailed Nuheat API and accessory activity to the Homebridge log."
124
+ },
125
+ "clientId": {
126
+ "title": "Nuheat Client ID",
127
+ "type": "string",
128
+ "description": "Advanced OAuth override. Current releases require this to be paired with a clientSecret; PKCE public-client support is planned but not active yet."
129
+ },
130
+ "clientSecret": {
131
+ "title": "Nuheat Client Secret (Legacy)",
132
+ "type": "string",
133
+ "format": "password",
134
+ "description": "Advanced OAuth override for confidential-client credentials. Required with a custom clientId until PKCE support ships. Do not publish or share this value."
135
+ },
136
+ "redirectUri": {
137
+ "title": "Nuheat Redirect URI",
138
+ "type": "string",
139
+ "default": "http://localhost",
140
+ "description": "Advanced OAuth redirect URI. The default is http://localhost."
108
141
  }
109
142
  }
110
143
  },
144
+ "layout": [
145
+ "platform",
146
+ "name",
147
+ "email",
148
+ "password",
149
+ {
150
+ "type": "fieldset",
151
+ "title": "Accessories",
152
+ "expandable": true,
153
+ "expanded": false,
154
+ "description": "Control which Nuheat thermostats and group switches appear in HomeKit.",
155
+ "items": [
156
+ "devices",
157
+ "autoPopulateAwayModeSwitches",
158
+ "groups",
159
+ "exposeScheduleSwitches"
160
+ ]
161
+ },
162
+ {
163
+ "type": "fieldset",
164
+ "title": "Behavior",
165
+ "expandable": true,
166
+ "expanded": false,
167
+ "description": "Tune holds, refresh behavior, notifications, and logging.",
168
+ "items": [
169
+ "holdLength",
170
+ "refresh",
171
+ "enableNotifications",
172
+ "debug"
173
+ ]
174
+ },
175
+ {
176
+ "type": "fieldset",
177
+ "title": "Advanced OAuth",
178
+ "expandable": true,
179
+ "expanded": false,
180
+ "description": "Only change these fields when testing Nuheat-issued API credentials.",
181
+ "items": [
182
+ "clientId",
183
+ "clientSecret",
184
+ "redirectUri"
185
+ ]
186
+ }
187
+ ],
111
188
  "form": null,
112
189
  "display": null
113
190
  }
@@ -0,0 +1,244 @@
1
+ <link rel="stylesheet" href="styles.css" />
2
+
3
+ <main class="container">
4
+ <header>
5
+ <h1>Nuheat Radiant Floor Heating</h1>
6
+ <p class="subtitle">
7
+ Configure MyNuheat account access, HomeKit accessory exposure, and
8
+ connection behavior.
9
+ </p>
10
+ </header>
11
+
12
+ <section class="panel">
13
+ <div class="section-header">
14
+ <div>
15
+ <h2>Account</h2>
16
+ <p class="help">
17
+ Most homes only need the MyNuheat email and password used by the
18
+ Nuheat mobile app or web portal.
19
+ </p>
20
+ </div>
21
+ <span id="auth-status" class="status-pill warn">Checking config</span>
22
+ </div>
23
+
24
+ <div class="settings-grid">
25
+ <label class="field">
26
+ <span>Platform Name</span>
27
+ <input id="name" type="text" placeholder="NuHeat" />
28
+ <small>Name shown in Homebridge logs for this platform instance.</small>
29
+ </label>
30
+
31
+ <label class="field">
32
+ <span>MyNuheat Email</span>
33
+ <input id="email" type="email" placeholder="name@example.com" />
34
+ <small>Email address for the account that owns the thermostats.</small>
35
+ </label>
36
+
37
+ <label id="password-row" class="field">
38
+ <span>MyNuheat Password</span>
39
+ <input id="password" type="password" placeholder="Password" />
40
+ <small>Leave blank to keep an existing saved password.</small>
41
+ </label>
42
+ </div>
43
+
44
+ <div class="actions">
45
+ <button id="save-account" class="primary">Save Account</button>
46
+ </div>
47
+ </section>
48
+
49
+ <section class="panel">
50
+ <div class="section-header">
51
+ <div>
52
+ <h2>Accessories</h2>
53
+ <p class="help">
54
+ Leave thermostat and group lists empty to use automatic discovery.
55
+ Add rows only when you want to limit what appears in HomeKit.
56
+ </p>
57
+ </div>
58
+ <span id="accessory-status" class="status-pill warn">Auto discovery</span>
59
+ </div>
60
+
61
+ <div class="split-grid">
62
+ <div class="list-panel">
63
+ <div class="list-heading">
64
+ <div>
65
+ <h3>Thermostat Allow-List</h3>
66
+ <p class="help">
67
+ Optional serial numbers from MyNuheat.
68
+ </p>
69
+ </div>
70
+ <button id="add-device" class="secondary" type="button">Add</button>
71
+ </div>
72
+ <div id="devices-list" class="dynamic-list"></div>
73
+ </div>
74
+
75
+ <div class="list-panel">
76
+ <div class="list-heading">
77
+ <div>
78
+ <h3>Away Mode Groups</h3>
79
+ <p class="help">
80
+ Optional group names exactly as shown in MyNuheat.
81
+ </p>
82
+ </div>
83
+ <button id="add-group" class="secondary" type="button">Add</button>
84
+ </div>
85
+ <div id="groups-list" class="dynamic-list"></div>
86
+ </div>
87
+ </div>
88
+
89
+ <label class="checkbox">
90
+ <input id="auto-populate-away-mode-switches" type="checkbox" />
91
+ <span>
92
+ Expose away-mode switches for all groups
93
+ <small>
94
+ Automatically creates HomeKit switches for every Nuheat group on the
95
+ account.
96
+ </small>
97
+ </span>
98
+ </label>
99
+
100
+ <label class="checkbox">
101
+ <input id="expose-schedule-switches" type="checkbox" />
102
+ <span>
103
+ Expose schedule switches
104
+ <small>
105
+ Adds one switch per thermostat that can resume the Nuheat schedule.
106
+ </small>
107
+ </span>
108
+ </label>
109
+
110
+ <div class="actions">
111
+ <button id="save-accessories" class="primary">Save Accessories</button>
112
+ </div>
113
+ </section>
114
+
115
+ <section class="panel">
116
+ <div class="section-header">
117
+ <div>
118
+ <h2>Behavior</h2>
119
+ <p class="help">
120
+ Tune setpoint holds, polling, Nuheat notifications, and debug logging.
121
+ </p>
122
+ </div>
123
+ <span id="behavior-status" class="status-pill good">Notifications on</span>
124
+ </div>
125
+
126
+ <div class="settings-grid">
127
+ <label class="field">
128
+ <span>Hold Length (minutes)</span>
129
+ <input id="hold-length" type="number" min="0" max="1440" step="1" />
130
+ <small>
131
+ 0 holds until the next schedule event; 1440 creates a permanent hold.
132
+ </small>
133
+ </label>
134
+
135
+ <label class="field">
136
+ <span>Polling Interval (seconds)</span>
137
+ <input id="refresh" type="number" min="30" step="1" />
138
+ <small>
139
+ Polling is a safety net. Values below 30 are raised to 30 at runtime.
140
+ </small>
141
+ </label>
142
+ </div>
143
+
144
+ <label class="checkbox">
145
+ <input id="enable-notifications" type="checkbox" />
146
+ <span>
147
+ Enable Nuheat notifications
148
+ <small>
149
+ Uses SignalR notifications for faster updates. Disable only while
150
+ troubleshooting.
151
+ </small>
152
+ </span>
153
+ </label>
154
+
155
+ <label class="checkbox">
156
+ <input id="debug" type="checkbox" />
157
+ <span>
158
+ Enable debug logging
159
+ <small>
160
+ Writes verbose Nuheat API, notification, and accessory activity to the
161
+ Homebridge log.
162
+ </small>
163
+ </span>
164
+ </label>
165
+
166
+ <div class="actions">
167
+ <button id="save-behavior" class="primary">Save Behavior</button>
168
+ </div>
169
+ </section>
170
+
171
+ <section class="panel">
172
+ <div class="section-header">
173
+ <div>
174
+ <h2>Advanced OAuth</h2>
175
+ <p class="help">
176
+ Leave these fields blank unless Nuheat has issued confidential-client
177
+ credentials for testing. PKCE public-client support is planned but is
178
+ not active yet.
179
+ </p>
180
+ </div>
181
+ <span id="oauth-status" class="status-pill warn">Built-in fallback</span>
182
+ </div>
183
+
184
+ <div class="settings-grid">
185
+ <label class="field">
186
+ <span>Nuheat Client ID</span>
187
+ <input id="client-id" type="text" placeholder="Optional client ID" />
188
+ <small>Advanced override for a Nuheat-issued OAuth client ID.</small>
189
+ </label>
190
+
191
+ <label id="client-secret-row" class="field">
192
+ <span>Nuheat Client Secret (Legacy)</span>
193
+ <input
194
+ id="client-secret"
195
+ type="password"
196
+ placeholder="Leave blank to keep existing"
197
+ />
198
+ <small>
199
+ Required with a custom client ID until PKCE support ships. Do not
200
+ publish or share this value.
201
+ </small>
202
+ </label>
203
+
204
+ <label class="field">
205
+ <span>Nuheat Redirect URI</span>
206
+ <input id="redirect-uri" type="text" placeholder="http://localhost" />
207
+ <small>Defaults to http://localhost.</small>
208
+ </label>
209
+ </div>
210
+
211
+ <div class="actions">
212
+ <button id="save-oauth" class="primary">Save OAuth Settings</button>
213
+ <button id="clear-oauth" class="secondary" type="button">
214
+ Clear Overrides
215
+ </button>
216
+ </div>
217
+ </section>
218
+
219
+ <section class="panel">
220
+ <div class="section-header">
221
+ <div>
222
+ <h2>Diagnostics</h2>
223
+ <p class="help">
224
+ Review the saved configuration and exposure strategy before
225
+ restarting Homebridge.
226
+ </p>
227
+ </div>
228
+ <button id="refresh-diagnostics" class="secondary" type="button">
229
+ Refresh
230
+ </button>
231
+ </div>
232
+ <div id="diagnostics-summary" class="diagnostics-summary muted">
233
+ No diagnostics loaded yet.
234
+ </div>
235
+ <div id="diagnostics-empty" class="help hidden">
236
+ Save settings to generate a configuration summary.
237
+ </div>
238
+ <div id="diagnostics-list" class="diagnostics-list"></div>
239
+ </section>
240
+ </main>
241
+
242
+ <div id="toast-container" class="toast-container"></div>
243
+
244
+ <script src="index.js"></script>