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