@camstack/addon-provider-bosch 0.1.31 → 0.1.33

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.
@@ -1,4 +1,4 @@
1
- import { A as customAction, C as switchCapability, D as DeviceType, F as array, L as literal, O as errMsg, P as _enum, R as number, V as string, W as EventCategory, f as deviceAdoptionCapability, g as heatingValveCapability, j as defineCustomActions, l as climateControlCapability, n as BaseDeviceProvider, s as batteryCapability, t as BaseDevice, u as connectionTestCapability, w as temperatureSensorCapability, z as object } from "../dist-DxO4cucS.mjs";
1
+ import { A as customAction, C as switchCapability, D as DeviceType, F as array, L as literal, O as errMsg, P as _enum, R as number, V as string, W as EventCategory, f as deviceAdoptionCapability, g as heatingValveCapability, j as defineCustomActions, l as climateControlCapability, n as BaseDeviceProvider, s as batteryCapability, t as BaseDevice, u as connectionTestCapability, w as temperatureSensorCapability, z as object } from "../dist-hH1ghdQ4.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { request } from "node:https";
@@ -6,7 +6,6 @@ import { execFileSync } from "node:child_process";
6
6
  import { mkdtempSync, readFileSync, rmSync } from "node:fs";
7
7
  import { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
9
- import { createSocket } from "node:dgram";
10
9
  import { connect } from "node:net";
11
10
  //#region \0rolldown/runtime.js
12
11
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
@@ -333,78 +332,18 @@ async function registerShcClient(deps) {
333
332
  }
334
333
  //#endregion
335
334
  //#region src/shc/shc-controller-discovery.ts
336
- /** `prefilledConfig` flag: the scan opens the integration form instead of adopting a device. */
337
- var SHC_SETUP_INTEGRATION_KEY = "setupIntegration";
338
- /** Top bit of the question class: ask for a unicast reply (RFC 6762). */
339
- var MDNS_QU_CLASS = 32769;
340
- var SHC_MDNS_SERVICE = "_http._tcp.local";
335
+ /** The controller announces itself as an `_http._tcp` service named `Bosch SHC`. */
336
+ var SHC_MDNS_SERVICE = "_http._tcp";
341
337
  var SHC_CONTROLLER_NAME_PREFIX = "Bosch SHC";
342
- var SHC_DISCOVERY_TIMEOUT_MS = 1500;
343
- var MDNS_PORT = 5353;
344
- var MDNS_ADDRESS = "224.0.0.251";
345
- var PTR = 12;
346
- var A = 1;
347
- var SRV = 33;
338
+ /** How the scan engine finds a controller: one mDNS browse, filtered on the instance name. */
339
+ var SHC_DISCOVERY_MODES = [{
340
+ kind: "mdns",
341
+ serviceType: SHC_MDNS_SERVICE,
342
+ instancePattern: `^${SHC_CONTROLLER_NAME_PREFIX}`
343
+ }];
348
344
  function isShcControllerName(name) {
349
345
  return (name.split("._")[0] ?? name).toLowerCase().startsWith(SHC_CONTROLLER_NAME_PREFIX.toLowerCase());
350
346
  }
351
- function readName(buf, start) {
352
- const labels = [];
353
- let pos = start;
354
- let end = start;
355
- let jumped = false;
356
- for (let hops = 0; hops < 16; hops++) {
357
- if (pos < 0 || pos >= buf.length) return null;
358
- const len = buf[pos] ?? 0;
359
- if (len === 0) {
360
- if (!jumped) end = pos + 1;
361
- return {
362
- name: labels.join("."),
363
- end
364
- };
365
- }
366
- if ((len & 192) === 192) {
367
- if (pos + 1 >= buf.length) return null;
368
- if (!jumped) end = pos + 2;
369
- jumped = true;
370
- pos = (len & 63) << 8 | (buf[pos + 1] ?? 0);
371
- continue;
372
- }
373
- if ((len & 192) !== 0 || pos + 1 + len > buf.length) return null;
374
- labels.push(buf.subarray(pos + 1, pos + 1 + len).toString("utf8"));
375
- pos += 1 + len;
376
- if (!jumped) end = pos;
377
- }
378
- return null;
379
- }
380
- function readRecords(buf, offset, count) {
381
- const records = [];
382
- let pos = offset;
383
- for (let i = 0; i < count; i++) {
384
- const name = readName(buf, pos);
385
- if (name === null || name.end + 10 > buf.length) return null;
386
- const type = buf.readUInt16BE(name.end);
387
- const dataLength = buf.readUInt16BE(name.end + 8);
388
- const dataOffset = name.end + 10;
389
- if (dataOffset + dataLength > buf.length) return null;
390
- records.push({
391
- name: name.name,
392
- type,
393
- message: buf,
394
- dataOffset,
395
- dataLength
396
- });
397
- pos = dataOffset + dataLength;
398
- }
399
- return {
400
- records,
401
- next: pos
402
- };
403
- }
404
- function ipv4(buf, offset, length) {
405
- if (length !== 4 || offset + 4 > buf.length) return null;
406
- return `${buf[offset] ?? 0}.${buf[offset + 1] ?? 0}.${buf[offset + 2] ?? 0}.${buf[offset + 3] ?? 0}`;
407
- }
408
347
  /** Identity used to recognise a controller already stored on an integration. */
409
348
  function shcControllerId(input) {
410
349
  const service = input.serviceHost?.trim().toLowerCase() ?? "";
@@ -413,40 +352,24 @@ function shcControllerId(input) {
413
352
  if (serial.length > 0) return serial;
414
353
  return input.host;
415
354
  }
416
- function hostsFromRecords(records) {
417
- const instances = [];
418
- const srv = /* @__PURE__ */ new Map();
419
- const addresses = /* @__PURE__ */ new Map();
420
- for (const rec of records) {
421
- const key = rec.name.toLowerCase();
422
- if (rec.type === PTR) {
423
- const target = readName(rec.message, rec.dataOffset);
424
- if (target !== null && isShcControllerName(target.name)) instances.push(target.name);
425
- } else if (rec.type === SRV && rec.dataLength >= 6) {
426
- const target = readName(rec.message, rec.dataOffset + 6);
427
- if (target !== null) srv.set(key, target.name);
428
- } else if (rec.type === A) {
429
- const ip = ipv4(rec.message, rec.dataOffset, rec.dataLength);
430
- if (ip !== null) addresses.set(key, ip);
431
- }
432
- }
355
+ /** Controllers among the engine's mDNS hits (a `Bosch SHC…` instance), one per host. */
356
+ function shcHostsFromHits(hits) {
433
357
  const out = [];
434
358
  const seen = /* @__PURE__ */ new Set();
435
- for (const instance of instances) {
436
- const viaSrv = srv.get(instance.toLowerCase());
437
- const host = (viaSrv !== void 0 ? addresses.get(viaSrv.toLowerCase()) : void 0) ?? addresses.get(instance.toLowerCase());
438
- if (host === void 0 || seen.has(host)) continue;
439
- seen.add(host);
440
- const label = instance.split("._")[0] ?? instance;
441
- const id = shcControllerId({
442
- name: label,
443
- host,
444
- ...viaSrv !== void 0 ? { serviceHost: viaSrv } : {}
445
- });
359
+ for (const hit of hits) {
360
+ const mdns = hit.mdns;
361
+ if (mdns === void 0 || !isShcControllerName(mdns.instance)) continue;
362
+ if (hit.host.length === 0 || seen.has(hit.host)) continue;
363
+ seen.add(hit.host);
364
+ const name = mdns.instance.split("._")[0] ?? mdns.instance;
446
365
  out.push({
447
- host,
448
- name: label,
449
- id
366
+ host: hit.host,
367
+ name,
368
+ id: shcControllerId({
369
+ name,
370
+ host: hit.host,
371
+ serviceHost: mdns.host
372
+ })
450
373
  });
451
374
  }
452
375
  return out;
@@ -466,11 +389,10 @@ function proposeShcControllers(found, existingHosts) {
466
389
  function shcDiscoveryCandidates(proposals) {
467
390
  return proposals.map((found) => ({
468
391
  stableId: `bosch-shc:${found.id}`,
392
+ type: DeviceType.Hub,
469
393
  suggestedName: found.name,
470
- prefilledConfig: {
471
- host: found.host,
472
- [SHC_SETUP_INTEGRATION_KEY]: true
473
- }
394
+ prefilledConfig: { host: found.host },
395
+ setupIntegration: true
474
396
  }));
475
397
  }
476
398
  function shcDiscoverySummary(foundCount, proposedCount) {
@@ -478,101 +400,6 @@ function shcDiscoverySummary(foundCount, proposedCount) {
478
400
  if (proposedCount === 0) return "No controller found on the network.";
479
401
  return `Found ${proposedCount} ${proposedCount === 1 ? "controller" : "controllers"}.`;
480
402
  }
481
- /** Hosts advertised as a Bosch Smart Home Controller on one mDNS message. */
482
- function shcHostsFromDnsMessage(buf) {
483
- if (buf.length < 12) return [];
484
- const questions = buf.readUInt16BE(4);
485
- const answers = buf.readUInt16BE(6);
486
- const authorities = buf.readUInt16BE(8);
487
- const extra = buf.readUInt16BE(10);
488
- let pos = 12;
489
- for (let i = 0; i < questions; i++) {
490
- const name = readName(buf, pos);
491
- if (name === null) return [];
492
- pos = name.end + 4;
493
- if (pos > buf.length) return [];
494
- }
495
- const answerRecords = readRecords(buf, pos, answers);
496
- if (answerRecords === null) return [];
497
- const authorityRecords = readRecords(buf, answerRecords.next, authorities);
498
- if (authorityRecords === null) return [];
499
- const extraRecords = readRecords(buf, authorityRecords.next, extra);
500
- if (extraRecords === null) return [];
501
- return hostsFromRecords([
502
- ...answerRecords.records,
503
- ...authorityRecords.records,
504
- ...extraRecords.records
505
- ]);
506
- }
507
- function mdnsPtrQuery(service) {
508
- const encoded = service.split(".").filter((part) => part.length > 0).map((label) => Buffer.concat([Buffer.from([label.length]), Buffer.from(label)]));
509
- const header = Buffer.alloc(12);
510
- header.writeUInt16BE(1, 4);
511
- const tail = Buffer.alloc(4);
512
- tail.writeUInt16BE(PTR, 0);
513
- tail.writeUInt16BE(MDNS_QU_CLASS, 2);
514
- return Buffer.concat([
515
- header,
516
- ...encoded,
517
- Buffer.from([0]),
518
- tail
519
- ]);
520
- }
521
- function closeQuietly(socket) {
522
- try {
523
- socket.close();
524
- } catch {}
525
- }
526
- function exchangeMdns(query, timeoutMs) {
527
- return new Promise((resolve) => {
528
- let socket;
529
- try {
530
- socket = createSocket({
531
- type: "udp4",
532
- reuseAddr: true
533
- });
534
- } catch {
535
- resolve([]);
536
- return;
537
- }
538
- const packets = [];
539
- let settled = false;
540
- const finish = () => {
541
- if (settled) return;
542
- settled = true;
543
- closeQuietly(socket);
544
- resolve(packets);
545
- };
546
- socket.on("error", () => finish());
547
- socket.on("message", (msg) => {
548
- packets.push(Buffer.from(msg));
549
- });
550
- socket.bind(0, () => {
551
- try {
552
- socket.send(query, MDNS_PORT, MDNS_ADDRESS);
553
- } catch {
554
- finish();
555
- }
556
- });
557
- setTimeout(finish, timeoutMs);
558
- });
559
- }
560
- async function browseShcControllers(exchange = exchangeMdns) {
561
- let packets;
562
- try {
563
- packets = await exchange(mdnsPtrQuery(SHC_MDNS_SERVICE), SHC_DISCOVERY_TIMEOUT_MS);
564
- } catch {
565
- return [];
566
- }
567
- const hosts = [];
568
- const seen = /* @__PURE__ */ new Set();
569
- for (const packet of packets) for (const found of shcHostsFromDnsMessage(packet)) {
570
- if (seen.has(found.host)) continue;
571
- seen.add(found.host);
572
- hosts.push(found);
573
- }
574
- return hosts;
575
- }
576
403
  /** Manual host wins. Otherwise the address discovery filled in. */
577
404
  function resolveShcPairingHost(input) {
578
405
  const manual = input.hostManual?.trim() ?? "";
@@ -1097,11 +924,11 @@ var TEMP_CAP = "temperature-sensor";
1097
924
  var BATTERY_CAP = "battery";
1098
925
  var VALVE_CAP = "heating-valve";
1099
926
  var TEMP_COLD = {
1100
- celsius: 0,
927
+ temperature: 0,
1101
928
  lastFetchedAt: 0
1102
929
  };
1103
930
  var BATTERY_COLD = {
1104
- percentage: null,
931
+ level: null,
1105
932
  charging: "unknown",
1106
933
  sleeping: false,
1107
934
  lastUpdated: 0
@@ -1138,12 +965,12 @@ var ShcTrvDevice = class extends ShcBaseDevice {
1138
965
  if (typeof temperature === "number" && Number.isFinite(temperature)) {
1139
966
  const prev = this.runtimeState.getCapState(TEMP_CAP);
1140
967
  this.runtimeState.setCapState(TEMP_CAP, {
1141
- celsius: temperature,
968
+ temperature,
1142
969
  lastFetchedAt: now
1143
970
  });
1144
- if (prev !== void 0 && prev.celsius !== temperature && prev.lastFetchedAt !== 0) this.deviceLog("info", "SHC temperature updated from controller", {
971
+ if (prev !== void 0 && prev.temperature !== temperature && prev.lastFetchedAt !== 0) this.deviceLog("info", "SHC temperature updated from controller", {
1145
972
  celsius: temperature,
1146
- previous: prev.celsius
973
+ previous: prev.temperature
1147
974
  });
1148
975
  }
1149
976
  } catch {}
@@ -1163,7 +990,7 @@ var ShcTrvDevice = class extends ShcBaseDevice {
1163
990
  try {
1164
991
  const batt = await client.getServiceState(this.shcDeviceId, "BatteryLevel");
1165
992
  this.runtimeState.setCapState(BATTERY_CAP, {
1166
- percentage: batt.batteryLevel ?? null,
993
+ level: batt.batteryLevel ?? null,
1167
994
  charging: "unknown",
1168
995
  sleeping: false,
1169
996
  lastUpdated: now
@@ -23865,14 +23692,15 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23865
23692
  customActions: boschShcActions,
23866
23693
  actionHandlers: {
23867
23694
  discoverController: async () => {
23868
- const { proposals, summary } = await this.discoverShcControllers();
23695
+ const { candidates } = await this.ctx.api.deviceManager.discoverProvider.mutate({ addonId: this.ctx.id });
23696
+ const hosts = candidates.map((candidate) => ({
23697
+ host: String(candidate.prefilledConfig["host"] ?? ""),
23698
+ name: candidate.suggestedName,
23699
+ id: candidate.stableId.replace(/^bosch-shc:/, "")
23700
+ }));
23869
23701
  return {
23870
- hosts: proposals.map((found) => ({
23871
- host: found.host,
23872
- name: found.name,
23873
- id: found.id
23874
- })),
23875
- summary
23702
+ hosts,
23703
+ summary: shcDiscoverySummary(hosts.length, hosts.length)
23876
23704
  };
23877
23705
  },
23878
23706
  registerClient: async (input) => {
@@ -23908,24 +23736,21 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23908
23736
  async supportsManualCreation() {
23909
23737
  return false;
23910
23738
  }
23911
- /**
23912
- * LAN mDNS probe. This addon is hub-only, so the socket is the hub's.
23913
- * The operator's hub is on br0 (its own LAN address), the same path the
23914
- * hub already uses to announce itself — multicast UDP 5353 reaches the
23915
- * LAN. The probe asks for a unicast reply and does not bind 5353, which
23916
- * the hub's announcer holds. Agents do not run this addon.
23917
- */
23739
+ /** A controller is found by the scan engine's mDNS browse; this addon only classifies the hits. */
23918
23740
  async supportsDiscovery() {
23919
23741
  return true;
23920
23742
  }
23921
- async discoverDevices() {
23922
- const { proposals } = await this.discoverShcControllers();
23923
- return shcDiscoveryCandidates(proposals).map((candidate) => ({
23924
- stableId: candidate.stableId,
23925
- type: DeviceType.Hub,
23926
- suggestedName: candidate.suggestedName,
23927
- prefilledConfig: { ...candidate.prefilledConfig }
23928
- }));
23743
+ async getDiscoveryModes() {
23744
+ return SHC_DISCOVERY_MODES;
23745
+ }
23746
+ async classifyDiscoveryHits(input) {
23747
+ const found = shcHostsFromHits(input.hits);
23748
+ const proposals = proposeShcControllers(found, await this.knownControllerHosts());
23749
+ if (found.length > proposals.length) this.ctx.logger.info("SHC discovery skipped controllers already added", { meta: {
23750
+ found: found.length,
23751
+ proposed: proposals.length
23752
+ } });
23753
+ return shcDiscoveryCandidates(proposals);
23929
23754
  }
23930
23755
  async adoptDiscoveredDevice(_input) {
23931
23756
  throw new Error("Bosch SHC controllers are paired from the integration form");
@@ -23943,24 +23768,6 @@ var BoschShcProviderAddon = class extends BaseDeviceProvider {
23943
23768
  if (typeof shcDeviceId !== "string" || shcDeviceId.length === 0) throw new Error("bosch-shc generateStableId: shcDeviceId required");
23944
23769
  return `bosch-shc:${integrationId}:${shcDeviceId}`;
23945
23770
  }
23946
- async discoverShcControllers() {
23947
- let found = [];
23948
- try {
23949
- found = await browseShcControllers();
23950
- } catch (err) {
23951
- this.ctx.logger.warn("SHC controller discovery failed", { meta: { error: errMsg(err) } });
23952
- }
23953
- const known = await this.knownControllerHosts();
23954
- const proposals = proposeShcControllers(found, known);
23955
- if (found.length > proposals.length) this.ctx.logger.info("SHC discovery skipped controllers already added", { meta: {
23956
- found: found.length,
23957
- proposed: proposals.length
23958
- } });
23959
- return {
23960
- proposals,
23961
- summary: shcDiscoverySummary(found.length, proposals.length)
23962
- };
23963
- }
23964
23771
  async knownControllerHosts() {
23965
23772
  try {
23966
23773
  const all = await this.ctx.api.integrations.list.query();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-bosch",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
4
4
  "description": "Bosch smart-home device-provider addons for CamStack — SHC, Home Connect, and HomeCom cloud integrations (scaffold)",
5
5
  "keywords": [
6
6
  "camstack",