@homebridge-plugins/homebridge-august 3.4.0-beta.2 → 3.4.0-beta.4

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
@@ -3,6 +3,14 @@
3
3
  ### Changed
4
4
 
5
5
  - feat(matter): expose lock battery via a PowerSource cluster (@zbuc)
6
+ - feat(ui): add, remove and hide devices from the config via the devices tab
7
+ - style(ui): standardise the custom ui layout and sync the support tab with the readme
8
+
9
+ ## v3.3.2 (2026-07-22)
10
+
11
+ ### Changed
12
+
13
+ - fix(deps): bump august-yale to 1.2.5 so US August accounts use the correct api key again instead of getting "API key is not valid" (#238)
6
14
 
7
15
  ## v3.3.1 (2026-07-20)
8
16
 
@@ -1,3 +1,19 @@
1
+ <style>
2
+ .min-w-50 {
3
+ min-width: 50px;
4
+ }
5
+ .action-desc {
6
+ font-size: 0.85rem;
7
+ }
8
+ .dark-mode .list-group-item {
9
+ background-color: #333333;
10
+ border-color: #444444;
11
+ color: #eeeeee;
12
+ }
13
+ .dark-mode .text-muted {
14
+ color: #aaaaaa !important;
15
+ }
16
+ </style>
1
17
  <p class="text-center">
2
18
  <img src="https://raw.githubusercontent.com/homebridge-plugins/homebridge-august/latest/branding/Homebridge_x_August.svg"
3
19
  alt="homebridge-august logo" style="width: 45%" />
@@ -16,18 +32,53 @@
16
32
  </div>
17
33
  </div>
18
34
  <div id="menuWrapper" class="btn-group w-100 mb-0" role="group" aria-label="UI Menu" style="display: none">
19
- <button type="button" class="btn btn-primary" id="menuDevices">Devices</button>
20
35
  <button type="button" class="btn btn-primary" id="menuSettings">Settings</button>
36
+ <button type="button" class="btn btn-primary" id="menuDevices">My Devices</button>
21
37
  <button type="button" class="btn btn-primary mr-0" id="menuHome">Support</button>
22
38
  </div>
23
39
  <div id="pageDevices" class="mt-4" style="display: none">
24
40
  <div id="deviceInfo">
25
41
  <form>
26
- <div class="form-group">
42
+ <div class="form-group mb-0">
27
43
  <select class="form-control" id="deviceSelect"></select>
28
44
  </div>
29
45
  </form>
30
- <table class="table w-100" id="deviceTable" style="display: none">
46
+ <ul class="list-group mt-4 mb-4 mx-0" id="configActionList" style="display: none">
47
+ <li class="list-group-item">
48
+ <div class="d-flex justify-content-between align-items-center">
49
+ <div class="pr-2">
50
+ <div>Add All Devices</div>
51
+ <div class="text-muted action-desc">Add all devices to the config that are not already in the config.</div>
52
+ </div>
53
+ <button type="button" class="btn btn-primary m-0 ml-3 py-1 min-w-50" id="addAllBtn" aria-label="Add all devices to the config">
54
+ <i aria-hidden="true" class="fas fa-arrow-right"></i>
55
+ </button>
56
+ </div>
57
+ </li>
58
+ <li class="list-group-item">
59
+ <div class="d-flex justify-content-between align-items-center">
60
+ <div class="pr-2">
61
+ <div id="deviceConfigTitle">Add This Device</div>
62
+ <div class="text-muted action-desc" id="deviceConfigText">Add this device to the plugin config.</div>
63
+ </div>
64
+ <button type="button" class="btn btn-primary m-0 ml-3 py-1 min-w-50" id="deviceConfigBtn" aria-label="Add this device to the config">
65
+ <i aria-hidden="true" class="fas fa-arrow-right"></i>
66
+ </button>
67
+ </div>
68
+ </li>
69
+ <li class="list-group-item">
70
+ <div class="d-flex justify-content-between align-items-center">
71
+ <div class="pr-2">
72
+ <div id="hideDeviceTitle">Hide This Device</div>
73
+ <div class="text-muted action-desc" id="hideDeviceText">Hide this device from HomeKit. This adds the device to the config if needed.</div>
74
+ </div>
75
+ <button type="button" class="btn btn-primary m-0 ml-3 py-1 min-w-50" id="hideDeviceBtn" aria-label="Hide this device from HomeKit">
76
+ <i aria-hidden="true" class="fas fa-arrow-right"></i>
77
+ </button>
78
+ </div>
79
+ </li>
80
+ </ul>
81
+ <table class="table w-100 mt-0" id="deviceTable" style="display: none">
31
82
  <thead>
32
83
  <tr class="table-active">
33
84
  <th scope="col" style="width: 40%">Device Name</th>
@@ -54,48 +105,284 @@
54
105
  </div>
55
106
  <div id="pageSupport" class="mt-4" style="display: none">
56
107
  <p class="text-center lead">Thank you for using <strong>homebridge-august</strong></p>
57
- <p class="text-center">The links below will take you to our GitHub wiki</p>
58
- <h5>Setup</h5>
108
+ <h5>Plugin Information</h5>
109
+ <ul>
110
+ <li>This plugin allows you to view and control your <a href="https://august.com" target="_blank">August</a> and <a href="https://shopyalehome.com" target="_blank">Yale</a> locks within HomeKit. The plugin:</li>
59
111
  <ul>
60
- <li>
61
- <a href="https://github.com/homebridge-plugins/homebridge-august/wiki" target="_blank">Wiki Home</a>
62
- </li>
63
- <li>
64
- <a href="https://github.com/homebridge-plugins/homebridge-august/wiki/Configuration" target="_blank">Configuration</a>
65
- </li>
66
- <li>
67
- <a href="https://github.com/homebridge-plugins/homebridge-august/wiki/Beta-Version" target="_blank">Beta Version</a>
68
- </li>
69
- <li>
70
- <a href="https://github.com/homebridge-plugins/homebridge-august/wiki/Node-Version" target="_blank">Node Version</a>
71
- </li>
72
- <li>
73
- <a href="https://github.com/homebridge-plugins/homebridge-august/wiki/Uninstallation"
74
- target="_blank">Uninstallation</a>
75
- </li>
112
+ <li>requires your August account credentials to work</li>
113
+ <li>connects to the August cloud to discover and control your locks</li>
76
114
  </ul>
77
- <h5>Features</h5>
115
+ </ul>
116
+ <p>&gt; [!IMPORTANT]</p>
117
+ <p>&gt; <strong>Yale Home accounts (common outside North America) can no longer sign in through this plugin.</strong> Yale has switched off password-based login on its servers (verified July 2026) and now requires an OAuth sign-in that is only available to officially registered partners. If you have a Yale Home account: check whether your lock supports <strong>native HomeKit</strong> (many Yale Home locks do — look for a HomeKit option in the Yale Home app), or use Home Assistant's official Yale integration with its HomeKit Bridge.</p>
118
+ <h5>Prerequisites</h5>
119
+ <ul>
120
+ <li>To use this plugin, you will need to already have:</li>
78
121
  <ul>
79
- <li>
80
- <a href="https://github.com/homebridge-plugins/homebridge-august/wiki/Supported-Devices" target="_blank">Supported
81
- Devices</a>
82
- </li>
122
+ <li><a href="https://nodejs.org" target="_blank">Node</a>: latest version of <code>v22</code> or <code>v24</code> - any other major version is not supported.</li>
123
+ <li><a href="https://homebridge.io" target="_blank">Homebridge</a>: <code>v2</code> - refer to link for more information and installation instructions.</li>
83
124
  </ul>
125
+ </ul>
126
+ <h5>Setup</h5>
127
+ <ul>
128
+ <li><a href="https://github.com/homebridge-plugins/homebridge-august/wiki/Installation" target="_blank">Installation</a></li>
129
+ <li><a href="https://github.com/homebridge-plugins/homebridge-august/wiki/Configuration" target="_blank">Configuration</a></li>
130
+ <li><a href="https://github.com/homebridge-plugins/homebridge-august/wiki/Beta-Version" target="_blank">Beta Version</a></li>
131
+ <li><a href="https://github.com/homebridge-plugins/homebridge-august/wiki/Node-Version" target="_blank">Node Version</a></li>
132
+ </ul>
133
+ <h5>Supported Devices</h5>
134
+ <ul>
135
+ <li>August Smart Lock (AUG-SL04-C03-N04)</li>
136
+ <li>August Smart Lock Pro 3rd Gen (AUG-SL03-C02-G03-C)</li>
137
+ <li>August WiFi Smart Lock (Gen 4)</li>
138
+ <li>Yale Assure Lock (AUG-MD01)</li>
139
+ <li>Yale Assure Lock 2 (YDR410)</li>
140
+ <li>Yale Assure Lock SL (YDR256)</li>
141
+ </ul>
84
142
  <h5>Help/About</h5>
85
- <ul>
86
- <li>
87
- <a href="https://github.com/homebridge-plugins/homebridge-august/issues/new/choose" target="_blank">Support Request</a>
88
- </li>
89
- <li>
90
- <a href="https://github.com/homebridge-plugins/homebridge-august/blob/latest/CHANGELOG.md"
91
- target="_blank">Changelog</a>
92
- </li>
93
- </ul>
143
+ <ul>
144
+ <li><a href="https://github.com/homebridge-plugins/homebridge-august/wiki/Common-Errors" target="_blank">Common Errors</a></li>
145
+ <li><a href="https://github.com/homebridge-plugins/homebridge-august/issues/new/choose" target="_blank">Support Request</a></li>
146
+ <li><a href="https://github.com/homebridge-plugins/homebridge-august/blob/latest/CHANGELOG.md" target="_blank">Changelog</a></li>
147
+ <li><a href="https://github.com/sponsors/bwp91" target="_blank">About Me</a></li>
148
+ </ul>
149
+ <h5>Credits</h5>
150
+ <ul>
151
+ <li>To <a href="https://github.com/donavanbecker" target="_blank">@donavanbecker</a>: the original creator and maintainer of this plugin.</li>
152
+ <li>To <a href="https://github.com/hufftheweevil" target="_blank">@hufftheweevil</a>: the author of the <a href="https://github.com/hufftheweevil/august-api" target="_blank">august-api</a> module this plugin builds on.</li>
153
+ <li>To the creators/contributors of <a href="https://homebridge.io" target="_blank">Homebridge</a> who make this plugin possible.</li>
154
+ </ul>
155
+ <h5>Disclaimer</h5>
156
+ <ul>
157
+ <li>I am in no way affiliated with August or Yale and this plugin is a personal project that I maintain in my free time.</li>
158
+ <li>Use this plugin entirely at your own risk - please see licence for more information.</li>
159
+ </ul>
94
160
  </div>
95
161
  <script>
96
162
  (async () => {
97
163
  try {
98
164
  const currentConfig = await homebridge.getPluginConfig();
165
+
166
+ // ===== Device picker (add/remove/hide config entries from the Devices tab) =====
167
+ // The control state is always computed from a fresh getPluginConfig() call,
168
+ // so staged-but-unsaved edits made in the Settings tab are respected
169
+ let pickerAccessories = [];
170
+ let removeConfirmTimer;
171
+
172
+ // The action buttons always show the settings-page arrow; the row text
173
+ // (and the button's aria-label) says what the arrow will do
174
+ const actionArrow = '<i aria-hidden="true" class="fas fa-arrow-right"></i>';
175
+
176
+ // Compare lock ids ignoring case and separators, the same comparison the
177
+ // platform itself uses when merging lock config
178
+ const looseId = (id) => (id || '').toString().toUpperCase().replace(/[^A-Z0-9]+/g, '');
179
+
180
+ const getConfigBlocks = async () => {
181
+ const blocks = await homebridge.getPluginConfig();
182
+ if (!blocks.length) {
183
+ blocks.push({ name: 'August' });
184
+ }
185
+ return blocks;
186
+ };
187
+
188
+ // The devices array lives nested under options
189
+ const getDevicesArray = (config, create) => {
190
+ if (!config.options || typeof config.options !== 'object') {
191
+ if (!create) {
192
+ return null;
193
+ }
194
+ config.options = {};
195
+ }
196
+ if (!Array.isArray(config.options.devices)) {
197
+ if (!create) {
198
+ return null;
199
+ }
200
+ config.options.devices = [];
201
+ }
202
+ return config.options.devices;
203
+ };
204
+
205
+ const findInConfig = (config, lockId) => {
206
+ const target = looseId(lockId);
207
+ const devices = getDevicesArray(config, false);
208
+ if (!devices) {
209
+ return null;
210
+ }
211
+ const index = devices.findIndex((entry) => entry && looseId(entry.lockId) === target);
212
+ return index === -1 ? null : { index };
213
+ };
214
+
215
+ const selectedAccessory = () => {
216
+ const uuid = document.getElementById('deviceSelect').value;
217
+ return pickerAccessories.find((x) => x.UUID === uuid);
218
+ };
219
+
220
+ const setHideControl = (hidden) => {
221
+ const btn = document.getElementById('hideDeviceBtn');
222
+ btn.dataset.hidden = hidden ? 'yes' : 'no';
223
+ btn.innerHTML = actionArrow;
224
+ btn.setAttribute('aria-label', hidden ? 'Show this device in HomeKit' : 'Hide this device from HomeKit');
225
+ document.getElementById('hideDeviceTitle').innerHTML = hidden ? 'Show This Device' : 'Hide This Device';
226
+ document.getElementById('hideDeviceText').innerHTML = hidden
227
+ ? 'Show this device in HomeKit again. The config entry is kept.'
228
+ : 'Hide this device from HomeKit. This adds the device to the config if needed.';
229
+ };
230
+
231
+ const setConfigButton = (inConfig) => {
232
+ const btn = document.getElementById('deviceConfigBtn');
233
+ clearTimeout(removeConfirmTimer);
234
+ delete btn.dataset.confirm;
235
+ btn.dataset.state = inConfig ? 'remove' : 'add';
236
+ btn.classList.remove('btn-danger');
237
+ btn.classList.add('btn-primary');
238
+ btn.innerHTML = actionArrow;
239
+ btn.setAttribute('aria-label', inConfig ? 'Remove this device from the config' : 'Add this device to the config');
240
+ document.getElementById('deviceConfigTitle').innerHTML = inConfig ? 'Remove This Device' : 'Add This Device';
241
+ document.getElementById('deviceConfigText').innerHTML = inConfig
242
+ ? 'Remove this device from the plugin config.'
243
+ : 'Add this device to the plugin config.';
244
+ };
245
+
246
+ const addDeviceToConfig = (config, acc) => {
247
+ const devices = getDevicesArray(config, true);
248
+ // configLockName is included so the entry is identifiable in the
249
+ // config editor; it matches the current name so nothing is renamed
250
+ devices.push({ lockId: acc.context.lockId, configLockName: acc.displayName });
251
+ };
252
+
253
+ document.getElementById('deviceConfigBtn').addEventListener('click', async () => {
254
+ try {
255
+ const btn = document.getElementById('deviceConfigBtn');
256
+ const acc = selectedAccessory();
257
+ if (!acc) {
258
+ return;
259
+ }
260
+ const blocks = await getConfigBlocks();
261
+ const config = blocks[0];
262
+ const found = findInConfig(config, acc.context.lockId);
263
+ if (btn.dataset.state === 'add') {
264
+ // The config may have changed since the button was drawn, so
265
+ // re-check and never add a duplicate - just correct the button
266
+ if (found) {
267
+ setConfigButton(true);
268
+ setHideControl(!!config.options.devices[found.index].hide_device);
269
+ homebridge.toast.info(acc.displayName + ' is already in the config.', 'Not Added');
270
+ return;
271
+ }
272
+ addDeviceToConfig(config, acc);
273
+ await homebridge.updatePluginConfig(blocks);
274
+ setConfigButton(true);
275
+ setHideControl(false);
276
+ homebridge.toast.success(acc.displayName + ' added - press Save to apply.', 'Added To Config');
277
+ } else {
278
+ if (!found) {
279
+ setConfigButton(false);
280
+ setHideControl(false);
281
+ homebridge.toast.info(acc.displayName + ' is not in the config.', 'Not Removed');
282
+ return;
283
+ }
284
+ const entry = config.options.devices[found.index];
285
+ const extraKeys = Object.keys(entry).filter((k) => !['lockId', 'configLockName'].includes(k));
286
+ if (extraKeys.length > 0 && btn.dataset.confirm !== 'yes') {
287
+ // The entry has settings beyond id and name which would be
288
+ // lost, so ask for a second click within five seconds
289
+ btn.dataset.confirm = 'yes';
290
+ btn.classList.remove('btn-primary');
291
+ btn.classList.add('btn-danger');
292
+ btn.innerHTML = actionArrow;
293
+ btn.setAttribute('aria-label', 'Confirm removing this device from the config');
294
+ document.getElementById('deviceConfigText').innerHTML = 'This entry has extra settings which will be lost. Click again to remove it.';
295
+ removeConfirmTimer = setTimeout(() => setConfigButton(true), 5000);
296
+ return;
297
+ }
298
+ config.options.devices.splice(found.index, 1);
299
+ if (config.options.devices.length === 0) {
300
+ delete config.options.devices;
301
+ }
302
+ await homebridge.updatePluginConfig(blocks);
303
+ setConfigButton(false);
304
+ setHideControl(false);
305
+ homebridge.toast.success(acc.displayName + ' removed - press Save to apply.', 'Removed From Config');
306
+ }
307
+ } catch (err) {
308
+ homebridge.toast.error(err.message, 'Error');
309
+ }
310
+ });
311
+
312
+ document.getElementById('hideDeviceBtn').addEventListener('click', async () => {
313
+ try {
314
+ const acc = selectedAccessory();
315
+ if (!acc) {
316
+ return;
317
+ }
318
+ const blocks = await getConfigBlocks();
319
+ const config = blocks[0];
320
+ let found = findInConfig(config, acc.context.lockId);
321
+ const currentlyHidden = document.getElementById('hideDeviceBtn').dataset.hidden === 'yes';
322
+ if (!currentlyHidden) {
323
+ // Hiding needs a config entry to carry the flag, so create one if
324
+ // the device is not in the config yet
325
+ if (!found) {
326
+ addDeviceToConfig(config, acc);
327
+ found = findInConfig(config, acc.context.lockId);
328
+ }
329
+ config.options.devices[found.index].hide_device = true;
330
+ await homebridge.updatePluginConfig(blocks);
331
+ setConfigButton(true);
332
+ setHideControl(true);
333
+ homebridge.toast.success(acc.displayName + ' will be hidden from HomeKit - press Save to apply.', 'Device Hidden');
334
+ } else {
335
+ if (found) {
336
+ // Drop the flag rather than storing hide_device false, to keep
337
+ // the entry clean
338
+ delete config.options.devices[found.index].hide_device;
339
+ await homebridge.updatePluginConfig(blocks);
340
+ }
341
+ setConfigButton(!!found);
342
+ setHideControl(false);
343
+ homebridge.toast.success(acc.displayName + ' will be shown in HomeKit - press Save to apply.', 'Device Unhidden');
344
+ }
345
+ } catch (err) {
346
+ homebridge.toast.error(err.message, 'Error');
347
+ }
348
+ });
349
+
350
+ document.getElementById('addAllBtn').addEventListener('click', async () => {
351
+ try {
352
+ const blocks = await getConfigBlocks();
353
+ const config = blocks[0];
354
+ let added = 0;
355
+ let skipped = 0;
356
+ pickerAccessories.forEach((acc) => {
357
+ if (!acc.context?.lockId) {
358
+ return;
359
+ }
360
+ if (findInConfig(config, acc.context.lockId)) {
361
+ skipped += 1;
362
+ return;
363
+ }
364
+ addDeviceToConfig(config, acc);
365
+ added += 1;
366
+ });
367
+ if (added > 0) {
368
+ await homebridge.updatePluginConfig(blocks);
369
+ homebridge.toast.success(added + ' device(s) added, ' + skipped + ' already in the config - press Save to apply.', 'Added To Config');
370
+ } else {
371
+ homebridge.toast.info('All devices are already in the config.', 'Nothing To Add');
372
+ }
373
+ const acc = selectedAccessory();
374
+ if (acc) {
375
+ const nowFound = findInConfig(config, acc.context.lockId);
376
+ setConfigButton(!!nowFound);
377
+ if (nowFound) {
378
+ setHideControl(!!config.options.devices[nowFound.index].hide_device);
379
+ }
380
+ }
381
+ } catch (err) {
382
+ homebridge.toast.error(err.message, 'Error');
383
+ }
384
+ });
385
+ // ===== End device picker =====
99
386
  showIntro = () => {
100
387
  const introLink = document.getElementById('introLink');
101
388
  introLink.addEventListener('click', () => {
@@ -127,6 +414,7 @@
127
414
  return a.displayName.toLowerCase() > b.displayName.toLowerCase() ? 1 : b.displayName.toLowerCase() > a.displayName.toLowerCase() ? -1 : 0;
128
415
  });
129
416
  }
417
+ pickerAccessories = cachedAccessories;
130
418
  const deviceSelect = document.getElementById('deviceSelect');
131
419
  deviceSelect.innerHTML = '';
132
420
  cachedAccessories.forEach((a) => {
@@ -145,6 +433,11 @@
145
433
  document.getElementById('model').innerHTML = context.model;
146
434
  document.getElementById('firmwareRevision').innerHTML = context.firmwareRevision || 'N/A';
147
435
  document.getElementById('deviceTable').style.display = 'inline-table';
436
+ const configBlocks = await getConfigBlocks();
437
+ const configEntry = findInConfig(configBlocks[0], context.lockId);
438
+ setConfigButton(!!configEntry);
439
+ setHideControl(!!(configEntry && configBlocks[0].options.devices[configEntry.index].hide_device));
440
+ document.getElementById('configActionList').style.display = 'block';
148
441
  homebridge.hideSpinner();
149
442
  };
150
443
  deviceSelect.addEventListener('change', (event) => showDeviceInfo(event.target.value));
@@ -154,6 +447,7 @@
154
447
  const option = document.createElement('option');
155
448
  option.text = 'No Devices';
156
449
  deviceSelect.add(option);
450
+ document.getElementById('configActionList').style.display = 'none';
157
451
  }
158
452
  homebridge.hideSpinner();
159
453
  };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@homebridge-plugins/homebridge-august",
3
3
  "displayName": "August",
4
4
  "type": "module",
5
- "version": "3.4.0-beta.2",
5
+ "version": "3.4.0-beta.4",
6
6
  "description": "The August plugin allows you to access your August & Yale device(s) from HomeKit.",
7
7
  "author": {
8
8
  "name": "bwp91",
@@ -42,6 +42,7 @@
42
42
  },
43
43
  "keywords": [
44
44
  "homebridge-plugin",
45
+ "supports-matter",
45
46
  "august",
46
47
  "yale",
47
48
  "lock"
@@ -69,7 +70,7 @@
69
70
  },
70
71
  "dependencies": {
71
72
  "@homebridge/plugin-ui-utils": "^2.2.5",
72
- "august-yale": "^1.2.4",
73
+ "august-yale": "^1.2.5",
73
74
  "rxjs": "^7.8.2"
74
75
  },
75
76
  "devDependencies": {