@camstack/addon-provider-ecowitt 0.1.4 → 0.1.6

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.
Files changed (3) hide show
  1. package/dist/addon.js +133 -39
  2. package/dist/addon.mjs +133 -39
  3. package/package.json +2 -2
package/dist/addon.js CHANGED
@@ -4647,7 +4647,7 @@ function preprocess(fn, schema) {
4647
4647
  });
4648
4648
  }
4649
4649
  //#endregion
4650
- //#region ../types/dist/sleep-NOH4yRwj.mjs
4650
+ //#region ../types/dist/sleep-DVmKHFGi.mjs
4651
4651
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4652
4652
  EventCategory["SystemBoot"] = "system.boot";
4653
4653
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -6447,6 +6447,17 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
6447
6447
  DeviceRole["HumiditySensor"] = "humidity-sensor";
6448
6448
  DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
6449
6449
  DeviceRole["PressureSensor"] = "pressure-sensor";
6450
+ /** Wind speed or direction (weather-station `wind-sensor` cap). */
6451
+ DeviceRole["WindSensor"] = "wind-sensor";
6452
+ /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
6453
+ DeviceRole["RainSensor"] = "rain-sensor";
6454
+ /** UV index (weather-station `uv-sensor` cap). */
6455
+ DeviceRole["UvSensor"] = "uv-sensor";
6456
+ /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
6457
+ * Distinct from AmbientLightSensor (lux). */
6458
+ DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
6459
+ /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
6460
+ DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
6450
6461
  DeviceRole["PowerSensor"] = "power-sensor";
6451
6462
  DeviceRole["EnergySensor"] = "energy-sensor";
6452
6463
  DeviceRole["VoltageSensor"] = "voltage-sensor";
@@ -10071,15 +10082,18 @@ var humiditySensorCapability = {
10071
10082
  runtimeState: HumiditySensorStatusSchema
10072
10083
  };
10073
10084
  /**
10074
- * Image display cap. Models HA `image.*` entities a single still image
10075
- * exposed by an integration (a snapshot, a chart, a generated picture).
10085
+ * Image display cap. Models a single still image exposed by an integration
10086
+ * a snapshot, a chart, a generated picture, or a robot's cleaning-map render.
10076
10087
  *
10077
- * Read-only: there are no setters. The provider resolves the HA
10078
- * `entity_picture` (a relative, signed-token path) into an ABSOLUTE URL
10079
- * the browser loads directly the token stays in the query string so no
10080
- * auth header is required. The slice carries that URL plus the upstream
10081
- * last-updated timestamp; the image changes when the entity state (a
10082
- * timestamp) changes.
10088
+ * Read-only: there are no setters. The provider resolves whatever upstream
10089
+ * source it has into an ABSOLUTE URL the browser loads directly:
10090
+ * - HA `image.*` entities the `entity_picture` signed-token path
10091
+ * (token stays in the query string, so no auth header is needed);
10092
+ * - a Dreame/robot map the cloud/OSS map-image URL (or an addon
10093
+ * data-plane URL serving the rendered map bytes), exposed as its own
10094
+ * Image child device grouped under the robot's container.
10095
+ * The slice carries that URL plus the upstream last-updated timestamp; the
10096
+ * image changes when the source's last-updated marker changes.
10083
10097
  */
10084
10098
  var ImageStatusSchema = object({
10085
10099
  /** Absolute signed URL the browser loads directly. Null when the
@@ -10105,18 +10119,47 @@ var imageCapability = {
10105
10119
  */
10106
10120
  runtimeState: ImageStatusSchema
10107
10121
  };
10122
+ /**
10123
+ * Robotic lawn-mower cap. Models HA `lawn_mower.*` entities — anything
10124
+ * with a mowing lifecycle plus a dock action.
10125
+ *
10126
+ * Activity follows HA's canonical lawn-mower lifecycle: `idle` /
10127
+ * `mowing` / `paused` / `docked` / `error`. `batteryLevel` (0..100) is
10128
+ * nullable — some mowers don't report a battery percentage.
10129
+ *
10130
+ * `startMowing` begins a mowing run, `pause` halts it in place, and
10131
+ * `dock` sends the mower back to its charging station.
10132
+ */
10133
+ var LawnMowerActivitySchema = _enum([
10134
+ "idle",
10135
+ "mowing",
10136
+ "paused",
10137
+ "docked",
10138
+ "error"
10139
+ ]);
10140
+ /** Severity of the current device/error code — info (status), warning, error. */
10141
+ var DeviceCodeSeveritySchema = _enum([
10142
+ "info",
10143
+ "warning",
10144
+ "error"
10145
+ ]);
10108
10146
  var LawnMowerControlStatusSchema = object({
10109
10147
  /** Lifecycle activity of the mower. */
10110
- activity: _enum([
10111
- "idle",
10112
- "mowing",
10113
- "paused",
10114
- "docked",
10115
- "error"
10116
- ]),
10148
+ activity: LawnMowerActivitySchema,
10117
10149
  /** 0..100 battery percentage. Null when the device has no battery
10118
10150
  * reading. */
10119
10151
  batteryLevel: number().min(0).max(100).nullable(),
10152
+ /** 0..100 mowing-completion percentage of the current task, or null when no
10153
+ * task is active / progress is unavailable. */
10154
+ progressPercent: number().min(0).max(100).nullable(),
10155
+ /** Current device/event code (dynamic — mostly status, sometimes an error),
10156
+ * or null when unknown. */
10157
+ currentCode: number().nullable(),
10158
+ /** Human label for {@link currentCode}, or null when undecodable. */
10159
+ currentCodeLabel: string().nullable(),
10160
+ /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
10161
+ * attention; `info` is normal status. */
10162
+ severity: DeviceCodeSeveritySchema,
10120
10163
  /** Ms epoch when the slice was last updated. */
10121
10164
  lastChangedAt: number()
10122
10165
  });
@@ -12342,6 +12385,7 @@ var VacuumStateSchema = _enum([
12342
12385
  "paused",
12343
12386
  "returning",
12344
12387
  "docked",
12388
+ "drying",
12345
12389
  "error"
12346
12390
  ]);
12347
12391
  /**
@@ -12380,6 +12424,12 @@ var VacuumControlStatusSchema = object({
12380
12424
  detergent: TankStatusSchema.nullable(),
12381
12425
  /** Dust bin. Null when the hardware has no dust bin. */
12382
12426
  dustBin: TankStatusSchema.nullable(),
12427
+ /** 0..100 cleaning-completion percentage of the current task, or null. */
12428
+ progressPercent: number().min(0).max(100).nullable(),
12429
+ /** Current error code (0 / null = no error). */
12430
+ errorCode: number().nullable(),
12431
+ /** Human label for {@link errorCode}, or null when none / undecodable. */
12432
+ errorLabel: string().nullable(),
12383
12433
  /** Ms epoch when the slice was last updated. */
12384
12434
  lastChangedAt: number()
12385
12435
  });
@@ -16895,6 +16945,9 @@ method(object({
16895
16945
  }), method(ReleaseInputSchema.extend({ addonId: string() }), _void(), {
16896
16946
  kind: "mutation",
16897
16947
  auth: "admin"
16948
+ }), method(ResyncInputSchema, ResyncResultSchema, {
16949
+ kind: "mutation",
16950
+ auth: "admin"
16898
16951
  }), method(object({
16899
16952
  deviceId: number(),
16900
16953
  key: string(),
@@ -20657,6 +20710,12 @@ Object.freeze({
20657
20710
  addonId: null,
20658
20711
  access: "create"
20659
20712
  },
20713
+ "deviceManager.adoptionResync": {
20714
+ capName: "device-manager",
20715
+ capScope: "system",
20716
+ addonId: null,
20717
+ access: "create"
20718
+ },
20660
20719
  "deviceManager.allocateDeviceId": {
20661
20720
  capName: "device-manager",
20662
20721
  capScope: "system",
@@ -44565,7 +44624,8 @@ function classifyKey(key) {
44565
44624
  const hex = lookupHexId(key);
44566
44625
  if (hex !== void 0) return {
44567
44626
  kind: "measurement",
44568
- quantity: hex.quantity
44627
+ quantity: hex.quantity,
44628
+ name: hex.name
44569
44629
  };
44570
44630
  const named = Object.prototype.hasOwnProperty.call(NAMED_QUANTITY, key) ? NAMED_QUANTITY[key] : void 0;
44571
44631
  if (named !== void 0) return {
@@ -44601,6 +44661,7 @@ var Station = class {
44601
44661
  ...owner !== void 0 ? { hardwareId: owner } : {},
44602
44662
  ...info?.model ? { model: info.model } : {},
44603
44663
  ...info?.channel !== void 0 ? { channel: info.channel } : {},
44664
+ ...cls.name !== void 0 ? { name: cls.name } : {},
44604
44665
  quantity: cls.quantity,
44605
44666
  value: r.value,
44606
44667
  unit: r.unit,
@@ -44634,6 +44695,7 @@ var Station = class {
44634
44695
  this.upsert(id, {
44635
44696
  id,
44636
44697
  ...r.channel !== void 0 ? { channel: r.channel } : {},
44698
+ ...cls.name !== void 0 ? { name: cls.name } : {},
44637
44699
  quantity: cls.quantity,
44638
44700
  value: r.value,
44639
44701
  unit: r.unit,
@@ -45597,27 +45659,46 @@ function buildEcowittBrokerProvider(deps) {
45597
45659
  //#endregion
45598
45660
  //#region src/ecowitt-mapping.ts
45599
45661
  /**
45600
- * Map an Ecowitt quantity to its camstack typed sensor cap. Pure, total.
45662
+ * Map an Ecowitt quantity to its camstack sensor cap. Pure, total.
45601
45663
  *
45602
- * Accepts a plain `string` (not just the {@link Quantity} union) so a persisted
45603
- * config value flows through without a cast any unrecognised string falls to
45604
- * `numeric-sensor`, which is the same safe default the union's tail uses.
45664
+ * Only temperature/humidity/pressure get a dedicated typed cap (the thermostat
45665
+ * surface consumes them); every other quantity rides `numeric-sensor` and is
45666
+ * typed by its {@link roleForQuantity} role instead. Accepts a plain `string`
45667
+ * so a persisted config value flows through without a cast — any unrecognised
45668
+ * string falls to `numeric-sensor`, the same safe default the tail uses.
45605
45669
  */
45606
45670
  function capForQuantity(quantity) {
45607
45671
  switch (quantity) {
45608
45672
  case "temperature": return "temperature-sensor";
45609
45673
  case "humidity": return "humidity-sensor";
45610
45674
  case "pressure": return "pressure-sensor";
45611
- case "irradiance": return "ambient-light-sensor";
45612
- case "uv":
45675
+ default: return "numeric-sensor";
45676
+ }
45677
+ }
45678
+ /**
45679
+ * Map an Ecowitt quantity to the semantic {@link DeviceRole} that drives the
45680
+ * child's icon/label (and, in future, its canonical unit). Pure, total — an
45681
+ * unrecognised quantity falls to {@link DeviceRole.NumericSensor}.
45682
+ *
45683
+ * This is where weather metrics get their typing: wind/rain/uv/irradiance ride
45684
+ * the generic `numeric-sensor` cap but carry a typed role, so the UI renders a
45685
+ * wind / rain / UV / solar glyph instead of a bare hash.
45686
+ */
45687
+ function roleForQuantity(quantity) {
45688
+ switch (quantity) {
45689
+ case "temperature": return DeviceRole.TemperatureSensor;
45690
+ case "humidity": return DeviceRole.HumiditySensor;
45691
+ case "pressure": return DeviceRole.PressureSensor;
45613
45692
  case "wind_speed":
45614
- case "wind_direction":
45693
+ case "wind_direction": return DeviceRole.WindSensor;
45615
45694
  case "precipitation":
45616
- case "precipitation_rate":
45695
+ case "precipitation_rate": return DeviceRole.RainSensor;
45696
+ case "uv": return DeviceRole.UvSensor;
45697
+ case "irradiance": return DeviceRole.SolarRadiationSensor;
45617
45698
  case "lightning_distance":
45618
45699
  case "lightning_count":
45619
- case "vpd": return "numeric-sensor";
45620
- default: return "numeric-sensor";
45700
+ case "vpd": return DeviceRole.NumericSensor;
45701
+ default: return DeviceRole.NumericSensor;
45621
45702
  }
45622
45703
  }
45623
45704
  /**
@@ -45640,15 +45721,18 @@ function valueFieldForCap(cap) {
45640
45721
  }
45641
45722
  }
45642
45723
  /**
45643
- * Build a human label for a sensor from its quantity + optional channel.
45644
- * `temperature` + channel 2 `Temperature CH2`; channel-less → `Temperature`.
45724
+ * Build a human label for a sensor. Prefers the SPECIFIC measurement `name` when
45725
+ * the library resolved one (e.g. "Outdoor Temperature", "Dewpoint Temperature",
45726
+ * "Wind Gust") so same-quantity sensors are distinguishable; otherwise falls back
45727
+ * to the quantity label. A channel suffix is appended when present
45728
+ * (`temperature` + channel 2 → `Temperature CH2`).
45645
45729
  *
45646
45730
  * Accepts a plain `string` (not just {@link Quantity}) so a persisted config
45647
45731
  * value flows through without a cast; an unknown quantity falls back to its raw
45648
45732
  * string label.
45649
45733
  */
45650
- function sensorLabel(quantity, channel) {
45651
- const base = isKnownQuantity(quantity) ? QUANTITY_LABEL[quantity] : quantity;
45734
+ function sensorLabel(quantity, channel, name) {
45735
+ const base = name !== void 0 && name.length > 0 ? name : isKnownQuantity(quantity) ? QUANTITY_LABEL[quantity] : quantity;
45652
45736
  return channel !== void 0 ? `${base} CH${channel}` : base;
45653
45737
  }
45654
45738
  /** Type guard: is `q` one of the known {@link Quantity} union members? */
@@ -45682,7 +45766,7 @@ var QUANTITY_LABEL = {
45682
45766
  function buildEcowittGatewayCandidate(input) {
45683
45767
  const children = input.sensors.map((sensor) => ({
45684
45768
  childNativeId: sensor.id,
45685
- name: sensorLabel(sensor.quantity, sensor.channel),
45769
+ name: sensorLabel(sensor.quantity, sensor.channel, sensor.name),
45686
45770
  type: DeviceType.Sensor,
45687
45771
  status: "online",
45688
45772
  metadata: sensor.model !== void 0 ? { model: sensor.model } : {},
@@ -46157,7 +46241,8 @@ function clampForCap(cap, value) {
46157
46241
  var ecowittSensorEntrySchema = object({
46158
46242
  sensorId: string(),
46159
46243
  quantity: string(),
46160
- channel: number().optional()
46244
+ channel: number().optional(),
46245
+ name: string().optional()
46161
46246
  });
46162
46247
  /**
46163
46248
  * Persisted config for an Ecowitt gateway Container. `brokerId` selects the
@@ -46199,7 +46284,8 @@ var EcowittContainerDevice = class extends BaseDevice {
46199
46284
  return this.config.values.sensors.map((sensor) => {
46200
46285
  const meta = {
46201
46286
  type: DeviceType.Sensor,
46202
- name: sensorLabel(sensor.quantity, sensor.channel),
46287
+ role: roleForQuantity(sensor.quantity),
46288
+ name: sensorLabel(sensor.quantity, sensor.channel, sensor.name),
46203
46289
  linkDeviceId: this.id,
46204
46290
  ...this.integrationId !== void 0 ? { integrationId: this.integrationId } : {}
46205
46291
  };
@@ -46354,7 +46440,8 @@ var EcowittProviderAddon = class extends BaseDeviceProvider {
46354
46440
  const nextSensors = sensors.map((s) => ({
46355
46441
  sensorId: s.id,
46356
46442
  quantity: s.quantity,
46357
- ...s.channel !== void 0 ? { channel: s.channel } : {}
46443
+ ...s.channel !== void 0 ? { channel: s.channel } : {},
46444
+ ...s.name !== void 0 ? { name: s.name } : {}
46358
46445
  }));
46359
46446
  const cfg = await devices.loadConfig(containerId).catch(() => ({}));
46360
46447
  if (sensorsEqual(Array.isArray(cfg["sensors"]) ? cfg["sensors"] : [], nextSensors)) return;
@@ -46475,7 +46562,8 @@ var EcowittProviderAddon = class extends BaseDeviceProvider {
46475
46562
  sensors: ecowittFacades.getSensors(brokerId).map((s) => ({
46476
46563
  sensorId: s.id,
46477
46564
  quantity: s.quantity,
46478
- ...s.channel !== void 0 ? { channel: s.channel } : {}
46565
+ ...s.channel !== void 0 ? { channel: s.channel } : {},
46566
+ ...s.name !== void 0 ? { name: s.name } : {}
46479
46567
  })),
46480
46568
  system: "ecowitt",
46481
46569
  integrationId,
@@ -46543,12 +46631,18 @@ var EcowittProviderAddon = class extends BaseDeviceProvider {
46543
46631
  /** Structural equality of two persisted sensor lists (order-independent by id). */
46544
46632
  function sensorsEqual(a, b) {
46545
46633
  if (a.length !== b.length) return false;
46546
- const aIds = /* @__PURE__ */ new Set();
46634
+ const aById = /* @__PURE__ */ new Map();
46547
46635
  for (const entry of a) if (typeof entry === "object" && entry !== null && "sensorId" in entry) {
46548
46636
  const id = entry.sensorId;
46549
- if (typeof id === "string") aIds.add(id);
46637
+ if (typeof id === "string") {
46638
+ const name = "name" in entry && typeof entry.name === "string" ? entry.name : void 0;
46639
+ aById.set(id, name);
46640
+ }
46641
+ }
46642
+ for (const entry of b) {
46643
+ if (!aById.has(entry.sensorId)) return false;
46644
+ if (aById.get(entry.sensorId) !== entry.name) return false;
46550
46645
  }
46551
- for (const entry of b) if (!aIds.has(entry.sensorId)) return false;
46552
46646
  return true;
46553
46647
  }
46554
46648
  //#endregion
package/dist/addon.mjs CHANGED
@@ -4648,7 +4648,7 @@ function preprocess(fn, schema) {
4648
4648
  });
4649
4649
  }
4650
4650
  //#endregion
4651
- //#region ../types/dist/sleep-NOH4yRwj.mjs
4651
+ //#region ../types/dist/sleep-DVmKHFGi.mjs
4652
4652
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4653
4653
  EventCategory["SystemBoot"] = "system.boot";
4654
4654
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -6448,6 +6448,17 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
6448
6448
  DeviceRole["HumiditySensor"] = "humidity-sensor";
6449
6449
  DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
6450
6450
  DeviceRole["PressureSensor"] = "pressure-sensor";
6451
+ /** Wind speed or direction (weather-station `wind-sensor` cap). */
6452
+ DeviceRole["WindSensor"] = "wind-sensor";
6453
+ /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
6454
+ DeviceRole["RainSensor"] = "rain-sensor";
6455
+ /** UV index (weather-station `uv-sensor` cap). */
6456
+ DeviceRole["UvSensor"] = "uv-sensor";
6457
+ /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
6458
+ * Distinct from AmbientLightSensor (lux). */
6459
+ DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
6460
+ /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
6461
+ DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
6451
6462
  DeviceRole["PowerSensor"] = "power-sensor";
6452
6463
  DeviceRole["EnergySensor"] = "energy-sensor";
6453
6464
  DeviceRole["VoltageSensor"] = "voltage-sensor";
@@ -10072,15 +10083,18 @@ var humiditySensorCapability = {
10072
10083
  runtimeState: HumiditySensorStatusSchema
10073
10084
  };
10074
10085
  /**
10075
- * Image display cap. Models HA `image.*` entities a single still image
10076
- * exposed by an integration (a snapshot, a chart, a generated picture).
10086
+ * Image display cap. Models a single still image exposed by an integration
10087
+ * a snapshot, a chart, a generated picture, or a robot's cleaning-map render.
10077
10088
  *
10078
- * Read-only: there are no setters. The provider resolves the HA
10079
- * `entity_picture` (a relative, signed-token path) into an ABSOLUTE URL
10080
- * the browser loads directly the token stays in the query string so no
10081
- * auth header is required. The slice carries that URL plus the upstream
10082
- * last-updated timestamp; the image changes when the entity state (a
10083
- * timestamp) changes.
10089
+ * Read-only: there are no setters. The provider resolves whatever upstream
10090
+ * source it has into an ABSOLUTE URL the browser loads directly:
10091
+ * - HA `image.*` entities the `entity_picture` signed-token path
10092
+ * (token stays in the query string, so no auth header is needed);
10093
+ * - a Dreame/robot map the cloud/OSS map-image URL (or an addon
10094
+ * data-plane URL serving the rendered map bytes), exposed as its own
10095
+ * Image child device grouped under the robot's container.
10096
+ * The slice carries that URL plus the upstream last-updated timestamp; the
10097
+ * image changes when the source's last-updated marker changes.
10084
10098
  */
10085
10099
  var ImageStatusSchema = object({
10086
10100
  /** Absolute signed URL the browser loads directly. Null when the
@@ -10106,18 +10120,47 @@ var imageCapability = {
10106
10120
  */
10107
10121
  runtimeState: ImageStatusSchema
10108
10122
  };
10123
+ /**
10124
+ * Robotic lawn-mower cap. Models HA `lawn_mower.*` entities — anything
10125
+ * with a mowing lifecycle plus a dock action.
10126
+ *
10127
+ * Activity follows HA's canonical lawn-mower lifecycle: `idle` /
10128
+ * `mowing` / `paused` / `docked` / `error`. `batteryLevel` (0..100) is
10129
+ * nullable — some mowers don't report a battery percentage.
10130
+ *
10131
+ * `startMowing` begins a mowing run, `pause` halts it in place, and
10132
+ * `dock` sends the mower back to its charging station.
10133
+ */
10134
+ var LawnMowerActivitySchema = _enum([
10135
+ "idle",
10136
+ "mowing",
10137
+ "paused",
10138
+ "docked",
10139
+ "error"
10140
+ ]);
10141
+ /** Severity of the current device/error code — info (status), warning, error. */
10142
+ var DeviceCodeSeveritySchema = _enum([
10143
+ "info",
10144
+ "warning",
10145
+ "error"
10146
+ ]);
10109
10147
  var LawnMowerControlStatusSchema = object({
10110
10148
  /** Lifecycle activity of the mower. */
10111
- activity: _enum([
10112
- "idle",
10113
- "mowing",
10114
- "paused",
10115
- "docked",
10116
- "error"
10117
- ]),
10149
+ activity: LawnMowerActivitySchema,
10118
10150
  /** 0..100 battery percentage. Null when the device has no battery
10119
10151
  * reading. */
10120
10152
  batteryLevel: number().min(0).max(100).nullable(),
10153
+ /** 0..100 mowing-completion percentage of the current task, or null when no
10154
+ * task is active / progress is unavailable. */
10155
+ progressPercent: number().min(0).max(100).nullable(),
10156
+ /** Current device/event code (dynamic — mostly status, sometimes an error),
10157
+ * or null when unknown. */
10158
+ currentCode: number().nullable(),
10159
+ /** Human label for {@link currentCode}, or null when undecodable. */
10160
+ currentCodeLabel: string().nullable(),
10161
+ /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
10162
+ * attention; `info` is normal status. */
10163
+ severity: DeviceCodeSeveritySchema,
10121
10164
  /** Ms epoch when the slice was last updated. */
10122
10165
  lastChangedAt: number()
10123
10166
  });
@@ -12343,6 +12386,7 @@ var VacuumStateSchema = _enum([
12343
12386
  "paused",
12344
12387
  "returning",
12345
12388
  "docked",
12389
+ "drying",
12346
12390
  "error"
12347
12391
  ]);
12348
12392
  /**
@@ -12381,6 +12425,12 @@ var VacuumControlStatusSchema = object({
12381
12425
  detergent: TankStatusSchema.nullable(),
12382
12426
  /** Dust bin. Null when the hardware has no dust bin. */
12383
12427
  dustBin: TankStatusSchema.nullable(),
12428
+ /** 0..100 cleaning-completion percentage of the current task, or null. */
12429
+ progressPercent: number().min(0).max(100).nullable(),
12430
+ /** Current error code (0 / null = no error). */
12431
+ errorCode: number().nullable(),
12432
+ /** Human label for {@link errorCode}, or null when none / undecodable. */
12433
+ errorLabel: string().nullable(),
12384
12434
  /** Ms epoch when the slice was last updated. */
12385
12435
  lastChangedAt: number()
12386
12436
  });
@@ -16896,6 +16946,9 @@ method(object({
16896
16946
  }), method(ReleaseInputSchema.extend({ addonId: string() }), _void(), {
16897
16947
  kind: "mutation",
16898
16948
  auth: "admin"
16949
+ }), method(ResyncInputSchema, ResyncResultSchema, {
16950
+ kind: "mutation",
16951
+ auth: "admin"
16899
16952
  }), method(object({
16900
16953
  deviceId: number(),
16901
16954
  key: string(),
@@ -20658,6 +20711,12 @@ Object.freeze({
20658
20711
  addonId: null,
20659
20712
  access: "create"
20660
20713
  },
20714
+ "deviceManager.adoptionResync": {
20715
+ capName: "device-manager",
20716
+ capScope: "system",
20717
+ addonId: null,
20718
+ access: "create"
20719
+ },
20661
20720
  "deviceManager.allocateDeviceId": {
20662
20721
  capName: "device-manager",
20663
20722
  capScope: "system",
@@ -44566,7 +44625,8 @@ function classifyKey(key) {
44566
44625
  const hex = lookupHexId(key);
44567
44626
  if (hex !== void 0) return {
44568
44627
  kind: "measurement",
44569
- quantity: hex.quantity
44628
+ quantity: hex.quantity,
44629
+ name: hex.name
44570
44630
  };
44571
44631
  const named = Object.prototype.hasOwnProperty.call(NAMED_QUANTITY, key) ? NAMED_QUANTITY[key] : void 0;
44572
44632
  if (named !== void 0) return {
@@ -44602,6 +44662,7 @@ var Station = class {
44602
44662
  ...owner !== void 0 ? { hardwareId: owner } : {},
44603
44663
  ...info?.model ? { model: info.model } : {},
44604
44664
  ...info?.channel !== void 0 ? { channel: info.channel } : {},
44665
+ ...cls.name !== void 0 ? { name: cls.name } : {},
44605
44666
  quantity: cls.quantity,
44606
44667
  value: r.value,
44607
44668
  unit: r.unit,
@@ -44635,6 +44696,7 @@ var Station = class {
44635
44696
  this.upsert(id, {
44636
44697
  id,
44637
44698
  ...r.channel !== void 0 ? { channel: r.channel } : {},
44699
+ ...cls.name !== void 0 ? { name: cls.name } : {},
44638
44700
  quantity: cls.quantity,
44639
44701
  value: r.value,
44640
44702
  unit: r.unit,
@@ -45598,27 +45660,46 @@ function buildEcowittBrokerProvider(deps) {
45598
45660
  //#endregion
45599
45661
  //#region src/ecowitt-mapping.ts
45600
45662
  /**
45601
- * Map an Ecowitt quantity to its camstack typed sensor cap. Pure, total.
45663
+ * Map an Ecowitt quantity to its camstack sensor cap. Pure, total.
45602
45664
  *
45603
- * Accepts a plain `string` (not just the {@link Quantity} union) so a persisted
45604
- * config value flows through without a cast any unrecognised string falls to
45605
- * `numeric-sensor`, which is the same safe default the union's tail uses.
45665
+ * Only temperature/humidity/pressure get a dedicated typed cap (the thermostat
45666
+ * surface consumes them); every other quantity rides `numeric-sensor` and is
45667
+ * typed by its {@link roleForQuantity} role instead. Accepts a plain `string`
45668
+ * so a persisted config value flows through without a cast — any unrecognised
45669
+ * string falls to `numeric-sensor`, the same safe default the tail uses.
45606
45670
  */
45607
45671
  function capForQuantity(quantity) {
45608
45672
  switch (quantity) {
45609
45673
  case "temperature": return "temperature-sensor";
45610
45674
  case "humidity": return "humidity-sensor";
45611
45675
  case "pressure": return "pressure-sensor";
45612
- case "irradiance": return "ambient-light-sensor";
45613
- case "uv":
45676
+ default: return "numeric-sensor";
45677
+ }
45678
+ }
45679
+ /**
45680
+ * Map an Ecowitt quantity to the semantic {@link DeviceRole} that drives the
45681
+ * child's icon/label (and, in future, its canonical unit). Pure, total — an
45682
+ * unrecognised quantity falls to {@link DeviceRole.NumericSensor}.
45683
+ *
45684
+ * This is where weather metrics get their typing: wind/rain/uv/irradiance ride
45685
+ * the generic `numeric-sensor` cap but carry a typed role, so the UI renders a
45686
+ * wind / rain / UV / solar glyph instead of a bare hash.
45687
+ */
45688
+ function roleForQuantity(quantity) {
45689
+ switch (quantity) {
45690
+ case "temperature": return DeviceRole.TemperatureSensor;
45691
+ case "humidity": return DeviceRole.HumiditySensor;
45692
+ case "pressure": return DeviceRole.PressureSensor;
45614
45693
  case "wind_speed":
45615
- case "wind_direction":
45694
+ case "wind_direction": return DeviceRole.WindSensor;
45616
45695
  case "precipitation":
45617
- case "precipitation_rate":
45696
+ case "precipitation_rate": return DeviceRole.RainSensor;
45697
+ case "uv": return DeviceRole.UvSensor;
45698
+ case "irradiance": return DeviceRole.SolarRadiationSensor;
45618
45699
  case "lightning_distance":
45619
45700
  case "lightning_count":
45620
- case "vpd": return "numeric-sensor";
45621
- default: return "numeric-sensor";
45701
+ case "vpd": return DeviceRole.NumericSensor;
45702
+ default: return DeviceRole.NumericSensor;
45622
45703
  }
45623
45704
  }
45624
45705
  /**
@@ -45641,15 +45722,18 @@ function valueFieldForCap(cap) {
45641
45722
  }
45642
45723
  }
45643
45724
  /**
45644
- * Build a human label for a sensor from its quantity + optional channel.
45645
- * `temperature` + channel 2 `Temperature CH2`; channel-less → `Temperature`.
45725
+ * Build a human label for a sensor. Prefers the SPECIFIC measurement `name` when
45726
+ * the library resolved one (e.g. "Outdoor Temperature", "Dewpoint Temperature",
45727
+ * "Wind Gust") so same-quantity sensors are distinguishable; otherwise falls back
45728
+ * to the quantity label. A channel suffix is appended when present
45729
+ * (`temperature` + channel 2 → `Temperature CH2`).
45646
45730
  *
45647
45731
  * Accepts a plain `string` (not just {@link Quantity}) so a persisted config
45648
45732
  * value flows through without a cast; an unknown quantity falls back to its raw
45649
45733
  * string label.
45650
45734
  */
45651
- function sensorLabel(quantity, channel) {
45652
- const base = isKnownQuantity(quantity) ? QUANTITY_LABEL[quantity] : quantity;
45735
+ function sensorLabel(quantity, channel, name) {
45736
+ const base = name !== void 0 && name.length > 0 ? name : isKnownQuantity(quantity) ? QUANTITY_LABEL[quantity] : quantity;
45653
45737
  return channel !== void 0 ? `${base} CH${channel}` : base;
45654
45738
  }
45655
45739
  /** Type guard: is `q` one of the known {@link Quantity} union members? */
@@ -45683,7 +45767,7 @@ var QUANTITY_LABEL = {
45683
45767
  function buildEcowittGatewayCandidate(input) {
45684
45768
  const children = input.sensors.map((sensor) => ({
45685
45769
  childNativeId: sensor.id,
45686
- name: sensorLabel(sensor.quantity, sensor.channel),
45770
+ name: sensorLabel(sensor.quantity, sensor.channel, sensor.name),
45687
45771
  type: DeviceType.Sensor,
45688
45772
  status: "online",
45689
45773
  metadata: sensor.model !== void 0 ? { model: sensor.model } : {},
@@ -46158,7 +46242,8 @@ function clampForCap(cap, value) {
46158
46242
  var ecowittSensorEntrySchema = object({
46159
46243
  sensorId: string(),
46160
46244
  quantity: string(),
46161
- channel: number().optional()
46245
+ channel: number().optional(),
46246
+ name: string().optional()
46162
46247
  });
46163
46248
  /**
46164
46249
  * Persisted config for an Ecowitt gateway Container. `brokerId` selects the
@@ -46200,7 +46285,8 @@ var EcowittContainerDevice = class extends BaseDevice {
46200
46285
  return this.config.values.sensors.map((sensor) => {
46201
46286
  const meta = {
46202
46287
  type: DeviceType.Sensor,
46203
- name: sensorLabel(sensor.quantity, sensor.channel),
46288
+ role: roleForQuantity(sensor.quantity),
46289
+ name: sensorLabel(sensor.quantity, sensor.channel, sensor.name),
46204
46290
  linkDeviceId: this.id,
46205
46291
  ...this.integrationId !== void 0 ? { integrationId: this.integrationId } : {}
46206
46292
  };
@@ -46355,7 +46441,8 @@ var EcowittProviderAddon = class extends BaseDeviceProvider {
46355
46441
  const nextSensors = sensors.map((s) => ({
46356
46442
  sensorId: s.id,
46357
46443
  quantity: s.quantity,
46358
- ...s.channel !== void 0 ? { channel: s.channel } : {}
46444
+ ...s.channel !== void 0 ? { channel: s.channel } : {},
46445
+ ...s.name !== void 0 ? { name: s.name } : {}
46359
46446
  }));
46360
46447
  const cfg = await devices.loadConfig(containerId).catch(() => ({}));
46361
46448
  if (sensorsEqual(Array.isArray(cfg["sensors"]) ? cfg["sensors"] : [], nextSensors)) return;
@@ -46476,7 +46563,8 @@ var EcowittProviderAddon = class extends BaseDeviceProvider {
46476
46563
  sensors: ecowittFacades.getSensors(brokerId).map((s) => ({
46477
46564
  sensorId: s.id,
46478
46565
  quantity: s.quantity,
46479
- ...s.channel !== void 0 ? { channel: s.channel } : {}
46566
+ ...s.channel !== void 0 ? { channel: s.channel } : {},
46567
+ ...s.name !== void 0 ? { name: s.name } : {}
46480
46568
  })),
46481
46569
  system: "ecowitt",
46482
46570
  integrationId,
@@ -46544,12 +46632,18 @@ var EcowittProviderAddon = class extends BaseDeviceProvider {
46544
46632
  /** Structural equality of two persisted sensor lists (order-independent by id). */
46545
46633
  function sensorsEqual(a, b) {
46546
46634
  if (a.length !== b.length) return false;
46547
- const aIds = /* @__PURE__ */ new Set();
46635
+ const aById = /* @__PURE__ */ new Map();
46548
46636
  for (const entry of a) if (typeof entry === "object" && entry !== null && "sensorId" in entry) {
46549
46637
  const id = entry.sensorId;
46550
- if (typeof id === "string") aIds.add(id);
46638
+ if (typeof id === "string") {
46639
+ const name = "name" in entry && typeof entry.name === "string" ? entry.name : void 0;
46640
+ aById.set(id, name);
46641
+ }
46642
+ }
46643
+ for (const entry of b) {
46644
+ if (!aById.has(entry.sensorId)) return false;
46645
+ if (aById.get(entry.sensorId) !== entry.name) return false;
46551
46646
  }
46552
- for (const entry of b) if (!aIds.has(entry.sensorId)) return false;
46553
46647
  return true;
46554
46648
  }
46555
46649
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-ecowitt",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Ecowitt weather-station device-provider addon for CamStack — wraps the @apocaliss92/nodewitt local-poll / push client",
5
5
  "keywords": [
6
6
  "camstack",
@@ -71,7 +71,7 @@
71
71
  "publish": "npm publish --access public"
72
72
  },
73
73
  "devDependencies": {
74
- "@apocaliss92/nodewitt": "^1.1.1",
74
+ "@apocaliss92/nodewitt": "^1.2.0",
75
75
  "@camstack/types": "*",
76
76
  "typescript": "~6.0.3",
77
77
  "vite": "^8.0.11",