@khalido/home 0.0.0-stage → 0.1.0
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/.env.example +25 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +77 -2
- package/dist/clock.d.ts +17 -0
- package/dist/clock.js +43 -0
- package/dist/config.d.ts +273 -0
- package/dist/config.js +392 -0
- package/dist/connector.d.ts +34 -0
- package/dist/connector.js +47 -0
- package/dist/connectors/amber/client.d.ts +61 -0
- package/dist/connectors/amber/client.js +106 -0
- package/dist/connectors/amber/index.d.ts +30 -0
- package/dist/connectors/amber/index.js +94 -0
- package/dist/connectors/cast/codec.d.ts +38 -0
- package/dist/connectors/cast/codec.js +102 -0
- package/dist/connectors/cast/index.d.ts +60 -0
- package/dist/connectors/cast/index.js +426 -0
- package/dist/connectors/cast/schemas.d.ts +167 -0
- package/dist/connectors/cast/schemas.js +87 -0
- package/dist/connectors/cast/serve.d.ts +11 -0
- package/dist/connectors/cast/serve.js +57 -0
- package/dist/connectors/cast/state.d.ts +62 -0
- package/dist/connectors/cast/state.js +133 -0
- package/dist/connectors/cast/tls.d.ts +3 -0
- package/dist/connectors/cast/tls.js +67 -0
- package/dist/connectors/cast/writes.d.ts +80 -0
- package/dist/connectors/cast/writes.js +390 -0
- package/dist/connectors/daikin/client.d.ts +65 -0
- package/dist/connectors/daikin/client.js +221 -0
- package/dist/connectors/daikin/index.d.ts +41 -0
- package/dist/connectors/daikin/index.js +108 -0
- package/dist/connectors/enphase/client.d.ts +65 -0
- package/dist/connectors/enphase/client.js +173 -0
- package/dist/connectors/enphase/index.d.ts +43 -0
- package/dist/connectors/enphase/index.js +174 -0
- package/dist/connectors/enphase/schemas.d.ts +51 -0
- package/dist/connectors/enphase/schemas.js +34 -0
- package/dist/connectors/hue/client.d.ts +48 -0
- package/dist/connectors/hue/client.js +95 -0
- package/dist/connectors/hue/events.d.ts +41 -0
- package/dist/connectors/hue/events.js +156 -0
- package/dist/connectors/hue/index.d.ts +42 -0
- package/dist/connectors/hue/index.js +205 -0
- package/dist/connectors/hue/schemas.d.ts +65 -0
- package/dist/connectors/hue/schemas.js +48 -0
- package/dist/connectors/hue/state.d.ts +43 -0
- package/dist/connectors/hue/state.js +84 -0
- package/dist/connectors/printers/client.d.ts +28 -0
- package/dist/connectors/printers/client.js +74 -0
- package/dist/connectors/printers/codec.d.ts +76 -0
- package/dist/connectors/printers/codec.js +246 -0
- package/dist/connectors/printers/found.d.ts +15 -0
- package/dist/connectors/printers/found.js +35 -0
- package/dist/connectors/printers/index.d.ts +52 -0
- package/dist/connectors/printers/index.js +282 -0
- package/dist/connectors/printers/state.d.ts +60 -0
- package/dist/connectors/printers/state.js +99 -0
- package/dist/connectors/spotify/changes.d.ts +32 -0
- package/dist/connectors/spotify/changes.js +84 -0
- package/dist/connectors/spotify/client.d.ts +199 -0
- package/dist/connectors/spotify/client.js +270 -0
- package/dist/connectors/spotify/index.d.ts +92 -0
- package/dist/connectors/spotify/index.js +745 -0
- package/dist/connectors/up/client.d.ts +183 -0
- package/dist/connectors/up/client.js +308 -0
- package/dist/connectors/up/index.d.ts +91 -0
- package/dist/connectors/up/index.js +213 -0
- package/dist/connectors/up/spend.d.ts +78 -0
- package/dist/connectors/up/spend.js +213 -0
- package/dist/connectors/up/webhook.d.ts +13 -0
- package/dist/connectors/up/webhook.js +32 -0
- package/dist/connectors/weather/blocks.d.ts +51 -0
- package/dist/connectors/weather/blocks.js +106 -0
- package/dist/connectors/weather/bom.d.ts +37 -0
- package/dist/connectors/weather/bom.js +237 -0
- package/dist/connectors/weather/ftp.d.ts +5 -0
- package/dist/connectors/weather/ftp.js +46 -0
- package/dist/connectors/weather/google.d.ts +31 -0
- package/dist/connectors/weather/google.js +364 -0
- package/dist/connectors/weather/index.d.ts +53 -0
- package/dist/connectors/weather/index.js +267 -0
- package/dist/connectors/weather/open-meteo.d.ts +15 -0
- package/dist/connectors/weather/open-meteo.js +285 -0
- package/dist/connectors/weather/rain.d.ts +19 -0
- package/dist/connectors/weather/rain.js +51 -0
- package/dist/connectors/weather/source.d.ts +149 -0
- package/dist/connectors/weather/source.js +77 -0
- package/dist/connectors/weather/sun.d.ts +48 -0
- package/dist/connectors/weather/sun.js +65 -0
- package/dist/discover.d.ts +195 -0
- package/dist/discover.js +446 -0
- package/dist/energy.d.ts +61 -0
- package/dist/energy.js +162 -0
- package/dist/finders-entry.d.ts +1 -0
- package/dist/finders-entry.js +2 -0
- package/dist/finders.d.ts +45 -0
- package/dist/finders.js +173 -0
- package/dist/health.d.ts +28 -0
- package/dist/health.js +35 -0
- package/dist/home.d.ts +81 -0
- package/dist/home.js +114 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/lane.d.ts +17 -0
- package/dist/lane.js +74 -0
- package/dist/locate.d.ts +27 -0
- package/dist/locate.js +59 -0
- package/dist/manifest.d.ts +31 -0
- package/dist/manifest.js +4 -0
- package/dist/manifests.d.ts +5 -0
- package/dist/manifests.js +23 -0
- package/dist/node-fetch.d.ts +9 -0
- package/dist/node-fetch.js +25 -0
- package/dist/poll.d.ts +10 -0
- package/dist/poll.js +33 -0
- package/docs/cast.md +133 -0
- package/docs/cli.md +39 -0
- package/docs/config.md +36 -0
- package/docs/decisions.md +150 -0
- package/docs/devices.md +128 -0
- package/docs/enphase-notes.md +24 -0
- package/docs/google.md +92 -0
- package/docs/ideas.md +69 -0
- package/docs/matter.md +55 -0
- package/docs/performance.md +12 -0
- package/docs/philosophy.md +23 -0
- package/docs/up.md +84 -0
- package/docs/using.md +65 -0
- package/docs/weather-probe.md +63 -0
- package/examples/home.jsonc +74 -0
- package/examples/home.svg +76 -0
- package/home.schema.json +474 -0
- package/llms.txt +32 -0
- package/package.json +143 -4
- package/skills/draw-home/README.md +26 -0
- package/skills/draw-home/SKILL.md +82 -0
- package/skills/use-home/SKILL.md +86 -0
- package/src/cli/cache.ts +29 -0
- package/src/cli/cast.ts +172 -0
- package/src/cli/config.ts +52 -0
- package/src/cli/context.ts +118 -0
- package/src/cli/discover.ts +175 -0
- package/src/cli/enphase.ts +62 -0
- package/src/cli/hue.ts +124 -0
- package/src/cli/locate.ts +47 -0
- package/src/cli/main.ts +182 -0
- package/src/cli/printers.ts +21 -0
- package/src/cli/probe.ts +274 -0
- package/src/cli/spotify.ts +76 -0
- package/src/cli/status.ts +228 -0
- package/src/cli/up.ts +350 -0
- package/src/clock.ts +62 -0
- package/src/config.ts +578 -0
- package/src/connector.ts +77 -0
- package/src/connectors/amber/README.md +40 -0
- package/src/connectors/amber/client.ts +157 -0
- package/src/connectors/amber/index.ts +118 -0
- package/src/connectors/cast/README.md +43 -0
- package/src/connectors/cast/codec.ts +126 -0
- package/src/connectors/cast/index.ts +551 -0
- package/src/connectors/cast/schemas.ts +101 -0
- package/src/connectors/cast/serve.ts +72 -0
- package/src/connectors/cast/state.ts +203 -0
- package/src/connectors/cast/tls.ts +100 -0
- package/src/connectors/cast/writes.ts +572 -0
- package/src/connectors/daikin/README.md +78 -0
- package/src/connectors/daikin/client.ts +295 -0
- package/src/connectors/daikin/index.ts +145 -0
- package/src/connectors/enphase/README.md +64 -0
- package/src/connectors/enphase/client.ts +244 -0
- package/src/connectors/enphase/index.ts +241 -0
- package/src/connectors/enphase/schemas.ts +45 -0
- package/src/connectors/hue/README.md +50 -0
- package/src/connectors/hue/client.ts +157 -0
- package/src/connectors/hue/events.ts +182 -0
- package/src/connectors/hue/index.ts +261 -0
- package/src/connectors/hue/schemas.ts +60 -0
- package/src/connectors/hue/state.ts +124 -0
- package/src/connectors/printers/README.md +46 -0
- package/src/connectors/printers/client.ts +114 -0
- package/src/connectors/printers/codec.ts +292 -0
- package/src/connectors/printers/found.ts +46 -0
- package/src/connectors/printers/index.ts +385 -0
- package/src/connectors/printers/state.ts +182 -0
- package/src/connectors/spotify/README.md +80 -0
- package/src/connectors/spotify/changes.ts +127 -0
- package/src/connectors/spotify/client.ts +387 -0
- package/src/connectors/spotify/index.ts +929 -0
- package/src/connectors/up/README.md +35 -0
- package/src/connectors/up/client.ts +518 -0
- package/src/connectors/up/index.ts +370 -0
- package/src/connectors/up/spend.ts +280 -0
- package/src/connectors/up/webhook.ts +62 -0
- package/src/connectors/weather/README.md +84 -0
- package/src/connectors/weather/blocks.ts +173 -0
- package/src/connectors/weather/bom.ts +344 -0
- package/src/connectors/weather/ftp.ts +48 -0
- package/src/connectors/weather/google.ts +425 -0
- package/src/connectors/weather/index.ts +380 -0
- package/src/connectors/weather/open-meteo.ts +309 -0
- package/src/connectors/weather/rain.ts +73 -0
- package/src/connectors/weather/source.ts +236 -0
- package/src/connectors/weather/sun.ts +114 -0
- package/src/discover.ts +634 -0
- package/src/energy.ts +221 -0
- package/src/finders-entry.ts +2 -0
- package/src/finders.ts +247 -0
- package/src/health.ts +74 -0
- package/src/home.ts +205 -0
- package/src/index.ts +62 -0
- package/src/lane.ts +110 -0
- package/src/locate.ts +85 -0
- package/src/manifest.ts +43 -0
- package/src/manifests.ts +27 -0
- package/src/node-fetch.ts +41 -0
- package/src/poll.ts +33 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { asOf, realClock } from "../../clock.js";
|
|
2
|
+
import { errorText } from "../../connector.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { envName } from "../../home.js";
|
|
5
|
+
import { energyNow } from "../../energy.js";
|
|
6
|
+
import { field } from "../../manifest.js";
|
|
7
|
+
import { poller } from "../../poll.js";
|
|
8
|
+
import { amberClient, AmberError } from "./client.js";
|
|
9
|
+
export { AmberError, amberClient, } from "./client.js";
|
|
10
|
+
/** Poll every 60 s: prices move in 5-minute intervals and the current one is revised inside it; 5 calls in 5 min, limit 50. */
|
|
11
|
+
export const POLL_MS = 60_000;
|
|
12
|
+
/** Amber in a home config: the key's env name, and the site when the account has more than one. */
|
|
13
|
+
export const configSchema = z.looseObject({
|
|
14
|
+
connector: z.literal("amber"),
|
|
15
|
+
key: envName.describe("env name holding the Amber API key"),
|
|
16
|
+
siteId: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("the site; the first active one when left out"),
|
|
20
|
+
});
|
|
21
|
+
/** Amber prices as a home integration: one read now, then every 60 s. */
|
|
22
|
+
export function amber(config) {
|
|
23
|
+
const client = amberClient(config);
|
|
24
|
+
const clock = config.clock ?? realClock;
|
|
25
|
+
const poll = poller(clock);
|
|
26
|
+
return {
|
|
27
|
+
name: "amber",
|
|
28
|
+
refresh: poll.nudge,
|
|
29
|
+
async run(signal, update) {
|
|
30
|
+
let siteId = config.siteId;
|
|
31
|
+
let last;
|
|
32
|
+
while (!signal.aborted) {
|
|
33
|
+
try {
|
|
34
|
+
siteId ??= await client.siteId(signal);
|
|
35
|
+
last = {
|
|
36
|
+
...(await client.prices(siteId, signal)),
|
|
37
|
+
asOf: asOf(clock),
|
|
38
|
+
};
|
|
39
|
+
update({ status: "connected", anomaly: last.anomaly, state: last });
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (signal.aborted)
|
|
43
|
+
return; // stopped mid-read: not a failure
|
|
44
|
+
if (error instanceof AmberError && error.status === 401)
|
|
45
|
+
return update({
|
|
46
|
+
status: "unavailable",
|
|
47
|
+
detail: "Amber rejected the key",
|
|
48
|
+
state: last,
|
|
49
|
+
});
|
|
50
|
+
update({
|
|
51
|
+
status: "disconnected",
|
|
52
|
+
detail: errorText(error),
|
|
53
|
+
state: last,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
await poll.wait(POLL_MS, signal);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Amber's prices in a few words: "buy 40.9c low (estimate) · sell 35.3c · 7% renewables", with a spike and a negative price when there is one. */
|
|
62
|
+
export function amberText(a) {
|
|
63
|
+
const { negativePrice } = energyNow(undefined, a);
|
|
64
|
+
return [
|
|
65
|
+
`buy ${a.importCents?.toFixed(1) ?? "?"}c${a.priceDescriptor ? ` ${a.priceDescriptor}` : ""}${a.estimate ? " (estimate)" : ""}`,
|
|
66
|
+
a.spike && a.spike !== "none" ? `spike ${a.spike}` : "",
|
|
67
|
+
`sell ${a.exportCents?.toFixed(1) ?? "?"}c`,
|
|
68
|
+
negativePrice ?? "",
|
|
69
|
+
field(a.renewables, (v) => `${Math.round(v)}% renewables`),
|
|
70
|
+
]
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.join(" · ");
|
|
73
|
+
}
|
|
74
|
+
export const manifest = {
|
|
75
|
+
name: "amber",
|
|
76
|
+
title: "Amber Electric",
|
|
77
|
+
iotClass: "cloud_polling",
|
|
78
|
+
pollSeconds: POLL_MS / 1000,
|
|
79
|
+
access: "cloud HTTPS, AMBER_KEY; 50 calls per 5 min",
|
|
80
|
+
env: [
|
|
81
|
+
{
|
|
82
|
+
name: "AMBER_KEY",
|
|
83
|
+
example: "psk_xxxxxxxx",
|
|
84
|
+
what: "Amber Electric API key (app.amber.com.au/developers)",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "AMBER_SITE_ID",
|
|
88
|
+
example: "",
|
|
89
|
+
optional: true,
|
|
90
|
+
what: "the first active site is used when unset",
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
lines: ({ state }) => state?.importCents === undefined ? [] : [amberText(state)],
|
|
94
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** One message on a Cast socket. `payload` is the JSON text; a binary payload (unused by any namespace here) comes as undefined. */
|
|
2
|
+
export type CastMessage = {
|
|
3
|
+
source: string;
|
|
4
|
+
destination: string;
|
|
5
|
+
namespace: string;
|
|
6
|
+
payload?: string;
|
|
7
|
+
};
|
|
8
|
+
/** The namespaces a state reader uses. */
|
|
9
|
+
export declare const NS: {
|
|
10
|
+
readonly connection: "urn:x-cast:com.google.cast.tp.connection";
|
|
11
|
+
readonly heartbeat: "urn:x-cast:com.google.cast.tp.heartbeat";
|
|
12
|
+
readonly receiver: "urn:x-cast:com.google.cast.receiver";
|
|
13
|
+
readonly media: "urn:x-cast:com.google.cast.media";
|
|
14
|
+
readonly multizone: "urn:x-cast:com.google.cast.multizone";
|
|
15
|
+
};
|
|
16
|
+
/** The largest message the protocol allows (64 KiB); a longer length prefix means the stream is out of step. */
|
|
17
|
+
export declare const MAX_MESSAGE = 65536;
|
|
18
|
+
/** A message as framed on the socket: version 0, string payload. */
|
|
19
|
+
export declare function encode(m: CastMessage): Uint8Array;
|
|
20
|
+
/** One message's bytes (without the length prefix). Unknown fields are skipped. */
|
|
21
|
+
export declare function decode(bytes: Uint8Array): CastMessage;
|
|
22
|
+
/** Splits a byte stream into messages: `push` each chunk as it arrives, get back every message it completed. */
|
|
23
|
+
export declare function framer(): (chunk: Uint8Array) => CastMessage[];
|
|
24
|
+
/** An open Cast socket. `peer` is its certificate's name (a device's id), `protocol` its TLS version. Tests pass a fake. */
|
|
25
|
+
export type CastChannel = {
|
|
26
|
+
peer?: string;
|
|
27
|
+
protocol?: string;
|
|
28
|
+
send(message: CastMessage): void;
|
|
29
|
+
messages: AsyncIterable<CastMessage>;
|
|
30
|
+
close(): void;
|
|
31
|
+
};
|
|
32
|
+
/** How to open one: `tlsConnect` from `@khalido/home/cast/tls` in Bun and Node. */
|
|
33
|
+
export type CastConnect = (to: {
|
|
34
|
+
host: string;
|
|
35
|
+
port: number;
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
}) => Promise<CastChannel>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// CastV2's one protobuf message, CastMessage (cast_channel.proto), by hand: six fields, two wire types. Each message on
|
|
2
|
+
// the socket is a 4-byte big-endian length, then the encoded message. Only string payloads (JSON) are used.
|
|
3
|
+
/** The namespaces a state reader uses. */
|
|
4
|
+
export const NS = {
|
|
5
|
+
connection: "urn:x-cast:com.google.cast.tp.connection",
|
|
6
|
+
heartbeat: "urn:x-cast:com.google.cast.tp.heartbeat",
|
|
7
|
+
receiver: "urn:x-cast:com.google.cast.receiver",
|
|
8
|
+
media: "urn:x-cast:com.google.cast.media",
|
|
9
|
+
multizone: "urn:x-cast:com.google.cast.multizone",
|
|
10
|
+
};
|
|
11
|
+
/** The largest message the protocol allows (64 KiB); a longer length prefix means the stream is out of step. */
|
|
12
|
+
export const MAX_MESSAGE = 65_536;
|
|
13
|
+
const text = new TextEncoder();
|
|
14
|
+
const varint = (n) => {
|
|
15
|
+
const out = [];
|
|
16
|
+
for (; n > 127; n = Math.floor(n / 128))
|
|
17
|
+
out.push((n % 128) | 128);
|
|
18
|
+
return [...out, n];
|
|
19
|
+
};
|
|
20
|
+
const field = (number, value) => {
|
|
21
|
+
const bytes = text.encode(value);
|
|
22
|
+
return [(number << 3) | 2, ...varint(bytes.length), ...bytes];
|
|
23
|
+
};
|
|
24
|
+
/** A message as framed on the socket: version 0, string payload. */
|
|
25
|
+
export function encode(m) {
|
|
26
|
+
const body = [
|
|
27
|
+
0x08,
|
|
28
|
+
0,
|
|
29
|
+
...field(2, m.source),
|
|
30
|
+
...field(3, m.destination),
|
|
31
|
+
...field(4, m.namespace),
|
|
32
|
+
0x28,
|
|
33
|
+
0,
|
|
34
|
+
...field(6, m.payload ?? ""),
|
|
35
|
+
];
|
|
36
|
+
const out = new Uint8Array(4 + body.length);
|
|
37
|
+
new DataView(out.buffer).setUint32(0, body.length);
|
|
38
|
+
out.set(body, 4);
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/** One message's bytes (without the length prefix). Unknown fields are skipped. */
|
|
42
|
+
export function decode(bytes) {
|
|
43
|
+
const strings = {};
|
|
44
|
+
let i = 0;
|
|
45
|
+
const read = () => {
|
|
46
|
+
let n = 0;
|
|
47
|
+
for (let scale = 1;; scale *= 128) {
|
|
48
|
+
if (i >= bytes.length)
|
|
49
|
+
throw new Error("cast: truncated varint");
|
|
50
|
+
const b = bytes[i++];
|
|
51
|
+
n += (b & 127) * scale;
|
|
52
|
+
if (b < 128)
|
|
53
|
+
return n;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const decoder = new TextDecoder();
|
|
57
|
+
while (i < bytes.length) {
|
|
58
|
+
const key = read();
|
|
59
|
+
const wire = key & 7;
|
|
60
|
+
if (wire === 0)
|
|
61
|
+
read();
|
|
62
|
+
else if (wire === 2) {
|
|
63
|
+
const length = read();
|
|
64
|
+
if (i + length > bytes.length)
|
|
65
|
+
throw new Error("cast: truncated field");
|
|
66
|
+
strings[key >> 3] = decoder.decode(bytes.subarray(i, i + length));
|
|
67
|
+
i += length;
|
|
68
|
+
}
|
|
69
|
+
else if (wire === 1 || wire === 5)
|
|
70
|
+
i += wire === 1 ? 8 : 4;
|
|
71
|
+
else
|
|
72
|
+
throw new Error(`cast: unknown wire type ${wire}`);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
source: strings[2] ?? "",
|
|
76
|
+
destination: strings[3] ?? "",
|
|
77
|
+
namespace: strings[4] ?? "",
|
|
78
|
+
payload: strings[6],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** Splits a byte stream into messages: `push` each chunk as it arrives, get back every message it completed. */
|
|
82
|
+
export function framer() {
|
|
83
|
+
let buffer = new Uint8Array(0);
|
|
84
|
+
return (chunk) => {
|
|
85
|
+
const joined = new Uint8Array(buffer.length + chunk.length);
|
|
86
|
+
joined.set(buffer);
|
|
87
|
+
joined.set(chunk, buffer.length);
|
|
88
|
+
const out = [];
|
|
89
|
+
let at = 0;
|
|
90
|
+
while (joined.length - at >= 4) {
|
|
91
|
+
const length = new DataView(joined.buffer, at).getUint32(0);
|
|
92
|
+
if (length > MAX_MESSAGE)
|
|
93
|
+
throw new Error(`cast: a ${length}-byte message, over the protocol's ${MAX_MESSAGE}`);
|
|
94
|
+
if (joined.length - at < 4 + length)
|
|
95
|
+
break;
|
|
96
|
+
out.push(decode(joined.subarray(at + 4, at + 4 + length)));
|
|
97
|
+
at += 4 + length;
|
|
98
|
+
}
|
|
99
|
+
buffer = joined.slice(at);
|
|
100
|
+
return out;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type Clock } from "../../clock.ts";
|
|
3
|
+
import { type Found } from "../../discover.ts";
|
|
4
|
+
import type { Integration } from "../../home.ts";
|
|
5
|
+
import type { Manifest } from "../../manifest.ts";
|
|
6
|
+
import { type CastConnect } from "./codec.ts";
|
|
7
|
+
import { type CastDevice, type CastSnapshot, type Resolved } from "./state.ts";
|
|
8
|
+
import { type CastActions, type ServeClip } from "./writes.ts";
|
|
9
|
+
export type { CastConnect } from "./codec.ts";
|
|
10
|
+
export type { CastDevice, CastSnapshot, Resolved } from "./state.ts";
|
|
11
|
+
export type { AnnounceResult, CastActions, CastClip, ServeClip, } from "./writes.ts";
|
|
12
|
+
/** A Cast device or speaker group in a home config. No host: a group's moves with its leader, so every address comes from discovery. */
|
|
13
|
+
export declare const configSchema: z.ZodObject<{
|
|
14
|
+
connector: z.ZodLiteral<"cast">;
|
|
15
|
+
name: z.ZodString;
|
|
16
|
+
castId: z.ZodString;
|
|
17
|
+
}, z.core.$loose>;
|
|
18
|
+
/** Discovery's answers as `resolve` returns them: Cast entries only, port 8009 unless discovery said otherwise. */
|
|
19
|
+
export declare const resolvedFrom: (found: Found[]) => Resolved[];
|
|
20
|
+
export type CastConfig = {
|
|
21
|
+
/** Every Cast device and group on the network (the CLI runs `dns-sd`). Called at start, after any drop, and on refresh while one is missing. */
|
|
22
|
+
resolve: () => Promise<Resolved[]>;
|
|
23
|
+
/** Opens a socket: `tlsConnect` from `@khalido/home/cast/tls`. */
|
|
24
|
+
connect: CastConnect;
|
|
25
|
+
/**
|
|
26
|
+
* The config's entries: one discovery does not find shows as disconnected rather than not at all. With a cached
|
|
27
|
+
* `host` and `port` (an address cache) an entry connects at once; discovery runs only when an entry has none, or a
|
|
28
|
+
* cached address fails. A cached port other than 8009 is a group's (devices always listen on 8009).
|
|
29
|
+
*/
|
|
30
|
+
expect?: {
|
|
31
|
+
castId: string;
|
|
32
|
+
name: string;
|
|
33
|
+
host?: string;
|
|
34
|
+
port?: number;
|
|
35
|
+
}[];
|
|
36
|
+
/** Each address that connected and passed the certificate check, for an address cache; again with the device's new name when it reports a rename. `name` is the device's own, never a config's. */
|
|
37
|
+
seen?: (id: string, address: {
|
|
38
|
+
host: string;
|
|
39
|
+
port: number;
|
|
40
|
+
name?: string;
|
|
41
|
+
}) => void;
|
|
42
|
+
clock?: Clock;
|
|
43
|
+
/** Heartbeat interval; a socket silent for three of them is dropped. */
|
|
44
|
+
pingMs?: number;
|
|
45
|
+
/** For `announce` with a clip of bytes: `serveClip` from `@khalido/home/cast/serve`. */
|
|
46
|
+
serve?: ServeClip;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Every Cast device and group as one integration, state only: one socket per entry, CONNECT, receiver and multizone
|
|
50
|
+
* status (a group's members, a device's own name), media status from a running media app's transport, then pushed updates; a PING every 5 s.
|
|
51
|
+
* A dropped socket starts one shared re-discovery (a group's leader may have changed) and reconnects with backoff at
|
|
52
|
+
* whatever address discovery last gave. While anything is down or missing, discovery repeats at 1, 5 and 15 minutes, then
|
|
53
|
+
* hourly, as the relocator does; never once per socket. The writes (`CastActions`) go over these same sockets.
|
|
54
|
+
*/
|
|
55
|
+
export declare function cast(config: CastConfig): Integration<CastSnapshot, "cast"> & CastActions;
|
|
56
|
+
/** One Cast device or group in a few words: "Nest Hub: idle · 47%", "Group: idle · 43% · 3 members: A, B, C, led by A". */
|
|
57
|
+
export declare function castText(d: CastDevice, s: CastSnapshot): string;
|
|
58
|
+
/** Devices first, then groups, each by name. */
|
|
59
|
+
export declare const castDevices: (s: CastSnapshot) => CastDevice[];
|
|
60
|
+
export declare const manifest: Manifest<CastSnapshot>;
|