@camstack/addon-provider-dreo 0.1.5 → 0.1.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/dist/addon.mjs CHANGED
@@ -4665,7 +4665,7 @@ function _instanceof(cls, params = {}) {
4665
4665
  return inst;
4666
4666
  }
4667
4667
  //#endregion
4668
- //#region ../types/dist/sleep-B3AOslwX.mjs
4668
+ //#region ../types/dist/sleep-C2M2zF7x.mjs
4669
4669
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4670
4670
  EventCategory["SystemBoot"] = "system.boot";
4671
4671
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -6233,6 +6233,12 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
6233
6233
  DeviceType["Switch"] = "switch";
6234
6234
  DeviceType["Sensor"] = "sensor";
6235
6235
  DeviceType["Thermostat"] = "thermostat";
6236
+ /** Air-conditioner / heat-pump climate device (HVAC) — shares the
6237
+ * `climate-control` cap surface with `Thermostat` but renders a
6238
+ * dedicated AC-appropriate control UI (mode chips, fan speed,
6239
+ * independent vertical/horizontal swing). Sources: native Gree, and
6240
+ * reusable by other AC integrations. */
6241
+ DeviceType["Climate"] = "climate";
6236
6242
  DeviceType["Button"] = "button";
6237
6243
  /** Generic stateless event emitter — carries a device's EXACT declared
6238
6244
  * event vocabulary verbatim (no normalization). Installed with the
@@ -9028,7 +9034,7 @@ var climateControlCapability = {
9028
9034
  scope: "device",
9029
9035
  deviceNative: true,
9030
9036
  mode: "singleton",
9031
- deviceTypes: [DeviceType.Thermostat],
9037
+ deviceTypes: [DeviceType.Thermostat, DeviceType.Climate],
9032
9038
  methods: {
9033
9039
  setMode: method(object({
9034
9040
  deviceId: number().int().nonnegative(),
@@ -13627,10 +13633,30 @@ var deviceProviderCapability = {
13627
13633
  type: string()
13628
13634
  }))),
13629
13635
  supportsDiscovery: method(object({}), boolean()),
13630
- discoverDevices: method(object({}), array(DiscoveryCandidateSchema), {
13636
+ /**
13637
+ * Run a network scan. `params` carries optional provider-specific scan
13638
+ * inputs (e.g. a broadcast address / subnet for cross-subnet discovery),
13639
+ * shaped by `getDiscoveryParamsSchema`. Omitted for the generic scan
13640
+ * (provider uses its local-network default).
13641
+ */
13642
+ discoverDevices: method(object({ params: record(string(), unknown()).optional() }), array(DiscoveryCandidateSchema), {
13631
13643
  kind: "mutation",
13632
13644
  auth: "admin"
13633
13645
  }),
13646
+ /**
13647
+ * Optional form schema (`ConfigUISchema`) for the EXTRA per-scan inputs a
13648
+ * provider accepts (e.g. Gree's broadcast address for a different subnet).
13649
+ * `null` when the provider takes no extra scan params — the generic
13650
+ * aggregated scan never renders this; the per-integration scan does.
13651
+ */
13652
+ getDiscoveryParamsSchema: method(object({}), CreationSchemaOutputSchema),
13653
+ /**
13654
+ * The DeviceType this provider creates via manual add (Camera for
13655
+ * Reolink/ONVIF, Container for Gree, Hub for Ecowitt). `null` when the
13656
+ * provider does not support manual creation. Lets the Add-Device dialog
13657
+ * pick the right type instead of assuming Camera.
13658
+ */
13659
+ getManualCreationType: method(object({}), object({ deviceType: _enum(DeviceType).nullable() })),
13634
13660
  adoptDiscoveredDevice: method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
13635
13661
  kind: "mutation",
13636
13662
  auth: "admin"
@@ -13754,9 +13780,23 @@ var BaseDeviceProvider = class extends BaseAddon {
13754
13780
  async supportsDiscovery() {
13755
13781
  return false;
13756
13782
  }
13757
- async discoverDevices() {
13783
+ async discoverDevices(_input) {
13758
13784
  return [];
13759
13785
  }
13786
+ /** Extra per-scan input form (e.g. a broadcast address for another subnet).
13787
+ * Null = no extra params. Override in providers that support scoped scans. */
13788
+ async getDiscoveryParamsSchema() {
13789
+ return null;
13790
+ }
13791
+ /**
13792
+ * The DeviceType this provider creates via manual add — derived from the
13793
+ * `deviceClasses` map (first registered type). `null` when manual creation is
13794
+ * unsupported. Lets the Add-Device dialog pick the right type per provider.
13795
+ */
13796
+ async getManualCreationType() {
13797
+ if (!await this.supportsManualCreation()) return { deviceType: null };
13798
+ return { deviceType: Object.values(DeviceType).find((t) => this.deviceClasses[t] !== void 0) ?? null };
13799
+ }
13760
13800
  async adoptDiscoveredDevice(_input) {
13761
13801
  throw new Error(`${this.providerName} provider does not support discovery-based adoption`);
13762
13802
  }
@@ -14803,83 +14843,34 @@ var GetStateInputSchema = object({
14803
14843
  * HA: entity_id (returns the cached entity state). */
14804
14844
  key: string()
14805
14845
  });
14806
- var brokerCapability = {
14807
- name: "broker",
14808
- scope: "system",
14809
- mode: "collection",
14810
- providerKind: "broker",
14811
- status: {
14812
- schema: RegistryStatusSchema,
14813
- kind: "poll"
14814
- },
14815
- methods: {
14816
- list: method(ListInputSchema, array(BrokerInfoSchema$1)),
14817
- get: method(GetInputSchema, BrokerInfoSchema$1.nullable()),
14818
- /** Enumerate which addon provides which broker kind(s) for the
14819
- * unified create picker. The auto-mount fans this array across
14820
- * every registered `broker` provider (array-output method), so the
14821
- * picker sees every kind from every provider in one call. */
14822
- listProviders: method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }),
14823
- add: method(AddInputSchema, AddResultSchema, {
14824
- kind: "mutation",
14825
- auth: "admin"
14826
- }),
14827
- remove: method(RemoveInputSchema, _void(), {
14828
- kind: "mutation",
14829
- auth: "admin"
14830
- }),
14831
- testConnection: method(GetInputSchema, TestConnectionResultSchema, {
14832
- kind: "mutation",
14833
- auth: "admin"
14834
- }),
14835
- /** Read the persisted settings record for a broker (kind-specific
14836
- * shape). Admin-only — settings may contain secrets. Returns `null`
14837
- * when the broker id is unknown to the provider (the collection
14838
- * fallback may route a foreign id to the first provider). */
14839
- getSettings: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
14840
- /** Overwrite the persisted settings record. The kind-specific
14841
- * provider validates the shape and applies the change (reconnects
14842
- * if credentials changed). */
14843
- setSettings: method(object({
14844
- id: string(),
14845
- settings: SettingsRecordSchema$1
14846
- }), _void(), {
14847
- kind: "mutation",
14848
- auth: "admin"
14849
- }),
14850
- /** Returns the kind-specific connection config the consumer needs
14851
- * to open its own client (MQTT pattern: `{url, username, password,
14852
- * clientIdPrefix}`). HA providers MAY return the auth envelope
14853
- * but typical HA consumers use `publish` / `subscribe` instead.
14854
- * Returns `null` when the broker id is unknown to the provider. */
14855
- getBrokerConfig: method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }),
14856
- getSettingsSchema: method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }),
14857
- testSettings: method(TestSettingsInputSchema, TestSettingsResultSchema, {
14858
- kind: "mutation",
14859
- auth: "admin"
14860
- }),
14861
- publish: method(PublishInputSchema, unknown(), {
14862
- kind: "mutation",
14863
- auth: "admin"
14864
- }),
14865
- subscribe: method(SubscribeInputSchema, SubscribeResultSchema, {
14866
- kind: "mutation",
14867
- auth: "admin"
14868
- }),
14869
- unsubscribe: method(UnsubscribeInputSchema, _void(), {
14870
- kind: "mutation",
14871
- auth: "admin"
14872
- }),
14873
- /** Read the broker's cached state for a key. Returns `null` when
14874
- * unknown to the broker (never published / unknown entity). */
14875
- getState: method(GetStateInputSchema, unknown().nullable()),
14876
- /** Status method — explicit registration with a `z.void()` input so
14877
- * the codegen-generated tRPC router types its input as
14878
- * `{addonId?: string, nodeId?: string}` (system-scoped collection
14879
- * shape) instead of the device-scoped `{deviceId}` fallback. */
14880
- getStatus: method(_void(), RegistryStatusSchema)
14881
- }
14882
- };
14846
+ method(ListInputSchema, array(BrokerInfoSchema$1)), method(GetInputSchema, BrokerInfoSchema$1.nullable()), method(_void(), array(BrokerProviderInfoSchema), { auth: "admin" }), method(AddInputSchema, AddResultSchema, {
14847
+ kind: "mutation",
14848
+ auth: "admin"
14849
+ }), method(RemoveInputSchema, _void(), {
14850
+ kind: "mutation",
14851
+ auth: "admin"
14852
+ }), method(GetInputSchema, TestConnectionResultSchema, {
14853
+ kind: "mutation",
14854
+ auth: "admin"
14855
+ }), method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }), method(object({
14856
+ id: string(),
14857
+ settings: SettingsRecordSchema$1
14858
+ }), _void(), {
14859
+ kind: "mutation",
14860
+ auth: "admin"
14861
+ }), method(GetInputSchema, SettingsRecordSchema$1.nullable(), { auth: "admin" }), method(SettingsSchemaInputSchema, SettingsSchemaResultSchema, { auth: "admin" }), method(TestSettingsInputSchema, TestSettingsResultSchema, {
14862
+ kind: "mutation",
14863
+ auth: "admin"
14864
+ }), method(PublishInputSchema, unknown(), {
14865
+ kind: "mutation",
14866
+ auth: "admin"
14867
+ }), method(SubscribeInputSchema, SubscribeResultSchema, {
14868
+ kind: "mutation",
14869
+ auth: "admin"
14870
+ }), method(UnsubscribeInputSchema, _void(), {
14871
+ kind: "mutation",
14872
+ auth: "admin"
14873
+ }), method(GetStateInputSchema, unknown().nullable()), method(_void(), RegistryStatusSchema);
14883
14874
  DeviceType.Camera;
14884
14875
  /**
14885
14876
  * `custom-model-registry` — collection cap exposing operator-registered
@@ -15667,7 +15658,10 @@ method(object({
15667
15658
  }), FieldProbeResultSchema, {
15668
15659
  kind: "mutation",
15669
15660
  auth: "admin"
15670
- }), method(ListCandidatesInputSchema.extend({ addonId: string() }), ListCandidatesOutputSchema, { auth: "admin" }), method(object({
15661
+ }), method(object({
15662
+ addonId: string(),
15663
+ integrationId: string()
15664
+ }), object({ filters: array(AdoptionFilterSchema) }), { auth: "admin" }), method(ListCandidatesInputSchema.extend({ addonId: string() }), ListCandidatesOutputSchema, { auth: "admin" }), method(object({
15671
15665
  addonId: string(),
15672
15666
  integrationId: string()
15673
15667
  }), AdoptionStatusSchema, {
@@ -15682,7 +15676,24 @@ method(object({
15682
15676
  }), method(ResyncInputSchema, ResyncResultSchema, {
15683
15677
  kind: "mutation",
15684
15678
  auth: "admin"
15679
+ }), method(object({}), object({ providers: array(object({
15680
+ addonId: string(),
15681
+ label: string()
15682
+ })).readonly() }), { auth: "admin" }), method(object({}), object({ groups: array(object({
15683
+ addonId: string(),
15684
+ label: string(),
15685
+ candidates: array(DiscoveryCandidateSchema).readonly(),
15686
+ error: string().nullable()
15687
+ })).readonly() }), {
15688
+ kind: "mutation",
15689
+ auth: "admin"
15685
15690
  }), method(object({
15691
+ addonId: string(),
15692
+ params: record(string(), unknown()).optional()
15693
+ }), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
15694
+ kind: "mutation",
15695
+ auth: "admin"
15696
+ }), method(object({ addonId: string() }), object({ deviceType: _enum(DeviceType).nullable() }), { auth: "admin" }), method(object({ addonId: string() }), unknown(), { auth: "admin" }), method(object({
15686
15697
  deviceId: number(),
15687
15698
  key: string(),
15688
15699
  value: unknown()
@@ -18334,6 +18345,17 @@ var AvailableIntegrationTypeSchema = object({
18334
18345
  iconUrl: string().nullable(),
18335
18346
  color: string(),
18336
18347
  instanceMode: string(),
18348
+ /**
18349
+ * Integration wizard `mode` (LOCKED MODEL): `standalone` (create
18350
+ * immediately then add devices, no config step/button), `account` (config
18351
+ * step), or `broker` (broker step). Derived server-side by
18352
+ * `getAvailableTypes` when the addon manifest omits an explicit `mode`.
18353
+ */
18354
+ mode: _enum([
18355
+ "standalone",
18356
+ "account",
18357
+ "broker"
18358
+ ]),
18337
18359
  discoveryMode: string(),
18338
18360
  /**
18339
18361
  * Which integration-marker cap the addon declared, so the wizard can
@@ -20794,6 +20816,12 @@ Object.freeze({
20794
20816
  addonId: null,
20795
20817
  access: "create"
20796
20818
  },
20819
+ "deviceManager.adoptionListCandidateFilters": {
20820
+ capName: "device-manager",
20821
+ capScope: "system",
20822
+ addonId: null,
20823
+ access: "view"
20824
+ },
20797
20825
  "deviceManager.adoptionListCandidates": {
20798
20826
  capName: "device-manager",
20799
20827
  capScope: "system",
@@ -20842,12 +20870,30 @@ Object.freeze({
20842
20870
  addonId: null,
20843
20871
  access: "create"
20844
20872
  },
20873
+ "deviceManager.discoverAllProviders": {
20874
+ capName: "device-manager",
20875
+ capScope: "system",
20876
+ addonId: null,
20877
+ access: "create"
20878
+ },
20845
20879
  "deviceManager.discoverDevices": {
20846
20880
  capName: "device-manager",
20847
20881
  capScope: "system",
20848
20882
  addonId: null,
20849
20883
  access: "create"
20850
20884
  },
20885
+ "deviceManager.discoverProvider": {
20886
+ capName: "device-manager",
20887
+ capScope: "system",
20888
+ addonId: null,
20889
+ access: "create"
20890
+ },
20891
+ "deviceManager.discoveryProviders": {
20892
+ capName: "device-manager",
20893
+ capScope: "system",
20894
+ addonId: null,
20895
+ access: "view"
20896
+ },
20851
20897
  "deviceManager.enable": {
20852
20898
  capName: "device-manager",
20853
20899
  capScope: "system",
@@ -20998,6 +21044,18 @@ Object.freeze({
20998
21044
  addonId: null,
20999
21045
  access: "create"
21000
21046
  },
21047
+ "deviceManager.providerCreationType": {
21048
+ capName: "device-manager",
21049
+ capScope: "system",
21050
+ addonId: null,
21051
+ access: "view"
21052
+ },
21053
+ "deviceManager.providerDiscoveryParamsSchema": {
21054
+ capName: "device-manager",
21055
+ capScope: "system",
21056
+ addonId: null,
21057
+ access: "view"
21058
+ },
21001
21059
  "deviceManager.registerDevice": {
21002
21060
  capName: "device-manager",
21003
21061
  capScope: "system",
@@ -21214,6 +21272,18 @@ Object.freeze({
21214
21272
  addonId: null,
21215
21273
  access: "view"
21216
21274
  },
21275
+ "deviceProvider.getDiscoveryParamsSchema": {
21276
+ capName: "device-provider",
21277
+ capScope: "system",
21278
+ addonId: null,
21279
+ access: "view"
21280
+ },
21281
+ "deviceProvider.getManualCreationType": {
21282
+ capName: "device-provider",
21283
+ capScope: "system",
21284
+ addonId: null,
21285
+ access: "view"
21286
+ },
21217
21287
  "deviceProvider.getStatus": {
21218
21288
  capName: "device-provider",
21219
21289
  capScope: "system",
@@ -24052,6 +24122,89 @@ object({
24052
24122
  schemaVersion: literal(1)
24053
24123
  });
24054
24124
  //#endregion
24125
+ //#region src/config.ts
24126
+ /**
24127
+ * Dreo cloud regions the wrapped `@apocaliss92/nodedreo` client accepts. The
24128
+ * library does not export a region enum (region is a free `opts.region` string),
24129
+ * so we mirror the common Dreo regions locally and validate the operator-supplied
24130
+ * value at the system boundary. `us` is the default (the most common Dreo cloud).
24131
+ */
24132
+ var DREO_REGIONS = ["us", "eu"];
24133
+ var DreoRegionSchema = _enum(DREO_REGIONS);
24134
+ /**
24135
+ * Operator-supplied Dreo account settings for ONE integration (= one cloud
24136
+ * account). The Dreo integration is cloud-only (REST auth + a persistent
24137
+ * WebSocket push), so the connection is just account credentials plus a region.
24138
+ */
24139
+ var dreoConfigSchema = object({
24140
+ email: string().min(1).describe("Dreo account email"),
24141
+ password: string().min(1).describe("Dreo account password"),
24142
+ region: DreoRegionSchema.default("us").describe("Dreo cloud region")
24143
+ });
24144
+ /**
24145
+ * Split the validated addon config into the two arguments the `Nodedreo`
24146
+ * constructor expects (`creds`, `opts`). Pure: same config in → same args out.
24147
+ */
24148
+ function toDreoConstructorArgs(config) {
24149
+ return {
24150
+ creds: {
24151
+ email: config.email,
24152
+ password: config.password
24153
+ },
24154
+ opts: { region: config.region }
24155
+ };
24156
+ }
24157
+ /**
24158
+ * Parse an integration's settings into a connection, or null when the mandatory
24159
+ * credentials (email + password) are missing. Used by the boot/lifecycle
24160
+ * reconcile to skip an integration whose account form was not completed.
24161
+ */
24162
+ function connectionFromSettings(settings) {
24163
+ const parsed = dreoConfigSchema.safeParse(settings);
24164
+ return parsed.success ? parsed.data : null;
24165
+ }
24166
+ /**
24167
+ * Hand-written connection form for the account/integration creation UI — the
24168
+ * wizard's `account` config step renders this via `getGlobalSettings`. A flat
24169
+ * set of sections the admin UI renders into the "Add Dreo account" modal.
24170
+ */
24171
+ function buildConnectionFormSchema() {
24172
+ return { sections: [{
24173
+ id: "credentials",
24174
+ title: "Dreo account",
24175
+ description: "Sign in with your Dreo app account credentials.",
24176
+ columns: 1,
24177
+ fields: [{
24178
+ type: "text",
24179
+ key: "email",
24180
+ label: "Email",
24181
+ required: true,
24182
+ placeholder: "you@example.com"
24183
+ }, {
24184
+ type: "password",
24185
+ key: "password",
24186
+ label: "Password",
24187
+ required: true,
24188
+ showToggle: true
24189
+ }]
24190
+ }, {
24191
+ id: "region",
24192
+ title: "Region",
24193
+ description: "Select the Dreo cloud region your account is registered in.",
24194
+ columns: 1,
24195
+ fields: [{
24196
+ type: "select",
24197
+ key: "region",
24198
+ label: "Region",
24199
+ default: "us",
24200
+ options: DREO_REGIONS.map((r) => ({
24201
+ value: r,
24202
+ label: r.toUpperCase()
24203
+ }))
24204
+ }]
24205
+ }] };
24206
+ }
24207
+ //#endregion
24055
24208
  //#region ../../node_modules/ws/lib/constants.js
24056
24209
  var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
24057
24210
  var BINARY_TYPES = [
@@ -28897,99 +29050,6 @@ var Nodedreo = class {
28897
29050
  }
28898
29051
  };
28899
29052
  //#endregion
28900
- //#region src/config.ts
28901
- /**
28902
- * Dreo cloud regions the wrapped `@apocaliss92/nodedreo` client accepts. The
28903
- * library does not export a region enum (region is a free `opts.region` string),
28904
- * so we mirror the common Dreo regions locally and validate the operator-supplied
28905
- * value at the system boundary. `us` is the default (the most common Dreo cloud).
28906
- */
28907
- var DREO_REGIONS = ["us", "eu"];
28908
- var DreoRegionSchema = _enum(DREO_REGIONS);
28909
- /**
28910
- * Operator-supplied Dreo account settings for ONE broker (= one cloud account).
28911
- * The Dreo integration is cloud-only (REST auth + a persistent WebSocket push),
28912
- * so the connection is just account credentials plus a region.
28913
- */
28914
- var dreoConfigSchema = object({
28915
- email: string().min(1).describe("Dreo account email"),
28916
- password: string().min(1).describe("Dreo account password"),
28917
- region: DreoRegionSchema.default("us").describe("Dreo cloud region")
28918
- });
28919
- /**
28920
- * Split the validated addon config into the two arguments the `Nodedreo`
28921
- * constructor expects (`creds`, `opts`). Pure: same config in → same args out.
28922
- */
28923
- function toDreoConstructorArgs(config) {
28924
- return {
28925
- creds: {
28926
- email: config.email,
28927
- password: config.password
28928
- },
28929
- opts: { region: config.region }
28930
- };
28931
- }
28932
- /** Top-level addon config — an ordered list of broker entries (default empty). */
28933
- var dreoAddonConfigSchema = object({ brokers: array(object({
28934
- /** Stable opaque identifier — e.g. 'dreo_001', 'dreo_002'. */
28935
- id: string().min(1),
28936
- /** Human-readable label shown in the admin UI. */
28937
- name: string().min(1),
28938
- /** Validated account connection settings. */
28939
- connection: dreoConfigSchema,
28940
- /** FK to the spawning integration — auto-cleanup on integration delete. */
28941
- integrationId: string().optional()
28942
- })).default([]) });
28943
- /**
28944
- * Coerce a loose settings blob (from the broker `add`/`setSettings` cap) through
28945
- * the connection schema, applying all defaults. Throws a `ZodError` on invalid
28946
- * input so the caller surfaces a clear error at the system boundary.
28947
- */
28948
- function settingsToDreoConfig(settings) {
28949
- return dreoConfigSchema.parse(settings ?? {});
28950
- }
28951
- /**
28952
- * Hand-written connection form for the broker/integration creation UI. Mirrors
28953
- * the Dreame / Homematic broker-settings form shape — a flat set of sections the
28954
- * admin UI renders into the "Add Dreo account" modal.
28955
- */
28956
- function buildConnectionFormSchema() {
28957
- return { sections: [{
28958
- id: "credentials",
28959
- title: "Dreo account",
28960
- description: "Sign in with your Dreo app account credentials.",
28961
- columns: 1,
28962
- fields: [{
28963
- type: "text",
28964
- key: "email",
28965
- label: "Email",
28966
- required: true,
28967
- placeholder: "you@example.com"
28968
- }, {
28969
- type: "password",
28970
- key: "password",
28971
- label: "Password",
28972
- required: true,
28973
- showToggle: true
28974
- }]
28975
- }, {
28976
- id: "region",
28977
- title: "Region",
28978
- description: "Select the Dreo cloud region your account is registered in.",
28979
- columns: 1,
28980
- fields: [{
28981
- type: "select",
28982
- key: "region",
28983
- label: "Region",
28984
- default: "us",
28985
- options: DREO_REGIONS.map((r) => ({
28986
- value: r,
28987
- label: r.toUpperCase()
28988
- }))
28989
- }]
28990
- }] };
28991
- }
28992
- //#endregion
28993
29053
  //#region src/dreo-integration-manager.ts
28994
29054
  function defaultFacade(config) {
28995
29055
  const { creds, opts } = toDreoConstructorArgs(config);
@@ -29147,15 +29207,20 @@ function sameConnection(a, b) {
29147
29207
  //#endregion
29148
29208
  //#region src/dreo-gateway.ts
29149
29209
  /**
29150
- * Per-broker registry that device classes use to reach a live Dreo device
29210
+ * Per-integration registry that device classes use to reach a live Dreo device
29151
29211
  * handle (and the discovery info) for a given Dreo account.
29152
29212
  *
29213
+ * `mode: account`: keyed by `integrationId`. The string key is generically named
29214
+ * `brokerId` in the method signatures below because the device classes pass their
29215
+ * persisted `brokerId` config field — which now holds the integrationId
29216
+ * (unchanged device-side code, see the addon migration note).
29217
+ *
29153
29218
  * The kernel constructs device classes with only a `DeviceContext` — it cannot
29154
29219
  * thread the handle in as a constructor arg. Like the Dreame addon's
29155
29220
  * `dreameFacades`, we keep it simple and in-process: the integration manager
29156
29221
  * owns the connection surface per registered account and publishes it here;
29157
29222
  * device classes resolve their live `BaseDevice` handle by `(brokerId,
29158
- * deviceId)`.
29223
+ * deviceId)` where `brokerId` = integrationId.
29159
29224
  */
29160
29225
  var DreoConnectionResolver = class {
29161
29226
  #surfaces = /* @__PURE__ */ new Map();
@@ -29192,31 +29257,51 @@ var DreoConnectionResolver = class {
29192
29257
  this.#surfaces.clear();
29193
29258
  }
29194
29259
  };
29195
- /** The single in-process per-broker connection resolver shared between the
29196
- * registry/manager and device instances. */
29260
+ /** The single in-process per-integration connection resolver shared between the
29261
+ * client registry/manager and device instances (keyed by integrationId). */
29197
29262
  var dreoConnections = new DreoConnectionResolver();
29198
29263
  //#endregion
29199
- //#region src/dreo-broker-registry.ts
29264
+ //#region src/dreo-client-registry.ts
29265
+ /**
29266
+ * Per-integration Dreo connection registry (account mode).
29267
+ *
29268
+ * Under the LOCKED integration/adoption model (design §7.1), Dreo is a
29269
+ * `mode: account` addon: each Dreo integration carries its own Dreo cloud
29270
+ * account credentials in its `integration.settings`, and this registry holds one
29271
+ * {@link DreoIntegrationManager} per `integrationId`
29272
+ * (`Map<integrationId, DreoIntegrationManager>`). Multi-account = multiple
29273
+ * integrations; there is NO shared broker.
29274
+ *
29275
+ * The live Dreo connection surface each manager owns (discovery info + per-device
29276
+ * handles) is published on the in-process {@link dreoConnections} resolver KEYED
29277
+ * BY `integrationId` — the manager's id IS the integrationId. Device classes
29278
+ * resolve their live handle by the `brokerId` config field they persist, which
29279
+ * now holds the integrationId (unchanged device-side code, see the addon
29280
+ * migration note). The WebSocket push lifecycle stays entirely inside the manager
29281
+ * + library (no leak): the registry only starts/stops managers.
29282
+ */
29200
29283
  /**
29201
- * Manages N live Dreo account connections ("brokers"), each backed by a
29202
- * {@link DreoIntegrationManager}. Allocates stable ids (`dreo_001`, …), supports
29203
- * CRUD, an integration FK index for cascade-delete, and lifecycle helpers.
29204
- * Mirrors `DreameBrokerRegistry`.
29284
+ * Owns the `Map<integrationId, DreoIntegrationManager>`. Callers reconcile the
29285
+ * map against the live integration list on boot + on every integration lifecycle
29286
+ * event; device classes + the adoption provider resolve their surface by
29287
+ * `integrationId` through {@link dreoConnections}.
29288
+ *
29289
+ * Structural twin of `DreameClientRegistry` / `WyzeClientRegistry` — `upsert` is
29290
+ * session-preserving when credentials are unchanged, `retain` drops vanished
29291
+ * integrations.
29205
29292
  */
29206
- var DreoBrokerRegistry = class {
29293
+ var DreoClientRegistry = class {
29207
29294
  #logger;
29208
- #onBrokerConnected;
29209
- #onBrokerDisconnected;
29295
+ #onConnected;
29296
+ #onDisconnected;
29210
29297
  #makeManager;
29211
- #managers = /* @__PURE__ */ new Map();
29212
- #integrationToBroker = /* @__PURE__ */ new Map();
29213
- #nextId = 1;
29298
+ #entries = /* @__PURE__ */ new Map();
29214
29299
  constructor(logger, deps = {}) {
29215
29300
  this.#logger = logger;
29216
- this.#onBrokerConnected = deps.onBrokerConnected ?? (() => void 0);
29217
- this.#onBrokerDisconnected = deps.onBrokerDisconnected ?? (() => void 0);
29301
+ this.#onConnected = deps.onConnected ?? (() => void 0);
29302
+ this.#onDisconnected = deps.onDisconnected ?? (() => void 0);
29218
29303
  this.#makeManager = deps.makeManager ?? ((opts) => new DreoIntegrationManager({
29219
- id: opts.id,
29304
+ id: opts.integrationId,
29220
29305
  name: opts.name,
29221
29306
  connection: opts.connection,
29222
29307
  logger: opts.logger,
@@ -29225,259 +29310,119 @@ var DreoBrokerRegistry = class {
29225
29310
  surfaceSink: dreoConnections
29226
29311
  }));
29227
29312
  }
29228
- /** Restore persisted broker entries on boot (best-effort per manager). */
29229
- async restore(entries) {
29230
- for (const entry of entries) this.#seedCounter(entry.id);
29231
- for (const entry of entries) try {
29232
- await this.#startManager(entry);
29233
- } catch (err) {
29234
- this.#logger.warn("DreoBrokerRegistry: failed to restore manager", {
29235
- tags: { brokerId: entry.id },
29236
- meta: { error: errMsg(err) }
29237
- });
29238
- }
29313
+ setOnConnected(cb) {
29314
+ this.#onConnected = cb;
29239
29315
  }
29240
- /** Stop all managers and clear state. */
29241
- async shutdown() {
29242
- const ids = Array.from(this.#managers.keys());
29243
- await Promise.all(ids.map(async (id) => {
29316
+ setOnDisconnected(cb) {
29317
+ this.#onDisconnected = cb;
29318
+ }
29319
+ /**
29320
+ * Ensure a manager exists for `integrationId` with the given credentials.
29321
+ * Idempotent: an existing entry with identical credentials is preserved (keeps
29322
+ * its live WebSocket session); a credentials change re-applies the connection
29323
+ * atomically (stop + start). Best-effort start — a failed login leaves the
29324
+ * entry registered so a later reconcile / lifecycle event retries.
29325
+ */
29326
+ async upsert(integrationId, name, connection) {
29327
+ const existing = this.#entries.get(integrationId);
29328
+ if (existing) {
29329
+ if (sameConnection(existing.connection, connection)) return;
29244
29330
  try {
29245
- await this.#managers.get(id)?.stop();
29331
+ await existing.manager.applyConnection(connection);
29332
+ this.#entries.set(integrationId, {
29333
+ manager: existing.manager,
29334
+ connection
29335
+ });
29246
29336
  } catch (err) {
29247
- this.#logger.warn("DreoBrokerRegistry: shutdown stop failed", {
29248
- tags: { brokerId: id },
29337
+ this.#logger.warn("DreoClientRegistry: applyConnection failed", {
29338
+ tags: { integrationId },
29249
29339
  meta: { error: errMsg(err) }
29250
29340
  });
29251
29341
  }
29252
- }));
29253
- this.#managers.clear();
29254
- this.#integrationToBroker.clear();
29255
- dreoConnections.clear();
29256
- }
29257
- setOnBrokerConnected(cb) {
29258
- this.#onBrokerConnected = cb;
29259
- }
29260
- setOnBrokerDisconnected(cb) {
29261
- this.#onBrokerDisconnected = cb;
29262
- }
29263
- async createEntry(name, connection, opts = {}) {
29264
- const id = this.#allocateId();
29265
- const entry = {
29266
- id,
29342
+ return;
29343
+ }
29344
+ const manager = this.#makeManager({
29345
+ integrationId,
29267
29346
  name,
29268
29347
  connection,
29269
- ...opts.integrationId !== void 0 ? { integrationId: opts.integrationId } : {}
29270
- };
29271
- await this.#startManager(entry);
29272
- if (opts.integrationId !== void 0) this.#integrationToBroker.set(opts.integrationId, id);
29273
- return entry;
29274
- }
29275
- async removeEntry(id) {
29276
- const mgr = this.#managers.get(id);
29277
- if (!mgr) throw new Error(`DreoBrokerRegistry: unknown broker id "${id}"`);
29278
- for (const [integrationId, brokerId] of this.#integrationToBroker.entries()) if (brokerId === id) this.#integrationToBroker.delete(integrationId);
29279
- this.#managers.delete(id);
29348
+ logger: this.#logger,
29349
+ onConnected: (id) => this.#onConnected(id),
29350
+ onDisconnected: (id) => this.#onDisconnected(id)
29351
+ });
29352
+ this.#entries.set(integrationId, {
29353
+ manager,
29354
+ connection
29355
+ });
29280
29356
  try {
29281
- await mgr.stop();
29357
+ await manager.start();
29282
29358
  } catch (err) {
29283
- this.#logger.warn("DreoBrokerRegistry: removeEntry stop failed", {
29284
- tags: { brokerId: id },
29359
+ this.#logger.warn("DreoClientRegistry: manager start failed", {
29360
+ tags: { integrationId },
29285
29361
  meta: { error: errMsg(err) }
29286
29362
  });
29287
29363
  }
29288
29364
  }
29289
- async updateEntry(id, connection) {
29290
- const mgr = this.#managers.get(id);
29291
- if (!mgr) throw new Error(`DreoBrokerRegistry: unknown broker id "${id}"`);
29292
- await mgr.applyConnection(connection);
29293
- return {
29294
- id,
29295
- name: mgr.getInfo().name,
29296
- connection
29297
- };
29365
+ /** Stop + drop the manager for an integration that no longer exists. */
29366
+ async remove(integrationId) {
29367
+ const entry = this.#entries.get(integrationId);
29368
+ if (!entry) return;
29369
+ this.#entries.delete(integrationId);
29370
+ try {
29371
+ await entry.manager.stop();
29372
+ } catch (err) {
29373
+ this.#logger.warn("DreoClientRegistry: remove stop failed", {
29374
+ tags: { integrationId },
29375
+ meta: { error: errMsg(err) }
29376
+ });
29377
+ }
29298
29378
  }
29299
- linkIntegration(integrationId, brokerId) {
29300
- this.#integrationToBroker.set(integrationId, brokerId);
29379
+ /** Stop + drop every manager whose integrationId is not in `keep`. */
29380
+ async retain(keep) {
29381
+ const stale = [...this.#entries.keys()].filter((id) => !keep.has(id));
29382
+ for (const id of stale) await this.remove(id);
29301
29383
  }
29302
- getBrokerIdByIntegrationId(integrationId) {
29303
- const brokerId = this.#integrationToBroker.get(integrationId);
29304
- if (brokerId === void 0) throw new Error(`DreoBrokerRegistry: no broker linked for integration id "${integrationId}"`);
29305
- return brokerId;
29384
+ /** Stop every manager and clear all state (full shutdown). */
29385
+ async shutdown() {
29386
+ const ids = [...this.#entries.keys()];
29387
+ await Promise.all(ids.map(async (id) => {
29388
+ try {
29389
+ await this.#entries.get(id)?.manager.stop();
29390
+ } catch (err) {
29391
+ this.#logger.warn("DreoClientRegistry: shutdown stop failed", {
29392
+ tags: { integrationId: id },
29393
+ meta: { error: errMsg(err) }
29394
+ });
29395
+ }
29396
+ }));
29397
+ this.#entries.clear();
29398
+ dreoConnections.clear();
29399
+ }
29400
+ /** True when a manager is registered for the integration. */
29401
+ has(integrationId) {
29402
+ return this.#entries.has(integrationId);
29306
29403
  }
29404
+ /** The registered integration ids (one per account). */
29307
29405
  list() {
29308
- return Array.from(this.#managers.values()).map((m) => m.getInfo());
29406
+ return [...this.#entries.keys()];
29309
29407
  }
29310
- get(id) {
29311
- return this.#managers.get(id)?.getInfo() ?? null;
29408
+ /** The current connection an integration's manager is configured with. */
29409
+ getConnection(integrationId) {
29410
+ return this.#entries.get(integrationId)?.connection ?? null;
29312
29411
  }
29313
- getConnection(id) {
29314
- return this.#managers.get(id)?.getConnection() ?? null;
29412
+ /** Immutable status snapshot for an integration's manager. */
29413
+ getInfo(integrationId) {
29414
+ return this.#entries.get(integrationId)?.manager.getInfo() ?? null;
29315
29415
  }
29316
29416
  size() {
29317
- return this.#managers.size;
29417
+ return this.#entries.size;
29318
29418
  }
29319
29419
  connectedCount() {
29320
29420
  let count = 0;
29321
- for (const mgr of this.#managers.values()) if (mgr.getInfo().status === "connected") count++;
29421
+ for (const entry of this.#entries.values()) if (entry.manager.getInfo().status === "connected") count++;
29322
29422
  return count;
29323
29423
  }
29324
- async #startManager(entry) {
29325
- if (this.#managers.has(entry.id)) {
29326
- if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
29327
- return;
29328
- }
29329
- const mgr = this.#makeManager({
29330
- id: entry.id,
29331
- name: entry.name,
29332
- connection: entry.connection,
29333
- logger: this.#logger,
29334
- onConnected: (id) => this.#onBrokerConnected(id),
29335
- onDisconnected: (id) => this.#onBrokerDisconnected(id)
29336
- });
29337
- this.#managers.set(entry.id, mgr);
29338
- if (entry.integrationId !== void 0) this.#integrationToBroker.set(entry.integrationId, entry.id);
29339
- await mgr.start();
29340
- }
29341
- #allocateId() {
29342
- const id = `dreo_${String(this.#nextId).padStart(3, "0")}`;
29343
- this.#nextId++;
29344
- return id;
29345
- }
29346
- #seedCounter(id) {
29347
- const match = /^dreo_(\d+)$/.exec(id);
29348
- if (match === null || match[1] === void 0) return;
29349
- const n = parseInt(match[1], 10);
29350
- if (!isNaN(n)) this.#nextId = Math.max(this.#nextId, n + 1);
29351
- }
29352
29424
  };
29353
29425
  //#endregion
29354
- //#region src/dreo-broker-provider.ts
29355
- /** Kind tag registered by this provider — matches the `listProviders` entry. */
29356
- var DREO_KIND = "dreo";
29357
- /**
29358
- * Construct the `broker` cap provider for the Dreo addon. Pure builder: all
29359
- * side-effecting deps are injected so the returned object is unit-testable.
29360
- * Mirrors `buildDreameBrokerProvider`.
29361
- */
29362
- function buildDreoBrokerProvider(deps) {
29363
- const { ownerAddonId, registry, getBrokers, persistBrokers, cascadeRemoveDevices, logger } = deps;
29364
- const owns = (id) => getBrokers().some((b) => b.id === id);
29365
- return {
29366
- list: async ({ kind }) => {
29367
- if (kind && kind !== DREO_KIND) return [];
29368
- return registry.list().map((info) => ({
29369
- ...info,
29370
- addonId: ownerAddonId
29371
- }));
29372
- },
29373
- get: async ({ id }) => {
29374
- const info = registry.get(id);
29375
- return info ? {
29376
- ...info,
29377
- addonId: ownerAddonId
29378
- } : null;
29379
- },
29380
- listProviders: async () => [{
29381
- addonId: ownerAddonId,
29382
- kinds: [{
29383
- kind: DREO_KIND,
29384
- label: "Dreo"
29385
- }]
29386
- }],
29387
- add: async ({ kind, name, settings }) => {
29388
- if (kind !== DREO_KIND) throw new Error(`provider-dreo: only kind '${DREO_KIND}' is handled here (got '${kind}')`);
29389
- const entry = await registry.createEntry(name, settingsToDreoConfig(settings));
29390
- await persistBrokers([...getBrokers(), entry]);
29391
- return { id: entry.id };
29392
- },
29393
- remove: async ({ id }) => {
29394
- if (!owns(id)) return;
29395
- await registry.removeEntry(id);
29396
- await cascadeRemoveDevices(id).catch((err) => {
29397
- logger.warn("dreo: broker cascade-remove threw", {
29398
- tags: { brokerId: id },
29399
- meta: { error: errMsg(err) }
29400
- });
29401
- });
29402
- await persistBrokers(getBrokers().filter((b) => b.id !== id));
29403
- },
29404
- testConnection: async ({ id }) => {
29405
- if (!owns(id)) return {
29406
- ok: false,
29407
- error: "unknown broker"
29408
- };
29409
- const info = registry.get(id);
29410
- if (!info) return {
29411
- ok: false,
29412
- error: "unknown broker"
29413
- };
29414
- if (info.status === "connected") return {
29415
- ok: true,
29416
- latencyMs: info.lastCheckedAt ? Math.max(0, Date.now() - info.lastCheckedAt) : 0
29417
- };
29418
- return {
29419
- ok: false,
29420
- error: info.error ?? `status: ${info.status}`
29421
- };
29422
- },
29423
- getSettings: async ({ id }) => {
29424
- const entry = getBrokers().find((b) => b.id === id);
29425
- if (!entry) return null;
29426
- return {
29427
- ...entry.connection,
29428
- password: ""
29429
- };
29430
- },
29431
- setSettings: async ({ id, settings }) => {
29432
- if (!owns(id)) return;
29433
- const existing = getBrokers().find((b) => b.id === id);
29434
- if (!existing) return;
29435
- const parsed = settingsToDreoConfig(settings);
29436
- const merged = {
29437
- ...parsed,
29438
- password: parsed.password.length > 0 ? parsed.password : existing.connection.password
29439
- };
29440
- const updated = await registry.updateEntry(id, merged);
29441
- await persistBrokers(getBrokers().map((b) => b.id === id ? updated : b));
29442
- },
29443
- getBrokerConfig: async ({ id }) => {
29444
- const entry = getBrokers().find((b) => b.id === id);
29445
- if (!entry) return null;
29446
- return {
29447
- ...entry.connection,
29448
- password: ""
29449
- };
29450
- },
29451
- getSettingsSchema: async ({ kind }) => {
29452
- if (kind !== DREO_KIND) return null;
29453
- return buildConnectionFormSchema();
29454
- },
29455
- testSettings: async ({ kind, settings }) => {
29456
- if (kind !== DREO_KIND) return {
29457
- ok: false,
29458
- error: `unsupported kind: ${kind}`
29459
- };
29460
- try {
29461
- settingsToDreoConfig(settings);
29462
- return { ok: true };
29463
- } catch (err) {
29464
- return {
29465
- ok: false,
29466
- error: errMsg(err)
29467
- };
29468
- }
29469
- },
29470
- publish: async () => null,
29471
- subscribe: async () => ({ subscriptionId: "" }),
29472
- unsubscribe: async () => void 0,
29473
- getState: async () => null,
29474
- getStatus: async () => ({
29475
- brokerCount: registry.size(),
29476
- connectedCount: registry.connectedCount()
29477
- })
29478
- };
29479
- }
29480
- //#endregion
29481
29426
  //#region src/dreo-domain-mapping.ts
29482
29427
  /** Library device-type strings we treat as a CamStack `fan` kind. */
29483
29428
  var FAN_LIB_TYPES = new Set([
@@ -29599,11 +29544,11 @@ var DEVICES_FILTER = {
29599
29544
  label: "Devices",
29600
29545
  isDefault: true
29601
29546
  };
29602
- /** Build a `dreoDeviceId → CamStack deviceId` map for a single broker. */
29603
- async function adoptedMapForBroker(brokerId, listAdoptedDreo) {
29547
+ /** Build a `dreoDeviceId → CamStack deviceId` map for a single integration. */
29548
+ async function adoptedMapForIntegration(integrationId, listAdoptedDreo) {
29604
29549
  const all = await listAdoptedDreo();
29605
29550
  const map = /* @__PURE__ */ new Map();
29606
- for (const device of all) if (device.config["system"] === "dreo" && device.config["brokerId"] === brokerId) {
29551
+ for (const device of all) if (device.config["system"] === "dreo" && device.config["brokerId"] === integrationId) {
29607
29552
  const dreoId = device.config["dreoDeviceId"];
29608
29553
  if (typeof dreoId === "string") map.set(dreoId, device.id);
29609
29554
  }
@@ -29611,15 +29556,15 @@ async function adoptedMapForBroker(brokerId, listAdoptedDreo) {
29611
29556
  }
29612
29557
  /**
29613
29558
  * Construct the `device-adoption` cap provider for the Dreo addon. Pure builder:
29614
- * all side-effecting deps are injected. Mirrors `buildDreameAdoptionProvider`
29615
- * with a single `devices` granularity (one Container per cloud device).
29559
+ * all side-effecting deps are injected. Single `devices` granularity (one
29560
+ * Container per cloud device); resolves each account by `integrationId`.
29616
29561
  */
29617
29562
  function buildDreoAdoptionProvider(deps) {
29618
- const { registry, getBrokerIdForIntegration, listAdoptedDreo, adoptDevice, removeDevice, findDeviceConfig, logger } = deps;
29619
- async function allCandidatesForBroker(brokerId) {
29563
+ const { registry, hasIntegration, listIntegrations, listAdoptedDreo, adoptDevice, removeDevice, findDeviceConfig, logger } = deps;
29564
+ async function allCandidatesForIntegration(integrationId) {
29620
29565
  return buildDreoCandidates({
29621
- devices: registry.infos(brokerId),
29622
- adopted: await adoptedMapForBroker(brokerId, listAdoptedDreo)
29566
+ devices: registry.infos(integrationId),
29567
+ adopted: await adoptedMapForIntegration(integrationId, listAdoptedDreo)
29623
29568
  });
29624
29569
  }
29625
29570
  function applyCandidateTextFilter(cands, filterText) {
@@ -29637,7 +29582,7 @@ function buildDreoAdoptionProvider(deps) {
29637
29582
  return {
29638
29583
  listCandidateFilters: async () => ({ filters: [DEVICES_FILTER] }),
29639
29584
  listCandidates: async ({ integrationId, page, pageSize, filterText }) => {
29640
- const filtered = applyCandidateTextFilter(await allCandidatesForBroker(await getBrokerIdForIntegration(integrationId)), filterText);
29585
+ const filtered = applyCandidateTextFilter(await allCandidatesForIntegration(integrationId), filterText);
29641
29586
  const start = (page - 1) * pageSize;
29642
29587
  return {
29643
29588
  candidates: filtered.slice(start, start + pageSize),
@@ -29647,13 +29592,12 @@ function buildDreoAdoptionProvider(deps) {
29647
29592
  };
29648
29593
  },
29649
29594
  getCandidate: async ({ integrationId, childNativeId }) => {
29650
- return (await allCandidatesForBroker(await getBrokerIdForIntegration(integrationId))).find((c) => c.childNativeId === childNativeId) ?? null;
29595
+ return (await allCandidatesForIntegration(integrationId)).find((c) => c.childNativeId === childNativeId) ?? null;
29651
29596
  },
29652
29597
  getStatus: async () => {
29653
29598
  try {
29654
- const brokers = registry.list();
29655
29599
  let candidateCount = 0;
29656
- for (const broker of brokers) candidateCount += (await allCandidatesForBroker(broker.id)).length;
29600
+ for (const integrationId of listIntegrations()) candidateCount += (await allCandidatesForIntegration(integrationId)).length;
29657
29601
  const adoptedCount = (await listAdoptedDreo()).filter((d) => d.config["system"] === "dreo").length;
29658
29602
  return {
29659
29603
  lastDiscoveryAt: Date.now(),
@@ -29672,9 +29616,8 @@ function buildDreoAdoptionProvider(deps) {
29672
29616
  }
29673
29617
  },
29674
29618
  refresh: async ({ integrationId }) => {
29675
- const brokerId = await getBrokerIdForIntegration(integrationId);
29676
- const candidateCount = (await allCandidatesForBroker(brokerId)).length;
29677
- const adoptedCount = (await listAdoptedDreo()).filter((d) => d.config["system"] === "dreo" && d.config["brokerId"] === brokerId).length;
29619
+ const candidateCount = (await allCandidatesForIntegration(integrationId)).length;
29620
+ const adoptedCount = (await adoptedMapForIntegration(integrationId, listAdoptedDreo)).size;
29678
29621
  return {
29679
29622
  lastDiscoveryAt: Date.now(),
29680
29623
  candidateCount,
@@ -29683,17 +29626,16 @@ function buildDreoAdoptionProvider(deps) {
29683
29626
  };
29684
29627
  },
29685
29628
  adopt: async ({ integrationId, childNativeIds, perCandidate }) => {
29686
- const brokerId = await getBrokerIdForIntegration(integrationId);
29687
- if (!registry.has(brokerId)) throw new Error(`dreo adopt: broker ${brokerId} not connected`);
29688
- const devices = registry.infos(brokerId);
29629
+ if (!registry.has(integrationId)) throw new Error(`dreo adopt: integration ${integrationId} not connected`);
29630
+ const devices = registry.infos(integrationId);
29689
29631
  const adopted = [];
29690
29632
  let failures = 0;
29691
29633
  for (const dreoDeviceId of childNativeIds) try {
29692
29634
  const dev = devices.find((d) => d.deviceId === dreoDeviceId);
29693
29635
  if (dev === void 0) {
29694
- logger.warn("dreo adopt: device not found on broker — skipping", { meta: {
29636
+ logger.warn("dreo adopt: device not found on integration — skipping", { meta: {
29695
29637
  dreoDeviceId,
29696
- brokerId
29638
+ integrationId
29697
29639
  } });
29698
29640
  failures++;
29699
29641
  continue;
@@ -29705,7 +29647,7 @@ function buildDreoAdoptionProvider(deps) {
29705
29647
  if (candidate === void 0) {
29706
29648
  logger.warn("dreo adopt: device model unsupported — skipping", { meta: {
29707
29649
  dreoDeviceId,
29708
- brokerId,
29650
+ integrationId,
29709
29651
  model: dev.model
29710
29652
  } });
29711
29653
  failures++;
@@ -29714,7 +29656,6 @@ function buildDreoAdoptionProvider(deps) {
29714
29656
  const name = perCandidate?.[dreoDeviceId]?.name ?? dev.name ?? dreoDeviceId;
29715
29657
  const { deviceId, accessoryDeviceIds } = await adoptDevice({
29716
29658
  dreoDeviceId,
29717
- brokerId,
29718
29659
  integrationId,
29719
29660
  type: candidate.type,
29720
29661
  name,
@@ -29728,7 +29669,7 @@ function buildDreoAdoptionProvider(deps) {
29728
29669
  } catch (err) {
29729
29670
  logger.warn("dreo adopt: failed to adopt device", { meta: {
29730
29671
  dreoDeviceId,
29731
- brokerId,
29672
+ integrationId,
29732
29673
  error: errMsg(err)
29733
29674
  } });
29734
29675
  failures++;
@@ -29743,9 +29684,10 @@ function buildDreoAdoptionProvider(deps) {
29743
29684
  const cfg = await findDeviceConfig(camDeviceId);
29744
29685
  if (cfg === null) throw new Error(`dreo resync: device ${camDeviceId} not found`);
29745
29686
  if (cfg["system"] !== "dreo") throw new Error(`dreo resync: device ${camDeviceId} is not a Dreo device`);
29746
- const brokerId = String(cfg["brokerId"]);
29687
+ const integrationId = String(cfg["brokerId"]);
29747
29688
  const dreoDeviceId = String(cfg["dreoDeviceId"]);
29748
- if (!registry.infos(brokerId).some((d) => d.deviceId === dreoDeviceId)) throw new Error(`dreo resync: device ${dreoDeviceId} no longer present on broker ${brokerId}`);
29689
+ if (!hasIntegration(integrationId)) throw new Error(`dreo resync: integration ${integrationId} not connected`);
29690
+ if (!registry.infos(integrationId).some((d) => d.deviceId === dreoDeviceId)) throw new Error(`dreo resync: device ${dreoDeviceId} no longer present on integration ${integrationId}`);
29749
29691
  return {
29750
29692
  changed: false,
29751
29693
  rebuiltChildren: 0
@@ -29754,29 +29696,31 @@ function buildDreoAdoptionProvider(deps) {
29754
29696
  };
29755
29697
  }
29756
29698
  //#endregion
29757
- //#region src/dreo-broker-device-cascade.ts
29699
+ //#region src/dreo-integration-device-cascade.ts
29758
29700
  /**
29759
29701
  * Remove every adopted Dreo PARENT device (children cascade via the kernel)
29760
- * whose persisted config carries `{ system: 'dreo', brokerId }`. Used when a
29761
- * broker is removed (broker.remove) and when its spawning integration is
29762
- * deleted. Best-effort per device; returns the count removed. Mirrors the
29763
- * Dreame cascade helper.
29702
+ * whose persisted config carries `{ system: 'dreo', brokerId: integrationId }`.
29703
+ * Used when an integration is deleted/disabled. Best-effort per device; returns
29704
+ * the count removed.
29705
+ *
29706
+ * NOTE: the persisted config field is still named `brokerId` (unchanged
29707
+ * device-side schema) but under the account model it holds the `integrationId`.
29764
29708
  */
29765
- async function cascadeRemoveDevicesForBroker(input) {
29766
- const { reg, devices, addonId, brokerId, logger } = input;
29709
+ async function cascadeRemoveDevicesForIntegration(input) {
29710
+ const { reg, devices, addonId, integrationId, logger } = input;
29767
29711
  let removed = 0;
29768
29712
  for (const d of reg.getAllForAddon(addonId)) {
29769
29713
  if (d.parentDeviceId !== null) continue;
29770
29714
  const cfg = await devices.loadConfig(d.id).catch(() => ({}));
29771
- if (cfg["system"] !== "dreo" || cfg["brokerId"] !== brokerId) continue;
29715
+ if (cfg["system"] !== "dreo" || cfg["brokerId"] !== integrationId) continue;
29772
29716
  try {
29773
29717
  await devices.remove(d.id);
29774
29718
  removed += 1;
29775
29719
  } catch (err) {
29776
- logger.warn("dreo: broker cascade-remove failed", {
29720
+ logger.warn("dreo: integration cascade-remove failed", {
29777
29721
  tags: { deviceId: d.id },
29778
29722
  meta: {
29779
- brokerId,
29723
+ integrationId,
29780
29724
  error: errMsg(err)
29781
29725
  }
29782
29726
  });
@@ -29785,20 +29729,21 @@ async function cascadeRemoveDevicesForBroker(input) {
29785
29729
  return removed;
29786
29730
  }
29787
29731
  //#endregion
29788
- //#region src/dreo-broker-offline.ts
29732
+ //#region src/dreo-integration-offline.ts
29789
29733
  /**
29790
- * Set every Dreo device belonging to `brokerId` to the requested online state.
29791
- * `devices` is the addon-scoped device list; every Dreo top-level Container
29792
- * carries `{ system: 'dreo', brokerId }` in its config blob.
29734
+ * Set every Dreo device belonging to `integrationId` to the requested online
29735
+ * state. `devices` is the addon-scoped device list; every Dreo top-level
29736
+ * Container carries `{ system: 'dreo', brokerId: integrationId }` in its config
29737
+ * blob (the `brokerId` field name is unchanged; it now holds the integrationId).
29793
29738
  *
29794
29739
  * Churn-free: skips any device already in the target state. Returns the count of
29795
- * devices actually transitioned. Mirrors the Dreame offline helper.
29740
+ * devices actually transitioned.
29796
29741
  */
29797
- function setBrokerDevicesOnline(devices, brokerId, online) {
29742
+ function setIntegrationDevicesOnline(devices, integrationId, online) {
29798
29743
  let count = 0;
29799
29744
  for (const dev of devices) {
29800
29745
  if (dev.config.get("system") !== "dreo") continue;
29801
- if (dev.config.get("brokerId") !== brokerId) continue;
29746
+ if (dev.config.get("brokerId") !== integrationId) continue;
29802
29747
  if (dev.online === online) continue;
29803
29748
  dev.markOnline(online);
29804
29749
  count += 1;
@@ -30344,199 +30289,137 @@ var DreoContainerDevice = class extends BaseDevice$1 {
30344
30289
  };
30345
30290
  //#endregion
30346
30291
  //#region src/addon.ts
30347
- /** Default multi-broker config — a fresh install starts with no accounts. */
30348
- var DEFAULTS = { brokers: [] };
30349
30292
  /**
30350
- * Dreo device-provider addon (multi-account).
30293
+ * Dreo device-provider addon — `mode: account` (multi-account, broker-less).
30351
30294
  *
30352
- * Wraps the `@apocaliss92/nodedreo` Dreo cloud client (REST + WebSocket). One
30353
- * registered account = one "broker"; each adopted cloud device becomes a
30354
- * {@link DeviceType.Container} parent that fans out a single typed accessory
30355
- * child (fan / purifier / heater / humidifier). Modelled directly on the Dreame
30356
- * device-provider template: `broker` + `device-adoption` cap providers for
30357
- * connection + adoption, the `device-provider` cap from the base class, and an
30358
- * in-process connection resolver (`dreoConnections`) the device classes read.
30295
+ * Wraps the `@apocaliss92/nodedreo` Dreo cloud client (REST + WebSocket). Each
30296
+ * Dreo integration carries its own account credentials in its
30297
+ * `integration.settings`; the addon holds one {@link DreoIntegrationManager} per
30298
+ * `integrationId` in {@link DreoClientRegistry} (`Map<integrationId, manager>`).
30299
+ * There is NO shared broker. The live connection surface each manager owns
30300
+ * (discovery info + per-device handles) is published on the in-process
30301
+ * {@link dreoConnections} resolver KEYED BY `integrationId`.
30302
+ *
30303
+ * A `device-adoption` cap provider enumerates each account's cloud devices and
30304
+ * adopts them; each adopted cloud device becomes a {@link DeviceType.Container}
30305
+ * parent that fans out a single typed accessory child (fan / purifier / heater /
30306
+ * humidifier) and its entity children. The device classes resolve their live
30307
+ * handle by the `brokerId` config field — UNCHANGED device-side code; that field
30308
+ * now holds the integrationId (see the migration note in the addon docs).
30359
30309
  *
30360
30310
  * Placement: hub-only — the Dreo cloud is reached over outbound HTTPS + a
30361
30311
  * persistent WebSocket from a single account session; no LAN proximity matters,
30362
- * so (like Dreame) the broker lives on the hub.
30312
+ * so (like Dreame) the connection lives on the hub.
30363
30313
  */
30364
30314
  var DreoProviderAddon = class extends BaseDeviceProvider {
30365
30315
  addonId = "provider-dreo";
30366
30316
  providerName = "Dreo";
30367
30317
  deviceClasses = { [DeviceType.Container]: DreoContainerDevice };
30368
- registry = null;
30318
+ clients = null;
30369
30319
  constructor() {
30370
- super({ ...DEFAULTS });
30320
+ super({});
30371
30321
  }
30372
30322
  async onInitialize() {
30373
30323
  const regs = await super.onInitialize();
30374
- this.registry = new DreoBrokerRegistry(this.ctx.logger);
30375
- this.registry.setOnBrokerConnected((brokerId) => {
30376
- this.ctx.logger.info("Dreo: broker connected", { meta: { brokerId } });
30377
- this.setBrokerDevicesOnline(brokerId, true);
30324
+ this.clients = new DreoClientRegistry(this.ctx.logger);
30325
+ this.clients.setOnConnected((integrationId) => {
30326
+ this.ctx.logger.info("Dreo: integration connected", { meta: { integrationId } });
30327
+ this.setIntegrationDevicesOnline(integrationId, true);
30378
30328
  });
30379
- this.registry.setOnBrokerDisconnected((brokerId) => {
30380
- this.setBrokerDevicesOnline(brokerId, false);
30329
+ this.clients.setOnDisconnected((integrationId) => {
30330
+ this.setIntegrationDevicesOnline(integrationId, false);
30381
30331
  });
30382
- await this.registry.restore(this.config.brokers);
30383
- this.ctx.logger.info("Dreo: provider initialised", { meta: { brokerCount: this.config.brokers.length } });
30384
- await this.reconcileIntegrationsToBrokers();
30332
+ await this.reconcileIntegrations();
30385
30333
  this.subscribeIntegrationLifecycle();
30386
- return [
30387
- ...regs,
30388
- {
30389
- capability: brokerCapability,
30390
- provider: this.buildBrokerProvider()
30391
- },
30392
- {
30393
- capability: deviceAdoptionCapability,
30394
- provider: this.buildAdoptionProvider()
30395
- }
30396
- ];
30397
- }
30398
- /** Reconcile the live registry against the persisted `brokers` array after a
30399
- * settings write (UI save or `broker.*` cap). Mirrors the Dreame addon. */
30400
- async onConfigChanged() {
30401
- const reg = this.registry;
30402
- if (!reg) return;
30403
- const persisted = this.config.brokers;
30404
- const liveIds = new Set(reg.list().map((b) => b.id));
30405
- const persistedIds = new Set(persisted.map((e) => e.id));
30406
- for (const liveId of liveIds) if (!persistedIds.has(liveId)) try {
30407
- await reg.removeEntry(liveId);
30408
- } catch (err) {
30409
- this.ctx.logger.warn("Dreo onConfigChanged: removeEntry failed", { meta: {
30410
- brokerId: liveId,
30411
- error: errMsg(err)
30412
- } });
30413
- }
30414
- for (const entry of persisted) {
30415
- if (entry.id && liveIds.has(entry.id)) {
30416
- if (sameConnection(entry.connection, reg.getConnection(entry.id))) continue;
30417
- try {
30418
- await reg.updateEntry(entry.id, entry.connection);
30419
- } catch (err) {
30420
- this.ctx.logger.warn("Dreo onConfigChanged: updateEntry failed", { meta: {
30421
- brokerId: entry.id,
30422
- error: errMsg(err)
30423
- } });
30424
- }
30425
- continue;
30426
- }
30427
- try {
30428
- const created = await reg.createEntry(entry.name, entry.connection);
30429
- if (created.id !== entry.id) {
30430
- const next = persisted.map((b) => b === entry ? {
30431
- ...b,
30432
- id: created.id
30433
- } : b);
30434
- await this.updateGlobalSettings({ brokers: next });
30435
- }
30436
- } catch (err) {
30437
- this.ctx.logger.warn("Dreo onConfigChanged: failed to start new broker", { meta: {
30438
- brokerName: entry.name,
30439
- error: errMsg(err)
30440
- } });
30441
- }
30442
- }
30334
+ this.ctx.logger.info("Dreo: provider initialised", { meta: { integrationCount: this.requireClients().size() } });
30335
+ return [...regs, {
30336
+ capability: deviceAdoptionCapability,
30337
+ provider: this.buildAdoptionProvider()
30338
+ }];
30443
30339
  }
30444
30340
  async onShutdown() {
30445
30341
  try {
30446
- await this.registry?.shutdown();
30342
+ await this.clients?.shutdown();
30447
30343
  } catch (err) {
30448
30344
  this.ctx.logger.warn("Dreo: provider shutdown error", { meta: { error: errMsg(err) } });
30449
30345
  }
30450
- this.registry = null;
30346
+ this.clients = null;
30451
30347
  await super.onShutdown();
30452
30348
  }
30453
- /** Stable id, broker-scoped so two accounts exposing the same device id don't
30454
- * collide (mirrors the Dreame broker-scoping). */
30349
+ requireClients() {
30350
+ if (!this.clients) throw new Error("Dreo provider not initialised");
30351
+ return this.clients;
30352
+ }
30353
+ /**
30354
+ * Stable id, integration-scoped so two accounts exposing the same cloud device
30355
+ * id don't collide. `brokerId` in the config is the integrationId (unchanged
30356
+ * field name).
30357
+ */
30455
30358
  generateStableId(_type, config) {
30456
30359
  return `dreo:${String(config?.["brokerId"] ?? "unknown")}:${String(config?.["dreoDeviceId"] ?? Date.now())}`;
30457
30360
  }
30458
- buildBrokerProvider() {
30459
- return buildDreoBrokerProvider({
30460
- ownerAddonId: this.ctx.id,
30461
- registry: this.requireRegistry(),
30462
- getBrokers: () => this.config.brokers,
30463
- persistBrokers: (brokers) => this.updateGlobalSettings({ brokers: [...brokers] }),
30464
- cascadeRemoveDevices: (brokerId) => this.cascadeRemoveDevicesForBroker(brokerId),
30465
- logger: this.ctx.logger
30466
- });
30467
- }
30468
- async cascadeRemoveDevicesForBroker(brokerId) {
30469
- const reg = this.ctx.kernel.deviceRegistry;
30470
- const devices = this.ctx.kernel.devices;
30471
- if (!reg || !devices) return;
30472
- await cascadeRemoveDevicesForBroker({
30473
- reg,
30474
- devices,
30475
- addonId: this.addonId,
30476
- brokerId,
30477
- logger: this.ctx.logger
30478
- });
30479
- }
30480
30361
  /**
30481
- * Link each surviving integration (carrying `{ brokerId }` in its settings) to
30482
- * its broker so the generic `device-adoption` cap resolves the account, and
30483
- * cascade-clean brokers (+ their adopted devices) whose spawning integration
30484
- * was deleted. Idempotent; runs on boot + on every integration lifecycle event.
30485
- * Guarded so a failure never fails init.
30362
+ * Rebuild the `Map<integrationId, manager>` from the live integration list:
30363
+ * for each surviving Dreo integration read its settings and upsert a manager
30364
+ * (session-preserving if credentials are unchanged); drop managers whose
30365
+ * integration was deleted/disabled cascade-removing their adopted devices.
30366
+ * Idempotent; runs on boot + on every integration lifecycle event. Guarded so
30367
+ * a failure never fails init.
30486
30368
  */
30487
- async reconcileIntegrationsToBrokers() {
30369
+ async reconcileIntegrations() {
30370
+ const reg = this.requireClients();
30488
30371
  try {
30489
- const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id);
30490
- const surviving = new Set(mine.map((i) => i.id));
30491
- for (const integration of mine) {
30492
- const settings = await this.ctx.api.integrations.getSettings.query({ id: integration.id });
30493
- const brokerId = typeof settings["brokerId"] === "string" ? settings["brokerId"] : void 0;
30494
- if (brokerId === void 0) continue;
30495
- if (!this.config.brokers.some((b) => b.id === brokerId)) {
30496
- this.ctx.logger.warn("Dreo integration→broker: linked broker not found", { meta: {
30497
- integrationId: integration.id,
30498
- brokerId
30499
- } });
30372
+ const mine = (await this.ctx.api.integrations.list.query()).filter((i) => i.addonId === this.ctx.id && i.enabled);
30373
+ const surviving = /* @__PURE__ */ new Set();
30374
+ for (const integration of mine) try {
30375
+ const connection = connectionFromSettings(await this.ctx.api.integrations.getSettings.query({ id: integration.id }));
30376
+ if (!connection) {
30377
+ this.ctx.logger.warn("Dreo integration has no complete credentials — skipping", { meta: { integrationId: integration.id } });
30500
30378
  continue;
30501
30379
  }
30502
- this.requireRegistry().linkIntegration(integration.id, brokerId);
30503
- }
30504
- const toRemove = this.config.brokers.filter((b) => b.integrationId !== void 0 && !surviving.has(b.integrationId)).map((b) => b.id);
30505
- if (toRemove.length === 0) return;
30506
- for (const id of toRemove) try {
30507
- await this.requireRegistry().removeEntry(id);
30508
- await this.cascadeRemoveDevicesForBroker(id);
30380
+ await reg.upsert(integration.id, integration.name, connection);
30381
+ surviving.add(integration.id);
30509
30382
  } catch (err) {
30510
- this.ctx.logger.warn("Dreo integration→broker: broker cleanup failed", { meta: {
30511
- brokerId: id,
30383
+ this.ctx.logger.warn("Dreo reconcile: failed to read integration settings", { meta: {
30384
+ integrationId: integration.id,
30512
30385
  error: errMsg(err)
30513
30386
  } });
30514
30387
  }
30515
- const nextBrokers = this.config.brokers.filter((b) => !toRemove.includes(b.id));
30516
- await this.updateGlobalSettings({ brokers: nextBrokers });
30388
+ const vanished = reg.list().filter((id) => !surviving.has(id));
30389
+ await reg.retain(surviving);
30390
+ for (const integrationId of vanished) await this.cascadeRemoveDevicesForIntegration(integrationId);
30517
30391
  } catch (err) {
30518
- this.ctx.logger.warn("Dreo integration→broker reconcile failed", { meta: { error: errMsg(err) } });
30392
+ this.ctx.logger.warn("Dreo integration reconcile failed", { meta: { error: errMsg(err) } });
30519
30393
  }
30520
30394
  }
30521
30395
  subscribeIntegrationLifecycle() {
30522
30396
  const handler = (event) => {
30523
30397
  const addonId = event.data["addonId"];
30524
30398
  if (typeof addonId === "string" && addonId !== this.ctx.id) return;
30525
- this.reconcileIntegrationsToBrokers();
30399
+ this.reconcileIntegrations();
30526
30400
  };
30527
30401
  this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationEnabled }, handler);
30528
30402
  this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDisabled }, handler);
30529
30403
  this.ctx.eventBus.subscribe({ category: EventCategory.IntegrationDeleted }, handler);
30530
30404
  }
30405
+ async cascadeRemoveDevicesForIntegration(integrationId) {
30406
+ const reg = this.ctx.kernel.deviceRegistry;
30407
+ const devices = this.ctx.kernel.devices;
30408
+ if (!reg || !devices) return;
30409
+ await cascadeRemoveDevicesForIntegration({
30410
+ reg,
30411
+ devices,
30412
+ addonId: this.addonId,
30413
+ integrationId,
30414
+ logger: this.ctx.logger
30415
+ });
30416
+ }
30531
30417
  buildAdoptionProvider() {
30532
30418
  return buildDreoAdoptionProvider({
30533
30419
  registry: dreoConnections,
30534
30420
  logger: this.ctx.logger,
30535
- getBrokerIdForIntegration: async (id) => {
30536
- const brokerId = (await this.ctx.api.integrations.getSettings.query({ id }))["brokerId"];
30537
- if (typeof brokerId !== "string") throw new Error(`integration ${id} has no linked brokerId`);
30538
- return brokerId;
30539
- },
30421
+ hasIntegration: (integrationId) => this.requireClients().has(integrationId),
30422
+ listIntegrations: () => this.requireClients().list(),
30540
30423
  listAdoptedDreo: async () => {
30541
30424
  const reg = this.ctx.kernel.deviceRegistry;
30542
30425
  const devices = this.ctx.kernel.devices;
@@ -30552,12 +30435,12 @@ var DreoProviderAddon = class extends BaseDeviceProvider {
30552
30435
  }
30553
30436
  return out;
30554
30437
  },
30555
- adoptDevice: async ({ dreoDeviceId, brokerId, integrationId, name, model }) => {
30438
+ adoptDevice: async ({ dreoDeviceId, integrationId, name, model }) => {
30556
30439
  const devices = this.ctx.kernel.devices;
30557
30440
  if (!devices) throw new Error("dreo adopt: kernel.devices unavailable");
30558
30441
  const config = {
30559
30442
  dreoDeviceId,
30560
- brokerId,
30443
+ brokerId: integrationId,
30561
30444
  model,
30562
30445
  system: "dreo",
30563
30446
  integrationId,
@@ -30586,18 +30469,7 @@ var DreoProviderAddon = class extends BaseDeviceProvider {
30586
30469
  });
30587
30470
  }
30588
30471
  globalSettingsSchema() {
30589
- return this.schema({ sections: [{
30590
- id: "dreo-broker",
30591
- title: "Dreo account",
30592
- description: "Dreo accounts are managed in the External systems → Brokers tab.",
30593
- columns: 1,
30594
- fields: [{
30595
- type: "info",
30596
- key: "brokerHelp",
30597
- label: "Accounts are managed separately",
30598
- content: "This integration links to a Dreo account broker. Add, edit, or remove accounts from External systems → Brokers. The integration only stores a reference to its broker — credentials live on the broker."
30599
- }]
30600
- }] });
30472
+ return buildConnectionFormSchema();
30601
30473
  }
30602
30474
  async supportsManualCreation() {
30603
30475
  return false;
@@ -30608,19 +30480,15 @@ var DreoProviderAddon = class extends BaseDeviceProvider {
30608
30480
  async onCreateDevice(_type, _config) {
30609
30481
  throw new Error("Dreo devices are adopted from the cloud, not created manually");
30610
30482
  }
30611
- setBrokerDevicesOnline(brokerId, online) {
30483
+ setIntegrationDevicesOnline(integrationId, online) {
30612
30484
  const reg = this.ctx.kernel.deviceRegistry;
30613
30485
  if (!reg) return;
30614
- const n = setBrokerDevicesOnline(reg.getAllForAddon(this.addonId), brokerId, online);
30615
- if (n > 0) this.ctx.logger.info("Dreo: broker devices " + (online ? "online" : "offline"), { meta: {
30616
- brokerId,
30486
+ const n = setIntegrationDevicesOnline(reg.getAllForAddon(this.addonId), integrationId, online);
30487
+ if (n > 0) this.ctx.logger.info("Dreo: integration devices " + (online ? "online" : "offline"), { meta: {
30488
+ integrationId,
30617
30489
  count: n
30618
30490
  } });
30619
30491
  }
30620
- requireRegistry() {
30621
- if (!this.registry) throw new Error("Dreo provider not initialised");
30622
- return this.registry;
30623
- }
30624
30492
  };
30625
30493
  //#endregion
30626
- export { DreoProviderAddon, classifyDreoModel as a, percentageStep as c, dreoAddonConfigSchema as d, dreoConfigSchema as f, clampPercent as i, percentageToLevel as l, DREO_MAX_FAN_LEVEL as n, deviceTypeForKind as o, toDreoConstructorArgs as p, capsForKind as r, levelToPercentage as s, buildDreoCandidates as t, buildConnectionFormSchema as u };
30494
+ export { DreoProviderAddon, classifyDreoModel as a, percentageStep as c, connectionFromSettings as d, dreoConfigSchema as f, clampPercent as i, percentageToLevel as l, DREO_MAX_FAN_LEVEL as n, deviceTypeForKind as o, toDreoConstructorArgs as p, capsForKind as r, levelToPercentage as s, buildDreoCandidates as t, buildConnectionFormSchema as u };