@nebulahome/package-sdk 1.0.1 → 1.2.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.
@@ -5594,6 +5594,39 @@ function superRefine(fn, params) {
5594
5594
  function number(params) {
5595
5595
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
5596
5596
  }
5597
+ //#endregion
5598
+ //#region ../contracts/dist/package-id.js
5599
+ /**
5600
+ * A package's identifier, apart from the manifest: the integrations' instance ids share its
5601
+ * pattern, and the modules that need only the pattern must not pull the manifest's whole grammar
5602
+ * - and what that imports - in with it.
5603
+ */
5604
+ /**
5605
+ * A package identifier: `mqtt`, `nvr`, `3d-home`. It may start with a digit; its entities' object
5606
+ * ids then do too (`sensor.3d_home_kitchen_temperature`).
5607
+ *
5608
+ * Mirrors `packages_id_format` in DATABASE.md. It shares one namespace with the integrations'
5609
+ * instance ids, since both answer `svc.<id>.command`.
5610
+ */
5611
+ const PACKAGE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{1,63}$/;
5612
+ /** The reserved ids, for a quick look-up. */
5613
+ const RESERVED = /* @__PURE__ */ new Set([
5614
+ "api",
5615
+ "backup",
5616
+ "core",
5617
+ "derived",
5618
+ "hub",
5619
+ "logger",
5620
+ "nebula",
5621
+ "packet-manager",
5622
+ "platform",
5623
+ "proxy",
5624
+ "supervisor",
5625
+ "system",
5626
+ "web-ui"
5627
+ ]);
5628
+ /** Validates a package identifier. */
5629
+ const packageIdSchema = string().regex(PACKAGE_ID_PATTERN, "Must be a package id").refine((id) => !RESERVED.has(id), { message: "Is the name of a part of the platform, which no package may take" });
5597
5630
  /** Validates an icon identifier. */
5598
5631
  const iconIdSchema = string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "Must be an icon id");
5599
5632
  /**
@@ -6102,38 +6135,6 @@ const integrationSetupSchema = strictObject({
6102
6135
  });
6103
6136
  });
6104
6137
  //#endregion
6105
- //#region ../contracts/dist/package-id.js
6106
- /**
6107
- * A package's identifier, apart from the manifest: the integrations' instance ids share its
6108
- * pattern, and the modules that need only the pattern must not pull the manifest's whole grammar
6109
- * - and what that imports - in with it.
6110
- */
6111
- /**
6112
- * A package identifier: `mqtt`, `nvr`.
6113
- *
6114
- * Mirrors `packages_id_format` in DATABASE.md. It shares one namespace with the integrations'
6115
- * instance ids, since both answer `svc.<id>.command`.
6116
- */
6117
- const PACKAGE_ID_PATTERN = /^[a-z][a-z0-9-]{1,63}$/;
6118
- /** The reserved ids, for a quick look-up. */
6119
- const RESERVED = /* @__PURE__ */ new Set([
6120
- "api",
6121
- "backup",
6122
- "core",
6123
- "derived",
6124
- "hub",
6125
- "logger",
6126
- "nebula",
6127
- "packet-manager",
6128
- "platform",
6129
- "proxy",
6130
- "supervisor",
6131
- "system",
6132
- "web-ui"
6133
- ]);
6134
- /** Validates a package identifier. */
6135
- const packageIdSchema = string().regex(PACKAGE_ID_PATTERN, "Must be a package id").refine((id) => !RESERVED.has(id), { message: "Is the name of a part of the platform, which no package may take" });
6136
- //#endregion
6137
6138
  //#region ../contracts/dist/package-requires.js
6138
6139
  /**
6139
6140
  * What a package - or a vendor, in its `manifest.json` - needs besides itself (PACKAGES.md 7).
@@ -6141,7 +6142,7 @@ const packageIdSchema = string().regex(PACKAGE_ID_PATTERN, "Must be a package id
6141
6142
  * Apart from the manifest so that the vendors' grammar can name it without pulling the package
6142
6143
  * manifest's whole grammar - and what that imports - in with it.
6143
6144
  */
6144
- /** A service a package provides and others require: `mqtt-broker`, `object-detection`. */
6145
+ /** A service others require: `object-detection`, or `mqtt-broker`, which the platform runs. */
6145
6146
  const packageServiceNameSchema = string().regex(/^[a-z][a-z0-9-]{1,63}$/, "Must be a lower-case service name");
6146
6147
  /**
6147
6148
  * What a package - or a vendor, in its `manifest.json` - needs besides itself: the platform, by
@@ -6173,8 +6174,9 @@ const packageRequiresSchema = strictObject({
6173
6174
  * - **`ui`** is what it adds to the interface: a page of its own, cards for the dashboard, views
6174
6175
  * for its own kinds of entity - one ES module, served same-origin and loaded on demand.
6175
6176
  * - **`requires`** and **`provides`** are its dependencies: packages by id and version range, and
6176
- * services by name - `mqtt-broker` - that whichever package provides them satisfies. A vendor's
6177
- * `manifest.json` writes `requires` the same way.
6177
+ * services by name - `object-detection` - that whichever package provides them satisfies, or
6178
+ * that the platform runs itself (`mqtt-broker`). A vendor's `manifest.json` writes `requires`
6179
+ * the same way.
6178
6180
  * - **`entityTypes`** are kinds of entity no capability describes - a recording, a playlist - with
6179
6181
  * the fields of their state and the commands they take; an entity of one has the `custom`
6180
6182
  * capability, and the package's `entityViews` draw it.
@@ -6883,4 +6885,4 @@ async function keepingNodeEnv(run) {
6883
6885
  }
6884
6886
  }
6885
6887
  //#endregion
6886
- export { url as C, union as S, date as _, isRecord as a, strictObject as b, packageServiceNameSchema as c, semverRangeSchema as d, semverSchema as f, array as g, _enum as h, generatePackage as i, packageIdSchema as l, ZodISODateTime as m, keepingNodeEnv as n, PACKAGE_CATEGORIES as o, iconIdSchema as p, RELEASE_PATHS as r, packageRequiresSchema as s, githubEnvironment as t, DEPENDENCY_ID_PATTERN as u, int as v, _isoDateTime as w, string as x, record as y };
6888
+ export { url as C, union as S, date as _, isRecord as a, strictObject as b, packageServiceNameSchema as c, semverSchema as d, iconIdSchema as f, array as g, _enum as h, generatePackage as i, DEPENDENCY_ID_PATTERN as l, ZodISODateTime as m, keepingNodeEnv as n, PACKAGE_CATEGORIES as o, packageIdSchema as p, RELEASE_PATHS as r, packageRequiresSchema as s, githubEnvironment as t, semverRangeSchema as u, int as v, _isoDateTime as w, string as x, record as y };
@@ -1,5 +1,5 @@
1
1
  import { t as PackageConfigError } from "./node-problems.js";
2
- import { C as url, S as union, _ as date, a as isRecord, b as strictObject, c as packageServiceNameSchema, d as semverRangeSchema, f as semverSchema, g as array, h as _enum, l as packageIdSchema, m as ZodISODateTime, o as PACKAGE_CATEGORIES, p as iconIdSchema, r as RELEASE_PATHS, s as packageRequiresSchema, t as githubEnvironment, v as int, w as _isoDateTime, x as string } from "./node-config.js";
2
+ import { C as url, S as union, _ as date, a as isRecord, b as strictObject, c as packageServiceNameSchema, d as semverSchema, f as iconIdSchema, g as array, h as _enum, m as ZodISODateTime, o as PACKAGE_CATEGORIES, p as packageIdSchema, r as RELEASE_PATHS, s as packageRequiresSchema, t as githubEnvironment, u as semverRangeSchema, v as int, w as _isoDateTime, x as string } from "./node-config.js";
3
3
  import ro, { readFile, readdir, writeFile } from "node:fs/promises";
4
4
  import path, { basename, join, posix, win32 } from "node:path";
5
5
  import { Buffer as Buffer$1 } from "buffer";
@@ -7,6 +7,6 @@
7
7
  * the two are compared before a package is installed or started (PACKAGES_DOCS.md 11).
8
8
  */
9
9
  /** The SDK's own version, `1.0.0`. */
10
- const SDK_VERSION = "1.0.1";
10
+ const SDK_VERSION = "1.2.0";
11
11
  //#endregion
12
12
  export { SDK_VERSION as t };
package/dist/index.d.ts CHANGED
@@ -152,7 +152,7 @@ type PackageUi<TCards extends readonly CardDefinition[], TTypes extends readonly
152
152
  };
153
153
  /** What every package says, whatever it runs. */
154
154
  type PackageBase<TCards extends readonly CardDefinition[], TTypes extends readonly EntityTypeDefinition[]> = {
155
- /** `^[a-z][a-z0-9-]{1,63}$`, not one of the platform's names. */
155
+ /** `^[a-z0-9][a-z0-9-]{1,63}$`, not one of the platform's names. */
156
156
  readonly id: string;
157
157
  readonly name: string;
158
158
  readonly description: string;
package/dist/index.js CHANGED
@@ -130,7 +130,7 @@ const ENTITY_DOMAINS = [
130
130
  "alias"
131
131
  ];
132
132
  /** The shape of an entity identifier: a domain, a dot, an object id. */
133
- const ENTITY_ID_PATTERN = /^([a-z][a-z0-9_]{0,31})\.([a-z][a-z0-9_]{0,63})$/;
133
+ const ENTITY_ID_PATTERN = /^([a-z][a-z0-9_]{0,31})\.([a-z0-9][a-z0-9_]{0,63})$/;
134
134
  /**
135
135
  * Says whether a word is one of the platform's domains.
136
136
  *
package/dist/server.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { i as SettingsDefinition } from "./chunks/json.js";
2
- import { A as StandardSchemaV1, C as StateChange, D as Schema, E as SafeParser, O as SchemaIssue, S as SettingsChange, T as Unsubscribe, _ as ServerContext, a as CustomEntityInput, b as ServiceCallFunction, c as EntityInput, d as Logger, f as ReportOptions, g as RpcMethod, h as RpcCaller, i as CustomEntities, k as StandardResult, l as EntitySnapshot, m as RpcCall, n as CapabilityEntityInput, o as DeviceInput, p as RpcAccess, r as CustomCommand, s as EntityFilter, t as Awaitable, u as LogMethod, v as ServerSettings, w as SyncResult, x as ServiceProvide, y as ServiceCall } from "./chunks/context.js";
2
+ import { a as TelemetryResolution, i as TelemetryPoint, n as HistorySeries, r as StateHistoryPoint, t as ChangeCause } from "./chunks/history.js";
3
+ import { A as Schema, C as ServiceProvide, D as TelemetryRead, E as SyncResult, M as StandardResult, N as StandardSchemaV1, O as Unsubscribe, S as ServiceCallFunction, T as StateChange, _ as RpcCaller, a as CustomEntityInput, b as ServerSettings, c as EntityInput, d as HistoryRead, f as LogMethod, g as RpcCall, h as RpcAccess, i as CustomEntities, j as SchemaIssue, k as SafeParser, l as EntitySnapshot, m as ReportOptions, n as CapabilityEntityInput, o as DeviceInput, p as Logger, r as CustomCommand, s as EntityFilter, t as Awaitable, u as HistoryAnswer, v as RpcMethod, w as SettingsChange, x as ServiceCall, y as ServerContext } from "./chunks/context.js";
3
4
  //#region src/server/index.d.ts
4
5
  /** What stops what `start` opened. */
5
6
  export type Stop = () => Awaitable<void>;
@@ -27,4 +28,4 @@ export type ServerDefinition<TSettings extends SettingsDefinition | undefined =
27
28
  */
28
29
  export declare function defineServer<TSettings extends SettingsDefinition | undefined = undefined>(server: Omit<ServerDefinition<TSettings>, 'kind' | 'sdk'>): ServerDefinition<TSettings>;
29
30
  //#endregion
30
- export type { Awaitable, CapabilityEntityInput, CustomCommand, CustomEntities, CustomEntityInput, DeviceInput, EntityFilter, EntityInput, EntitySnapshot, LogMethod, Logger, ReportOptions, RpcAccess, RpcCall, RpcCaller, RpcMethod, SafeParser, Schema, SchemaIssue, ServerContext, ServerSettings, ServiceCall, ServiceCallFunction, ServiceProvide, SettingsChange, StandardResult, StandardSchemaV1, StateChange, SyncResult, Unsubscribe };
31
+ export type { Awaitable, CapabilityEntityInput, ChangeCause, CustomCommand, CustomEntities, CustomEntityInput, DeviceInput, EntityFilter, EntityInput, EntitySnapshot, HistoryAnswer, HistoryRead, HistorySeries, LogMethod, Logger, ReportOptions, RpcAccess, RpcCall, RpcCaller, RpcMethod, SafeParser, Schema, SchemaIssue, ServerContext, ServerSettings, ServiceCall, ServiceCallFunction, ServiceProvide, SettingsChange, StandardResult, StandardSchemaV1, StateChange, StateHistoryPoint, SyncResult, TelemetryPoint, TelemetryRead, TelemetryResolution, Unsubscribe };
package/dist/styles.css CHANGED
@@ -547,3 +547,15 @@
547
547
  @utility tabular {
548
548
  font-variant-numeric: tabular-nums;
549
549
  }
550
+
551
+ /*
552
+ * The Matter mark's square, lit violet from its top-left corner: the broker's hero and its
553
+ * dialogs' lead. Over the square's own ground, so it sits on `bg-surface-raised`.
554
+ */
555
+ @utility matter-glow {
556
+ background-image: radial-gradient(
557
+ 120% 120% at 0% 0%,
558
+ color-mix(in srgb, var(--color-violet) 22%, transparent),
559
+ transparent 60%
560
+ );
561
+ }
package/dist/sun.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ //#region src/sun.d.ts
2
+ /** A place on Earth, in degrees: north and east positive. */
3
+ export type GeoPosition = {
4
+ readonly latitude: number;
5
+ readonly longitude: number;
6
+ };
7
+ /** Where the sun stands, in degrees. */
8
+ export type SunPosition = {
9
+ /** Above the horizon, positive; below it, negative. */
10
+ readonly elevation: number;
11
+ /** Clockwise from true north: 90 east, 180 south. */
12
+ readonly azimuth: number;
13
+ };
14
+ /** A day's sunrise, solar noon and sunset; no rise or set on a polar day or night. */
15
+ export type SunTimes = {
16
+ readonly sunrise: Date | null;
17
+ readonly solarNoon: Date;
18
+ readonly sunset: Date | null;
19
+ };
20
+ /** Where the sun stands, and which way it goes. */
21
+ export type SunState = SunPosition & {
22
+ readonly isRising: boolean;
23
+ readonly isMidnightSun: boolean;
24
+ readonly isPolarNight: boolean;
25
+ };
26
+ /** The light the sun gives, in a word. */
27
+ export type SunPhaseLabel = 'day' | 'golden_hour' | 'blue_hour' | 'night' | 'polar_night';
28
+ /** The platform's sun entities, reported every minute while the house has a position. */
29
+ export declare const SUN_ELEVATION_ENTITY_ID = "sensor.system_sun_elevation";
30
+ export declare const SUN_AZIMUTH_ENTITY_ID = "sensor.system_sun_azimuth";
31
+ /**
32
+ * Where the sun stands at a moment, seen from a place.
33
+ *
34
+ * @param at - The moment.
35
+ * @param position - The place.
36
+ * @returns Its elevation and azimuth, in degrees.
37
+ */
38
+ export declare function sunPosition(at: Date, position: GeoPosition): SunPosition;
39
+ /**
40
+ * The sunrise, solar noon and sunset of the day a moment falls in, at a place.
41
+ *
42
+ * @param at - A moment of the day.
43
+ * @param position - The place.
44
+ * @returns Them; no rise or set on a polar day or night.
45
+ */
46
+ export declare function sunTimes(at: Date, position: GeoPosition): SunTimes;
47
+ /**
48
+ * Where the sun stands at a moment, and whether it rises.
49
+ *
50
+ * @param at - The moment.
51
+ * @param position - The place.
52
+ * @returns Its position, and how its day goes.
53
+ */
54
+ export declare function sunState(at: Date, position: GeoPosition): SunState;
55
+ /**
56
+ * The light of a moment, in a word: day, golden hour, blue hour, night.
57
+ *
58
+ * @param state - Where the sun stands.
59
+ * @returns The word.
60
+ */
61
+ export declare function sunPhaseLabel(state: SunState): SunPhaseLabel;
62
+ //#endregion
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
  *