@nebulahome/package-sdk 1.1.0 → 1.3.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/dist/sun.js ADDED
@@ -0,0 +1,328 @@
1
+ //#region ../contracts/dist/sun.js
2
+ /**
3
+ * Where the sun is, and when it gets somewhere, worked out on this machine.
4
+ *
5
+ * The house knows its latitude and longitude and nothing else is needed: the sun's height and
6
+ * bearing at any instant follow from the date with a few lines of spherical astronomy, so there is
7
+ * no service to call and nothing leaves the house. The formulas are the ones the widely used
8
+ * `suncalc` library takes from the astronomy pages of aa.quae.nl, good to about a minute for any
9
+ * date this platform will see.
10
+ *
11
+ * Shared by `core`, which wakes automations on sun events and publishes the sun's elevation and
12
+ * azimuth as two entities of the system device, and by the web interface, which previews the next
13
+ * event under a block and today's sunrise and sunset in the first-run setup. One implementation,
14
+ * so the preview never disagrees with what the engine does.
15
+ *
16
+ * Elevations are geometric (no refraction); the horizon events use the standard -0.833°, which
17
+ * includes refraction and the sun's half-width, so "sunrise" here is the moment almanacs print.
18
+ */
19
+ /** The elevations the named sun events are defined by, in degrees. */
20
+ const SUN_ANGLES = {
21
+ /** Sunrise and sunset: the top of the disc on the horizon, refraction included. */
22
+ horizon: -.833,
23
+ /** Civil dawn and dusk. */
24
+ civil: -6,
25
+ /** Where the blue hour gives way to the golden hour. */
26
+ blueToGolden: -4,
27
+ /** The upper edge of the golden hour. */
28
+ goldenTop: 6
29
+ };
30
+ const RAD = Math.PI / 180;
31
+ const DAY_MS = 864e5;
32
+ const J1970 = 2440588;
33
+ const J2000 = 2451545;
34
+ const J0 = 9e-4;
35
+ const OBLIQUITY = RAD * 23.4397;
36
+ /** How finely a crossing is pinned down: well under a second. */
37
+ const BISECTION_STEPS = 32;
38
+ /**
39
+ * Days since the J2000 epoch.
40
+ *
41
+ * @param ms - An instant, in Unix milliseconds.
42
+ * @returns The day number.
43
+ */
44
+ function toDays(ms) {
45
+ return ms / DAY_MS - .5 + J1970 - J2000;
46
+ }
47
+ /**
48
+ * An instant from a Julian date.
49
+ *
50
+ * @param julian - The Julian date.
51
+ * @returns Unix milliseconds.
52
+ */
53
+ function fromJulian(julian) {
54
+ return (julian + .5 - J1970) * DAY_MS;
55
+ }
56
+ /**
57
+ * The sun's mean anomaly.
58
+ *
59
+ * @param days - Days since J2000.
60
+ * @returns Radians.
61
+ */
62
+ function meanAnomaly(days) {
63
+ return RAD * (357.5291 + .98560028 * days);
64
+ }
65
+ /**
66
+ * The sun's ecliptic longitude.
67
+ *
68
+ * @param anomaly - Its mean anomaly.
69
+ * @returns Radians.
70
+ */
71
+ function eclipticLongitude(anomaly) {
72
+ return anomaly + RAD * (1.9148 * Math.sin(anomaly) + .02 * Math.sin(2 * anomaly) + 3e-4 * Math.sin(3 * anomaly)) + RAD * 102.9372 + Math.PI;
73
+ }
74
+ /**
75
+ * The sun's height and bearing at an instant, in radians, with the bearing from the south.
76
+ *
77
+ * @param ms - The instant.
78
+ * @param position - Where.
79
+ * @returns Altitude and azimuth.
80
+ */
81
+ function horizontal(ms, position) {
82
+ const lw = RAD * -position.longitude;
83
+ const phi = RAD * position.latitude;
84
+ const days = toDays(ms);
85
+ const longitude = eclipticLongitude(meanAnomaly(days));
86
+ const declination = Math.asin(Math.sin(OBLIQUITY) * Math.sin(longitude));
87
+ const ascension = Math.atan2(Math.sin(longitude) * Math.cos(OBLIQUITY), Math.cos(longitude));
88
+ const hourAngle = RAD * (280.16 + 360.9856235 * days) - lw - ascension;
89
+ return {
90
+ altitude: Math.asin(Math.sin(phi) * Math.sin(declination) + Math.cos(phi) * Math.cos(declination) * Math.cos(hourAngle)),
91
+ azimuth: Math.atan2(Math.sin(hourAngle), Math.cos(hourAngle) * Math.sin(phi) - Math.tan(declination) * Math.cos(phi))
92
+ };
93
+ }
94
+ /**
95
+ * The sun's elevation at an instant.
96
+ *
97
+ * @param ms - The instant.
98
+ * @param position - Where.
99
+ * @returns Degrees above the horizon.
100
+ */
101
+ function elevationAt(ms, position) {
102
+ return horizontal(ms, position).altitude / RAD;
103
+ }
104
+ /**
105
+ * Where the sun stands at an instant.
106
+ *
107
+ * @param at - The instant.
108
+ * @param position - Where.
109
+ * @returns Its elevation and its azimuth from north, in degrees.
110
+ */
111
+ function sunPosition$1(at, position) {
112
+ const { altitude, azimuth } = horizontal(at.getTime(), position);
113
+ const fromNorth = (azimuth / RAD + 180) % 360;
114
+ return {
115
+ elevation: altitude / RAD,
116
+ azimuth: fromNorth < 0 ? fromNorth + 360 : fromNorth
117
+ };
118
+ }
119
+ /**
120
+ * Which solar day an instant falls in: the cycle whose noon is nearest.
121
+ *
122
+ * @param ms - The instant.
123
+ * @param position - Where.
124
+ * @returns The cycle number.
125
+ */
126
+ function cycleOf(ms, position) {
127
+ const lw = RAD * -position.longitude;
128
+ return Math.round(toDays(ms) - J0 - lw / (2 * Math.PI));
129
+ }
130
+ /**
131
+ * When the sun is highest in one solar day.
132
+ *
133
+ * @param cycle - The solar day.
134
+ * @param position - Where.
135
+ * @returns Unix milliseconds.
136
+ */
137
+ function noonOf(cycle, position) {
138
+ const lw = RAD * -position.longitude;
139
+ const days = J0 + lw / (2 * Math.PI) + cycle;
140
+ const anomaly = meanAnomaly(days);
141
+ const longitude = eclipticLongitude(anomaly);
142
+ return fromJulian(J2000 + days + .0053 * Math.sin(anomaly) - .0069 * Math.sin(2 * longitude));
143
+ }
144
+ /**
145
+ * Finds where the elevation reaches an angle between two instants it lies on either side of.
146
+ *
147
+ * @param from - One end.
148
+ * @param to - The other end.
149
+ * @param angle - The elevation sought.
150
+ * @param position - Where.
151
+ * @returns The instant, in Unix milliseconds.
152
+ */
153
+ function bisect(from, to, angle, position) {
154
+ let low = from;
155
+ let high = to;
156
+ const isLowBelow = elevationAt(low, position) < angle;
157
+ for (let step = 0; step < BISECTION_STEPS; step += 1) {
158
+ const middle = (low + high) / 2;
159
+ if (elevationAt(middle, position) < angle === isLowBelow) low = middle;
160
+ else high = middle;
161
+ }
162
+ return Math.round((low + high) / 2);
163
+ }
164
+ /**
165
+ * Reads one solar day.
166
+ *
167
+ * @param cycle - Which.
168
+ * @param position - Where.
169
+ * @returns Its noon and midnights.
170
+ */
171
+ function solarDay(cycle, position) {
172
+ const noon = noonOf(cycle, position);
173
+ const midnightBefore = noon - DAY_MS / 2;
174
+ const midnightAfter = noon + DAY_MS / 2;
175
+ return {
176
+ noon,
177
+ midnightBefore,
178
+ midnightAfter,
179
+ noonElevation: elevationAt(noon, position),
180
+ beforeElevation: elevationAt(midnightBefore, position),
181
+ afterElevation: elevationAt(midnightAfter, position)
182
+ };
183
+ }
184
+ /**
185
+ * When the sun rises through an angle in one solar day.
186
+ *
187
+ * @param day - The day.
188
+ * @param angle - The elevation.
189
+ * @param position - Where.
190
+ * @returns The instant, or null when it does not cross that day.
191
+ */
192
+ function risingThrough(day, angle, position) {
193
+ return day.beforeElevation < angle && day.noonElevation >= angle ? bisect(day.midnightBefore, day.noon, angle, position) : null;
194
+ }
195
+ /**
196
+ * When the sun sets through an angle in one solar day.
197
+ *
198
+ * @param day - The day.
199
+ * @param angle - The elevation.
200
+ * @param position - Where.
201
+ * @returns The instant, or null when it does not cross that day.
202
+ */
203
+ function settingThrough(day, angle, position) {
204
+ return day.noonElevation >= angle && day.afterElevation < angle ? bisect(day.noon, day.midnightAfter, angle, position) : null;
205
+ }
206
+ /**
207
+ * The sunrise, noon and sunset of the solar day an instant falls in.
208
+ *
209
+ * @param at - Any instant of the day.
210
+ * @param position - Where.
211
+ * @returns The three moments; sunrise or sunset is null on a day the sun does not cross the
212
+ * horizon.
213
+ */
214
+ function sunTimes$1(at, position) {
215
+ const day = solarDay(cycleOf(at.getTime(), position), position);
216
+ const sunrise = risingThrough(day, SUN_ANGLES.horizon, position);
217
+ const sunset = settingThrough(day, SUN_ANGLES.horizon, position);
218
+ return {
219
+ sunrise: sunrise === null ? null : new Date(sunrise),
220
+ solarNoon: new Date(day.noon),
221
+ sunset: sunset === null ? null : new Date(sunset)
222
+ };
223
+ }
224
+ /**
225
+ * What the sun is doing at an instant.
226
+ *
227
+ * @param at - The instant.
228
+ * @param position - Where.
229
+ * @returns Its position, whether it is rising, and whether today is a polar day or night.
230
+ */
231
+ function sunState$1(at, position) {
232
+ const ms = at.getTime();
233
+ const day = solarDay(cycleOf(ms, position), position);
234
+ const { horizon } = SUN_ANGLES;
235
+ return {
236
+ ...sunPosition$1(at, position),
237
+ isRising: ms < day.noon,
238
+ isMidnightSun: Math.min(day.beforeElevation, day.afterElevation) >= horizon,
239
+ isPolarNight: day.noonElevation < horizon
240
+ };
241
+ }
242
+ /**
243
+ * The one word for what the sun is doing, for the sun entity's attributes.
244
+ *
245
+ * @param state - What the sun is doing.
246
+ * @returns Day, golden hour, blue hour, night or polar night.
247
+ */
248
+ function sunPhaseLabel$1(state) {
249
+ const { civil, blueToGolden, goldenTop } = SUN_ANGLES;
250
+ if (state.isPolarNight) return "polar_night";
251
+ if (state.elevation >= goldenTop) return "day";
252
+ if (state.elevation > blueToGolden) return "golden_hour";
253
+ return state.elevation > civil ? "blue_hour" : "night";
254
+ }
255
+ [{
256
+ reading: "elevation",
257
+ entityId: "sensor.system_sun_elevation",
258
+ name: "Sun elevation",
259
+ icon: "sun",
260
+ unitOfMeasurement: "°",
261
+ stateClass: "measurement",
262
+ displayPrecision: 1,
263
+ capabilities: { sensor_numeric: {} }
264
+ }, {
265
+ reading: "azimuth",
266
+ entityId: "sensor.system_sun_azimuth",
267
+ name: "Sun azimuth",
268
+ icon: "compass",
269
+ unitOfMeasurement: "°",
270
+ stateClass: "measurement",
271
+ displayPrecision: 1,
272
+ capabilities: { sensor_numeric: {} }
273
+ }].map((entity) => entity.entityId);
274
+ //#endregion
275
+ //#region src/sun.ts
276
+ /**
277
+ * `@nebulahome/package-sdk/sun`: where the sun is, for a package that draws or decides by it
278
+ * (PACKAGES_DOCS.md 6.7). Since 1.2.
279
+ *
280
+ * The same formulas the platform runs for its sun entities and its sun triggers, so a package that
281
+ * works out a past or a future day agrees with what the house said live: `sensor.system_sun_*`
282
+ * for now, these for any other moment. The house's position is the house area's
283
+ * (`useAreas()` on the interface).
284
+ */
285
+ /** The platform's sun entities, reported every minute while the house has a position. */
286
+ const SUN_ELEVATION_ENTITY_ID = "sensor.system_sun_elevation";
287
+ const SUN_AZIMUTH_ENTITY_ID = "sensor.system_sun_azimuth";
288
+ /**
289
+ * Where the sun stands at a moment, seen from a place.
290
+ *
291
+ * @param at - The moment.
292
+ * @param position - The place.
293
+ * @returns Its elevation and azimuth, in degrees.
294
+ */
295
+ function sunPosition(at, position) {
296
+ return sunPosition$1(at, position);
297
+ }
298
+ /**
299
+ * The sunrise, solar noon and sunset of the day a moment falls in, at a place.
300
+ *
301
+ * @param at - A moment of the day.
302
+ * @param position - The place.
303
+ * @returns Them; no rise or set on a polar day or night.
304
+ */
305
+ function sunTimes(at, position) {
306
+ return sunTimes$1(at, position);
307
+ }
308
+ /**
309
+ * Where the sun stands at a moment, and whether it rises.
310
+ *
311
+ * @param at - The moment.
312
+ * @param position - The place.
313
+ * @returns Its position, and how its day goes.
314
+ */
315
+ function sunState(at, position) {
316
+ return sunState$1(at, position);
317
+ }
318
+ /**
319
+ * The light of a moment, in a word: day, golden hour, blue hour, night.
320
+ *
321
+ * @param state - Where the sun stands.
322
+ * @returns The word.
323
+ */
324
+ function sunPhaseLabel(state) {
325
+ return sunPhaseLabel$1(state);
326
+ }
327
+ //#endregion
328
+ export { SUN_AZIMUTH_ENTITY_ID, SUN_ELEVATION_ENTITY_ID, sunPhaseLabel, sunPosition, sunState, sunTimes };
package/dist/testing.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { V as CapabilityCommand, et as ReportedCapabilityState, i as SettingsDefinition, lt as EntityId, r as JsonValue, t as JsonObject } from "./chunks/json.js";
2
- import { C as StateChange, _ as ServerContext, f as ReportOptions, h as RpcCaller, l as EntitySnapshot, o as DeviceInput, t as Awaitable } from "./chunks/context.js";
2
+ import { i as TelemetryPoint, r as StateHistoryPoint } from "./chunks/history.js";
3
+ import { T as StateChange, _ as RpcCaller, l as EntitySnapshot, m as ReportOptions, o as DeviceInput, t as Awaitable, y as ServerContext } from "./chunks/context.js";
3
4
  //#region src/testing/index.d.ts
4
5
  /** What a test context starts with. */
5
6
  export type TestContextOptions<TSettings extends SettingsDefinition | undefined> = {
@@ -13,9 +14,16 @@ export type TestContextOptions<TSettings extends SettingsDefinition | undefined>
13
14
  readonly settings?: JsonObject;
14
15
  /** Entities of the house `ctx.entities` reads. */
15
16
  readonly entities?: readonly EntitySnapshot[];
17
+ /** The past `ctx.history` reads, by entity: points in any order. */
18
+ readonly history?: Readonly<Record<string, RecordedHistory>>;
16
19
  readonly dataDir?: string;
17
20
  readonly cacheDir?: string;
18
21
  };
22
+ /** One entity's past, as a test gives it. */
23
+ export type RecordedHistory = {
24
+ readonly states?: readonly StateHistoryPoint[];
25
+ readonly telemetry?: readonly TelemetryPoint[];
26
+ };
19
27
  /** One report the package made. */
20
28
  export type RecordedReport = {
21
29
  readonly entityId: EntityId;
@@ -96,6 +104,13 @@ export type TestContext<TSettings extends SettingsDefinition | undefined> = {
96
104
  * @returns Once every handler ran.
97
105
  */
98
106
  readonly emitStateChange: (change: StateChange) => Promise<void>;
107
+ /**
108
+ * Gives an entity a past, where `ctx.history` reads it.
109
+ *
110
+ * @param entityId - The entity.
111
+ * @param history - Its changes and readings, in any order.
112
+ */
113
+ readonly setHistory: (entityId: EntityId, history: RecordedHistory) => void;
99
114
  /**
100
115
  * Answers a service the package calls.
101
116
  *
package/dist/testing.js CHANGED
@@ -38,6 +38,52 @@ async function check(schema, value) {
38
38
  }
39
39
  //#endregion
40
40
  //#region src/testing/index.ts
41
+ /** The most entities and points one read of the past takes, as the platform has it. */
42
+ const HISTORY_MAX_ENTITIES = 64;
43
+ const HISTORY_MAX_POINTS = 1e4;
44
+ /** An hour and a day, in milliseconds. */
45
+ const HOUR_MS = 36e5;
46
+ const DAY_MS = 24 * HOUR_MS;
47
+ /**
48
+ * The resolution the platform reads a window at when a read leaves it to it.
49
+ *
50
+ * @param spanMs - How long the window is.
51
+ * @returns Raw up to an hour, minutes up to 30 hours, hours up to 80 days, days beyond.
52
+ */
53
+ function resolutionFor(spanMs) {
54
+ if (spanMs <= HOUR_MS) return "raw";
55
+ if (spanMs <= 30 * HOUR_MS) return "1m";
56
+ return spanMs <= 80 * DAY_MS ? "1h" : "1d";
57
+ }
58
+ /**
59
+ * Reads a window of points as the platform does: checked, oldest first, the newest kept.
60
+ *
61
+ * @param read - Which entities, which window, how many points each.
62
+ * @param pointsOf - An entity's points, in any order.
63
+ * @returns Each entity's points in the window, and which lost their oldest.
64
+ * @throws {Error} When the read names no entity or too many, or its window is backwards.
65
+ */
66
+ function readWindow(read, pointsOf) {
67
+ const from = new Date(read.from).getTime();
68
+ const to = new Date(read.to).getTime();
69
+ const limit = read.limit ?? HISTORY_MAX_POINTS;
70
+ if (read.entityIds.length === 0 || read.entityIds.length > HISTORY_MAX_ENTITIES) throw new Error(`A read of the past names 1 to ${String(HISTORY_MAX_ENTITIES)} entities`);
71
+ if (!(from < to)) throw new Error("The window starts at or after it ends");
72
+ const series = {};
73
+ const truncated = [];
74
+ for (const entityId of read.entityIds) {
75
+ const inWindow = pointsOf(entityId).filter((point) => {
76
+ const time = new Date(point.time).getTime();
77
+ return time >= from && time < to;
78
+ }).toSorted((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
79
+ series[entityId] = inWindow.slice(Math.max(0, inWindow.length - limit));
80
+ if (inWindow.length > limit) truncated.push(entityId);
81
+ }
82
+ return {
83
+ series,
84
+ truncated
85
+ };
86
+ }
41
87
  /** Service names the platform keeps for itself. */
42
88
  const RESERVED_SERVICES = /* @__PURE__ */ new Set([
43
89
  "command",
@@ -99,6 +145,7 @@ function createTestContext(options = {}) {
99
145
  const reports = [];
100
146
  const logs = [];
101
147
  const entities = new Map((options.entities ?? []).map((each) => [each.entityId, each]));
148
+ const histories = new Map(Object.entries(options.history ?? {}));
102
149
  const methods = /* @__PURE__ */ new Map();
103
150
  const services = /* @__PURE__ */ new Map();
104
151
  const answers = /* @__PURE__ */ new Map();
@@ -462,6 +509,29 @@ function createTestContext(options = {}) {
462
509
  };
463
510
  }
464
511
  },
512
+ history: {
513
+ /**
514
+ * The changes of some entities in a window.
515
+ *
516
+ * @param read - Which, when, how many.
517
+ * @returns Them, oldest first.
518
+ */
519
+ states: async (read) => Promise.resolve(readWindow(read, (entityId) => histories.get(entityId)?.states ?? [])),
520
+ /**
521
+ * The readings of some entities in a window, as given: a test gives the buckets it wants.
522
+ *
523
+ * @param read - Which, when, how many, at which resolution.
524
+ * @returns Them, oldest first, and the resolution.
525
+ */
526
+ telemetry: async (read) => {
527
+ const span = new Date(read.to).getTime() - new Date(read.from).getTime();
528
+ const resolution = read.resolution === void 0 || read.resolution === "auto" ? resolutionFor(span) : read.resolution;
529
+ return Promise.resolve({
530
+ ...readWindow(read, (entityId) => histories.get(entityId)?.telemetry ?? []),
531
+ resolution
532
+ });
533
+ }
534
+ },
465
535
  rpc: { method: rpcMethod },
466
536
  services: {
467
537
  provide,
@@ -578,6 +648,15 @@ function createTestContext(options = {}) {
578
648
  entities.set(snapshot.entityId, snapshot);
579
649
  },
580
650
  /**
651
+ * Gives an entity a past.
652
+ *
653
+ * @param entityId - The entity.
654
+ * @param history - Its changes and readings.
655
+ */
656
+ setHistory: (entityId, history) => {
657
+ histories.set(entityId, history);
658
+ },
659
+ /**
581
660
  * Changes an entity of the house.
582
661
  *
583
662
  * @param change - The change.
package/dist/ui.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { G as CapabilityState, H as CapabilityCommands, S as FormValues, ct as EntityDomain, d as EntityTypeDefinition, f as ValuesOf, i as SettingsDefinition, lt as EntityId, q as CommandableCapability, r as JsonValue, s as ArgsOf, t as JsonObject, u as CommandsOf, z as ValueOfField } from "./chunks/json.js";
1
+ import { G as CapabilityState, H as CapabilityCommands, S as FormValues, Y as DeclaredCapabilities, ct as EntityDomain, d as EntityTypeDefinition, f as ValuesOf, i as SettingsDefinition, lt as EntityId, q as CommandableCapability, r as JsonValue, s as ArgsOf, t as JsonObject, u as CommandsOf, z as ValueOfField } from "./chunks/json.js";
2
2
  import { a as PageProps, n as EntityViewProps, t as CardProps } from "./chunks/props.js";
3
+ import { a as TelemetryResolution, i as TelemetryPoint, n as HistorySeries, r as StateHistoryPoint, t as ChangeCause } from "./chunks/history.js";
3
4
  import { AnchorHTMLAttributes, ComponentType, ReactElement, ReactNode } from "react";
4
5
  //#region src/ui/types.d.ts
5
6
  /** The package drawn. */
@@ -15,7 +16,13 @@ type EntityView = {
15
16
  readonly name: string;
16
17
  readonly icon: string | null;
17
18
  readonly deviceId: string | null;
19
+ /** Its area's key - the entity's own, else its device's - as {@link AreaView} `key` says it. */
18
20
  readonly areaId: string | null;
21
+ /**
22
+ * What it declares it can do, with the device's limits: a cover's tilt, a thermostat's range
23
+ * and modes, a select's options. Since 1.2.
24
+ */
25
+ readonly capabilities: DeclaredCapabilities;
19
26
  readonly state: CapabilityState;
20
27
  readonly attributes: JsonObject;
21
28
  readonly isAvailable: boolean;
@@ -24,6 +31,100 @@ type EntityView = {
24
31
  readonly deviceClass: string | null;
25
32
  readonly displayPrecision: number | null;
26
33
  };
34
+ /** A window of the past: between two moments (ISO 8601), or the last so many milliseconds. */
35
+ type HistoryWindow = {
36
+ readonly from: string;
37
+ readonly to: string;
38
+ } | {
39
+ readonly lastMs: number;
40
+ };
41
+ /** Which entities' past `useStateHistory` reads, over which window. Since 1.2. */
42
+ type HistoryQuery = {
43
+ /** At most 64. */
44
+ readonly entityIds: readonly EntityId[];
45
+ readonly window: HistoryWindow;
46
+ /** False holds the read back. */
47
+ readonly isEnabled?: boolean;
48
+ /** Reads it again every so often; a window of the last so many milliseconds moves with it. */
49
+ readonly refetchIntervalMs?: number;
50
+ };
51
+ /** Which entities' readings `useTelemetry` reads: `auto` picks the resolution by the window. */
52
+ type TelemetryQuery = HistoryQuery & {
53
+ readonly resolution?: TelemetryResolution | 'auto';
54
+ };
55
+ /** A read of the past, as it stands. */
56
+ type History<TPoint> = {
57
+ /** Each entity's points, oldest first; empty while it loads. */
58
+ readonly series: HistorySeries<TPoint>;
59
+ /** The window read, in milliseconds since the epoch; undefined before the first answer. */
60
+ readonly window: {
61
+ readonly from: number;
62
+ readonly to: number;
63
+ } | undefined;
64
+ /** The entities whose window held more than one read returns: their oldest points are gone. */
65
+ readonly truncated: readonly EntityId[];
66
+ readonly isLoading: boolean;
67
+ readonly error: Error | null;
68
+ /** Reads it again. */
69
+ readonly refetch: () => void;
70
+ };
71
+ /** A read of readings, and the resolution it was read at. */
72
+ type Telemetry = History<TelemetryPoint> & {
73
+ readonly resolution: TelemetryResolution;
74
+ };
75
+ /** What kind of place an area is. */
76
+ type AreaKind = 'house' | 'floor' | 'room';
77
+ /** One area of the house: the house itself, a floor, a room. Since 1.2. */
78
+ type AreaView = {
79
+ /** What `EntityView.areaId` names it by. */
80
+ readonly key: string;
81
+ readonly name: string;
82
+ readonly kind: AreaKind;
83
+ /** The floor a room is on, the house a floor is in; null for the house. */
84
+ readonly parentKey: string | null;
85
+ readonly icon: string | null;
86
+ readonly sortOrder: number;
87
+ /** A floor's storey: 0 the ground floor, -1 the one below; null for the rest. */
88
+ readonly level: number | null;
89
+ /** The house's position, when the owner has set it; null for the rest. */
90
+ readonly latitude: number | null;
91
+ readonly longitude: number | null;
92
+ };
93
+ /** A floor or a room to add to the house. Since 1.3. */
94
+ type AreaDraft = {
95
+ readonly kind: 'floor' | 'room';
96
+ readonly name: string;
97
+ /** The house a floor is in, the floor a room is on. */
98
+ readonly parentKey: string;
99
+ /** A floor's storey: 0 the ground floor, -1 the one below. */
100
+ readonly level?: number;
101
+ };
102
+ /** Changes to the house's areas, with the viewer's permissions. Since 1.3. */
103
+ type AreaActions = {
104
+ /** Whether the viewer may change the areas (`area:write`). */
105
+ readonly canEdit: boolean;
106
+ /** Adds a floor or a room; its key comes from its name. */
107
+ readonly create: (draft: AreaDraft) => Promise<AreaView>;
108
+ readonly rename: (key: string, name: string) => Promise<void>;
109
+ /** Moves a floor to another storey. */
110
+ readonly setLevel: (key: string, level: number) => Promise<void>;
111
+ /** Removes an area and everything under it. */
112
+ readonly remove: (key: string) => Promise<void>;
113
+ };
114
+ /** How a picture is fetched. */
115
+ type PictureOptions = {
116
+ /** A camera's still is fetched again this often; 15 seconds when not said, at least 2. */
117
+ readonly refreshMs?: number;
118
+ /** False holds it back. */
119
+ readonly isEnabled?: boolean;
120
+ };
121
+ /** A camera's latest still, or an image entity's picture, as an address an `<img>` shows. */
122
+ type EntityPicture = {
123
+ /** Null before there is one, or when the entity has none. */
124
+ readonly url: string | null;
125
+ readonly isLoading: boolean;
126
+ readonly error: Error | null;
127
+ };
27
128
  /** Which entities `useEntities` reads. */
28
129
  type EntitiesFilter = {
29
130
  /** Whose: the package's own (the default), anyone's, or another package's by id. */
@@ -193,6 +294,10 @@ type PackageLinkProps = LinkAttributes & {
193
294
  type PlatformLinkProps = LinkAttributes & {
194
295
  readonly to: PlatformTarget;
195
296
  };
297
+ /** What the header of the package's page shows. Since 1.3. */
298
+ type PageActionsProps = {
299
+ readonly children?: ReactNode;
300
+ };
196
301
  /** What the interface lends to implement `/ui`. */
197
302
  type UiHost = {
198
303
  readonly usePackage: () => PackageInfo;
@@ -208,6 +313,14 @@ type UiHost = {
208
313
  readonly useLocale: () => Locale;
209
314
  /** Since 1.1. */
210
315
  readonly useTranslation: () => Translation;
316
+ /** Since 1.2. */
317
+ readonly useStateHistory: (query: HistoryQuery) => History<StateHistoryPoint>;
318
+ /** Since 1.2. */
319
+ readonly useTelemetry: (query: TelemetryQuery) => Telemetry;
320
+ /** Since 1.2. */
321
+ readonly useAreas: () => readonly AreaView[];
322
+ /** Since 1.2. */
323
+ readonly useEntityPicture: (entityId: EntityId, options?: PictureOptions) => EntityPicture;
211
324
  readonly useTheme: () => Theme;
212
325
  readonly usePortalContainer: () => HTMLElement | null;
213
326
  readonly usePackageRoute: () => PackageRoute;
@@ -215,6 +328,10 @@ type UiHost = {
215
328
  readonly usePlatformNavigate: () => PlatformNavigate;
216
329
  readonly PackageLink: ComponentType<PackageLinkProps>;
217
330
  readonly PlatformLink: ComponentType<PlatformLinkProps>;
331
+ /** Since 1.3. */
332
+ readonly PageActions: ComponentType<PageActionsProps>;
333
+ /** Since 1.3. */
334
+ readonly useAreaActions: () => AreaActions;
218
335
  };
219
336
  //#endregion
220
337
  //#region src/ui/index.d.ts
@@ -306,6 +423,46 @@ export declare function useLocale(): Locale;
306
423
  * @returns `t`, and the language it speaks.
307
424
  */
308
425
  export declare function useTranslation(): Translation;
426
+ /**
427
+ * The recorded changes of some entities over a window, live as the window moves: what a chart of
428
+ * an on/off thing, an activity list or a replay of a day is drawn from. Read with the viewer's
429
+ * permissions: an entity they may not read has no points.
430
+ *
431
+ * @param query - Which entities (at most 64), which window, how often to read it again.
432
+ * @returns Each entity's changes, oldest first, and whether the read is still on its way.
433
+ */
434
+ export declare function useStateHistory(query: HistoryQuery): History<StateHistoryPoint>;
435
+ /**
436
+ * The numeric readings of some entities over a window: what a sensor's chart is drawn from. A
437
+ * long window is read in buckets of a minute, an hour or a day, each with its lowest and highest.
438
+ *
439
+ * @param query - Which entities (at most 64), which window, which resolution (`auto` by default).
440
+ * @returns Each entity's readings, oldest first, and the resolution they were read at.
441
+ */
442
+ export declare function useTelemetry(query: TelemetryQuery): Telemetry;
443
+ /**
444
+ * The areas of the house: the house itself (with its position, when set), its floors and its
445
+ * rooms, as the owner arranged them.
446
+ *
447
+ * @returns Them, in the owner's order.
448
+ */
449
+ export declare function useAreas(): readonly AreaView[];
450
+ /**
451
+ * Adds, renames and removes the house's floors and rooms, as the platform's areas page does, with
452
+ * the viewer's permissions: `canEdit` says whether they hold them. Since 1.3.
453
+ *
454
+ * @returns What changes the areas.
455
+ */
456
+ export declare function useAreaActions(): AreaActions;
457
+ /**
458
+ * A camera's latest still, fetched again every so often, or an image entity's picture, fetched
459
+ * again when it changes: an address an `<img>` shows, let go of when a newer one replaces it.
460
+ *
461
+ * @param entityId - A `camera.*` or an `image.*` entity.
462
+ * @param options - How often a still is fetched again, and whether at all.
463
+ * @returns The address, or null before there is one.
464
+ */
465
+ export declare function useEntityPicture(entityId: EntityId, options?: PictureOptions): EntityPicture;
309
466
  /**
310
467
  * The theme the interface draws in, for what draws its own colours: a canvas, a chart.
311
468
  *
@@ -350,5 +507,13 @@ export declare function PackageLink(props: PackageLinkProps): ReactElement;
350
507
  * @returns The link.
351
508
  */
352
509
  export declare function PlatformLink(props: PlatformLinkProps): ReactElement;
510
+ /**
511
+ * Buttons and links drawn in the header of the package's page, in place of the platform's own:
512
+ * the page's actions, beside its title. On a card or a view it draws nothing. Since 1.3.
513
+ *
514
+ * @param props - What the header shows.
515
+ * @returns The actions.
516
+ */
517
+ export declare function PageActions(props: PageActionsProps): ReactElement;
353
518
  //#endregion
354
- export type { CardProps, CustomEntityState, EntitiesFilter, EntityView, EntityViewProps, Locale, Mutation, MutationOptions, PackageCall, PackageInfo, PackageLinkProps, PackageNavigate, PackageRoute, PackageSettings, PageProps, PlatformLinkProps, PlatformNavigate, PlatformTarget, PluralEnglish, RpcOptions, RpcState, SendCommand, SettingsPatch, Theme, Translate, Translation, TranslationValues, UiHost, Viewer, VisibleSettings };
519
+ export type { AreaActions, AreaDraft, AreaKind, AreaView, CardProps, ChangeCause, CustomEntityState, EntitiesFilter, EntityPicture, EntityView, EntityViewProps, History, HistoryQuery, HistorySeries, HistoryWindow, Locale, Mutation, MutationOptions, PackageCall, PackageInfo, PackageLinkProps, PackageNavigate, PackageRoute, PackageSettings, PageActionsProps, PageProps, PictureOptions, PlatformLinkProps, PlatformNavigate, PlatformTarget, PluralEnglish, RpcOptions, RpcState, SendCommand, SettingsPatch, StateHistoryPoint, Telemetry, TelemetryPoint, TelemetryQuery, TelemetryResolution, Theme, Translate, Translation, TranslationValues, UiHost, Viewer, VisibleSettings };