@camstack/addon-provider-ecowitt 0.1.6 → 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.
Files changed (3) hide show
  1. package/dist/addon.js +126 -22
  2. package/dist/addon.mjs +126 -22
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -13396,9 +13396,29 @@ var BaseDevice = class {
13396
13396
  * is open (Reolink writes `hasPtz/hasIntercom`, Hikvision writes
13397
13397
  * `hasSupplementalLight/hasAlarmIo`, etc).
13398
13398
  *
13399
- * Default: no-op (driver had no probe to run).
13400
- */
13401
- async onProbe() {}
13399
+ * Default: nothing to probe mark the device PROBED (set `lastProbedAt`) so
13400
+ * the kernel treats it as ready immediately. A device that derives its shape
13401
+ * from a spec (a container, or an accessory sensor) rather than from a
13402
+ * hardware probe has no probe to "complete"; without stamping `lastProbedAt`
13403
+ * it would look perpetually un-probed — logging "Initial probe did not
13404
+ * complete" on every boot and spinning a pointless retry chain. Drivers that
13405
+ * DO probe override this and write their own `feature-probe` slice (including
13406
+ * `lastProbedAt`) once their probe actually succeeds.
13407
+ */
13408
+ async onProbe() {
13409
+ const base = this.runtimeState.getCapState("feature-probe") ?? {
13410
+ flags: {},
13411
+ deviceType: null,
13412
+ model: null,
13413
+ channelCount: null,
13414
+ lastProbedAt: 0,
13415
+ lastFetchedAt: 0
13416
+ };
13417
+ this.runtimeState.setCapState("feature-probe", {
13418
+ ...base,
13419
+ lastProbedAt: Date.now()
13420
+ });
13421
+ }
13402
13422
  /**
13403
13423
  * Phase 5 — fired after the device + its accessories are registered.
13404
13424
  * Drivers publish streams to the broker, kick off background tasks,
@@ -14860,17 +14880,32 @@ var ReleaseInputSchema = object({
14860
14880
  * the parent cascades into every accessory. */
14861
14881
  camDeviceId: number().int().nonnegative()
14862
14882
  });
14863
- var ResyncInputSchema = object({
14864
- /** Parent CamStack device id of an adopted device. The provider resolves its
14865
- * source (integration/broker + native id) and re-aligns the device's
14866
- * structural spec (type/role/capabilities/units) with the live mapping,
14867
- * rebuilding any child whose class changed while preserving operator edits. */
14868
- camDeviceId: number().int().nonnegative() });
14883
+ var ResyncInputSchema = object({
14884
+ /** Parent CamStack device id of an adopted device. The provider resolves its
14885
+ * source (integration/broker + native id) and re-aligns the device's
14886
+ * structural spec (type/role/capabilities/units) with the live mapping,
14887
+ * rebuilding any child whose class changed while preserving operator edits. */
14888
+ camDeviceId: number().int().nonnegative(),
14889
+ /** "Resync from zero" (#19). When true, the kernel PURGES every accessory
14890
+ * child of `camDeviceId` BEFORE the provider re-derives the device, so the
14891
+ * children are rebuilt fresh from source — correct names, coords, and units —
14892
+ * instead of being preserved by the incremental reconcile. Use to recover from
14893
+ * legacy generic/placeholder names that the normal name-precedence keeps frozen
14894
+ * (the operator's explicit reset). Push-driven integrations (no-op resync)
14895
+ * rebuild on their next snapshot; pull/command integrations rebuild in `resync`.
14896
+ * Operator edits on the PARENT (its name, layout, primary-child pick) survive —
14897
+ * only the children are torn down. Omitted/false ⇒ the normal incremental
14898
+ * re-sync that preserves children. */
14899
+ resetToSource: boolean().optional()
14900
+ });
14869
14901
  var ResyncResultSchema = object({
14870
14902
  /** True when the persisted spec actually changed (children may have been rebuilt). */
14871
14903
  changed: boolean(),
14872
14904
  /** Number of child devices rebuilt into a new class by this re-sync. */
14873
- rebuiltChildren: number().int().nonnegative()
14905
+ rebuiltChildren: number().int().nonnegative(),
14906
+ /** Number of accessory children torn down by a `resetToSource` purge before the
14907
+ * provider re-derived the device. 0/absent for a normal incremental re-sync. */
14908
+ removedChildren: number().int().nonnegative().optional()
14874
14909
  });
14875
14910
  var deviceAdoptionCapability = {
14876
14911
  name: "device-adoption",
@@ -16635,6 +16670,11 @@ var DeviceMetaSchema = object({
16635
16670
  addonId: string(),
16636
16671
  type: string(),
16637
16672
  name: string(),
16673
+ /** True once an operator explicitly renamed the device via `setName`. Drives
16674
+ * reconcile name-precedence (preserve operator name vs adopt fresh provider
16675
+ * name). Absent ⇒ treated as user-named (PRESERVE) for legacy rows. See
16676
+ * `DeviceMeta.userNamed`. */
16677
+ userNamed: boolean().optional(),
16638
16678
  location: string().nullable(),
16639
16679
  disabled: boolean(),
16640
16680
  parentDeviceId: number().nullable(),
@@ -45721,20 +45761,84 @@ function valueFieldForCap(cap) {
45721
45761
  }
45722
45762
  }
45723
45763
  /**
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`).
45764
+ * Build a human label for a sensor. The label is resolved in priority order so
45765
+ * SAME-QUANTITY sensors are always distinguishable:
45766
+ *
45767
+ * 1. The SPECIFIC measurement `name` the library resolved from its hex-id
45768
+ * table (e.g. "Outdoor Temperature", "Wind Gust") present for the poll /
45769
+ * hex-id path.
45770
+ * 2. A specific name derived from the raw gateway FIELD KEY (e.g. `dailyrainin`
45771
+ * → "Daily Rain", `tempinf` → "Indoor Temperature") — covers the push /
45772
+ * named-and-pattern-key path where the library leaves `name` absent and
45773
+ * every same-quantity reading would otherwise collapse to one generic
45774
+ * label (five rain readings all "Precipitation", indoor+outdoor temp both
45775
+ * "Temperature", etc.). See {@link FIELD_KEY_LABEL}.
45776
+ * 3. The generic quantity label as a last resort.
45729
45777
  *
45730
- * Accepts a plain `string` (not just {@link Quantity}) so a persisted config
45731
- * value flows through without a cast; an unknown quantity falls back to its raw
45732
- * string label.
45778
+ * A channel suffix is appended when present (`temperature` + channel 2
45779
+ * `Temperature CH2`).
45780
+ *
45781
+ * `quantity`/`name` accept a plain `string` (not just {@link Quantity}) so a
45782
+ * persisted config value flows through without a cast; an unknown quantity falls
45783
+ * back to its raw string label. `sensorId` is the nodewitt `Sensor.id`
45784
+ * (`<owner>:<key>` or `<owner>:ch<n>:<key>`); the field key is its final
45785
+ * colon-segment.
45733
45786
  */
45734
- function sensorLabel(quantity, channel, name) {
45735
- const base = name !== void 0 && name.length > 0 ? name : isKnownQuantity(quantity) ? QUANTITY_LABEL[quantity] : quantity;
45787
+ function sensorLabel(quantity, channel, name, sensorId) {
45788
+ const base = labelFromFieldKey(sensorId) ?? (name !== void 0 && name.length > 0 ? name : isKnownQuantity(quantity) ? QUANTITY_LABEL[quantity] : quantity);
45736
45789
  return channel !== void 0 ? `${base} CH${channel}` : base;
45737
45790
  }
45791
+ /**
45792
+ * Resolve a specific human label from a nodewitt sensor id by extracting its raw
45793
+ * gateway field key (the final colon-segment of `<owner>[:ch<n>]:<key>`) and
45794
+ * looking it up in {@link FIELD_KEY_LABEL}. Returns `undefined` when no id is
45795
+ * given or the key is unrecognised, so the caller falls through to the generic
45796
+ * quantity label.
45797
+ */
45798
+ function labelFromFieldKey(sensorId) {
45799
+ if (sensorId === void 0) return void 0;
45800
+ const key = sensorId.slice(sensorId.lastIndexOf(":") + 1);
45801
+ return Object.prototype.hasOwnProperty.call(FIELD_KEY_LABEL, key) ? FIELD_KEY_LABEL[key] : void 0;
45802
+ }
45803
+ /**
45804
+ * Specific human labels for the raw Ecowitt gateway field keys that the library
45805
+ * classifies via its named / pattern tables (the push path) and therefore leaves
45806
+ * WITHOUT a `name`. Mirrors the vocabulary the library's hex-id table already
45807
+ * uses for the poll path, so a station reached over either transport reads the
45808
+ * same. Channel-suffixed keys (`temp3f`, `humidity2`, …) are handled by the
45809
+ * `channel` argument and intentionally omitted here.
45810
+ */
45811
+ var FIELD_KEY_LABEL = {
45812
+ tempinf: "Indoor Temperature",
45813
+ tempf: "Outdoor Temperature",
45814
+ humidityin: "Indoor Humidity",
45815
+ humidity: "Outdoor Humidity",
45816
+ baromrelin: "Relative Pressure",
45817
+ baromabsin: "Absolute Pressure",
45818
+ windspeedmph: "Wind Speed",
45819
+ windgustmph: "Wind Gust",
45820
+ maxdailygust: "Max Daily Gust",
45821
+ winddir: "Wind Direction",
45822
+ solarradiation: "Solar Radiation",
45823
+ uv: "UV Index",
45824
+ lightning: "Lightning Distance",
45825
+ lightning_num: "Lightning Count",
45826
+ rainratein: "Rain Rate",
45827
+ eventrainin: "Rain Event",
45828
+ hourlyrainin: "Hourly Rain",
45829
+ dailyrainin: "Daily Rain",
45830
+ weeklyrainin: "Weekly Rain",
45831
+ monthlyrainin: "Monthly Rain",
45832
+ yearlyrainin: "Yearly Rain",
45833
+ totalrainin: "Total Rain",
45834
+ rrain_piezo: "Rain Rate",
45835
+ erain_piezo: "Rain Event",
45836
+ hrain_piezo: "Hourly Rain",
45837
+ drain_piezo: "Daily Rain",
45838
+ wrain_piezo: "Weekly Rain",
45839
+ mrain_piezo: "Monthly Rain",
45840
+ yrain_piezo: "Yearly Rain"
45841
+ };
45738
45842
  /** Type guard: is `q` one of the known {@link Quantity} union members? */
45739
45843
  function isKnownQuantity(q) {
45740
45844
  return Object.prototype.hasOwnProperty.call(QUANTITY_LABEL, q);
@@ -45766,7 +45870,7 @@ var QUANTITY_LABEL = {
45766
45870
  function buildEcowittGatewayCandidate(input) {
45767
45871
  const children = input.sensors.map((sensor) => ({
45768
45872
  childNativeId: sensor.id,
45769
- name: sensorLabel(sensor.quantity, sensor.channel, sensor.name),
45873
+ name: sensorLabel(sensor.quantity, sensor.channel, sensor.name, sensor.id),
45770
45874
  type: DeviceType.Sensor,
45771
45875
  status: "online",
45772
45876
  metadata: sensor.model !== void 0 ? { model: sensor.model } : {},
@@ -46285,7 +46389,7 @@ var EcowittContainerDevice = class extends BaseDevice {
46285
46389
  const meta = {
46286
46390
  type: DeviceType.Sensor,
46287
46391
  role: roleForQuantity(sensor.quantity),
46288
- name: sensorLabel(sensor.quantity, sensor.channel, sensor.name),
46392
+ name: sensorLabel(sensor.quantity, sensor.channel, sensor.name, sensor.sensorId),
46289
46393
  linkDeviceId: this.id,
46290
46394
  ...this.integrationId !== void 0 ? { integrationId: this.integrationId } : {}
46291
46395
  };
package/dist/addon.mjs CHANGED
@@ -13397,9 +13397,29 @@ var BaseDevice = class {
13397
13397
  * is open (Reolink writes `hasPtz/hasIntercom`, Hikvision writes
13398
13398
  * `hasSupplementalLight/hasAlarmIo`, etc).
13399
13399
  *
13400
- * Default: no-op (driver had no probe to run).
13401
- */
13402
- async onProbe() {}
13400
+ * Default: nothing to probe mark the device PROBED (set `lastProbedAt`) so
13401
+ * the kernel treats it as ready immediately. A device that derives its shape
13402
+ * from a spec (a container, or an accessory sensor) rather than from a
13403
+ * hardware probe has no probe to "complete"; without stamping `lastProbedAt`
13404
+ * it would look perpetually un-probed — logging "Initial probe did not
13405
+ * complete" on every boot and spinning a pointless retry chain. Drivers that
13406
+ * DO probe override this and write their own `feature-probe` slice (including
13407
+ * `lastProbedAt`) once their probe actually succeeds.
13408
+ */
13409
+ async onProbe() {
13410
+ const base = this.runtimeState.getCapState("feature-probe") ?? {
13411
+ flags: {},
13412
+ deviceType: null,
13413
+ model: null,
13414
+ channelCount: null,
13415
+ lastProbedAt: 0,
13416
+ lastFetchedAt: 0
13417
+ };
13418
+ this.runtimeState.setCapState("feature-probe", {
13419
+ ...base,
13420
+ lastProbedAt: Date.now()
13421
+ });
13422
+ }
13403
13423
  /**
13404
13424
  * Phase 5 — fired after the device + its accessories are registered.
13405
13425
  * Drivers publish streams to the broker, kick off background tasks,
@@ -14861,17 +14881,32 @@ var ReleaseInputSchema = object({
14861
14881
  * the parent cascades into every accessory. */
14862
14882
  camDeviceId: number().int().nonnegative()
14863
14883
  });
14864
- var ResyncInputSchema = object({
14865
- /** Parent CamStack device id of an adopted device. The provider resolves its
14866
- * source (integration/broker + native id) and re-aligns the device's
14867
- * structural spec (type/role/capabilities/units) with the live mapping,
14868
- * rebuilding any child whose class changed while preserving operator edits. */
14869
- camDeviceId: number().int().nonnegative() });
14884
+ var ResyncInputSchema = object({
14885
+ /** Parent CamStack device id of an adopted device. The provider resolves its
14886
+ * source (integration/broker + native id) and re-aligns the device's
14887
+ * structural spec (type/role/capabilities/units) with the live mapping,
14888
+ * rebuilding any child whose class changed while preserving operator edits. */
14889
+ camDeviceId: number().int().nonnegative(),
14890
+ /** "Resync from zero" (#19). When true, the kernel PURGES every accessory
14891
+ * child of `camDeviceId` BEFORE the provider re-derives the device, so the
14892
+ * children are rebuilt fresh from source — correct names, coords, and units —
14893
+ * instead of being preserved by the incremental reconcile. Use to recover from
14894
+ * legacy generic/placeholder names that the normal name-precedence keeps frozen
14895
+ * (the operator's explicit reset). Push-driven integrations (no-op resync)
14896
+ * rebuild on their next snapshot; pull/command integrations rebuild in `resync`.
14897
+ * Operator edits on the PARENT (its name, layout, primary-child pick) survive —
14898
+ * only the children are torn down. Omitted/false ⇒ the normal incremental
14899
+ * re-sync that preserves children. */
14900
+ resetToSource: boolean().optional()
14901
+ });
14870
14902
  var ResyncResultSchema = object({
14871
14903
  /** True when the persisted spec actually changed (children may have been rebuilt). */
14872
14904
  changed: boolean(),
14873
14905
  /** Number of child devices rebuilt into a new class by this re-sync. */
14874
- rebuiltChildren: number().int().nonnegative()
14906
+ rebuiltChildren: number().int().nonnegative(),
14907
+ /** Number of accessory children torn down by a `resetToSource` purge before the
14908
+ * provider re-derived the device. 0/absent for a normal incremental re-sync. */
14909
+ removedChildren: number().int().nonnegative().optional()
14875
14910
  });
14876
14911
  var deviceAdoptionCapability = {
14877
14912
  name: "device-adoption",
@@ -16636,6 +16671,11 @@ var DeviceMetaSchema = object({
16636
16671
  addonId: string(),
16637
16672
  type: string(),
16638
16673
  name: string(),
16674
+ /** True once an operator explicitly renamed the device via `setName`. Drives
16675
+ * reconcile name-precedence (preserve operator name vs adopt fresh provider
16676
+ * name). Absent ⇒ treated as user-named (PRESERVE) for legacy rows. See
16677
+ * `DeviceMeta.userNamed`. */
16678
+ userNamed: boolean().optional(),
16639
16679
  location: string().nullable(),
16640
16680
  disabled: boolean(),
16641
16681
  parentDeviceId: number().nullable(),
@@ -45722,20 +45762,84 @@ function valueFieldForCap(cap) {
45722
45762
  }
45723
45763
  }
45724
45764
  /**
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`).
45765
+ * Build a human label for a sensor. The label is resolved in priority order so
45766
+ * SAME-QUANTITY sensors are always distinguishable:
45767
+ *
45768
+ * 1. The SPECIFIC measurement `name` the library resolved from its hex-id
45769
+ * table (e.g. "Outdoor Temperature", "Wind Gust") present for the poll /
45770
+ * hex-id path.
45771
+ * 2. A specific name derived from the raw gateway FIELD KEY (e.g. `dailyrainin`
45772
+ * → "Daily Rain", `tempinf` → "Indoor Temperature") — covers the push /
45773
+ * named-and-pattern-key path where the library leaves `name` absent and
45774
+ * every same-quantity reading would otherwise collapse to one generic
45775
+ * label (five rain readings all "Precipitation", indoor+outdoor temp both
45776
+ * "Temperature", etc.). See {@link FIELD_KEY_LABEL}.
45777
+ * 3. The generic quantity label as a last resort.
45730
45778
  *
45731
- * Accepts a plain `string` (not just {@link Quantity}) so a persisted config
45732
- * value flows through without a cast; an unknown quantity falls back to its raw
45733
- * string label.
45779
+ * A channel suffix is appended when present (`temperature` + channel 2
45780
+ * `Temperature CH2`).
45781
+ *
45782
+ * `quantity`/`name` accept a plain `string` (not just {@link Quantity}) so a
45783
+ * persisted config value flows through without a cast; an unknown quantity falls
45784
+ * back to its raw string label. `sensorId` is the nodewitt `Sensor.id`
45785
+ * (`<owner>:<key>` or `<owner>:ch<n>:<key>`); the field key is its final
45786
+ * colon-segment.
45734
45787
  */
45735
- function sensorLabel(quantity, channel, name) {
45736
- const base = name !== void 0 && name.length > 0 ? name : isKnownQuantity(quantity) ? QUANTITY_LABEL[quantity] : quantity;
45788
+ function sensorLabel(quantity, channel, name, sensorId) {
45789
+ const base = labelFromFieldKey(sensorId) ?? (name !== void 0 && name.length > 0 ? name : isKnownQuantity(quantity) ? QUANTITY_LABEL[quantity] : quantity);
45737
45790
  return channel !== void 0 ? `${base} CH${channel}` : base;
45738
45791
  }
45792
+ /**
45793
+ * Resolve a specific human label from a nodewitt sensor id by extracting its raw
45794
+ * gateway field key (the final colon-segment of `<owner>[:ch<n>]:<key>`) and
45795
+ * looking it up in {@link FIELD_KEY_LABEL}. Returns `undefined` when no id is
45796
+ * given or the key is unrecognised, so the caller falls through to the generic
45797
+ * quantity label.
45798
+ */
45799
+ function labelFromFieldKey(sensorId) {
45800
+ if (sensorId === void 0) return void 0;
45801
+ const key = sensorId.slice(sensorId.lastIndexOf(":") + 1);
45802
+ return Object.prototype.hasOwnProperty.call(FIELD_KEY_LABEL, key) ? FIELD_KEY_LABEL[key] : void 0;
45803
+ }
45804
+ /**
45805
+ * Specific human labels for the raw Ecowitt gateway field keys that the library
45806
+ * classifies via its named / pattern tables (the push path) and therefore leaves
45807
+ * WITHOUT a `name`. Mirrors the vocabulary the library's hex-id table already
45808
+ * uses for the poll path, so a station reached over either transport reads the
45809
+ * same. Channel-suffixed keys (`temp3f`, `humidity2`, …) are handled by the
45810
+ * `channel` argument and intentionally omitted here.
45811
+ */
45812
+ var FIELD_KEY_LABEL = {
45813
+ tempinf: "Indoor Temperature",
45814
+ tempf: "Outdoor Temperature",
45815
+ humidityin: "Indoor Humidity",
45816
+ humidity: "Outdoor Humidity",
45817
+ baromrelin: "Relative Pressure",
45818
+ baromabsin: "Absolute Pressure",
45819
+ windspeedmph: "Wind Speed",
45820
+ windgustmph: "Wind Gust",
45821
+ maxdailygust: "Max Daily Gust",
45822
+ winddir: "Wind Direction",
45823
+ solarradiation: "Solar Radiation",
45824
+ uv: "UV Index",
45825
+ lightning: "Lightning Distance",
45826
+ lightning_num: "Lightning Count",
45827
+ rainratein: "Rain Rate",
45828
+ eventrainin: "Rain Event",
45829
+ hourlyrainin: "Hourly Rain",
45830
+ dailyrainin: "Daily Rain",
45831
+ weeklyrainin: "Weekly Rain",
45832
+ monthlyrainin: "Monthly Rain",
45833
+ yearlyrainin: "Yearly Rain",
45834
+ totalrainin: "Total Rain",
45835
+ rrain_piezo: "Rain Rate",
45836
+ erain_piezo: "Rain Event",
45837
+ hrain_piezo: "Hourly Rain",
45838
+ drain_piezo: "Daily Rain",
45839
+ wrain_piezo: "Weekly Rain",
45840
+ mrain_piezo: "Monthly Rain",
45841
+ yrain_piezo: "Yearly Rain"
45842
+ };
45739
45843
  /** Type guard: is `q` one of the known {@link Quantity} union members? */
45740
45844
  function isKnownQuantity(q) {
45741
45845
  return Object.prototype.hasOwnProperty.call(QUANTITY_LABEL, q);
@@ -45767,7 +45871,7 @@ var QUANTITY_LABEL = {
45767
45871
  function buildEcowittGatewayCandidate(input) {
45768
45872
  const children = input.sensors.map((sensor) => ({
45769
45873
  childNativeId: sensor.id,
45770
- name: sensorLabel(sensor.quantity, sensor.channel, sensor.name),
45874
+ name: sensorLabel(sensor.quantity, sensor.channel, sensor.name, sensor.id),
45771
45875
  type: DeviceType.Sensor,
45772
45876
  status: "online",
45773
45877
  metadata: sensor.model !== void 0 ? { model: sensor.model } : {},
@@ -46286,7 +46390,7 @@ var EcowittContainerDevice = class extends BaseDevice {
46286
46390
  const meta = {
46287
46391
  type: DeviceType.Sensor,
46288
46392
  role: roleForQuantity(sensor.quantity),
46289
- name: sensorLabel(sensor.quantity, sensor.channel, sensor.name),
46393
+ name: sensorLabel(sensor.quantity, sensor.channel, sensor.name, sensor.sensorId),
46290
46394
  linkDeviceId: this.id,
46291
46395
  ...this.integrationId !== void 0 ? { integrationId: this.integrationId } : {}
46292
46396
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-ecowitt",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
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",