homebridge-bedjet 0.3.0 → 0.3.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/dist/accessory.js CHANGED
@@ -76,7 +76,7 @@ class BedJetAccessory {
76
76
  .setCharacteristic(Characteristic.SerialNumber, config.address);
77
77
  // Thermostat service
78
78
  this.thermostatService = this.accessory.getService(Service.Thermostat)
79
- ?? this.accessory.addService(Service.Thermostat, safeName, 'thermostat');
79
+ ?? this.accessory.addService(Service.Thermostat, 'BedJet', 'thermostat');
80
80
  this.thermostatService.getCharacteristic(Characteristic.TemperatureDisplayUnits)
81
81
  .onGet(() => Characteristic.TemperatureDisplayUnits.CELSIUS);
82
82
  this.thermostatService.getCharacteristic(Characteristic.CurrentTemperature)
@@ -115,7 +115,7 @@ class BedJetAccessory {
115
115
  });
116
116
  // FanV2 service
117
117
  this.fanService = this.accessory.getService(Service.Fanv2)
118
- ?? this.accessory.addService(Service.Fanv2, `${safeName} Fan`, 'fan');
118
+ ?? this.accessory.addService(Service.Fanv2, 'BedJet Fan', 'fan');
119
119
  this.fanService.getCharacteristic(Characteristic.Active)
120
120
  .onGet(() => this.bedjet.state.operatingMode !== constants_1.OperatingMode.STANDBY
121
121
  ? Characteristic.Active.ACTIVE
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>BedJet Discovery</title>
6
+ <title>BedJet</title>
7
7
  <style>
8
8
  body {
9
9
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
@@ -20,9 +20,17 @@
20
20
  text-transform: uppercase;
21
21
  letter-spacing: 0.06em;
22
22
  opacity: 0.5;
23
- margin: 0 0 12px;
23
+ margin: 0 0 10px;
24
24
  }
25
25
 
26
+ hr.divider {
27
+ border: none;
28
+ border-top: 1px solid rgba(128,128,128,0.2);
29
+ margin: 20px 0;
30
+ }
31
+
32
+ /* ── Scan row ────────────────────────────────────────────────────────── */
33
+
26
34
  .scan-row {
27
35
  display: flex;
28
36
  align-items: center;
@@ -77,7 +85,7 @@
77
85
  transition: width 0.3s linear;
78
86
  }
79
87
 
80
- /* ── Device card ─────────────────────────────────────────────────────── */
88
+ /* ── Device card (shared by both sections) ───────────────────────────── */
81
89
 
82
90
  .device-card {
83
91
  border: 1px solid rgba(128,128,128,0.25);
@@ -86,13 +94,7 @@
86
94
  margin-bottom: 10px;
87
95
  }
88
96
 
89
- .card-header {
90
- display: flex;
91
- align-items: center;
92
- justify-content: space-between;
93
- margin-bottom: 10px;
94
- }
95
-
97
+ .card-header { margin-bottom: 10px; }
96
98
  .device-name { font-weight: 500; }
97
99
  .device-addr {
98
100
  font-size: 11px;
@@ -101,7 +103,7 @@
101
103
  margin-top: 2px;
102
104
  }
103
105
 
104
- /* ── Defaults row ────────────────────────────────────────────────────── */
106
+ /* ── Defaults fields ─────────────────────────────────────────────────── */
105
107
 
106
108
  .defaults-row {
107
109
  display: flex;
@@ -145,7 +147,7 @@
145
147
  border-color: #e25c00;
146
148
  }
147
149
 
148
- /* ── Footer row (button + message) ──────────────────────────────────── */
150
+ /* ── Card footer ─────────────────────────────────────────────────────── */
149
151
 
150
152
  .card-footer {
151
153
  display: flex;
@@ -161,7 +163,7 @@
161
163
  .card-msg.ok { color: #4caf50; }
162
164
  .card-msg.err { color: #f28b82; }
163
165
 
164
- .add-btn {
166
+ .primary-btn {
165
167
  padding: 6px 16px;
166
168
  border: none;
167
169
  border-radius: 5px;
@@ -174,8 +176,8 @@
174
176
  white-space: nowrap;
175
177
  flex-shrink: 0;
176
178
  }
177
- .add-btn:hover:not(:disabled) { opacity: 0.85; }
178
- .add-btn:disabled { opacity: 0.55; cursor: default; }
179
+ .primary-btn:hover:not(:disabled) { opacity: 0.85; }
180
+ .primary-btn:disabled { opacity: 0.55; cursor: default; }
179
181
 
180
182
  /* ── Notices ─────────────────────────────────────────────────────────── */
181
183
 
@@ -191,18 +193,22 @@
191
193
  </head>
192
194
  <body>
193
195
 
194
- <h6>Bluetooth Discovery</h6>
196
+ <!-- ── Section 1: Configured devices ────────────────────────────────────── -->
197
+ <h6>Configured Devices</h6>
198
+ <div id="configuredDevices"><div class="notice" style="opacity:0.4">Loading…</div></div>
199
+
200
+ <hr class="divider" />
195
201
 
202
+ <!-- ── Section 2: Bluetooth discovery ───────────────────────────────────── -->
203
+ <h6>Add New Device</h6>
196
204
  <div class="scan-row">
197
205
  <button id="scanBtn">Scan for BedJets</button>
198
206
  <div id="scanStatus"></div>
199
207
  </div>
200
-
201
208
  <div class="progress-wrap" id="progressWrap">
202
209
  <div class="progress-fill" id="progressFill"></div>
203
210
  </div>
204
-
205
- <div id="results"></div>
211
+ <div id="scanResults"></div>
206
212
 
207
213
  <script>
208
214
  var SCAN_MS = 12000;
@@ -216,114 +222,128 @@
216
222
  { value: 'dry', label: 'Dry' },
217
223
  ];
218
224
 
219
- // ── Scan button ───────────────────────────────────────────────────────────
225
+ // ── On load: show configured devices ─────────────────────────────────────
220
226
 
221
- document.getElementById('scanBtn').addEventListener('click', startScan);
227
+ loadConfiguredDevices();
228
+
229
+ async function loadConfiguredDevices() {
230
+ var container = document.getElementById('configuredDevices');
231
+ try {
232
+ var res = await homebridge.request('/get-devices');
233
+ var devices = (res && Array.isArray(res.devices)) ? res.devices : [];
234
+ if (devices.length === 0) {
235
+ container.innerHTML = '<div class="notice" style="opacity:0.5">No devices configured yet. Use the scanner below to add one.</div>';
236
+ } else {
237
+ container.innerHTML = '';
238
+ devices.forEach(function (d) {
239
+ container.appendChild(buildEditCard(d));
240
+ });
241
+ }
242
+ } catch (err) {
243
+ container.innerHTML = '<div class="notice error">Could not load config: ' + esc(err && err.message ? err.message : String(err)) + '</div>';
244
+ }
245
+ }
222
246
 
223
- // Event delegation for Add buttons
224
- document.getElementById('results').addEventListener('click', function (e) {
247
+ // ── Event delegation ──────────────────────────────────────────────────────
248
+
249
+ document.getElementById('configuredDevices').addEventListener('click', function (e) {
225
250
  var btn = e.target;
226
- if (!btn.classList.contains('add-btn') || btn.disabled) return;
251
+ if (!btn.classList.contains('primary-btn') || btn.disabled) return;
227
252
  var card = btn.closest('.device-card');
228
253
  if (!card) return;
254
+ saveDefaults(btn, card);
255
+ });
229
256
 
257
+ document.getElementById('scanResults').addEventListener('click', function (e) {
258
+ var btn = e.target;
259
+ if (!btn.classList.contains('primary-btn') || btn.disabled) return;
260
+ var card = btn.closest('.device-card');
261
+ if (!card) return;
230
262
  var name = card.getAttribute('data-name');
231
263
  var address = card.getAttribute('data-address');
232
-
233
- // Read defaults from the card's inputs
234
- var modeEl = card.querySelector('.field-mode');
235
- var tempEl = card.querySelector('.field-temp');
236
- var fanEl = card.querySelector('.field-fan');
237
-
238
- var defaultMode = modeEl ? modeEl.value : 'heat';
239
- var defaultTemperature = tempEl && tempEl.value !== '' ? parseFloat(tempEl.value) : null;
240
- var defaultFanSpeed = fanEl && fanEl.value !== '' ? parseInt(fanEl.value, 10) : null;
241
-
242
- addDevice(btn, card, name, address, defaultMode, defaultTemperature, defaultFanSpeed);
264
+ addDevice(btn, card, name, address);
243
265
  });
244
266
 
245
- // ── Scan ──────────────────────────────────────────────────────────────────
267
+ document.getElementById('scanBtn').addEventListener('click', startScan);
246
268
 
247
- async function startScan() {
248
- if (scanning) return;
249
- scanning = true;
269
+ // ── Build card for an already-configured device (edit mode) ──────────────
250
270
 
251
- var scanBtn = document.getElementById('scanBtn');
252
- var status = document.getElementById('scanStatus');
253
- var pWrap = document.getElementById('progressWrap');
254
- var pFill = document.getElementById('progressFill');
255
- var results = document.getElementById('results');
271
+ function buildEditCard(d) {
272
+ var card = document.createElement('div');
273
+ card.className = 'device-card';
274
+ card.setAttribute('data-address', d.address);
256
275
 
257
- scanBtn.disabled = true;
258
- results.innerHTML = '';
259
- pFill.style.width = '0%';
260
- pWrap.style.display = 'block';
261
- status.innerHTML = '<div class="spinner"></div>&nbsp;Scanning…';
276
+ var header = document.createElement('div');
277
+ header.className = 'card-header';
278
+ var nameEl = document.createElement('div');
279
+ nameEl.className = 'device-name';
280
+ nameEl.textContent = d.name;
281
+ var addrEl = document.createElement('div');
282
+ addrEl.className = 'device-addr';
283
+ addrEl.textContent = d.address;
284
+ header.appendChild(nameEl);
285
+ header.appendChild(addrEl);
286
+ card.appendChild(header);
262
287
 
263
- var elapsed = 0;
264
- var tickMs = 200;
265
- var timer = setInterval(function () {
266
- elapsed += tickMs;
267
- pFill.style.width = Math.min(100, (elapsed / SCAN_MS) * 100) + '%';
268
- if (elapsed >= SCAN_MS) clearInterval(timer);
269
- }, tickMs);
288
+ card.appendChild(buildDefaultsRow(d.defaultMode, d.defaultTemperature, d.defaultFanSpeed));
270
289
 
271
- try {
272
- var res = await homebridge.request('/scan');
273
- clearInterval(timer);
274
- pFill.style.width = '100%';
275
- status.innerHTML = '';
290
+ var footer = document.createElement('div');
291
+ footer.className = 'card-footer';
292
+ var msg = document.createElement('div');
293
+ msg.className = 'card-msg';
294
+ var btn = document.createElement('button');
295
+ btn.className = 'primary-btn';
296
+ btn.textContent = 'Save';
297
+ footer.appendChild(msg);
298
+ footer.appendChild(btn);
299
+ card.appendChild(footer);
276
300
 
277
- var devices = (res && Array.isArray(res.devices)) ? res.devices : [];
278
- if (devices.length === 0) {
279
- results.innerHTML = '<div class="notice">No BedJet devices found nearby. Make sure your BedJet is powered on and not already connected to another app, then try again.</div>';
280
- } else {
281
- // Build cards via DOM (no inline JS strings → no quote escaping issues)
282
- results.innerHTML = '';
283
- devices.forEach(function (d) {
284
- results.appendChild(buildCard(d));
285
- });
286
- }
287
- } catch (err) {
288
- clearInterval(timer);
289
- status.innerHTML = '';
290
- results.innerHTML = '<div class="notice error">Scan failed: ' + esc(err && err.message ? err.message : String(err)) + '</div>';
291
- } finally {
292
- setTimeout(function () { pWrap.style.display = 'none'; pFill.style.width = '0%'; }, 500);
293
- scanBtn.disabled = false;
294
- scanning = false;
295
- }
301
+ return card;
296
302
  }
297
303
 
298
- // ── Card builder (pure DOM, no innerHTML with user data) ──────────────────
304
+ // ── Build card for a newly discovered device (add mode) ───────────────────
299
305
 
300
- function buildCard(d) {
306
+ function buildDiscoverCard(d) {
301
307
  var card = document.createElement('div');
302
308
  card.className = 'device-card';
303
309
  card.setAttribute('data-name', d.name);
304
310
  card.setAttribute('data-address', d.address);
305
311
 
306
- // — Header: name + address —
307
312
  var header = document.createElement('div');
308
313
  header.className = 'card-header';
309
-
310
- var info = document.createElement('div');
311
314
  var nameEl = document.createElement('div');
312
315
  nameEl.className = 'device-name';
313
316
  nameEl.textContent = d.name;
314
317
  var addrEl = document.createElement('div');
315
318
  addrEl.className = 'device-addr';
316
319
  addrEl.textContent = d.address;
317
- info.appendChild(nameEl);
318
- info.appendChild(addrEl);
319
- header.appendChild(info);
320
+ header.appendChild(nameEl);
321
+ header.appendChild(addrEl);
320
322
  card.appendChild(header);
321
323
 
322
- // Defaults row —
324
+ card.appendChild(buildDefaultsRow(null, null, null));
325
+
326
+ var footer = document.createElement('div');
327
+ footer.className = 'card-footer';
328
+ var msg = document.createElement('div');
329
+ msg.className = 'card-msg';
330
+ var btn = document.createElement('button');
331
+ btn.className = 'primary-btn';
332
+ btn.textContent = 'Add to Config';
333
+ footer.appendChild(msg);
334
+ footer.appendChild(btn);
335
+ card.appendChild(footer);
336
+
337
+ return card;
338
+ }
339
+
340
+ // ── Shared defaults row builder ───────────────────────────────────────────
341
+
342
+ function buildDefaultsRow(currentMode, currentTemp, currentFan) {
323
343
  var row = document.createElement('div');
324
344
  row.className = 'defaults-row';
325
345
 
326
- // Mode select
346
+ // Mode
327
347
  var modeField = document.createElement('div');
328
348
  modeField.className = 'field';
329
349
  modeField.style.flex = '2';
@@ -335,14 +355,14 @@
335
355
  var opt = document.createElement('option');
336
356
  opt.value = m.value;
337
357
  opt.textContent = m.label;
338
- if (m.value === 'heat') opt.selected = true;
358
+ if (m.value === (currentMode || 'heat')) opt.selected = true;
339
359
  modeSelect.appendChild(opt);
340
360
  });
341
361
  modeField.appendChild(modeLabel);
342
362
  modeField.appendChild(modeSelect);
343
363
  row.appendChild(modeField);
344
364
 
345
- // Temperature input
365
+ // Temperature
346
366
  var tempField = document.createElement('div');
347
367
  tempField.className = 'field';
348
368
  var tempLabel = document.createElement('label');
@@ -354,11 +374,12 @@
354
374
  tempInput.max = '43';
355
375
  tempInput.step = '0.5';
356
376
  tempInput.placeholder = 'Optional';
377
+ if (currentTemp != null) tempInput.value = currentTemp;
357
378
  tempField.appendChild(tempLabel);
358
379
  tempField.appendChild(tempInput);
359
380
  row.appendChild(tempField);
360
381
 
361
- // Fan speed input
382
+ // Fan speed
362
383
  var fanField = document.createElement('div');
363
384
  fanField.className = 'field';
364
385
  var fanLabel = document.createElement('label');
@@ -370,46 +391,66 @@
370
391
  fanInput.max = '100';
371
392
  fanInput.step = '5';
372
393
  fanInput.placeholder = 'Optional';
394
+ if (currentFan != null) fanInput.value = currentFan;
373
395
  fanField.appendChild(fanLabel);
374
396
  fanField.appendChild(fanInput);
375
397
  row.appendChild(fanField);
376
398
 
377
- card.appendChild(row);
378
-
379
- // — Footer: message + button —
380
- var footer = document.createElement('div');
381
- footer.className = 'card-footer';
399
+ return row;
400
+ }
382
401
 
383
- var msg = document.createElement('div');
384
- msg.className = 'card-msg';
402
+ // ── Save defaults for existing device ─────────────────────────────────────
385
403
 
386
- var btn = document.createElement('button');
387
- btn.className = 'add-btn';
388
- btn.textContent = 'Add to Config';
404
+ async function saveDefaults(btn, card) {
405
+ var msg = card.querySelector('.card-msg');
406
+ var address = card.getAttribute('data-address');
407
+ var defaultMode = card.querySelector('.field-mode').value;
408
+ var tempVal = card.querySelector('.field-temp').value;
409
+ var fanVal = card.querySelector('.field-fan').value;
410
+ var defaultTemp = tempVal !== '' ? parseFloat(tempVal) : null;
411
+ var defaultFan = fanVal !== '' ? parseInt(fanVal, 10) : null;
389
412
 
390
- footer.appendChild(msg);
391
- footer.appendChild(btn);
392
- card.appendChild(footer);
413
+ btn.disabled = true;
414
+ btn.textContent = 'Saving…';
415
+ if (msg) { msg.textContent = ''; msg.className = 'card-msg'; }
393
416
 
394
- return card;
417
+ try {
418
+ await homebridge.request('/update-device', {
419
+ address: address,
420
+ defaultMode: defaultMode,
421
+ defaultTemperature: defaultTemp,
422
+ defaultFanSpeed: defaultFan,
423
+ });
424
+ btn.disabled = false;
425
+ btn.textContent = 'Save';
426
+ if (msg) { msg.textContent = '✓ Saved'; msg.className = 'card-msg ok'; }
427
+ setTimeout(function () { if (msg) msg.textContent = ''; }, 3000);
428
+ } catch (err) {
429
+ btn.disabled = false;
430
+ btn.textContent = 'Save';
431
+ if (msg) { msg.textContent = 'Error: ' + esc(err && err.message ? err.message : String(err)); msg.className = 'card-msg err'; }
432
+ }
395
433
  }
396
434
 
397
- // ── Add device ────────────────────────────────────────────────────────────
435
+ // ── Add newly discovered device ───────────────────────────────────────────
398
436
 
399
- async function addDevice(btn, card, name, address, defaultMode, defaultTemperature, defaultFanSpeed) {
400
- var msg = card.querySelector('.card-msg');
437
+ async function addDevice(btn, card, name, address) {
438
+ var msg = card.querySelector('.card-msg');
439
+ var defaultMode = card.querySelector('.field-mode').value;
440
+ var tempVal = card.querySelector('.field-temp').value;
441
+ var fanVal = card.querySelector('.field-fan').value;
442
+ var defaultTemp = tempVal !== '' ? parseFloat(tempVal) : null;
443
+ var defaultFan = fanVal !== '' ? parseInt(fanVal, 10) : null;
401
444
 
402
445
  btn.disabled = true;
403
446
  btn.textContent = 'Saving…';
404
- if (msg) { msg.textContent = ''; msg.className = 'card-msg'; }
405
-
406
- // Lock inputs while saving
407
447
  card.querySelectorAll('select, input').forEach(function (el) { el.disabled = true; });
448
+ if (msg) { msg.textContent = ''; msg.className = 'card-msg'; }
408
449
 
409
450
  try {
410
451
  var payload = { name: name, address: address, defaultMode: defaultMode };
411
- if (defaultTemperature !== null) payload.defaultTemperature = defaultTemperature;
412
- if (defaultFanSpeed !== null) payload.defaultFanSpeed = defaultFanSpeed;
452
+ if (defaultTemp !== null) payload.defaultTemperature = defaultTemp;
453
+ if (defaultFan !== null) payload.defaultFanSpeed = defaultFan;
413
454
 
414
455
  var result = await homebridge.request('/add-device', payload);
415
456
 
@@ -420,23 +461,72 @@
420
461
  btn.textContent = '✓ Added!';
421
462
  btn.style.background = '#145c30';
422
463
  if (msg) { msg.textContent = 'Saved — restart Homebridge.'; msg.className = 'card-msg ok'; }
464
+ // Refresh the configured devices section
465
+ loadConfiguredDevices();
423
466
  }
424
467
  } catch (err) {
425
- // Re-enable on failure so the user can retry
426
468
  btn.disabled = false;
427
469
  btn.textContent = 'Add to Config';
428
470
  card.querySelectorAll('select, input').forEach(function (el) { el.disabled = false; });
429
- var errMsg = err && err.message ? err.message : String(err);
430
- if (msg) { msg.textContent = 'Error: ' + errMsg; msg.className = 'card-msg err'; }
471
+ if (msg) { msg.textContent = 'Error: ' + esc(err && err.message ? err.message : String(err)); msg.className = 'card-msg err'; }
472
+ }
473
+ }
474
+
475
+ // ── Scan ──────────────────────────────────────────────────────────────────
476
+
477
+ async function startScan() {
478
+ if (scanning) return;
479
+ scanning = true;
480
+
481
+ var scanBtn = document.getElementById('scanBtn');
482
+ var status = document.getElementById('scanStatus');
483
+ var pWrap = document.getElementById('progressWrap');
484
+ var pFill = document.getElementById('progressFill');
485
+ var results = document.getElementById('scanResults');
486
+
487
+ scanBtn.disabled = true;
488
+ results.innerHTML = '';
489
+ pFill.style.width = '0%';
490
+ pWrap.style.display = 'block';
491
+ status.innerHTML = '<div class="spinner"></div>&nbsp;Scanning…';
492
+
493
+ var elapsed = 0;
494
+ var timer = setInterval(function () {
495
+ elapsed += 200;
496
+ pFill.style.width = Math.min(100, (elapsed / SCAN_MS) * 100) + '%';
497
+ if (elapsed >= SCAN_MS) clearInterval(timer);
498
+ }, 200);
499
+
500
+ try {
501
+ var res = await homebridge.request('/scan');
502
+ clearInterval(timer);
503
+ pFill.style.width = '100%';
504
+ status.innerHTML = '';
505
+
506
+ var devices = (res && Array.isArray(res.devices)) ? res.devices : [];
507
+ if (devices.length === 0) {
508
+ results.innerHTML = '<div class="notice">No BedJet devices found nearby. Make sure your BedJet is powered on and not already connected to another app, then try again.</div>';
509
+ } else {
510
+ results.innerHTML = '';
511
+ devices.forEach(function (d) {
512
+ results.appendChild(buildDiscoverCard(d));
513
+ });
514
+ }
515
+ } catch (err) {
516
+ clearInterval(timer);
517
+ status.innerHTML = '';
518
+ results.innerHTML = '<div class="notice error">Scan failed: ' + esc(err && err.message ? err.message : String(err)) + '</div>';
519
+ } finally {
520
+ setTimeout(function () { pWrap.style.display = 'none'; pFill.style.width = '0%'; }, 500);
521
+ scanBtn.disabled = false;
522
+ scanning = false;
431
523
  }
432
524
  }
433
525
 
434
526
  function esc(s) {
435
527
  return String(s)
436
- .replace(/&/g, '&amp;')
437
- .replace(/</g, '&lt;')
438
- .replace(/>/g, '&gt;')
439
- .replace(/"/g, '&quot;');
528
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;')
529
+ .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
440
530
  }
441
531
  </script>
442
532
  </body>
@@ -28,8 +28,10 @@ function getConfigPath() {
28
28
  class BedJetUiServer extends HomebridgePluginUiServer {
29
29
  constructor() {
30
30
  super();
31
- this.onRequest('/scan', this.handleScan.bind(this));
32
- this.onRequest('/add-device', this.handleAddDevice.bind(this));
31
+ this.onRequest('/scan', this.handleScan.bind(this));
32
+ this.onRequest('/add-device', this.handleAddDevice.bind(this));
33
+ this.onRequest('/get-devices', this.handleGetDevices.bind(this));
34
+ this.onRequest('/update-device', this.handleUpdateDevice.bind(this));
33
35
  this.ready();
34
36
  }
35
37
 
@@ -124,6 +126,63 @@ class BedJetUiServer extends HomebridgePluginUiServer {
124
126
 
125
127
  return { status: 'ok' };
126
128
  }
129
+
130
+ // ── Read configured devices ───────────────────────────────────────────────
131
+
132
+ async handleGetDevices() {
133
+ const configPath = getConfigPath();
134
+ try {
135
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
136
+ const platform = (config.platforms || []).find(p => p.platform === 'BedJetPlatform');
137
+ return { devices: (platform && Array.isArray(platform.devices)) ? platform.devices : [] };
138
+ } catch (err) {
139
+ throw new Error(`Cannot read config: ${err.message}`);
140
+ }
141
+ }
142
+
143
+ // ── Update defaults for an existing device ────────────────────────────────
144
+
145
+ async handleUpdateDevice(body) {
146
+ const { address, defaultMode, defaultTemperature, defaultFanSpeed } = body || {};
147
+ if (!address) throw new Error('Missing address');
148
+
149
+ const configPath = getConfigPath();
150
+ let config;
151
+ try {
152
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
153
+ } catch (err) {
154
+ throw new Error(`Cannot read config: ${err.message}`);
155
+ }
156
+
157
+ const platform = (config.platforms || []).find(p => p.platform === 'BedJetPlatform');
158
+ if (!platform) throw new Error('BedJetPlatform not found in config');
159
+
160
+ const device = (platform.devices || []).find(
161
+ d => d.address && d.address.toUpperCase() === address.toUpperCase(),
162
+ );
163
+ if (!device) throw new Error('Device not found in config');
164
+
165
+ if (defaultMode) device.defaultMode = defaultMode;
166
+ else delete device.defaultMode;
167
+ if (defaultTemperature !== undefined && defaultTemperature !== null && defaultTemperature !== '') {
168
+ device.defaultTemperature = Number(defaultTemperature);
169
+ } else {
170
+ delete device.defaultTemperature;
171
+ }
172
+ if (defaultFanSpeed !== undefined && defaultFanSpeed !== null && defaultFanSpeed !== '') {
173
+ device.defaultFanSpeed = Number(defaultFanSpeed);
174
+ } else {
175
+ delete device.defaultFanSpeed;
176
+ }
177
+
178
+ try {
179
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 4), 'utf8');
180
+ } catch (err) {
181
+ throw new Error(`Cannot write config: ${err.message}`);
182
+ }
183
+
184
+ return { status: 'ok' };
185
+ }
127
186
  }
128
187
 
129
188
  new BedJetUiServer();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-bedjet",
3
3
  "displayName": "BedJet",
4
- "version": "0.3.0",
4
+ "version": "0.3.2",
5
5
  "description": "Homebridge plugin for BedJet V3 via Bluetooth LE",
6
6
  "license": "MIT",
7
7
  "main": "dist/index.js",
package/src/accessory.ts CHANGED
@@ -96,7 +96,7 @@ export class BedJetAccessory {
96
96
 
97
97
  // Thermostat service
98
98
  this.thermostatService = this.accessory.getService(Service.Thermostat)
99
- ?? this.accessory.addService(Service.Thermostat, safeName, 'thermostat');
99
+ ?? this.accessory.addService(Service.Thermostat, 'BedJet', 'thermostat');
100
100
 
101
101
  this.thermostatService.getCharacteristic(Characteristic.TemperatureDisplayUnits)
102
102
  .onGet(() => Characteristic.TemperatureDisplayUnits.CELSIUS);
@@ -143,7 +143,7 @@ export class BedJetAccessory {
143
143
 
144
144
  // FanV2 service
145
145
  this.fanService = this.accessory.getService(Service.Fanv2)
146
- ?? this.accessory.addService(Service.Fanv2, `${safeName} Fan`, 'fan');
146
+ ?? this.accessory.addService(Service.Fanv2, 'BedJet Fan', 'fan');
147
147
 
148
148
  this.fanService.getCharacteristic(Characteristic.Active)
149
149
  .onGet(() =>