@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.
- package/CHANGELOG.md +39 -0
- package/README.md +64 -9
- package/dist/chunks/context.d.ts +46 -1
- package/dist/chunks/history.d.ts +39 -0
- package/dist/chunks/host.js +21 -7
- package/dist/chunks/json.d.ts +1 -1
- package/dist/chunks/node-build.js +185 -13
- package/dist/chunks/node-config.js +38 -36
- package/dist/chunks/node-pack.js +1 -1
- package/dist/chunks/version.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/server.d.ts +3 -2
- package/dist/styles.css +12 -0
- package/dist/sun.d.ts +62 -0
- package/dist/sun.js +328 -0
- package/dist/testing.d.ts +16 -1
- package/dist/testing.js +79 -0
- package/dist/ui.d.ts +157 -2
- package/dist/ui.js +97 -2
- package/dist/vite.d.ts +2 -2
- package/package.json +7 -3
|
@@ -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
|
|
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 - `
|
|
6177
|
-
* `manifest.json` writes `requires`
|
|
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,
|
|
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 };
|
package/dist/chunks/node-pack.js
CHANGED
|
@@ -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
|
|
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";
|
package/dist/chunks/version.js
CHANGED
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-
|
|
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-
|
|
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 {
|
|
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 {
|
|
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
|
*
|