@camstack/addon-import-alexa 0.2.179 → 0.2.181
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/CHANGELOG.md +12 -0
- package/dist/addon.js +539 -224
- package/dist/addon.mjs +539 -224
- package/package.json +2 -2
- package/python/alexa_gateway.py +34 -0
- package/python/alexa_protocol.py +4 -0
- package/python/test_alexa_gateway_echo.py +32 -1
package/dist/addon.js
CHANGED
|
@@ -834,7 +834,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
834
834
|
return EventCategory;
|
|
835
835
|
}({});
|
|
836
836
|
//#endregion
|
|
837
|
-
//#region ../types/dist/device-type-
|
|
837
|
+
//#region ../types/dist/device-type-D1fH5kaC.mjs
|
|
838
838
|
/**
|
|
839
839
|
import { errMsg } from '@camstack/types'
|
|
840
840
|
* Extract a human-readable message from an unknown error value.
|
|
@@ -1065,6 +1065,12 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
|
|
|
1065
1065
|
DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
|
|
1066
1066
|
/** Media player exposes stop distinct from pause. */
|
|
1067
1067
|
DeviceFeature["MediaPlayerStop"] = "media-player-stop";
|
|
1068
|
+
/** Media player exposes volume up / down in steps (an infrared TV has no level). */
|
|
1069
|
+
DeviceFeature["MediaPlayerVolumeStep"] = "media-player-volume-step";
|
|
1070
|
+
/** Media player exposes channel up / down in steps. */
|
|
1071
|
+
DeviceFeature["MediaPlayerChannelStep"] = "media-player-channel-step";
|
|
1072
|
+
/** Media player exposes power on / off. */
|
|
1073
|
+
DeviceFeature["MediaPlayerPower"] = "media-player-power";
|
|
1068
1074
|
/** Alarm panel requires a PIN code on arm/disarm. */
|
|
1069
1075
|
DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
|
|
1070
1076
|
/** Presence device carries GPS coordinates (lat/lng/accuracy) in
|
|
@@ -6038,7 +6044,7 @@ function number(params) {
|
|
|
6038
6044
|
return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
|
|
6039
6045
|
}
|
|
6040
6046
|
//#endregion
|
|
6041
|
-
//#region ../types/dist/sleep-
|
|
6047
|
+
//#region ../types/dist/sleep-DV4qxEyD.mjs
|
|
6042
6048
|
/**
|
|
6043
6049
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
6044
6050
|
* window to float samples (D455).
|
|
@@ -6117,6 +6123,22 @@ function buildAlawTable() {
|
|
|
6117
6123
|
}
|
|
6118
6124
|
buildUlawTable();
|
|
6119
6125
|
buildAlawTable();
|
|
6126
|
+
/**
|
|
6127
|
+
* An addon whose own migrations did not apply refuses to initialise (D987). The error is typed
|
|
6128
|
+
* so a runner hosting several addons can tell "this addon is blocked" from a crash and keep its
|
|
6129
|
+
* co-located siblings running. Recognised by NAME and shape, never `instanceof`: a forked addon
|
|
6130
|
+
* bundles its own copy of this module, so the class the runner sees is not the one that threw.
|
|
6131
|
+
*/
|
|
6132
|
+
var MIGRATIONS_BLOCKED_ERROR_NAME = "AddonMigrationsBlockedError";
|
|
6133
|
+
var AddonMigrationsBlockedError = class extends Error {
|
|
6134
|
+
/** The ledger ids that did not apply and block the owner. */
|
|
6135
|
+
migrationIds;
|
|
6136
|
+
constructor(migrationIds) {
|
|
6137
|
+
super(`migration(s) ${migrationIds.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
|
|
6138
|
+
this.name = MIGRATIONS_BLOCKED_ERROR_NAME;
|
|
6139
|
+
this.migrationIds = migrationIds;
|
|
6140
|
+
}
|
|
6141
|
+
};
|
|
6120
6142
|
/** Is this field's stored value a credential? */
|
|
6121
6143
|
function isSecretConfigField(field) {
|
|
6122
6144
|
if (field.type === "password") return true;
|
|
@@ -7770,7 +7792,7 @@ var BaseAddon = class {
|
|
|
7770
7792
|
startMigration: (input) => api.startMigration.mutate(input),
|
|
7771
7793
|
finishMigration: (input) => api.finishMigration.mutate(input)
|
|
7772
7794
|
}, migrations, this.ctx.logger);
|
|
7773
|
-
if (report.blockOwner.length > 0) throw new
|
|
7795
|
+
if (report.blockOwner.length > 0) throw new AddonMigrationsBlockedError(report.blockOwner);
|
|
7774
7796
|
return report;
|
|
7775
7797
|
}
|
|
7776
7798
|
/**
|
|
@@ -8110,6 +8132,10 @@ var BaseAddon = class {
|
|
|
8110
8132
|
const stored = keychain ? await this.readAddonStoreWithRetry() : {};
|
|
8111
8133
|
const patch = {};
|
|
8112
8134
|
for (const key of keys) {
|
|
8135
|
+
if (tree.get(key)?.kind === "array") {
|
|
8136
|
+
patch[key] = [];
|
|
8137
|
+
continue;
|
|
8138
|
+
}
|
|
8113
8139
|
const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
|
|
8114
8140
|
patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
|
|
8115
8141
|
}
|
|
@@ -11766,7 +11792,7 @@ object({
|
|
|
11766
11792
|
params: record(string(), union([string(), number$1()])).optional()
|
|
11767
11793
|
});
|
|
11768
11794
|
//#endregion
|
|
11769
|
-
//#region ../types/dist/composition-
|
|
11795
|
+
//#region ../types/dist/composition-CD1GdFuv.mjs
|
|
11770
11796
|
/**
|
|
11771
11797
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
11772
11798
|
*
|
|
@@ -12903,7 +12929,14 @@ var CompositionForwardCommandSchema = object({
|
|
|
12903
12929
|
method: string().min(1),
|
|
12904
12930
|
args: record(string().min(1), CompositionForwardArgSchema).optional()
|
|
12905
12931
|
});
|
|
12906
|
-
var CompositionCommandTargetSchema = discriminatedUnion("kind", [
|
|
12932
|
+
var CompositionCommandTargetSchema = discriminatedUnion("kind", [
|
|
12933
|
+
CompositionForwardCommandSchema,
|
|
12934
|
+
CompositionCodeSourceSchema,
|
|
12935
|
+
object({
|
|
12936
|
+
kind: literal("extension"),
|
|
12937
|
+
target: literal("remote")
|
|
12938
|
+
})
|
|
12939
|
+
]);
|
|
12907
12940
|
/** An item key inside an item-array cap (`consumables.items[<key>]`). */
|
|
12908
12941
|
var COMPOSITION_ITEM_KEY_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
12909
12942
|
/**
|
|
@@ -12948,7 +12981,7 @@ var CompositionSchema = object({
|
|
|
12948
12981
|
kind: literal("existing"),
|
|
12949
12982
|
device: CompositionSourceRefSchema
|
|
12950
12983
|
})]),
|
|
12951
|
-
features: array(CompositionFeatureSchema).min(
|
|
12984
|
+
features: array(CompositionFeatureSchema).min(0).max(16),
|
|
12952
12985
|
/**
|
|
12953
12986
|
* Present when the block is a helper template. Absent on a free
|
|
12954
12987
|
* composition (the room example, device 4603). The editor reads this,
|
|
@@ -19527,6 +19560,48 @@ var APPLIANCE_CAP_VOCABULARY = {
|
|
|
19527
19560
|
scope: "device",
|
|
19528
19561
|
noun: t("Media player", "Lettore multimediale"),
|
|
19529
19562
|
actions: [
|
|
19563
|
+
{
|
|
19564
|
+
id: "media.turnOn",
|
|
19565
|
+
method: "turnOn",
|
|
19566
|
+
label: t("Turn on", "Accendi"),
|
|
19567
|
+
phrase: t("Turn on {device}", "Accendi {device}"),
|
|
19568
|
+
icon: "power"
|
|
19569
|
+
},
|
|
19570
|
+
{
|
|
19571
|
+
id: "media.turnOff",
|
|
19572
|
+
method: "turnOff",
|
|
19573
|
+
label: t("Turn off", "Spegni"),
|
|
19574
|
+
phrase: t("Turn off {device}", "Spegni {device}"),
|
|
19575
|
+
icon: "power"
|
|
19576
|
+
},
|
|
19577
|
+
{
|
|
19578
|
+
id: "media.volumeUp",
|
|
19579
|
+
method: "volumeUp",
|
|
19580
|
+
label: t("Volume up", "Alza il volume"),
|
|
19581
|
+
phrase: t("Raise the volume of {device}", "Alza il volume di {device}"),
|
|
19582
|
+
icon: "volume-2"
|
|
19583
|
+
},
|
|
19584
|
+
{
|
|
19585
|
+
id: "media.volumeDown",
|
|
19586
|
+
method: "volumeDown",
|
|
19587
|
+
label: t("Volume down", "Abbassa il volume"),
|
|
19588
|
+
phrase: t("Lower the volume of {device}", "Abbassa il volume di {device}"),
|
|
19589
|
+
icon: "volume-2"
|
|
19590
|
+
},
|
|
19591
|
+
{
|
|
19592
|
+
id: "media.channelUp",
|
|
19593
|
+
method: "channelUp",
|
|
19594
|
+
label: t("Channel up", "Canale successivo"),
|
|
19595
|
+
phrase: t("Go to the next channel on {device}", "Passa al canale successivo su {device}"),
|
|
19596
|
+
icon: "skip-forward"
|
|
19597
|
+
},
|
|
19598
|
+
{
|
|
19599
|
+
id: "media.channelDown",
|
|
19600
|
+
method: "channelDown",
|
|
19601
|
+
label: t("Channel down", "Canale precedente"),
|
|
19602
|
+
phrase: t("Go to the previous channel on {device}", "Torna al canale precedente su {device}"),
|
|
19603
|
+
icon: "skip-back"
|
|
19604
|
+
},
|
|
19530
19605
|
{
|
|
19531
19606
|
id: "media.play",
|
|
19532
19607
|
method: "play",
|
|
@@ -49154,6 +49229,244 @@ var controlCapability = {
|
|
|
49154
49229
|
*/
|
|
49155
49230
|
history: "none"
|
|
49156
49231
|
};
|
|
49232
|
+
/**
|
|
49233
|
+
* Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
|
|
49234
|
+
* Apple TV, Spotify, Roku, generic OTT receivers, …) on
|
|
49235
|
+
* `DeviceType.MediaPlayer`. Disjoint from the camera streaming
|
|
49236
|
+
* surface — those use `DeviceType.Camera` + the broker pipeline.
|
|
49237
|
+
*
|
|
49238
|
+
* The cap declares a wide method surface; not every backend supports
|
|
49239
|
+
* every method. Drivers signal sub-capability via the
|
|
49240
|
+
* `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
|
|
49241
|
+
* corresponding method) so the UI gates buttons without a slice
|
|
49242
|
+
* fetch — same pattern as `PanTiltZoom` for cameras.
|
|
49243
|
+
*/
|
|
49244
|
+
var MediaPlayerStateSchema = _enum([
|
|
49245
|
+
"off",
|
|
49246
|
+
"on",
|
|
49247
|
+
"idle",
|
|
49248
|
+
"playing",
|
|
49249
|
+
"paused",
|
|
49250
|
+
"buffering",
|
|
49251
|
+
"standby"
|
|
49252
|
+
]);
|
|
49253
|
+
var MediaPlayerRepeatSchema = _enum([
|
|
49254
|
+
"off",
|
|
49255
|
+
"all",
|
|
49256
|
+
"one"
|
|
49257
|
+
]);
|
|
49258
|
+
var MediaInfoSchema = object({
|
|
49259
|
+
/** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
|
|
49260
|
+
* `podcast`, …). */
|
|
49261
|
+
type: string(),
|
|
49262
|
+
/** Human-readable title. */
|
|
49263
|
+
title: string(),
|
|
49264
|
+
/** Optional artist / channel / station label. */
|
|
49265
|
+
artist: string().optional(),
|
|
49266
|
+
/** Optional album / season / show name. */
|
|
49267
|
+
album: string().optional(),
|
|
49268
|
+
/** Optional cover-art / thumbnail URL. */
|
|
49269
|
+
imageUrl: string().optional()
|
|
49270
|
+
});
|
|
49271
|
+
/**
|
|
49272
|
+
* Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
|
|
49273
|
+
* the device said so. `assumed`: the driver has no feedback (infrared) and
|
|
49274
|
+
* tracks what it last SENT; it moves only after a send was accepted, and a
|
|
49275
|
+
* human at the real remote makes it wrong. A UI shows an assumed value as a
|
|
49276
|
+
* guess, never as a reading.
|
|
49277
|
+
*/
|
|
49278
|
+
var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
|
|
49279
|
+
/** The fields of a media-player status a driver can only guess. */
|
|
49280
|
+
var MediaPlayerAssumedFieldSchema = _enum([
|
|
49281
|
+
"state",
|
|
49282
|
+
"volumeLevel",
|
|
49283
|
+
"isMuted",
|
|
49284
|
+
"source"
|
|
49285
|
+
]);
|
|
49286
|
+
var MediaPlayerStatusSchema = object({
|
|
49287
|
+
/** Playback lifecycle state. */
|
|
49288
|
+
state: MediaPlayerStateSchema,
|
|
49289
|
+
/**
|
|
49290
|
+
* Whether the state fields are readings or the driver's own bookkeeping.
|
|
49291
|
+
* A slice written before this field existed parses as `reported`.
|
|
49292
|
+
*/
|
|
49293
|
+
stateSource: MediaPlayerStateSourceSchema.default("reported"),
|
|
49294
|
+
/**
|
|
49295
|
+
* Which fields are the driver's own guess. `stateSource` is `assumed` when
|
|
49296
|
+
* this is not empty; a field not listed here is a reading even then (power
|
|
49297
|
+
* from a running-state source, mute guessed from the last send).
|
|
49298
|
+
*/
|
|
49299
|
+
assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
|
|
49300
|
+
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
49301
|
+
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
49302
|
+
volumeLevel: number$1().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
49303
|
+
/** Mute toggle distinct from volume=0. Null when no mute surface.
|
|
49304
|
+
* Pair with `DeviceFeature.MediaPlayerMute`. */
|
|
49305
|
+
isMuted: boolean().nullable(),
|
|
49306
|
+
/** Active source / input. Empty when no source surface. */
|
|
49307
|
+
source: string(),
|
|
49308
|
+
/** Selectable sources. Empty when no source surface.
|
|
49309
|
+
* Pair with `DeviceFeature.MediaPlayerSelectSource`. */
|
|
49310
|
+
availableSources: array(string()),
|
|
49311
|
+
/** Currently-playing media info. Null when nothing is playing. */
|
|
49312
|
+
currentMedia: MediaInfoSchema.nullable(),
|
|
49313
|
+
/** Current playback position in ms. Null when not seekable or
|
|
49314
|
+
* nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
|
|
49315
|
+
position: number$1().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
49316
|
+
/** Total duration of the current media in ms. Null when unknown
|
|
49317
|
+
* (live stream) or nothing is playing. */
|
|
49318
|
+
duration: number$1().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
49319
|
+
/** Shuffle toggle. Null when no shuffle surface.
|
|
49320
|
+
* Pair with `DeviceFeature.MediaPlayerShuffle`. */
|
|
49321
|
+
shuffle: boolean().nullable(),
|
|
49322
|
+
/** Repeat mode. Null when no repeat surface.
|
|
49323
|
+
* Pair with `DeviceFeature.MediaPlayerRepeat`. */
|
|
49324
|
+
repeat: MediaPlayerRepeatSchema.nullable(),
|
|
49325
|
+
/** Ms epoch when the slice was last updated. */
|
|
49326
|
+
lastChangedAt: number$1()
|
|
49327
|
+
});
|
|
49328
|
+
var stepInput = object({
|
|
49329
|
+
deviceId: number$1().int().nonnegative(),
|
|
49330
|
+
/** How many presses: the caller's count (Alexa `volumeSteps`, `channelCount`). */
|
|
49331
|
+
steps: number$1().int().min(1).max(50).default(1)
|
|
49332
|
+
});
|
|
49333
|
+
var deviceOnly = object({ deviceId: number$1().int().nonnegative() });
|
|
49334
|
+
var mediaPlayerCapability = {
|
|
49335
|
+
name: "media-player",
|
|
49336
|
+
scope: "device",
|
|
49337
|
+
deviceNative: true,
|
|
49338
|
+
mode: "singleton",
|
|
49339
|
+
deviceTypes: [DeviceType.MediaPlayer],
|
|
49340
|
+
methods: {
|
|
49341
|
+
turnOn: method(deviceOnly, _void(), {
|
|
49342
|
+
kind: "mutation",
|
|
49343
|
+
auth: "protected"
|
|
49344
|
+
}),
|
|
49345
|
+
turnOff: method(deviceOnly, _void(), {
|
|
49346
|
+
kind: "mutation",
|
|
49347
|
+
auth: "protected"
|
|
49348
|
+
}),
|
|
49349
|
+
volumeUp: method(stepInput, _void(), {
|
|
49350
|
+
kind: "mutation",
|
|
49351
|
+
auth: "protected"
|
|
49352
|
+
}),
|
|
49353
|
+
volumeDown: method(stepInput, _void(), {
|
|
49354
|
+
kind: "mutation",
|
|
49355
|
+
auth: "protected"
|
|
49356
|
+
}),
|
|
49357
|
+
channelUp: method(stepInput, _void(), {
|
|
49358
|
+
kind: "mutation",
|
|
49359
|
+
auth: "protected"
|
|
49360
|
+
}),
|
|
49361
|
+
channelDown: method(stepInput, _void(), {
|
|
49362
|
+
kind: "mutation",
|
|
49363
|
+
auth: "protected"
|
|
49364
|
+
}),
|
|
49365
|
+
play: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
|
|
49366
|
+
kind: "mutation",
|
|
49367
|
+
auth: "protected"
|
|
49368
|
+
}),
|
|
49369
|
+
pause: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
|
|
49370
|
+
kind: "mutation",
|
|
49371
|
+
auth: "protected"
|
|
49372
|
+
}),
|
|
49373
|
+
stop: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
|
|
49374
|
+
kind: "mutation",
|
|
49375
|
+
auth: "protected"
|
|
49376
|
+
}),
|
|
49377
|
+
next: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
|
|
49378
|
+
kind: "mutation",
|
|
49379
|
+
auth: "protected"
|
|
49380
|
+
}),
|
|
49381
|
+
previous: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
|
|
49382
|
+
kind: "mutation",
|
|
49383
|
+
auth: "protected"
|
|
49384
|
+
}),
|
|
49385
|
+
seek: method(object({
|
|
49386
|
+
deviceId: number$1().int().nonnegative(),
|
|
49387
|
+
position: number$1().int().nonnegative()
|
|
49388
|
+
}), _void(), {
|
|
49389
|
+
kind: "mutation",
|
|
49390
|
+
auth: "protected"
|
|
49391
|
+
}),
|
|
49392
|
+
setVolume: method(object({
|
|
49393
|
+
deviceId: number$1().int().nonnegative(),
|
|
49394
|
+
volumeLevel: number$1().min(0).max(100)
|
|
49395
|
+
}), _void(), {
|
|
49396
|
+
kind: "mutation",
|
|
49397
|
+
auth: "protected"
|
|
49398
|
+
}),
|
|
49399
|
+
setMute: method(object({
|
|
49400
|
+
deviceId: number$1().int().nonnegative(),
|
|
49401
|
+
muted: boolean()
|
|
49402
|
+
}), _void(), {
|
|
49403
|
+
kind: "mutation",
|
|
49404
|
+
auth: "protected"
|
|
49405
|
+
}),
|
|
49406
|
+
setShuffle: method(object({
|
|
49407
|
+
deviceId: number$1().int().nonnegative(),
|
|
49408
|
+
shuffle: boolean()
|
|
49409
|
+
}), _void(), {
|
|
49410
|
+
kind: "mutation",
|
|
49411
|
+
auth: "protected"
|
|
49412
|
+
}),
|
|
49413
|
+
setRepeat: method(object({
|
|
49414
|
+
deviceId: number$1().int().nonnegative(),
|
|
49415
|
+
repeat: MediaPlayerRepeatSchema
|
|
49416
|
+
}), _void(), {
|
|
49417
|
+
kind: "mutation",
|
|
49418
|
+
auth: "protected"
|
|
49419
|
+
}),
|
|
49420
|
+
selectSource: method(object({
|
|
49421
|
+
deviceId: number$1().int().nonnegative(),
|
|
49422
|
+
source: string().min(1)
|
|
49423
|
+
}), _void(), {
|
|
49424
|
+
kind: "mutation",
|
|
49425
|
+
auth: "protected"
|
|
49426
|
+
}),
|
|
49427
|
+
playMedia: method(object({
|
|
49428
|
+
deviceId: number$1().int().nonnegative(),
|
|
49429
|
+
/** Media identifier / URL. */
|
|
49430
|
+
mediaId: string().min(1),
|
|
49431
|
+
/** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
|
|
49432
|
+
* passes it through to the upstream service. */
|
|
49433
|
+
mediaType: string().min(1)
|
|
49434
|
+
}), _void(), {
|
|
49435
|
+
kind: "mutation",
|
|
49436
|
+
auth: "protected"
|
|
49437
|
+
})
|
|
49438
|
+
},
|
|
49439
|
+
status: {
|
|
49440
|
+
schema: MediaPlayerStatusSchema,
|
|
49441
|
+
kind: "push"
|
|
49442
|
+
},
|
|
49443
|
+
/**
|
|
49444
|
+
* Runtime-state slice — mirrored by the kernel. UI players read the
|
|
49445
|
+
* full slice for live now-playing, volume, and progress updates
|
|
49446
|
+
* without polling.
|
|
49447
|
+
*/
|
|
49448
|
+
runtimeState: MediaPlayerStatusSchema,
|
|
49449
|
+
/**
|
|
49450
|
+
* Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
|
|
49451
|
+
*
|
|
49452
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
49453
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
49454
|
+
*/
|
|
49455
|
+
durability: "session",
|
|
49456
|
+
/**
|
|
49457
|
+
* History (D850): volume (a level an operator or an automation sets; the
|
|
49458
|
+
* default 10 s floor folds a slider drag into its end value) and mute.
|
|
49459
|
+
* `position` is not recorded: it moves continuously while playing.
|
|
49460
|
+
*/
|
|
49461
|
+
history: { fields: {
|
|
49462
|
+
volumeLevel: {
|
|
49463
|
+
kind: "gauge",
|
|
49464
|
+
unit: "%",
|
|
49465
|
+
deadband: 1
|
|
49466
|
+
},
|
|
49467
|
+
isMuted: { kind: "boolean" }
|
|
49468
|
+
} }
|
|
49469
|
+
};
|
|
49157
49470
|
DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Heater, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Fan, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Heater, DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder, DeviceType.Heater;
|
|
49158
49471
|
var NUMBER = {
|
|
49159
49472
|
kind: "fixed",
|
|
@@ -57847,214 +58160,6 @@ var LoginMethodContributionSchema = discriminatedUnion("kind", [
|
|
|
57847
58160
|
]);
|
|
57848
58161
|
method(_void(), array(LoginMethodContributionSchema).readonly());
|
|
57849
58162
|
/**
|
|
57850
|
-
* Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
|
|
57851
|
-
* Apple TV, Spotify, Roku, generic OTT receivers, …) on
|
|
57852
|
-
* `DeviceType.MediaPlayer`. Disjoint from the camera streaming
|
|
57853
|
-
* surface — those use `DeviceType.Camera` + the broker pipeline.
|
|
57854
|
-
*
|
|
57855
|
-
* The cap declares a wide method surface; not every backend supports
|
|
57856
|
-
* every method. Drivers signal sub-capability via the
|
|
57857
|
-
* `DeviceFeature.MediaPlayerXxx` flags (paired one-to-one with the
|
|
57858
|
-
* corresponding method) so the UI gates buttons without a slice
|
|
57859
|
-
* fetch — same pattern as `PanTiltZoom` for cameras.
|
|
57860
|
-
*/
|
|
57861
|
-
var MediaPlayerStateSchema = _enum([
|
|
57862
|
-
"off",
|
|
57863
|
-
"on",
|
|
57864
|
-
"idle",
|
|
57865
|
-
"playing",
|
|
57866
|
-
"paused",
|
|
57867
|
-
"buffering",
|
|
57868
|
-
"standby"
|
|
57869
|
-
]);
|
|
57870
|
-
var MediaPlayerRepeatSchema = _enum([
|
|
57871
|
-
"off",
|
|
57872
|
-
"all",
|
|
57873
|
-
"one"
|
|
57874
|
-
]);
|
|
57875
|
-
var MediaInfoSchema = object({
|
|
57876
|
-
/** Free-form media kind (`music`, `tvshow`, `movie`, `app`, `channel`,
|
|
57877
|
-
* `podcast`, …). */
|
|
57878
|
-
type: string(),
|
|
57879
|
-
/** Human-readable title. */
|
|
57880
|
-
title: string(),
|
|
57881
|
-
/** Optional artist / channel / station label. */
|
|
57882
|
-
artist: string().optional(),
|
|
57883
|
-
/** Optional album / season / show name. */
|
|
57884
|
-
album: string().optional(),
|
|
57885
|
-
/** Optional cover-art / thumbnail URL. */
|
|
57886
|
-
imageUrl: string().optional()
|
|
57887
|
-
});
|
|
57888
|
-
/**
|
|
57889
|
-
* Where `state`, `volumeLevel`, `isMuted` and `source` come from. `reported`:
|
|
57890
|
-
* the device said so. `assumed`: the driver has no feedback (infrared) and
|
|
57891
|
-
* tracks what it last SENT; it moves only after a send was accepted, and a
|
|
57892
|
-
* human at the real remote makes it wrong. A UI shows an assumed value as a
|
|
57893
|
-
* guess, never as a reading.
|
|
57894
|
-
*/
|
|
57895
|
-
var MediaPlayerStateSourceSchema = _enum(["reported", "assumed"]);
|
|
57896
|
-
/** The fields of a media-player status a driver can only guess. */
|
|
57897
|
-
var MediaPlayerAssumedFieldSchema = _enum([
|
|
57898
|
-
"state",
|
|
57899
|
-
"volumeLevel",
|
|
57900
|
-
"isMuted",
|
|
57901
|
-
"source"
|
|
57902
|
-
]);
|
|
57903
|
-
var MediaPlayerStatusSchema = object({
|
|
57904
|
-
/** Playback lifecycle state. */
|
|
57905
|
-
state: MediaPlayerStateSchema,
|
|
57906
|
-
/**
|
|
57907
|
-
* Whether the state fields are readings or the driver's own bookkeeping.
|
|
57908
|
-
* A slice written before this field existed parses as `reported`.
|
|
57909
|
-
*/
|
|
57910
|
-
stateSource: MediaPlayerStateSourceSchema.default("reported"),
|
|
57911
|
-
/**
|
|
57912
|
-
* Which fields are the driver's own guess. `stateSource` is `assumed` when
|
|
57913
|
-
* this is not empty; a field not listed here is a reading even then (power
|
|
57914
|
-
* from a running-state source, mute guessed from the last send).
|
|
57915
|
-
*/
|
|
57916
|
-
assumedFields: array(MediaPlayerAssumedFieldSchema).default([]),
|
|
57917
|
-
/** Volume as 0..100 inclusive. Null when the device has no volume
|
|
57918
|
-
* surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
|
|
57919
|
-
volumeLevel: number$1().min(0).max(100).nullable().meta(readingMeta("percentage")),
|
|
57920
|
-
/** Mute toggle distinct from volume=0. Null when no mute surface.
|
|
57921
|
-
* Pair with `DeviceFeature.MediaPlayerMute`. */
|
|
57922
|
-
isMuted: boolean().nullable(),
|
|
57923
|
-
/** Active source / input. Empty when no source surface. */
|
|
57924
|
-
source: string(),
|
|
57925
|
-
/** Selectable sources. Empty when no source surface.
|
|
57926
|
-
* Pair with `DeviceFeature.MediaPlayerSelectSource`. */
|
|
57927
|
-
availableSources: array(string()),
|
|
57928
|
-
/** Currently-playing media info. Null when nothing is playing. */
|
|
57929
|
-
currentMedia: MediaInfoSchema.nullable(),
|
|
57930
|
-
/** Current playback position in ms. Null when not seekable or
|
|
57931
|
-
* nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
|
|
57932
|
-
position: number$1().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
57933
|
-
/** Total duration of the current media in ms. Null when unknown
|
|
57934
|
-
* (live stream) or nothing is playing. */
|
|
57935
|
-
duration: number$1().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
|
|
57936
|
-
/** Shuffle toggle. Null when no shuffle surface.
|
|
57937
|
-
* Pair with `DeviceFeature.MediaPlayerShuffle`. */
|
|
57938
|
-
shuffle: boolean().nullable(),
|
|
57939
|
-
/** Repeat mode. Null when no repeat surface.
|
|
57940
|
-
* Pair with `DeviceFeature.MediaPlayerRepeat`. */
|
|
57941
|
-
repeat: MediaPlayerRepeatSchema.nullable(),
|
|
57942
|
-
/** Ms epoch when the slice was last updated. */
|
|
57943
|
-
lastChangedAt: number$1()
|
|
57944
|
-
});
|
|
57945
|
-
var mediaPlayerCapability = {
|
|
57946
|
-
name: "media-player",
|
|
57947
|
-
scope: "device",
|
|
57948
|
-
deviceNative: true,
|
|
57949
|
-
mode: "singleton",
|
|
57950
|
-
deviceTypes: [DeviceType.MediaPlayer],
|
|
57951
|
-
methods: {
|
|
57952
|
-
play: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
|
|
57953
|
-
kind: "mutation",
|
|
57954
|
-
auth: "protected"
|
|
57955
|
-
}),
|
|
57956
|
-
pause: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
|
|
57957
|
-
kind: "mutation",
|
|
57958
|
-
auth: "protected"
|
|
57959
|
-
}),
|
|
57960
|
-
stop: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
|
|
57961
|
-
kind: "mutation",
|
|
57962
|
-
auth: "protected"
|
|
57963
|
-
}),
|
|
57964
|
-
next: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
|
|
57965
|
-
kind: "mutation",
|
|
57966
|
-
auth: "protected"
|
|
57967
|
-
}),
|
|
57968
|
-
previous: method(object({ deviceId: number$1().int().nonnegative() }), _void(), {
|
|
57969
|
-
kind: "mutation",
|
|
57970
|
-
auth: "protected"
|
|
57971
|
-
}),
|
|
57972
|
-
seek: method(object({
|
|
57973
|
-
deviceId: number$1().int().nonnegative(),
|
|
57974
|
-
position: number$1().int().nonnegative()
|
|
57975
|
-
}), _void(), {
|
|
57976
|
-
kind: "mutation",
|
|
57977
|
-
auth: "protected"
|
|
57978
|
-
}),
|
|
57979
|
-
setVolume: method(object({
|
|
57980
|
-
deviceId: number$1().int().nonnegative(),
|
|
57981
|
-
volumeLevel: number$1().min(0).max(100)
|
|
57982
|
-
}), _void(), {
|
|
57983
|
-
kind: "mutation",
|
|
57984
|
-
auth: "protected"
|
|
57985
|
-
}),
|
|
57986
|
-
setMute: method(object({
|
|
57987
|
-
deviceId: number$1().int().nonnegative(),
|
|
57988
|
-
muted: boolean()
|
|
57989
|
-
}), _void(), {
|
|
57990
|
-
kind: "mutation",
|
|
57991
|
-
auth: "protected"
|
|
57992
|
-
}),
|
|
57993
|
-
setShuffle: method(object({
|
|
57994
|
-
deviceId: number$1().int().nonnegative(),
|
|
57995
|
-
shuffle: boolean()
|
|
57996
|
-
}), _void(), {
|
|
57997
|
-
kind: "mutation",
|
|
57998
|
-
auth: "protected"
|
|
57999
|
-
}),
|
|
58000
|
-
setRepeat: method(object({
|
|
58001
|
-
deviceId: number$1().int().nonnegative(),
|
|
58002
|
-
repeat: MediaPlayerRepeatSchema
|
|
58003
|
-
}), _void(), {
|
|
58004
|
-
kind: "mutation",
|
|
58005
|
-
auth: "protected"
|
|
58006
|
-
}),
|
|
58007
|
-
selectSource: method(object({
|
|
58008
|
-
deviceId: number$1().int().nonnegative(),
|
|
58009
|
-
source: string().min(1)
|
|
58010
|
-
}), _void(), {
|
|
58011
|
-
kind: "mutation",
|
|
58012
|
-
auth: "protected"
|
|
58013
|
-
}),
|
|
58014
|
-
playMedia: method(object({
|
|
58015
|
-
deviceId: number$1().int().nonnegative(),
|
|
58016
|
-
/** Media identifier / URL. */
|
|
58017
|
-
mediaId: string().min(1),
|
|
58018
|
-
/** Media kind (`music`, `tvshow`, `movie`, `app`, …) — provider
|
|
58019
|
-
* passes it through to the upstream service. */
|
|
58020
|
-
mediaType: string().min(1)
|
|
58021
|
-
}), _void(), {
|
|
58022
|
-
kind: "mutation",
|
|
58023
|
-
auth: "protected"
|
|
58024
|
-
})
|
|
58025
|
-
},
|
|
58026
|
-
status: {
|
|
58027
|
-
schema: MediaPlayerStatusSchema,
|
|
58028
|
-
kind: "push"
|
|
58029
|
-
},
|
|
58030
|
-
/**
|
|
58031
|
-
* Runtime-state slice — mirrored by the kernel. UI players read the
|
|
58032
|
-
* full slice for live now-playing, volume, and progress updates
|
|
58033
|
-
* without polling.
|
|
58034
|
-
*/
|
|
58035
|
-
runtimeState: MediaPlayerStatusSchema,
|
|
58036
|
-
/**
|
|
58037
|
-
* Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
|
|
58038
|
-
*
|
|
58039
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
58040
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
58041
|
-
*/
|
|
58042
|
-
durability: "session",
|
|
58043
|
-
/**
|
|
58044
|
-
* History (D850): volume (a level an operator or an automation sets; the
|
|
58045
|
-
* default 10 s floor folds a slider drag into its end value) and mute.
|
|
58046
|
-
* `position` is not recorded: it moves continuously while playing.
|
|
58047
|
-
*/
|
|
58048
|
-
history: { fields: {
|
|
58049
|
-
volumeLevel: {
|
|
58050
|
-
kind: "gauge",
|
|
58051
|
-
unit: "%",
|
|
58052
|
-
deadband: 1
|
|
58053
|
-
},
|
|
58054
|
-
isMuted: { kind: "boolean" }
|
|
58055
|
-
} }
|
|
58056
|
-
};
|
|
58057
|
-
/**
|
|
58058
58163
|
* mesh-network — collection cap for mesh-VPN providers.
|
|
58059
58164
|
*
|
|
58060
58165
|
* Different shape from `network-access` (public tunnels):
|
|
@@ -66813,6 +66918,21 @@ var NcTemplateVarKindSchema = _enum([
|
|
|
66813
66918
|
"sound",
|
|
66814
66919
|
"system"
|
|
66815
66920
|
]);
|
|
66921
|
+
/**
|
|
66922
|
+
* The default title/body a rule of one SITUATION is delivered with when the
|
|
66923
|
+
* operator wrote no template — served so an editor can pre-fill it (and the
|
|
66924
|
+
* operator can rebuild it from placeholders) instead of starting from a blank
|
|
66925
|
+
* box that, once saved, silently replaces it. `id` names the situation inside
|
|
66926
|
+
* a rule `kind`: `appliance-cycle` is a `sensor` rule whose device is an
|
|
66927
|
+
* appliance finishing a cycle. Texts are raw templates (`{{var}}` intact), in
|
|
66928
|
+
* the hub's current language and with its overrides applied.
|
|
66929
|
+
*/
|
|
66930
|
+
var NcTemplateDefaultSchema = object({
|
|
66931
|
+
id: _enum(["appliance-cycle"]),
|
|
66932
|
+
kind: NcRuleKindSchema,
|
|
66933
|
+
title: string(),
|
|
66934
|
+
body: string()
|
|
66935
|
+
});
|
|
66816
66936
|
/** Which producer renders a template: an ordinary rule, a timelapse, a digest. */
|
|
66817
66937
|
var NcTemplateFamilySchema = _enum([
|
|
66818
66938
|
"rule",
|
|
@@ -67668,7 +67788,11 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
67668
67788
|
}), method(object({}), object({
|
|
67669
67789
|
catalog: array(NcConditionDescriptorSchema),
|
|
67670
67790
|
taxonomy: NcTaxonomySchema.optional()
|
|
67671
|
-
})), method(object({}), object({
|
|
67791
|
+
})), method(object({}), object({
|
|
67792
|
+
vars: array(NcTemplateVarDescriptorSchema),
|
|
67793
|
+
/** Per-situation default texts; absent on a hub that predates them. */
|
|
67794
|
+
defaults: array(NcTemplateDefaultSchema)
|
|
67795
|
+
})), method(NcTemplatePreviewInputSchema, NcTemplatePreviewSchema, {
|
|
67672
67796
|
kind: "mutation",
|
|
67673
67797
|
access: "view"
|
|
67674
67798
|
}), method(object({ filter: NcHistoryFilterSchema.default({ limit: 100 }) }), object({ entries: array(NcHistoryEntrySchema) }), { auth: "admin" }), method(object({}), NcRuleLastRunsSchema, { auth: "admin" }), method(object({ artifactId: string().min(1) }), object({ url: string().nullable() }), { auth: "admin" }), method(object({}), object({ snoozes: array(NcSnoozeSchema) }), { caller: "required" }), method(object({ snooze: NcSnoozeInputSchema }), object({ snooze: NcSnoozeSchema }), {
|
|
@@ -70259,6 +70383,14 @@ var powerMeterCapability = {
|
|
|
70259
70383
|
deviceNative: true,
|
|
70260
70384
|
mode: "singleton",
|
|
70261
70385
|
deviceTypes: POWER_METER_DEVICE_TYPES,
|
|
70386
|
+
deviceConfig: { ui: {
|
|
70387
|
+
kind: "widget",
|
|
70388
|
+
widgetId: "host/power-meter-line",
|
|
70389
|
+
tab: "power",
|
|
70390
|
+
placement: "hero",
|
|
70391
|
+
label: "Power",
|
|
70392
|
+
order: 4
|
|
70393
|
+
} },
|
|
70262
70394
|
methods: {},
|
|
70263
70395
|
status: {
|
|
70264
70396
|
schema: PowerMeterStatusSchema,
|
|
@@ -73820,7 +73952,13 @@ var RemoteMediaCommandSchema = _enum([
|
|
|
73820
73952
|
"next",
|
|
73821
73953
|
"previous",
|
|
73822
73954
|
"setMute",
|
|
73823
|
-
"selectSource"
|
|
73955
|
+
"selectSource",
|
|
73956
|
+
"turnOn",
|
|
73957
|
+
"turnOff",
|
|
73958
|
+
"volumeUp",
|
|
73959
|
+
"volumeDown",
|
|
73960
|
+
"channelUp",
|
|
73961
|
+
"channelDown"
|
|
73824
73962
|
]);
|
|
73825
73963
|
/** A send result, or `not-routed`: the role is not bound, so the native cap answers. */
|
|
73826
73964
|
var RemoteMediaCommandResultSchema = discriminatedUnion("status", [...RemoteSendResultSchema.options, object({ status: literal("not-routed") })]);
|
|
@@ -73841,7 +73979,7 @@ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.e
|
|
|
73841
73979
|
}), RemoteSendResultSchema, {
|
|
73842
73980
|
kind: "mutation",
|
|
73843
73981
|
auth: "protected"
|
|
73844
|
-
}), method(deviceInput.extend({
|
|
73982
|
+
}), method(deviceInput, MediaPlayerStatusSchema.nullable(), { auth: "protected" }), method(deviceInput.extend({
|
|
73845
73983
|
command: RemoteMediaCommandSchema,
|
|
73846
73984
|
muted: boolean().optional(),
|
|
73847
73985
|
source: string().optional(),
|
|
@@ -76087,7 +76225,11 @@ listSignals: method(_void(), array(StreamSignalSchema).readonly()) }
|
|
|
76087
76225
|
* `getSettingsUISchema()` surface.
|
|
76088
76226
|
*/
|
|
76089
76227
|
var SwitchStatusSchema = object({
|
|
76090
|
-
|
|
76228
|
+
/**
|
|
76229
|
+
* `null` = the device has never reported its state (a measurement that
|
|
76230
|
+
* has not happened is unknown, never `false`).
|
|
76231
|
+
*/
|
|
76232
|
+
on: boolean().nullable().meta({ label: "On" }),
|
|
76091
76233
|
/** Ms epoch of the last state change. Useful for UI "X minutes ago". */
|
|
76092
76234
|
lastChangedAt: number$1()
|
|
76093
76235
|
});
|
|
@@ -76112,7 +76254,7 @@ var switchCapability = {
|
|
|
76112
76254
|
}) },
|
|
76113
76255
|
events: { onStateChanged: { data: object({
|
|
76114
76256
|
deviceId: number$1(),
|
|
76115
|
-
on: boolean(),
|
|
76257
|
+
on: boolean().nullable(),
|
|
76116
76258
|
lastChangedAt: number$1()
|
|
76117
76259
|
}) } },
|
|
76118
76260
|
status: {
|
|
@@ -79669,6 +79811,7 @@ function buildDescriptor(capName, kind) {
|
|
|
79669
79811
|
* contributes one entry per bound cap present in this map.
|
|
79670
79812
|
*/
|
|
79671
79813
|
var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
|
|
79814
|
+
DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
|
|
79672
79815
|
var btn = (idSuffix, row, col, init) => ({
|
|
79673
79816
|
idSuffix,
|
|
79674
79817
|
row,
|
|
@@ -85847,6 +85990,20 @@ Object.freeze({
|
|
|
85847
85990
|
access: "view",
|
|
85848
85991
|
role: "admin"
|
|
85849
85992
|
},
|
|
85993
|
+
"mediaPlayer.channelDown": {
|
|
85994
|
+
capName: "media-player",
|
|
85995
|
+
capScope: "device",
|
|
85996
|
+
addonId: null,
|
|
85997
|
+
access: "create",
|
|
85998
|
+
role: "viewer"
|
|
85999
|
+
},
|
|
86000
|
+
"mediaPlayer.channelUp": {
|
|
86001
|
+
capName: "media-player",
|
|
86002
|
+
capScope: "device",
|
|
86003
|
+
addonId: null,
|
|
86004
|
+
access: "create",
|
|
86005
|
+
role: "viewer"
|
|
86006
|
+
},
|
|
85850
86007
|
"mediaPlayer.getStatus": {
|
|
85851
86008
|
capName: "media-player",
|
|
85852
86009
|
capScope: "device",
|
|
@@ -85938,6 +86095,34 @@ Object.freeze({
|
|
|
85938
86095
|
access: "create",
|
|
85939
86096
|
role: "viewer"
|
|
85940
86097
|
},
|
|
86098
|
+
"mediaPlayer.turnOff": {
|
|
86099
|
+
capName: "media-player",
|
|
86100
|
+
capScope: "device",
|
|
86101
|
+
addonId: null,
|
|
86102
|
+
access: "create",
|
|
86103
|
+
role: "viewer"
|
|
86104
|
+
},
|
|
86105
|
+
"mediaPlayer.turnOn": {
|
|
86106
|
+
capName: "media-player",
|
|
86107
|
+
capScope: "device",
|
|
86108
|
+
addonId: null,
|
|
86109
|
+
access: "create",
|
|
86110
|
+
role: "viewer"
|
|
86111
|
+
},
|
|
86112
|
+
"mediaPlayer.volumeDown": {
|
|
86113
|
+
capName: "media-player",
|
|
86114
|
+
capScope: "device",
|
|
86115
|
+
addonId: null,
|
|
86116
|
+
access: "create",
|
|
86117
|
+
role: "viewer"
|
|
86118
|
+
},
|
|
86119
|
+
"mediaPlayer.volumeUp": {
|
|
86120
|
+
capName: "media-player",
|
|
86121
|
+
capScope: "device",
|
|
86122
|
+
addonId: null,
|
|
86123
|
+
access: "create",
|
|
86124
|
+
role: "viewer"
|
|
86125
|
+
},
|
|
85941
86126
|
"meshNetwork.getStatus": {
|
|
85942
86127
|
capName: "mesh-network",
|
|
85943
86128
|
capScope: "system",
|
|
@@ -89494,6 +89679,13 @@ Object.freeze({
|
|
|
89494
89679
|
access: "create",
|
|
89495
89680
|
role: "admin"
|
|
89496
89681
|
},
|
|
89682
|
+
"remote.getAssumedStatus": {
|
|
89683
|
+
capName: "remote",
|
|
89684
|
+
capScope: "system",
|
|
89685
|
+
addonId: null,
|
|
89686
|
+
access: "view",
|
|
89687
|
+
role: "admin"
|
|
89688
|
+
},
|
|
89497
89689
|
"remote.getConfig": {
|
|
89498
89690
|
capName: "remote",
|
|
89499
89691
|
capScope: "system",
|
|
@@ -93160,6 +93352,16 @@ Object.freeze({
|
|
|
93160
93352
|
form: "single",
|
|
93161
93353
|
optional: true
|
|
93162
93354
|
}],
|
|
93355
|
+
"mediaPlayer.channelDown": [{
|
|
93356
|
+
name: "deviceId",
|
|
93357
|
+
form: "single",
|
|
93358
|
+
optional: false
|
|
93359
|
+
}],
|
|
93360
|
+
"mediaPlayer.channelUp": [{
|
|
93361
|
+
name: "deviceId",
|
|
93362
|
+
form: "single",
|
|
93363
|
+
optional: false
|
|
93364
|
+
}],
|
|
93163
93365
|
"mediaPlayer.getStatus": [{
|
|
93164
93366
|
name: "deviceId",
|
|
93165
93367
|
form: "single",
|
|
@@ -93225,6 +93427,26 @@ Object.freeze({
|
|
|
93225
93427
|
form: "single",
|
|
93226
93428
|
optional: false
|
|
93227
93429
|
}],
|
|
93430
|
+
"mediaPlayer.turnOff": [{
|
|
93431
|
+
name: "deviceId",
|
|
93432
|
+
form: "single",
|
|
93433
|
+
optional: false
|
|
93434
|
+
}],
|
|
93435
|
+
"mediaPlayer.turnOn": [{
|
|
93436
|
+
name: "deviceId",
|
|
93437
|
+
form: "single",
|
|
93438
|
+
optional: false
|
|
93439
|
+
}],
|
|
93440
|
+
"mediaPlayer.volumeDown": [{
|
|
93441
|
+
name: "deviceId",
|
|
93442
|
+
form: "single",
|
|
93443
|
+
optional: false
|
|
93444
|
+
}],
|
|
93445
|
+
"mediaPlayer.volumeUp": [{
|
|
93446
|
+
name: "deviceId",
|
|
93447
|
+
form: "single",
|
|
93448
|
+
optional: false
|
|
93449
|
+
}],
|
|
93228
93450
|
"meter.getStatus": [{
|
|
93229
93451
|
name: "deviceId",
|
|
93230
93452
|
form: "single",
|
|
@@ -94390,6 +94612,11 @@ Object.freeze({
|
|
|
94390
94612
|
form: "single",
|
|
94391
94613
|
optional: true
|
|
94392
94614
|
}],
|
|
94615
|
+
"remote.getAssumedStatus": [{
|
|
94616
|
+
name: "deviceId",
|
|
94617
|
+
form: "single",
|
|
94618
|
+
optional: false
|
|
94619
|
+
}],
|
|
94393
94620
|
"remote.getConfig": [{
|
|
94394
94621
|
name: "deviceId",
|
|
94395
94622
|
form: "single",
|
|
@@ -95852,6 +96079,7 @@ var STATE_SUMMARY_VARS = [
|
|
|
95852
96079
|
[...RULE_TEXT],
|
|
95853
96080
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
95854
96081
|
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
96082
|
+
([...RULE_TEXT], [...COMBINED_ALARM]),
|
|
95855
96083
|
([...RULE_TEXT], [...DEVICE_EVENTS]),
|
|
95856
96084
|
([...RULE_TEXT], [...BATTERY, ...CONSUMABLE]),
|
|
95857
96085
|
([...RULE_TEXT], [...CONSUMABLE]),
|
|
@@ -141978,6 +142206,7 @@ function isKnownCap(cap) {
|
|
|
141978
142206
|
var EchoSessionRegistry = class {
|
|
141979
142207
|
#resolve = null;
|
|
141980
142208
|
#dnd = /* @__PURE__ */ new Map();
|
|
142209
|
+
#volumes = /* @__PURE__ */ new Map();
|
|
141981
142210
|
setResolver(fn) {
|
|
141982
142211
|
this.#resolve = fn;
|
|
141983
142212
|
}
|
|
@@ -142000,9 +142229,26 @@ var EchoSessionRegistry = class {
|
|
|
142000
142229
|
});
|
|
142001
142230
|
return pending;
|
|
142002
142231
|
}
|
|
142232
|
+
/** Volume and mute of every device of the account. A failed read is not kept for the next device. */
|
|
142233
|
+
volumes(brokerId, now) {
|
|
142234
|
+
const shared = this.#volumes.get(brokerId);
|
|
142235
|
+
if (shared !== void 0 && now - shared.at < 25e3) return shared.pending;
|
|
142236
|
+
const session = this.get(brokerId);
|
|
142237
|
+
if (session === null) return Promise.reject(/* @__PURE__ */ new Error("no Echo session for this account"));
|
|
142238
|
+
const pending = session.deviceVolumes();
|
|
142239
|
+
this.#volumes.set(brokerId, {
|
|
142240
|
+
at: now,
|
|
142241
|
+
pending
|
|
142242
|
+
});
|
|
142243
|
+
pending.catch(() => {
|
|
142244
|
+
if (this.#volumes.get(brokerId)?.pending === pending) this.#volumes.delete(brokerId);
|
|
142245
|
+
});
|
|
142246
|
+
return pending;
|
|
142247
|
+
}
|
|
142003
142248
|
clear() {
|
|
142004
142249
|
this.#resolve = null;
|
|
142005
142250
|
this.#dnd.clear();
|
|
142251
|
+
this.#volumes.clear();
|
|
142006
142252
|
}
|
|
142007
142253
|
};
|
|
142008
142254
|
/** The single in-process registry shared across the addon. */
|
|
@@ -142085,6 +142331,18 @@ function mapPlayerInfoToStatus(info, input) {
|
|
|
142085
142331
|
repeat: repeatOf(info.transport?.repeat)
|
|
142086
142332
|
};
|
|
142087
142333
|
}
|
|
142334
|
+
/**
|
|
142335
|
+
* Fill the volume and mute the player read left null from the account's volume
|
|
142336
|
+
* list. The player read wins when it has a value; a value neither source has stays null.
|
|
142337
|
+
*/
|
|
142338
|
+
function withListedVolume(status, listed) {
|
|
142339
|
+
if (listed === void 0) return status;
|
|
142340
|
+
return {
|
|
142341
|
+
...status,
|
|
142342
|
+
volumeLevel: status.volumeLevel ?? percent(listed.volume),
|
|
142343
|
+
isMuted: status.isMuted ?? listed.muted
|
|
142344
|
+
};
|
|
142345
|
+
}
|
|
142088
142346
|
//#endregion
|
|
142089
142347
|
//#region src/speak/worker-protocol.ts
|
|
142090
142348
|
/** Line-delimited JSON the long-lived Python worker speaks. One object per line. */
|
|
@@ -142202,6 +142460,19 @@ function parseDndState(result) {
|
|
|
142202
142460
|
if (!parsed.success) throw new Error("worker returned an unexpected Do Not Disturb list");
|
|
142203
142461
|
return new Map(parsed.data.devices.map((entry) => [entry.serial, entry.enabled]));
|
|
142204
142462
|
}
|
|
142463
|
+
var DeviceVolumeEntrySchema = object({
|
|
142464
|
+
serial: string().min(1),
|
|
142465
|
+
volume: number$1().finite().optional(),
|
|
142466
|
+
muted: boolean().optional()
|
|
142467
|
+
});
|
|
142468
|
+
function parseDeviceVolumes(result) {
|
|
142469
|
+
const parsed = object({ devices: array(DeviceVolumeEntrySchema) }).safeParse(result);
|
|
142470
|
+
if (!parsed.success) throw new Error("worker returned an unexpected device volume list");
|
|
142471
|
+
return new Map(parsed.data.devices.map((entry) => [entry.serial, {
|
|
142472
|
+
volume: entry.volume ?? null,
|
|
142473
|
+
muted: entry.muted ?? null
|
|
142474
|
+
}]));
|
|
142475
|
+
}
|
|
142205
142476
|
//#endregion
|
|
142206
142477
|
//#region src/devices/alexa-echo-device.ts
|
|
142207
142478
|
/**
|
|
@@ -142216,6 +142487,8 @@ var alexaEchoDeviceSchema = object({
|
|
|
142216
142487
|
system: literal("alexa").optional(),
|
|
142217
142488
|
integrationId: string().optional()
|
|
142218
142489
|
});
|
|
142490
|
+
/** Percent points one volume-up / volume-down moves an Echo. */
|
|
142491
|
+
var VOLUME_STEP = 10;
|
|
142219
142492
|
var COLD_DND = {
|
|
142220
142493
|
enabled: null,
|
|
142221
142494
|
lastChangedAt: 0
|
|
@@ -142236,6 +142509,7 @@ var AlexaEchoDevice = class extends BaseDevice {
|
|
|
142236
142509
|
rereadTimer = null;
|
|
142237
142510
|
polling = false;
|
|
142238
142511
|
failureLogged = false;
|
|
142512
|
+
volumeFailureLogged = false;
|
|
142239
142513
|
health = "ok";
|
|
142240
142514
|
constructor(ctx) {
|
|
142241
142515
|
const persisted = alexaEchoDeviceSchema.parse(ctx.persistedConfig ?? {});
|
|
@@ -142308,12 +142582,11 @@ var AlexaEchoDevice = class extends BaseDevice {
|
|
|
142308
142582
|
}
|
|
142309
142583
|
const deviceType = this.amazonDeviceType !== "" ? this.amazonDeviceType : listed?.deviceType ?? "";
|
|
142310
142584
|
try {
|
|
142311
|
-
const
|
|
142312
|
-
const now = Date.now();
|
|
142313
|
-
this.runtimeState.setCapState("media-player", mapPlayerInfoToStatus(info, {
|
|
142585
|
+
const fromPlayer = mapPlayerInfoToStatus(await session.playerState(this.serial, deviceType), {
|
|
142314
142586
|
online: true,
|
|
142315
|
-
now
|
|
142316
|
-
})
|
|
142587
|
+
now: Date.now()
|
|
142588
|
+
});
|
|
142589
|
+
this.runtimeState.setCapState("media-player", await this.completeVolume(fromPlayer));
|
|
142317
142590
|
this.markOnline(true);
|
|
142318
142591
|
this.noteRecovered();
|
|
142319
142592
|
} catch (err) {
|
|
@@ -142322,6 +142595,29 @@ var AlexaEchoDevice = class extends BaseDevice {
|
|
|
142322
142595
|
}
|
|
142323
142596
|
await this.readDoNotDisturb();
|
|
142324
142597
|
}
|
|
142598
|
+
/**
|
|
142599
|
+
* An idle Echo's player read carries no volume. Ask the account's volume list
|
|
142600
|
+
* (shared by every Echo of the account in this cycle); a failed read leaves null.
|
|
142601
|
+
*/
|
|
142602
|
+
async completeVolume(status) {
|
|
142603
|
+
if (status.volumeLevel !== null && status.isMuted !== null) return status;
|
|
142604
|
+
try {
|
|
142605
|
+
const all = await alexaEchoSessions.volumes(this.brokerId, Date.now());
|
|
142606
|
+
if (this.volumeFailureLogged) this.ctx.logger.info("alexa echo: the volume list reads again", { tags: { deviceId: this.id } });
|
|
142607
|
+
this.volumeFailureLogged = false;
|
|
142608
|
+
return withListedVolume(status, all.get(this.serial));
|
|
142609
|
+
} catch (err) {
|
|
142610
|
+
if (err instanceof RateLimitedError) return status;
|
|
142611
|
+
if (!this.volumeFailureLogged) {
|
|
142612
|
+
this.volumeFailureLogged = true;
|
|
142613
|
+
this.ctx.logger.warn("alexa echo: could not read the volume list, volume stays unknown", {
|
|
142614
|
+
tags: { deviceId: this.id },
|
|
142615
|
+
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
142616
|
+
});
|
|
142617
|
+
}
|
|
142618
|
+
return status;
|
|
142619
|
+
}
|
|
142620
|
+
}
|
|
142325
142621
|
async readDoNotDisturb() {
|
|
142326
142622
|
try {
|
|
142327
142623
|
const enabled = (await alexaEchoSessions.dnd(this.brokerId, Date.now())).get(this.serial);
|
|
@@ -142357,6 +142653,14 @@ var AlexaEchoDevice = class extends BaseDevice {
|
|
|
142357
142653
|
await this.session().npCommand(this.serial, command);
|
|
142358
142654
|
this.rereadSoon();
|
|
142359
142655
|
}
|
|
142656
|
+
/** Amazon has no step command: move from the last known level, refuse when there is none. */
|
|
142657
|
+
async stepVolume(delta) {
|
|
142658
|
+
const level = this.runtimeState.getCapState("media-player")?.volumeLevel;
|
|
142659
|
+
if (level === null || level === void 0) throw new Error("the Echo volume is not known yet");
|
|
142660
|
+
const next = Math.max(0, Math.min(100, level + delta));
|
|
142661
|
+
await this.session().setVolume(this.serial, next);
|
|
142662
|
+
this.rereadSoon();
|
|
142663
|
+
}
|
|
142360
142664
|
registerMediaPlayer() {
|
|
142361
142665
|
this.ctx.registerNativeCap(mediaPlayerCapability, {
|
|
142362
142666
|
getStatus: async () => this.runtimeState.getCapState("media-player") ?? emptyEchoStatus("off", 0),
|
|
@@ -142369,6 +142673,12 @@ var AlexaEchoDevice = class extends BaseDevice {
|
|
|
142369
142673
|
this.rereadSoon();
|
|
142370
142674
|
},
|
|
142371
142675
|
stop: async () => unsupported("stop"),
|
|
142676
|
+
turnOn: async () => unsupported("power"),
|
|
142677
|
+
turnOff: async () => unsupported("power"),
|
|
142678
|
+
volumeUp: async () => this.stepVolume(VOLUME_STEP),
|
|
142679
|
+
volumeDown: async () => this.stepVolume(-10),
|
|
142680
|
+
channelUp: async () => unsupported("channel steps"),
|
|
142681
|
+
channelDown: async () => unsupported("channel steps"),
|
|
142372
142682
|
seek: async () => unsupported("seek"),
|
|
142373
142683
|
setMute: async () => unsupported("mute"),
|
|
142374
142684
|
setShuffle: async () => unsupported("shuffle"),
|
|
@@ -142873,6 +143183,11 @@ var BrokerEcho = class {
|
|
|
142873
143183
|
await this.requireReady();
|
|
142874
143184
|
return this.limited(async () => parseDndState(await this.deps.worker.request({ op: "dnd_state" })));
|
|
142875
143185
|
}
|
|
143186
|
+
/** Speaker volume and mute of every device of the account, by serial, in one call. */
|
|
143187
|
+
async deviceVolumes() {
|
|
143188
|
+
await this.requireReady();
|
|
143189
|
+
return this.limited(async () => parseDeviceVolumes(await this.deps.worker.request({ op: "device_volumes" })));
|
|
143190
|
+
}
|
|
142876
143191
|
async setDnd(serial, enabled) {
|
|
142877
143192
|
await this.requireReady();
|
|
142878
143193
|
await this.limited(() => this.deps.worker.request({
|