@camstack/addon-provider-dreame 0.2.148 → 0.2.150

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.js CHANGED
@@ -44,7 +44,7 @@ let net = require("net");
44
44
  net = __toESM(net, 1);
45
45
  let zlib = require("zlib");
46
46
  zlib = __toESM(zlib, 1);
47
- //#region ../types/dist/event-category-7pZEBmVg.mjs
47
+ //#region ../types/dist/event-category-BfJwUd2S.mjs
48
48
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
49
49
  EventCategory["SystemBoot"] = "system.boot";
50
50
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -361,6 +361,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
361
361
  */
362
362
  EventCategory["PipelineDetectionBlind"] = "pipeline.detection-blind";
363
363
  /**
364
+ * A camera's motion source has fired constantly with no subject for an hour
365
+ * (motion duty or session count over the bar, zero detections) — it is not
366
+ * blind, its motion zones or sensitivity want tuning (D907). Paired with
367
+ * {@link PipelineMotionCalm}.
368
+ */
369
+ EventCategory["PipelineMotionNoisy"] = "pipeline.motion-noisy";
370
+ /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
371
+ EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
372
+ /**
364
373
  * Per-camera pipeline config was mutated by the orchestrator
365
374
  * (3-level settings change via `setAgentAddonDefaults` /
366
375
  * `setCameraStepToggle` / `setCameraPipelineForAgent` or a
@@ -5467,7 +5476,7 @@ var ZodIssueCode$1 = {
5467
5476
  var ZodFirstPartyTypeKind$1;
5468
5477
  ZodFirstPartyTypeKind$1 || (ZodFirstPartyTypeKind$1 = {});
5469
5478
  //#endregion
5470
- //#region ../types/dist/sleep-CueztlR1.mjs
5479
+ //#region ../types/dist/sleep-DJFUfKvA.mjs
5471
5480
  /**
5472
5481
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5473
5482
  * window to float samples (D455).
@@ -7996,7 +8005,7 @@ var EntryDescriptorSchema = object({
7996
8005
  /** JSON Schema of the entry body; `secret: true` marks a password field. */
7997
8006
  fields: record(string(), unknown())
7998
8007
  }).strict();
7999
- /** What crosses the wire and sits in the engine's catalog: JSON only, no Zod. */
8008
+ /** What crosses the wire from an addon: JSON only, no Zod. An unknown key is refused. */
8000
8009
  var RecordKindDeclarationSchema = object({
8001
8010
  database: _enum(RECORD_DATABASES).default("settings"),
8002
8011
  class: _enum(RECORD_CLASSES),
@@ -8015,14 +8024,6 @@ var RecordKindDeclarationSchema = object({
8015
8024
  secretPaths: array(string().min(1)).default([]),
8016
8025
  /** The id key of each array that carries a secret (`{ headers: 'name' }`). */
8017
8026
  secretArrayKeys: record(string(), string()).default({}),
8018
- /**
8019
- * RETIRED (D900): the legacy table a kind adopted from during the records
8020
- * migration. Nothing declares it any more and nothing reads it; the schema
8021
- * still ACCEPTS it because the catalog rows the migration wrote carry it
8022
- * (an orphaned kind is never redeclared) and an addon built before the
8023
- * removal still sends it. Never set it.
8024
- */
8025
- adoptsFrom: string().min(1).optional(),
8026
8027
  /** Kinds that must agree through `transact` sit in one file under one family name. */
8027
8028
  transactFamily: string().min(1).optional(),
8028
8029
  entry: EntryDescriptorSchema.optional()
@@ -12714,17 +12715,6 @@ object({
12714
12715
  createdAt: number(),
12715
12716
  updatedAt: number()
12716
12717
  });
12717
- object({
12718
- id: string(),
12719
- label: string(),
12720
- isAdmin: boolean().default(false),
12721
- allowedProviders: AllowedProviderSchema,
12722
- allowedDevices: AllowedDevicesSchema,
12723
- tokenHash: string(),
12724
- tokenPrefix: string(),
12725
- createdAt: number(),
12726
- lastUsedAt: number().optional()
12727
- });
12728
12718
  object({
12729
12719
  id: string(),
12730
12720
  userId: string(),
@@ -16706,16 +16696,36 @@ var RecordKindStateSchema = _enum([
16706
16696
  var DeclareKindResultSchema = object({
16707
16697
  kindId: number().int(),
16708
16698
  state: RecordKindStateSchema,
16709
- createdIndexes: array(string()),
16710
- /**
16711
- * RETIRED (D900): always `null`. It carried the records-migration boot guard;
16712
- * it stays on the wire, null, because an addon built before the removal reads
16713
- * an ABSENT field as "adoption pending" and refuses to start.
16714
- */
16715
- adoptionPending: _null()
16699
+ createdIndexes: array(string())
16716
16700
  });
16717
16701
  /** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
16718
16702
  var SecretRefsList = array(string());
16703
+ /**
16704
+ * One op of a `transact`: the single-row writes, each on a kind of the caller.
16705
+ * All ops of one call share ONE database file and ONE `transactFamily`.
16706
+ */
16707
+ var RecordTransactOpSchema = discriminatedUnion("op", [
16708
+ object({
16709
+ op: literal("put"),
16710
+ kind: RecordKindNameSchema,
16711
+ record: RecordInputSchema,
16712
+ ifRev: number().int().min(0).optional()
16713
+ }),
16714
+ object({
16715
+ op: literal("patch"),
16716
+ kind: RecordKindNameSchema,
16717
+ id: RecordIdSchema,
16718
+ data: RecordBodySchema,
16719
+ at: number().int().nullable().optional(),
16720
+ expiresAt: number().int().nullable().optional(),
16721
+ ifRev: number().int().min(1).optional()
16722
+ }),
16723
+ object({
16724
+ op: literal("delete"),
16725
+ kind: RecordKindNameSchema,
16726
+ id: RecordIdSchema
16727
+ })
16728
+ ]);
16719
16729
  var RecordSecretRefsSchema = record(string(), record(string(), string()));
16720
16730
  method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
16721
16731
  kind: "mutation",
@@ -16822,6 +16832,16 @@ method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema)
16822
16832
  }), {
16823
16833
  kind: "mutation",
16824
16834
  auth: "admin"
16835
+ }), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
16836
+ results: array(object({
16837
+ rev: number().int().nullable(),
16838
+ deleted: boolean()
16839
+ })),
16840
+ releasedRefs: SecretRefsList,
16841
+ secretRefs: SecretRefsList
16842
+ }), {
16843
+ kind: "mutation",
16844
+ auth: "admin"
16825
16845
  }), method(object({
16826
16846
  deviceId: number().int(),
16827
16847
  kinds: array(RecordKindNameSchema).optional()
@@ -42445,9 +42465,13 @@ var NcSystemEventKindSchema = _enum([
42445
42465
  "node-updating",
42446
42466
  "node-inference-unavailable",
42447
42467
  "detection-blind",
42468
+ "motion-noisy",
42469
+ "motion-calm",
42448
42470
  "recording-stalled",
42449
42471
  "recording-healthy",
42450
42472
  "recording-degraded",
42473
+ "device-attention-required",
42474
+ "device-attention-resolved",
42451
42475
  "storage-location-low",
42452
42476
  "storage-location-critical",
42453
42477
  "storage-location-ok",
@@ -48008,7 +48032,6 @@ var RecommendationReasonSchema = discriminatedUnion("kind", [
48008
48032
  modelId: string()
48009
48033
  }),
48010
48034
  object({ kind: literal("cpu-device") }),
48011
- object({ kind: literal("coral-untouched") }),
48012
48035
  object({ kind: literal("device-disabled") }),
48013
48036
  object({ kind: literal("device-unavailable") }),
48014
48037
  object({ kind: literal("node-offline") }),
@@ -48106,11 +48129,6 @@ var AutoTuneWarningSchema = discriminatedUnion("kind", [
48106
48129
  kind: literal("detector-store-unreadable"),
48107
48130
  error: string()
48108
48131
  }),
48109
- object({
48110
- kind: literal("coral-enabled"),
48111
- nodeId: string(),
48112
- deviceKey: string()
48113
- }),
48114
48132
  object({
48115
48133
  kind: literal("custom-pin-kept"),
48116
48134
  nodeId: string(),
@@ -55796,6 +55814,16 @@ var DishwasherStatusSchema = object({
55796
55814
  powerState: string().nullable(),
55797
55815
  saltLevel: DishwasherConsumableLevelSchema.nullable(),
55798
55816
  rinseAidLevel: DishwasherConsumableLevelSchema.nullable(),
55817
+ /**
55818
+ * True while the appliance asks for its filter to be cleaned (smart-filter, filter-system or
55819
+ * combined care-and-filter reminder raised); false when every such reminder reads clear; null
55820
+ * when none is reported. A cleared reminder is not an unknown one.
55821
+ */
55822
+ filterCleaningDue: boolean().nullable(),
55823
+ /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
55824
+ machineCareDue: boolean().nullable(),
55825
+ /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
55826
+ machineCareRemainingRuns: number().int().nonnegative().nullable(),
55799
55827
  /** Option states: null when the appliance does not expose the option or has not said. */
55800
55828
  halfLoad: boolean().nullable(),
55801
55829
  hygienePlus: boolean().nullable(),
@@ -64594,16 +64622,6 @@ var UpdateUserInputSchema = object({
64594
64622
  allowedDevices: record(string(), union([array(string()), literal("*")])).optional(),
64595
64623
  scopes: array(TokenScopeSchema).optional()
64596
64624
  });
64597
- var ApiKeySummarySchema = object({
64598
- id: string(),
64599
- label: string(),
64600
- isAdmin: boolean().default(false),
64601
- allowedProviders: union([array(string()), literal("*")]).optional(),
64602
- allowedDevices: record(string(), union([array(string()), literal("*")])).optional(),
64603
- tokenPrefix: string(),
64604
- createdAt: number(),
64605
- lastUsedAt: number().optional()
64606
- });
64607
64625
  var OauthSessionSummarySchema = object({
64608
64626
  id: string(),
64609
64627
  userId: string(),
@@ -64625,8 +64643,7 @@ var TotpStatusSchema = object({
64625
64643
  confirmedAt: number().nullable()
64626
64644
  });
64627
64645
  /**
64628
- * user-management — singleton capability for user accounts and the
64629
- * legacy API-key rows.
64646
+ * user-management — singleton capability for user accounts.
64630
64647
  *
64631
64648
  * v2 model: every method gated by `auth: 'admin'`. Admin-only delegation
64632
64649
  * — the subset check on `setUserScopes` is moot
@@ -64682,10 +64699,6 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
64682
64699
  }), UserSummarySchema.extend({ passwordHash: string() }).nullable(), {
64683
64700
  kind: "mutation",
64684
64701
  access: "view"
64685
- }), method(_void(), array(ApiKeySummarySchema), { auth: "admin" }), method(object({ id: string() }), object({ success: literal(true) }), {
64686
- kind: "mutation",
64687
- auth: "admin",
64688
- access: "delete"
64689
64702
  }), method(object({ userId: string() }), TotpSetupResultSchema, {
64690
64703
  kind: "mutation",
64691
64704
  auth: "admin",
@@ -74157,6 +74170,12 @@ Object.freeze({
74157
74170
  addonId: null,
74158
74171
  access: "view"
74159
74172
  },
74173
+ "records.transact": {
74174
+ capName: "records",
74175
+ capScope: "system",
74176
+ addonId: null,
74177
+ access: "create"
74178
+ },
74160
74179
  "sceneMonitor.captureReference": {
74161
74180
  capName: "scene-monitor",
74162
74181
  capScope: "device",
@@ -75489,12 +75508,6 @@ Object.freeze({
75489
75508
  addonId: null,
75490
75509
  access: "view"
75491
75510
  },
75492
- "userManagement.listApiKeys": {
75493
- capName: "user-management",
75494
- capScope: "system",
75495
- addonId: null,
75496
- access: "view"
75497
- },
75498
75511
  "userManagement.listExternalIdentities": {
75499
75512
  capName: "user-management",
75500
75513
  capScope: "system",
@@ -75549,12 +75562,6 @@ Object.freeze({
75549
75562
  addonId: null,
75550
75563
  access: "create"
75551
75564
  },
75552
- "userManagement.revokeApiKey": {
75553
- capName: "user-management",
75554
- capScope: "system",
75555
- addonId: null,
75556
- access: "delete"
75557
- },
75558
75565
  "userManagement.revokeOauthSession": {
75559
75566
  capName: "user-management",
75560
75567
  capScope: "system",
@@ -78919,9 +78926,13 @@ new Map(Object.entries({
78919
78926
  "stream-online": "device-events",
78920
78927
  "stream-offline": "device-events",
78921
78928
  "detection-blind": "device-events",
78929
+ "motion-noisy": "device-events",
78930
+ "motion-calm": "device-events",
78922
78931
  "recording-stalled": "device-events",
78923
78932
  "recording-healthy": "device-events",
78924
78933
  "recording-degraded": "device-events",
78934
+ "device-attention-required": "device-events",
78935
+ "device-attention-resolved": "device-events",
78925
78936
  "node-online": "system",
78926
78937
  "node-offline": "system",
78927
78938
  "node-restarting": "system",
@@ -79004,6 +79015,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
79004
79015
  label: "Detecting nothing",
79005
79016
  kinds: ["detection-blind"]
79006
79017
  },
79018
+ {
79019
+ id: "motion-noise",
79020
+ family: "device-events",
79021
+ label: "Motion without a subject",
79022
+ kinds: ["motion-noisy", "motion-calm"]
79023
+ },
79007
79024
  {
79008
79025
  id: "recording",
79009
79026
  family: "device-events",
@@ -79014,6 +79031,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
79014
79031
  "recording-degraded"
79015
79032
  ]
79016
79033
  },
79034
+ {
79035
+ id: "device-attention",
79036
+ family: "device-events",
79037
+ label: "Needs attention",
79038
+ kinds: ["device-attention-required", "device-attention-resolved"]
79039
+ },
79017
79040
  {
79018
79041
  id: "nodes",
79019
79042
  family: "system",
@@ -79153,6 +79176,7 @@ var ALERT_EPISODE = [
79153
79176
  "alert-error",
79154
79177
  "alert-critical-resolved"
79155
79178
  ];
79179
+ var DEVICE_ATTENTION = ["device-attention-required", "device-attention-resolved"];
79156
79180
  var UPDATES = [
79157
79181
  "addon-update-available",
79158
79182
  "server-update-available",
@@ -79182,9 +79206,12 @@ var DEVICE_EVENTS = [
79182
79206
  "stream-online",
79183
79207
  "stream-offline",
79184
79208
  "detection-blind",
79209
+ "motion-noisy",
79210
+ "motion-calm",
79185
79211
  "recording-stalled",
79186
79212
  "recording-healthy",
79187
79213
  "recording-degraded",
79214
+ ...DEVICE_ATTENTION,
79188
79215
  ...ALARM
79189
79216
  ];
79190
79217
  /** Every kind a summary may speak about — the two summarisable families. */
@@ -79352,7 +79379,9 @@ var STATE_SUMMARY_VARS = [
79352
79379
  [...RULE_TEXT],
79353
79380
  [...RULE_TEXT],
79354
79381
  [...RULE_TEXT],
79355
- ([...RULE_TEXT], [...ALERT_EPISODE]),
79382
+ [...RULE_TEXT],
79383
+ [...RULE_TEXT],
79384
+ ([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
79356
79385
  [...RULE_TEXT],
79357
79386
  [...RULE_TEXT],
79358
79387
  [...RULE_TEXT],
@@ -79374,11 +79403,11 @@ var STATE_SUMMARY_VARS = [
79374
79403
  [...RULE_TEXT],
79375
79404
  [...RULE_TEXT],
79376
79405
  [...RULE_TEXT],
79406
+ ([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
79377
79407
  ([...RULE_TEXT], [...ALERT_EPISODE]),
79408
+ ([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
79378
79409
  ([...RULE_TEXT], [...ALERT_EPISODE]),
79379
- ([...RULE_TEXT], [...ALERT_EPISODE]),
79380
- ([...RULE_TEXT], [...ALERT_EPISODE]),
79381
- ([...RULE_TEXT], [...ALERT_EPISODE]),
79410
+ ([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
79382
79411
  [...RULE_TEXT],
79383
79412
  [...RULE_TEXT],
79384
79413
  [...RULE_TEXT],
@@ -79454,6 +79483,15 @@ new Map([
79454
79483
  snapshotLabel: "Tanks low or full now",
79455
79484
  snapshotSection: "tanks"
79456
79485
  },
79486
+ {
79487
+ id: "motion-noise",
79488
+ family: "device-events",
79489
+ label: "Motion without a subject (constant / calm again)",
79490
+ bad: ["motion-noisy"],
79491
+ recovery: ["motion-calm"],
79492
+ snapshotLabel: "Cameras with constant motion now",
79493
+ snapshotSection: null
79494
+ },
79457
79495
  {
79458
79496
  id: "recording-health",
79459
79497
  family: "device-events",
@@ -79463,6 +79501,15 @@ new Map([
79463
79501
  snapshotLabel: "Recordings stalled now",
79464
79502
  snapshotSection: null
79465
79503
  },
79504
+ {
79505
+ id: "device-attention",
79506
+ family: "device-events",
79507
+ label: "Device needs attention (raised / resolved)",
79508
+ bad: ["device-attention-required"],
79509
+ recovery: ["device-attention-resolved"],
79510
+ snapshotLabel: "Devices needing attention now",
79511
+ snapshotSection: null
79512
+ },
79466
79513
  {
79467
79514
  id: "node-connectivity",
79468
79515
  family: "system",
@@ -132949,8 +132996,6 @@ function buildConnectionFormSchema() {
132949
132996
  id: "credentials",
132950
132997
  title: "Dreamehome account",
132951
132998
  titleKey: "provider-dreame:connection.s_credentials.title",
132952
- description: "Sign in with your Dreamehome (Dreame app) account credentials.",
132953
- descriptionKey: "provider-dreame:connection.s_credentials.description",
132954
132999
  columns: 1,
132955
133000
  fields: [{
132956
133001
  type: "text",
@@ -132972,8 +133017,6 @@ function buildConnectionFormSchema() {
132972
133017
  id: "region",
132973
133018
  title: "Region",
132974
133019
  titleKey: "provider-dreame:connection.s_region.title",
132975
- description: "Select the Dreame cloud region your account is registered in.",
132976
- descriptionKey: "provider-dreame:connection.s_region.description",
132977
133020
  columns: 1,
132978
133021
  fields: [{
132979
133022
  type: "select",
@@ -135392,6 +135435,81 @@ function rfcUrl(host, port) {
135392
135435
  return `tcp://${host}:${String(port)}`;
135393
135436
  }
135394
135437
  //#endregion
135438
+ //#region src/devices/dreame-pin-attention.ts
135439
+ var REFUSED_CODE_MINUS_ONE = /startMonitor refused \(code -1\)/;
135440
+ /** The refusal an error carries, or `null` when it is some other failure. */
135441
+ function classifyPinRefusal(err, accessCodeSet) {
135442
+ const message = err instanceof Error ? err.message : String(err);
135443
+ if (!REFUSED_CODE_MINUS_ONE.test(message)) return null;
135444
+ return accessCodeSet ? "rejected" : "missing";
135445
+ }
135446
+ function pinAlertId(deviceId) {
135447
+ return `dreame.pin-required:${String(deviceId)}`;
135448
+ }
135449
+ function buildPinAlert(deviceId, deviceName, reason) {
135450
+ const now = Date.now();
135451
+ return {
135452
+ id: pinAlertId(deviceId),
135453
+ category: "device.credentials-required",
135454
+ severity: "critical",
135455
+ title: reason === "missing" ? "Camera PIN required" : "Camera PIN rejected",
135456
+ message: reason === "missing" ? `${deviceName} needs its camera PIN before its video can open. Enter it in the device settings.` : `${deviceName} refused the camera PIN that is set. Enter the correct PIN in the device settings.`,
135457
+ action: {
135458
+ label: "Enter PIN",
135459
+ href: `/devices/${String(deviceId)}`,
135460
+ navigation: "app"
135461
+ },
135462
+ status: "active",
135463
+ read: false,
135464
+ createdAt: now,
135465
+ updatedAt: now,
135466
+ source: {
135467
+ type: "device",
135468
+ id: String(deviceId)
135469
+ },
135470
+ metadata: { episodeKey: reason }
135471
+ };
135472
+ }
135473
+ /**
135474
+ * One device's PIN episode. It raises the alert ONCE per episode, and while the
135475
+ * episode is open it tells the wake path to stand down: a refusal that needs the
135476
+ * operator is terminal until the PIN setting changes, so the broker's 30 s
135477
+ * re-dial must not re-open the monitor and fail again.
135478
+ */
135479
+ var PinAttention = class {
135480
+ ports;
135481
+ open = null;
135482
+ constructor(ports) {
135483
+ this.ports = ports;
135484
+ }
135485
+ /** True while the PIN is known to be unusable — the wake must not try. */
135486
+ get blocked() {
135487
+ return this.open !== null;
135488
+ }
135489
+ /** A wake failed. Raises the alert on the first refusal of an episode. */
135490
+ async refused(reason) {
135491
+ if (this.open !== null) return "already-open";
135492
+ this.open = reason;
135493
+ await this.ports.emit(buildPinAlert(this.ports.deviceId, this.ports.deviceName(), reason));
135494
+ return "raised";
135495
+ }
135496
+ /** A monitor opened: the PIN works, the episode is over. */
135497
+ async succeeded() {
135498
+ return this.settingChanged();
135499
+ }
135500
+ /**
135501
+ * The PIN setting changed: the episode is no longer known to be terminal, so
135502
+ * the next wake tries again. The alert is resolved here — a still-wrong PIN
135503
+ * raises a fresh episode on that wake.
135504
+ */
135505
+ async settingChanged() {
135506
+ if (this.open === null) return false;
135507
+ this.open = null;
135508
+ await this.ports.resolve(pinAlertId(this.ports.deviceId));
135509
+ return true;
135510
+ }
135511
+ };
135512
+ //#endregion
135395
135513
  //#region src/devices/dreame-navigation-actions.ts
135396
135514
  /** Forward throttle magnitude for a full-forward nudge (device speed units). */
135397
135515
  var FORWARD_SPEED = 200;
@@ -181583,15 +181701,13 @@ var CAMERA_SETTINGS_SCHEMA = { sections: [{
181583
181701
  id: "camera-backend",
181584
181702
  title: "Video backend",
181585
181703
  titleKey: "provider-dreame:camera_settings.s_camera-backend.title",
181586
- description: "Which cloud video service this robot streams through. The two are not interchangeable: only the Aliyun path is implemented here — a robot on Tencent can be seen in the Dreame app but not by CamStack, because Tencent offers it a proprietary P2P plane we do not speak. Changing this asks the ROBOT to switch and waits until its SDK reports ready; the robot is the authority on where it actually is.",
181704
+ description: "Only the Aliyun path is implemented; a robot on Tencent cannot be viewed by CamStack. Changing this asks the robot to switch.",
181587
181705
  descriptionKey: "provider-dreame:camera_settings.s_camera-backend.description",
181588
181706
  fields: [{
181589
181707
  type: "select",
181590
181708
  key: "videoVendor",
181591
181709
  label: "Backend",
181592
181710
  labelKey: "provider-dreame:camera_settings.s_camera-backend.videoVendor.label",
181593
- description: "Aliyun streams through a plain relay and works today. Tencent is offered so the other path can be tested once it exists.",
181594
- descriptionKey: "provider-dreame:camera_settings.s_camera-backend.videoVendor.description",
181595
181711
  options: [{
181596
181712
  value: "ali",
181597
181713
  label: "Aliyun LinkVisual (supported)",
@@ -181777,6 +181893,10 @@ var DreameCameraDevice = class extends DreameChildDevice {
181777
181893
  const data = event.data;
181778
181894
  if ((typeof data === "object" && data !== null && "deviceId" in data ? data.deviceId : null) !== this.id) return;
181779
181895
  if (this.session === null) {
181896
+ if (this.pinAttention.blocked) {
181897
+ this.ctx.logger.debug("robot camera: wake skipped — the camera PIN needs the operator", { tags: { deviceId: this.id } });
181898
+ return;
181899
+ }
181780
181900
  this.openStream().catch(() => void 0);
181781
181901
  return;
181782
181902
  }
@@ -181863,8 +181983,65 @@ var DreameCameraDevice = class extends DreameChildDevice {
181863
181983
  durationMs: Date.now() - startedAt
181864
181984
  };
181865
181985
  }
181986
+ /**
181987
+ * The PIN episode (D911): a monitor refused for want of the camera PIN needs
181988
+ * the operator, so it raises ONE critical alert and the wake stands down
181989
+ * until the PIN setting changes.
181990
+ */
181991
+ pinAttention = new PinAttention({
181992
+ deviceId: this.id,
181993
+ deviceName: () => this.name,
181994
+ emit: async (alert) => {
181995
+ const emit = this.ctx.api.alerts?.emit;
181996
+ if (emit === void 0) {
181997
+ this.ctx.logger.warn("robot camera: PIN alert skipped — alerts capability missing", { tags: { deviceId: this.id } });
181998
+ return;
181999
+ }
182000
+ try {
182001
+ await emit.mutate(alert);
182002
+ this.ctx.logger.warn("robot camera: PIN alert raised — the wake stops until the PIN changes", {
182003
+ tags: { deviceId: this.id },
182004
+ meta: {
182005
+ alertId: alert.id,
182006
+ episodeKey: alert.metadata?.["episodeKey"]
182007
+ }
182008
+ });
182009
+ } catch (err) {
182010
+ this.ctx.logger.warn("robot camera: PIN alert emit failed", {
182011
+ tags: { deviceId: this.id },
182012
+ meta: { error: errMsg(err) }
182013
+ });
182014
+ }
182015
+ },
182016
+ resolve: async (alertId) => {
182017
+ const update = this.ctx.api.alerts?.update;
182018
+ if (update === void 0) return;
182019
+ try {
182020
+ await update.mutate({
182021
+ alertId,
182022
+ patch: {
182023
+ status: "completed",
182024
+ read: true
182025
+ }
182026
+ });
182027
+ this.ctx.logger.info("robot camera: PIN alert resolved", {
182028
+ tags: { deviceId: this.id },
182029
+ meta: { alertId }
182030
+ });
182031
+ } catch (err) {
182032
+ this.ctx.logger.warn("robot camera: PIN alert resolve failed", {
182033
+ tags: { deviceId: this.id },
182034
+ meta: {
182035
+ alertId,
182036
+ error: errMsg(err)
182037
+ }
182038
+ });
182039
+ }
182040
+ }
182041
+ });
181866
182042
  openStream() {
181867
182043
  if (this.opening !== null) return this.opening;
182044
+ if (this.pinAttention.blocked) return Promise.reject(/* @__PURE__ */ new Error("camera PIN unusable — waiting for the operator"));
181868
182045
  const run = (async () => {
181869
182046
  const startedAt = Date.now();
181870
182047
  try {
@@ -181896,6 +182073,7 @@ var DreameCameraDevice = class extends DreameChildDevice {
181896
182073
  this.publishBattery();
181897
182074
  this.publishCatalog();
181898
182075
  await this.publishRelay(this.session);
182076
+ this.pinAttention.succeeded();
181899
182077
  this.ctx.logger.info("robot camera: monitor open, RTP server published", {
181900
182078
  tags: { deviceId: this.id },
181901
182079
  meta: {
@@ -181912,6 +182090,9 @@ var DreameCameraDevice = class extends DreameChildDevice {
181912
182090
  error: errMsg(err)
181913
182091
  }
181914
182092
  });
182093
+ const code = this.config.get("navigationAccessCode");
182094
+ const refusal = classifyPinRefusal(err, code !== void 0 && code !== "");
182095
+ if (refusal !== null) await this.pinAttention.refused(refusal);
181915
182096
  throw err;
181916
182097
  }
181917
182098
  })();
@@ -182129,6 +182310,7 @@ var DreameCameraDevice = class extends DreameChildDevice {
182129
182310
  }
182130
182311
  }
182131
182312
  if (!("navigationAccessCode" in patch)) return;
182313
+ await this.pinAttention.settingChanged();
182132
182314
  await this.closeStream("removed");
182133
182315
  this.controller = null;
182134
182316
  this.controllerHandle = null;
@@ -183948,8 +184130,6 @@ var DreameVacuumMapDevice = class extends DreameChildDevice {
183948
184130
  id: "map-style",
183949
184131
  title: "Map Style",
183950
184132
  titleKey: "provider-dreame:schema.s_map-style.title",
183951
- description: "Colour scheme and overlays for the rendered cleaning map.",
183952
- descriptionKey: "provider-dreame:schema.s_map-style.description",
183953
184133
  columns: 2,
183954
184134
  fields: [
183955
184135
  {
package/dist/addon.mjs CHANGED
@@ -41,7 +41,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
41
41
  var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
42
42
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
43
43
  //#endregion
44
- //#region ../types/dist/event-category-7pZEBmVg.mjs
44
+ //#region ../types/dist/event-category-BfJwUd2S.mjs
45
45
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
46
46
  EventCategory["SystemBoot"] = "system.boot";
47
47
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -358,6 +358,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
358
358
  */
359
359
  EventCategory["PipelineDetectionBlind"] = "pipeline.detection-blind";
360
360
  /**
361
+ * A camera's motion source has fired constantly with no subject for an hour
362
+ * (motion duty or session count over the bar, zero detections) — it is not
363
+ * blind, its motion zones or sensitivity want tuning (D907). Paired with
364
+ * {@link PipelineMotionCalm}.
365
+ */
366
+ EventCategory["PipelineMotionNoisy"] = "pipeline.motion-noisy";
367
+ /** The recovery edge of {@link PipelineMotionNoisy}: the motion fell back, or a detection arrived. */
368
+ EventCategory["PipelineMotionCalm"] = "pipeline.motion-calm";
369
+ /**
361
370
  * Per-camera pipeline config was mutated by the orchestrator
362
371
  * (3-level settings change via `setAgentAddonDefaults` /
363
372
  * `setCameraStepToggle` / `setCameraPipelineForAgent` or a
@@ -5464,7 +5473,7 @@ var ZodIssueCode$1 = {
5464
5473
  var ZodFirstPartyTypeKind$1;
5465
5474
  ZodFirstPartyTypeKind$1 || (ZodFirstPartyTypeKind$1 = {});
5466
5475
  //#endregion
5467
- //#region ../types/dist/sleep-CueztlR1.mjs
5476
+ //#region ../types/dist/sleep-DJFUfKvA.mjs
5468
5477
  /**
5469
5478
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5470
5479
  * window to float samples (D455).
@@ -7993,7 +8002,7 @@ var EntryDescriptorSchema = object({
7993
8002
  /** JSON Schema of the entry body; `secret: true` marks a password field. */
7994
8003
  fields: record(string(), unknown())
7995
8004
  }).strict();
7996
- /** What crosses the wire and sits in the engine's catalog: JSON only, no Zod. */
8005
+ /** What crosses the wire from an addon: JSON only, no Zod. An unknown key is refused. */
7997
8006
  var RecordKindDeclarationSchema = object({
7998
8007
  database: _enum(RECORD_DATABASES).default("settings"),
7999
8008
  class: _enum(RECORD_CLASSES),
@@ -8012,14 +8021,6 @@ var RecordKindDeclarationSchema = object({
8012
8021
  secretPaths: array(string().min(1)).default([]),
8013
8022
  /** The id key of each array that carries a secret (`{ headers: 'name' }`). */
8014
8023
  secretArrayKeys: record(string(), string()).default({}),
8015
- /**
8016
- * RETIRED (D900): the legacy table a kind adopted from during the records
8017
- * migration. Nothing declares it any more and nothing reads it; the schema
8018
- * still ACCEPTS it because the catalog rows the migration wrote carry it
8019
- * (an orphaned kind is never redeclared) and an addon built before the
8020
- * removal still sends it. Never set it.
8021
- */
8022
- adoptsFrom: string().min(1).optional(),
8023
8024
  /** Kinds that must agree through `transact` sit in one file under one family name. */
8024
8025
  transactFamily: string().min(1).optional(),
8025
8026
  entry: EntryDescriptorSchema.optional()
@@ -12711,17 +12712,6 @@ object({
12711
12712
  createdAt: number(),
12712
12713
  updatedAt: number()
12713
12714
  });
12714
- object({
12715
- id: string(),
12716
- label: string(),
12717
- isAdmin: boolean().default(false),
12718
- allowedProviders: AllowedProviderSchema,
12719
- allowedDevices: AllowedDevicesSchema,
12720
- tokenHash: string(),
12721
- tokenPrefix: string(),
12722
- createdAt: number(),
12723
- lastUsedAt: number().optional()
12724
- });
12725
12715
  object({
12726
12716
  id: string(),
12727
12717
  userId: string(),
@@ -16703,16 +16693,36 @@ var RecordKindStateSchema = _enum([
16703
16693
  var DeclareKindResultSchema = object({
16704
16694
  kindId: number().int(),
16705
16695
  state: RecordKindStateSchema,
16706
- createdIndexes: array(string()),
16707
- /**
16708
- * RETIRED (D900): always `null`. It carried the records-migration boot guard;
16709
- * it stays on the wire, null, because an addon built before the removal reads
16710
- * an ABSENT field as "adoption pending" and refuses to start.
16711
- */
16712
- adoptionPending: _null()
16696
+ createdIndexes: array(string())
16713
16697
  });
16714
16698
  /** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
16715
16699
  var SecretRefsList = array(string());
16700
+ /**
16701
+ * One op of a `transact`: the single-row writes, each on a kind of the caller.
16702
+ * All ops of one call share ONE database file and ONE `transactFamily`.
16703
+ */
16704
+ var RecordTransactOpSchema = discriminatedUnion("op", [
16705
+ object({
16706
+ op: literal("put"),
16707
+ kind: RecordKindNameSchema,
16708
+ record: RecordInputSchema,
16709
+ ifRev: number().int().min(0).optional()
16710
+ }),
16711
+ object({
16712
+ op: literal("patch"),
16713
+ kind: RecordKindNameSchema,
16714
+ id: RecordIdSchema,
16715
+ data: RecordBodySchema,
16716
+ at: number().int().nullable().optional(),
16717
+ expiresAt: number().int().nullable().optional(),
16718
+ ifRev: number().int().min(1).optional()
16719
+ }),
16720
+ object({
16721
+ op: literal("delete"),
16722
+ kind: RecordKindNameSchema,
16723
+ id: RecordIdSchema
16724
+ })
16725
+ ]);
16716
16726
  var RecordSecretRefsSchema = record(string(), record(string(), string()));
16717
16727
  method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
16718
16728
  kind: "mutation",
@@ -16819,6 +16829,16 @@ method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema)
16819
16829
  }), {
16820
16830
  kind: "mutation",
16821
16831
  auth: "admin"
16832
+ }), method(object({ ops: array(RecordTransactOpSchema).min(1).max(RECORD_BATCH_MAX) }), object({
16833
+ results: array(object({
16834
+ rev: number().int().nullable(),
16835
+ deleted: boolean()
16836
+ })),
16837
+ releasedRefs: SecretRefsList,
16838
+ secretRefs: SecretRefsList
16839
+ }), {
16840
+ kind: "mutation",
16841
+ auth: "admin"
16822
16842
  }), method(object({
16823
16843
  deviceId: number().int(),
16824
16844
  kinds: array(RecordKindNameSchema).optional()
@@ -42442,9 +42462,13 @@ var NcSystemEventKindSchema = _enum([
42442
42462
  "node-updating",
42443
42463
  "node-inference-unavailable",
42444
42464
  "detection-blind",
42465
+ "motion-noisy",
42466
+ "motion-calm",
42445
42467
  "recording-stalled",
42446
42468
  "recording-healthy",
42447
42469
  "recording-degraded",
42470
+ "device-attention-required",
42471
+ "device-attention-resolved",
42448
42472
  "storage-location-low",
42449
42473
  "storage-location-critical",
42450
42474
  "storage-location-ok",
@@ -48005,7 +48029,6 @@ var RecommendationReasonSchema = discriminatedUnion("kind", [
48005
48029
  modelId: string()
48006
48030
  }),
48007
48031
  object({ kind: literal("cpu-device") }),
48008
- object({ kind: literal("coral-untouched") }),
48009
48032
  object({ kind: literal("device-disabled") }),
48010
48033
  object({ kind: literal("device-unavailable") }),
48011
48034
  object({ kind: literal("node-offline") }),
@@ -48103,11 +48126,6 @@ var AutoTuneWarningSchema = discriminatedUnion("kind", [
48103
48126
  kind: literal("detector-store-unreadable"),
48104
48127
  error: string()
48105
48128
  }),
48106
- object({
48107
- kind: literal("coral-enabled"),
48108
- nodeId: string(),
48109
- deviceKey: string()
48110
- }),
48111
48129
  object({
48112
48130
  kind: literal("custom-pin-kept"),
48113
48131
  nodeId: string(),
@@ -55793,6 +55811,16 @@ var DishwasherStatusSchema = object({
55793
55811
  powerState: string().nullable(),
55794
55812
  saltLevel: DishwasherConsumableLevelSchema.nullable(),
55795
55813
  rinseAidLevel: DishwasherConsumableLevelSchema.nullable(),
55814
+ /**
55815
+ * True while the appliance asks for its filter to be cleaned (smart-filter, filter-system or
55816
+ * combined care-and-filter reminder raised); false when every such reminder reads clear; null
55817
+ * when none is reported. A cleared reminder is not an unknown one.
55818
+ */
55819
+ filterCleaningDue: boolean().nullable(),
55820
+ /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
55821
+ machineCareDue: boolean().nullable(),
55822
+ /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
55823
+ machineCareRemainingRuns: number().int().nonnegative().nullable(),
55796
55824
  /** Option states: null when the appliance does not expose the option or has not said. */
55797
55825
  halfLoad: boolean().nullable(),
55798
55826
  hygienePlus: boolean().nullable(),
@@ -64591,16 +64619,6 @@ var UpdateUserInputSchema = object({
64591
64619
  allowedDevices: record(string(), union([array(string()), literal("*")])).optional(),
64592
64620
  scopes: array(TokenScopeSchema).optional()
64593
64621
  });
64594
- var ApiKeySummarySchema = object({
64595
- id: string(),
64596
- label: string(),
64597
- isAdmin: boolean().default(false),
64598
- allowedProviders: union([array(string()), literal("*")]).optional(),
64599
- allowedDevices: record(string(), union([array(string()), literal("*")])).optional(),
64600
- tokenPrefix: string(),
64601
- createdAt: number(),
64602
- lastUsedAt: number().optional()
64603
- });
64604
64622
  var OauthSessionSummarySchema = object({
64605
64623
  id: string(),
64606
64624
  userId: string(),
@@ -64622,8 +64640,7 @@ var TotpStatusSchema = object({
64622
64640
  confirmedAt: number().nullable()
64623
64641
  });
64624
64642
  /**
64625
- * user-management — singleton capability for user accounts and the
64626
- * legacy API-key rows.
64643
+ * user-management — singleton capability for user accounts.
64627
64644
  *
64628
64645
  * v2 model: every method gated by `auth: 'admin'`. Admin-only delegation
64629
64646
  * — the subset check on `setUserScopes` is moot
@@ -64679,10 +64696,6 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
64679
64696
  }), UserSummarySchema.extend({ passwordHash: string() }).nullable(), {
64680
64697
  kind: "mutation",
64681
64698
  access: "view"
64682
- }), method(_void(), array(ApiKeySummarySchema), { auth: "admin" }), method(object({ id: string() }), object({ success: literal(true) }), {
64683
- kind: "mutation",
64684
- auth: "admin",
64685
- access: "delete"
64686
64699
  }), method(object({ userId: string() }), TotpSetupResultSchema, {
64687
64700
  kind: "mutation",
64688
64701
  auth: "admin",
@@ -74154,6 +74167,12 @@ Object.freeze({
74154
74167
  addonId: null,
74155
74168
  access: "view"
74156
74169
  },
74170
+ "records.transact": {
74171
+ capName: "records",
74172
+ capScope: "system",
74173
+ addonId: null,
74174
+ access: "create"
74175
+ },
74157
74176
  "sceneMonitor.captureReference": {
74158
74177
  capName: "scene-monitor",
74159
74178
  capScope: "device",
@@ -75486,12 +75505,6 @@ Object.freeze({
75486
75505
  addonId: null,
75487
75506
  access: "view"
75488
75507
  },
75489
- "userManagement.listApiKeys": {
75490
- capName: "user-management",
75491
- capScope: "system",
75492
- addonId: null,
75493
- access: "view"
75494
- },
75495
75508
  "userManagement.listExternalIdentities": {
75496
75509
  capName: "user-management",
75497
75510
  capScope: "system",
@@ -75546,12 +75559,6 @@ Object.freeze({
75546
75559
  addonId: null,
75547
75560
  access: "create"
75548
75561
  },
75549
- "userManagement.revokeApiKey": {
75550
- capName: "user-management",
75551
- capScope: "system",
75552
- addonId: null,
75553
- access: "delete"
75554
- },
75555
75562
  "userManagement.revokeOauthSession": {
75556
75563
  capName: "user-management",
75557
75564
  capScope: "system",
@@ -78916,9 +78923,13 @@ new Map(Object.entries({
78916
78923
  "stream-online": "device-events",
78917
78924
  "stream-offline": "device-events",
78918
78925
  "detection-blind": "device-events",
78926
+ "motion-noisy": "device-events",
78927
+ "motion-calm": "device-events",
78919
78928
  "recording-stalled": "device-events",
78920
78929
  "recording-healthy": "device-events",
78921
78930
  "recording-degraded": "device-events",
78931
+ "device-attention-required": "device-events",
78932
+ "device-attention-resolved": "device-events",
78922
78933
  "node-online": "system",
78923
78934
  "node-offline": "system",
78924
78935
  "node-restarting": "system",
@@ -79001,6 +79012,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
79001
79012
  label: "Detecting nothing",
79002
79013
  kinds: ["detection-blind"]
79003
79014
  },
79015
+ {
79016
+ id: "motion-noise",
79017
+ family: "device-events",
79018
+ label: "Motion without a subject",
79019
+ kinds: ["motion-noisy", "motion-calm"]
79020
+ },
79004
79021
  {
79005
79022
  id: "recording",
79006
79023
  family: "device-events",
@@ -79011,6 +79028,12 @@ var NC_SYSTEM_EVENT_KIND_GROUP_SPECS = [
79011
79028
  "recording-degraded"
79012
79029
  ]
79013
79030
  },
79031
+ {
79032
+ id: "device-attention",
79033
+ family: "device-events",
79034
+ label: "Needs attention",
79035
+ kinds: ["device-attention-required", "device-attention-resolved"]
79036
+ },
79014
79037
  {
79015
79038
  id: "nodes",
79016
79039
  family: "system",
@@ -79150,6 +79173,7 @@ var ALERT_EPISODE = [
79150
79173
  "alert-error",
79151
79174
  "alert-critical-resolved"
79152
79175
  ];
79176
+ var DEVICE_ATTENTION = ["device-attention-required", "device-attention-resolved"];
79153
79177
  var UPDATES = [
79154
79178
  "addon-update-available",
79155
79179
  "server-update-available",
@@ -79179,9 +79203,12 @@ var DEVICE_EVENTS = [
79179
79203
  "stream-online",
79180
79204
  "stream-offline",
79181
79205
  "detection-blind",
79206
+ "motion-noisy",
79207
+ "motion-calm",
79182
79208
  "recording-stalled",
79183
79209
  "recording-healthy",
79184
79210
  "recording-degraded",
79211
+ ...DEVICE_ATTENTION,
79185
79212
  ...ALARM
79186
79213
  ];
79187
79214
  /** Every kind a summary may speak about — the two summarisable families. */
@@ -79349,7 +79376,9 @@ var STATE_SUMMARY_VARS = [
79349
79376
  [...RULE_TEXT],
79350
79377
  [...RULE_TEXT],
79351
79378
  [...RULE_TEXT],
79352
- ([...RULE_TEXT], [...ALERT_EPISODE]),
79379
+ [...RULE_TEXT],
79380
+ [...RULE_TEXT],
79381
+ ([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
79353
79382
  [...RULE_TEXT],
79354
79383
  [...RULE_TEXT],
79355
79384
  [...RULE_TEXT],
@@ -79371,11 +79400,11 @@ var STATE_SUMMARY_VARS = [
79371
79400
  [...RULE_TEXT],
79372
79401
  [...RULE_TEXT],
79373
79402
  [...RULE_TEXT],
79403
+ ([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
79374
79404
  ([...RULE_TEXT], [...ALERT_EPISODE]),
79405
+ ([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
79375
79406
  ([...RULE_TEXT], [...ALERT_EPISODE]),
79376
- ([...RULE_TEXT], [...ALERT_EPISODE]),
79377
- ([...RULE_TEXT], [...ALERT_EPISODE]),
79378
- ([...RULE_TEXT], [...ALERT_EPISODE]),
79407
+ ([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
79379
79408
  [...RULE_TEXT],
79380
79409
  [...RULE_TEXT],
79381
79410
  [...RULE_TEXT],
@@ -79451,6 +79480,15 @@ new Map([
79451
79480
  snapshotLabel: "Tanks low or full now",
79452
79481
  snapshotSection: "tanks"
79453
79482
  },
79483
+ {
79484
+ id: "motion-noise",
79485
+ family: "device-events",
79486
+ label: "Motion without a subject (constant / calm again)",
79487
+ bad: ["motion-noisy"],
79488
+ recovery: ["motion-calm"],
79489
+ snapshotLabel: "Cameras with constant motion now",
79490
+ snapshotSection: null
79491
+ },
79454
79492
  {
79455
79493
  id: "recording-health",
79456
79494
  family: "device-events",
@@ -79460,6 +79498,15 @@ new Map([
79460
79498
  snapshotLabel: "Recordings stalled now",
79461
79499
  snapshotSection: null
79462
79500
  },
79501
+ {
79502
+ id: "device-attention",
79503
+ family: "device-events",
79504
+ label: "Device needs attention (raised / resolved)",
79505
+ bad: ["device-attention-required"],
79506
+ recovery: ["device-attention-resolved"],
79507
+ snapshotLabel: "Devices needing attention now",
79508
+ snapshotSection: null
79509
+ },
79463
79510
  {
79464
79511
  id: "node-connectivity",
79465
79512
  family: "system",
@@ -132946,8 +132993,6 @@ function buildConnectionFormSchema() {
132946
132993
  id: "credentials",
132947
132994
  title: "Dreamehome account",
132948
132995
  titleKey: "provider-dreame:connection.s_credentials.title",
132949
- description: "Sign in with your Dreamehome (Dreame app) account credentials.",
132950
- descriptionKey: "provider-dreame:connection.s_credentials.description",
132951
132996
  columns: 1,
132952
132997
  fields: [{
132953
132998
  type: "text",
@@ -132969,8 +133014,6 @@ function buildConnectionFormSchema() {
132969
133014
  id: "region",
132970
133015
  title: "Region",
132971
133016
  titleKey: "provider-dreame:connection.s_region.title",
132972
- description: "Select the Dreame cloud region your account is registered in.",
132973
- descriptionKey: "provider-dreame:connection.s_region.description",
132974
133017
  columns: 1,
132975
133018
  fields: [{
132976
133019
  type: "select",
@@ -135389,6 +135432,81 @@ function rfcUrl(host, port) {
135389
135432
  return `tcp://${host}:${String(port)}`;
135390
135433
  }
135391
135434
  //#endregion
135435
+ //#region src/devices/dreame-pin-attention.ts
135436
+ var REFUSED_CODE_MINUS_ONE = /startMonitor refused \(code -1\)/;
135437
+ /** The refusal an error carries, or `null` when it is some other failure. */
135438
+ function classifyPinRefusal(err, accessCodeSet) {
135439
+ const message = err instanceof Error ? err.message : String(err);
135440
+ if (!REFUSED_CODE_MINUS_ONE.test(message)) return null;
135441
+ return accessCodeSet ? "rejected" : "missing";
135442
+ }
135443
+ function pinAlertId(deviceId) {
135444
+ return `dreame.pin-required:${String(deviceId)}`;
135445
+ }
135446
+ function buildPinAlert(deviceId, deviceName, reason) {
135447
+ const now = Date.now();
135448
+ return {
135449
+ id: pinAlertId(deviceId),
135450
+ category: "device.credentials-required",
135451
+ severity: "critical",
135452
+ title: reason === "missing" ? "Camera PIN required" : "Camera PIN rejected",
135453
+ message: reason === "missing" ? `${deviceName} needs its camera PIN before its video can open. Enter it in the device settings.` : `${deviceName} refused the camera PIN that is set. Enter the correct PIN in the device settings.`,
135454
+ action: {
135455
+ label: "Enter PIN",
135456
+ href: `/devices/${String(deviceId)}`,
135457
+ navigation: "app"
135458
+ },
135459
+ status: "active",
135460
+ read: false,
135461
+ createdAt: now,
135462
+ updatedAt: now,
135463
+ source: {
135464
+ type: "device",
135465
+ id: String(deviceId)
135466
+ },
135467
+ metadata: { episodeKey: reason }
135468
+ };
135469
+ }
135470
+ /**
135471
+ * One device's PIN episode. It raises the alert ONCE per episode, and while the
135472
+ * episode is open it tells the wake path to stand down: a refusal that needs the
135473
+ * operator is terminal until the PIN setting changes, so the broker's 30 s
135474
+ * re-dial must not re-open the monitor and fail again.
135475
+ */
135476
+ var PinAttention = class {
135477
+ ports;
135478
+ open = null;
135479
+ constructor(ports) {
135480
+ this.ports = ports;
135481
+ }
135482
+ /** True while the PIN is known to be unusable — the wake must not try. */
135483
+ get blocked() {
135484
+ return this.open !== null;
135485
+ }
135486
+ /** A wake failed. Raises the alert on the first refusal of an episode. */
135487
+ async refused(reason) {
135488
+ if (this.open !== null) return "already-open";
135489
+ this.open = reason;
135490
+ await this.ports.emit(buildPinAlert(this.ports.deviceId, this.ports.deviceName(), reason));
135491
+ return "raised";
135492
+ }
135493
+ /** A monitor opened: the PIN works, the episode is over. */
135494
+ async succeeded() {
135495
+ return this.settingChanged();
135496
+ }
135497
+ /**
135498
+ * The PIN setting changed: the episode is no longer known to be terminal, so
135499
+ * the next wake tries again. The alert is resolved here — a still-wrong PIN
135500
+ * raises a fresh episode on that wake.
135501
+ */
135502
+ async settingChanged() {
135503
+ if (this.open === null) return false;
135504
+ this.open = null;
135505
+ await this.ports.resolve(pinAlertId(this.ports.deviceId));
135506
+ return true;
135507
+ }
135508
+ };
135509
+ //#endregion
135392
135510
  //#region src/devices/dreame-navigation-actions.ts
135393
135511
  /** Forward throttle magnitude for a full-forward nudge (device speed units). */
135394
135512
  var FORWARD_SPEED = 200;
@@ -181580,15 +181698,13 @@ var CAMERA_SETTINGS_SCHEMA = { sections: [{
181580
181698
  id: "camera-backend",
181581
181699
  title: "Video backend",
181582
181700
  titleKey: "provider-dreame:camera_settings.s_camera-backend.title",
181583
- description: "Which cloud video service this robot streams through. The two are not interchangeable: only the Aliyun path is implemented here — a robot on Tencent can be seen in the Dreame app but not by CamStack, because Tencent offers it a proprietary P2P plane we do not speak. Changing this asks the ROBOT to switch and waits until its SDK reports ready; the robot is the authority on where it actually is.",
181701
+ description: "Only the Aliyun path is implemented; a robot on Tencent cannot be viewed by CamStack. Changing this asks the robot to switch.",
181584
181702
  descriptionKey: "provider-dreame:camera_settings.s_camera-backend.description",
181585
181703
  fields: [{
181586
181704
  type: "select",
181587
181705
  key: "videoVendor",
181588
181706
  label: "Backend",
181589
181707
  labelKey: "provider-dreame:camera_settings.s_camera-backend.videoVendor.label",
181590
- description: "Aliyun streams through a plain relay and works today. Tencent is offered so the other path can be tested once it exists.",
181591
- descriptionKey: "provider-dreame:camera_settings.s_camera-backend.videoVendor.description",
181592
181708
  options: [{
181593
181709
  value: "ali",
181594
181710
  label: "Aliyun LinkVisual (supported)",
@@ -181774,6 +181890,10 @@ var DreameCameraDevice = class extends DreameChildDevice {
181774
181890
  const data = event.data;
181775
181891
  if ((typeof data === "object" && data !== null && "deviceId" in data ? data.deviceId : null) !== this.id) return;
181776
181892
  if (this.session === null) {
181893
+ if (this.pinAttention.blocked) {
181894
+ this.ctx.logger.debug("robot camera: wake skipped — the camera PIN needs the operator", { tags: { deviceId: this.id } });
181895
+ return;
181896
+ }
181777
181897
  this.openStream().catch(() => void 0);
181778
181898
  return;
181779
181899
  }
@@ -181860,8 +181980,65 @@ var DreameCameraDevice = class extends DreameChildDevice {
181860
181980
  durationMs: Date.now() - startedAt
181861
181981
  };
181862
181982
  }
181983
+ /**
181984
+ * The PIN episode (D911): a monitor refused for want of the camera PIN needs
181985
+ * the operator, so it raises ONE critical alert and the wake stands down
181986
+ * until the PIN setting changes.
181987
+ */
181988
+ pinAttention = new PinAttention({
181989
+ deviceId: this.id,
181990
+ deviceName: () => this.name,
181991
+ emit: async (alert) => {
181992
+ const emit = this.ctx.api.alerts?.emit;
181993
+ if (emit === void 0) {
181994
+ this.ctx.logger.warn("robot camera: PIN alert skipped — alerts capability missing", { tags: { deviceId: this.id } });
181995
+ return;
181996
+ }
181997
+ try {
181998
+ await emit.mutate(alert);
181999
+ this.ctx.logger.warn("robot camera: PIN alert raised — the wake stops until the PIN changes", {
182000
+ tags: { deviceId: this.id },
182001
+ meta: {
182002
+ alertId: alert.id,
182003
+ episodeKey: alert.metadata?.["episodeKey"]
182004
+ }
182005
+ });
182006
+ } catch (err) {
182007
+ this.ctx.logger.warn("robot camera: PIN alert emit failed", {
182008
+ tags: { deviceId: this.id },
182009
+ meta: { error: errMsg(err) }
182010
+ });
182011
+ }
182012
+ },
182013
+ resolve: async (alertId) => {
182014
+ const update = this.ctx.api.alerts?.update;
182015
+ if (update === void 0) return;
182016
+ try {
182017
+ await update.mutate({
182018
+ alertId,
182019
+ patch: {
182020
+ status: "completed",
182021
+ read: true
182022
+ }
182023
+ });
182024
+ this.ctx.logger.info("robot camera: PIN alert resolved", {
182025
+ tags: { deviceId: this.id },
182026
+ meta: { alertId }
182027
+ });
182028
+ } catch (err) {
182029
+ this.ctx.logger.warn("robot camera: PIN alert resolve failed", {
182030
+ tags: { deviceId: this.id },
182031
+ meta: {
182032
+ alertId,
182033
+ error: errMsg(err)
182034
+ }
182035
+ });
182036
+ }
182037
+ }
182038
+ });
181863
182039
  openStream() {
181864
182040
  if (this.opening !== null) return this.opening;
182041
+ if (this.pinAttention.blocked) return Promise.reject(/* @__PURE__ */ new Error("camera PIN unusable — waiting for the operator"));
181865
182042
  const run = (async () => {
181866
182043
  const startedAt = Date.now();
181867
182044
  try {
@@ -181893,6 +182070,7 @@ var DreameCameraDevice = class extends DreameChildDevice {
181893
182070
  this.publishBattery();
181894
182071
  this.publishCatalog();
181895
182072
  await this.publishRelay(this.session);
182073
+ this.pinAttention.succeeded();
181896
182074
  this.ctx.logger.info("robot camera: monitor open, RTP server published", {
181897
182075
  tags: { deviceId: this.id },
181898
182076
  meta: {
@@ -181909,6 +182087,9 @@ var DreameCameraDevice = class extends DreameChildDevice {
181909
182087
  error: errMsg(err)
181910
182088
  }
181911
182089
  });
182090
+ const code = this.config.get("navigationAccessCode");
182091
+ const refusal = classifyPinRefusal(err, code !== void 0 && code !== "");
182092
+ if (refusal !== null) await this.pinAttention.refused(refusal);
181912
182093
  throw err;
181913
182094
  }
181914
182095
  })();
@@ -182126,6 +182307,7 @@ var DreameCameraDevice = class extends DreameChildDevice {
182126
182307
  }
182127
182308
  }
182128
182309
  if (!("navigationAccessCode" in patch)) return;
182310
+ await this.pinAttention.settingChanged();
182129
182311
  await this.closeStream("removed");
182130
182312
  this.controller = null;
182131
182313
  this.controllerHandle = null;
@@ -183945,8 +184127,6 @@ var DreameVacuumMapDevice = class extends DreameChildDevice {
183945
184127
  id: "map-style",
183946
184128
  title: "Map Style",
183947
184129
  titleKey: "provider-dreame:schema.s_map-style.title",
183948
- description: "Colour scheme and overlays for the rendered cleaning map.",
183949
- descriptionKey: "provider-dreame:schema.s_map-style.description",
183950
184130
  columns: 2,
183951
184131
  fields: [
183952
184132
  {
package/i18n/en.json CHANGED
@@ -1,24 +1,20 @@
1
1
  {
2
2
  "connection.s_credentials.title": "Dreamehome account",
3
- "connection.s_credentials.description": "Sign in with your Dreamehome (Dreame app) account credentials.",
4
3
  "connection.s_credentials.username.label": "Email / username",
5
4
  "connection.s_credentials.password.label": "Password",
6
5
  "connection.s_region.title": "Region",
7
- "connection.s_region.description": "Select the Dreame cloud region your account is registered in.",
8
6
  "connection.s_region.region.label": "Region",
9
7
  "connection.s_advanced.title": "Advanced",
10
8
  "connection.s_advanced.pollIntervalMs.label": "Poll fallback interval (ms, 0 = disabled)",
11
9
  "camera_settings.s_camera-backend.title": "Video backend",
12
- "camera_settings.s_camera-backend.description": "Which cloud video service this robot streams through. The two are not interchangeable: only the Aliyun path is implemented here — a robot on Tencent can be seen in the Dreame app but not by CamStack, because Tencent offers it a proprietary P2P plane we do not speak. Changing this asks the ROBOT to switch and waits until its SDK reports ready; the robot is the authority on where it actually is.",
10
+ "camera_settings.s_camera-backend.description": "Only the Aliyun path is implemented; a robot on Tencent cannot be viewed by CamStack. Changing this asks the robot to switch.",
13
11
  "camera_settings.s_camera-backend.videoVendor.label": "Backend",
14
- "camera_settings.s_camera-backend.videoVendor.description": "Aliyun streams through a plain relay and works today. Tencent is offered so the other path can be tested once it exists.",
15
12
  "camera_settings.s_camera-backend.videoVendor.o_ali.label": "Aliyun LinkVisual (supported)",
16
13
  "camera_settings.s_camera-backend.videoVendor.o_tx.label": "Tencent IoT-Video (not implemented yet)",
17
14
  "camera_settings.s_camera-access.title": "Camera access",
18
15
  "camera_settings.s_camera-access.description": "The privacy code set when the robot camera was paired. The robot refuses to open its camera without it.",
19
16
  "camera_settings.s_camera-access.navigationAccessCode.label": "Camera access code",
20
17
  "schema.s_map-style.title": "Map Style",
21
- "schema.s_map-style.description": "Colour scheme and overlays for the rendered cleaning map.",
22
18
  "schema.s_map-style.mapColorScheme.label": "Colour scheme",
23
19
  "schema.s_map-style.mapShowPath.label": "Cleaning path",
24
20
  "schema.s_map-style.mapShowRobot.label": "Robot",
package/i18n/it.json CHANGED
@@ -1,24 +1,20 @@
1
1
  {
2
2
  "connection.s_credentials.title": "Account Dreamehome",
3
- "connection.s_credentials.description": "Accedi con le credenziali del tuo account Dreamehome (app Dreame).",
4
3
  "connection.s_credentials.username.label": "Email / nome utente",
5
4
  "connection.s_credentials.password.label": "Password",
6
5
  "connection.s_region.title": "Regione",
7
- "connection.s_region.description": "Seleziona la regione del cloud Dreame in cui è registrato il tuo account.",
8
6
  "connection.s_region.region.label": "Regione",
9
7
  "connection.s_advanced.title": "Avanzate",
10
8
  "connection.s_advanced.pollIntervalMs.label": "Intervallo di polling di ripiego (ms, 0 = disattivato)",
11
9
  "camera_settings.s_camera-backend.title": "Servizio video",
12
- "camera_settings.s_camera-backend.description": "Il servizio video cloud attraverso cui questo robot trasmette. I due non sono intercambiabili: qui è implementato solo il percorso Aliyun. Un robot su Tencent si vede nell'app Dreame ma non da CamStack, perché Tencent gli offre un piano P2P proprietario che non parliamo. Cambiare questa impostazione chiede al ROBOT di passare e attende che il suo SDK segnali pronto: è il robot a decidere dove si trova davvero.",
10
+ "camera_settings.s_camera-backend.description": "È implementato solo il percorso Aliyun; un robot su Tencent non può essere visto da CamStack. Il cambio chiede al robot di passare.",
13
11
  "camera_settings.s_camera-backend.videoVendor.label": "Servizio",
14
- "camera_settings.s_camera-backend.videoVendor.description": "Aliyun trasmette tramite un relay semplice e funziona già. Tencent è previsto per poter testare l'altro percorso quando esisterà.",
15
12
  "camera_settings.s_camera-backend.videoVendor.o_ali.label": "Aliyun LinkVisual (supportato)",
16
13
  "camera_settings.s_camera-backend.videoVendor.o_tx.label": "Tencent IoT-Video (non ancora implementato)",
17
14
  "camera_settings.s_camera-access.title": "Accesso alla telecamera",
18
15
  "camera_settings.s_camera-access.description": "Il codice privacy impostato quando la telecamera del robot è stata associata. Il robot non apre la telecamera senza di esso.",
19
16
  "camera_settings.s_camera-access.navigationAccessCode.label": "Codice di accesso alla telecamera",
20
17
  "schema.s_map-style.title": "Stile della mappa",
21
- "schema.s_map-style.description": "Schema di colori e sovrapposizioni per la mappa di pulizia.",
22
18
  "schema.s_map-style.mapColorScheme.label": "Schema di colori",
23
19
  "schema.s_map-style.mapShowPath.label": "Percorso di pulizia",
24
20
  "schema.s_map-style.mapShowRobot.label": "Robot",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreame",
3
- "version": "0.2.148",
3
+ "version": "0.2.150",
4
4
  "description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
5
5
  "keywords": [
6
6
  "camstack",