@glasshome/sync-layer 0.1.12 → 0.1.14
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/energy/prefs.d.ts +56 -0
- package/dist/history/query.d.ts +2 -0
- package/dist/history/statistics.d.ts +55 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +104 -1
- package/dist/solid/hooks.d.ts +10 -2
- package/dist/solid/index.d.ts +1 -1
- package/dist/solid/index.js +33 -26
- package/dist/solid/indices.d.ts +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Energy Preferences Fetching
|
|
3
|
+
*
|
|
4
|
+
* Reads HA's energy dashboard config via `energy/get_prefs`.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import type { SyncLayerConnection } from "../connection/types";
|
|
9
|
+
/**
|
|
10
|
+
* An energy source entry. Only the fields the widgets read are typed; the
|
|
11
|
+
* rest is passthrough. HA emits one of several shapes keyed by `type`:
|
|
12
|
+
* - grid: `flow_from[].stat_energy_from`, `flow_to[].stat_energy_to`
|
|
13
|
+
* - solar: `stat_energy_from`
|
|
14
|
+
* - battery: `stat_energy_from`, `stat_energy_to`
|
|
15
|
+
*/
|
|
16
|
+
export interface EnergySource {
|
|
17
|
+
type: "grid" | "solar" | "battery" | "gas" | "water" | string;
|
|
18
|
+
stat_energy_from?: string;
|
|
19
|
+
stat_energy_to?: string;
|
|
20
|
+
flow_from?: Array<{
|
|
21
|
+
stat_energy_from: string;
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
}>;
|
|
24
|
+
flow_to?: Array<{
|
|
25
|
+
stat_energy_to: string;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}>;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
/** A device tracked on the energy dashboard. */
|
|
31
|
+
export interface EnergyDeviceConsumption {
|
|
32
|
+
stat_consumption: string;
|
|
33
|
+
name?: string;
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* HA energy dashboard preferences.
|
|
38
|
+
*
|
|
39
|
+
* Based on HA frontend: EnergyPreferences.
|
|
40
|
+
*/
|
|
41
|
+
export interface EnergyPreferences {
|
|
42
|
+
energy_sources: EnergySource[];
|
|
43
|
+
device_consumption: EnergyDeviceConsumption[];
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
/** Connection surface used by energy prefs fetching. */
|
|
47
|
+
type EnergyConnection = Pick<SyncLayerConnection, "sendMessagePromise">;
|
|
48
|
+
/**
|
|
49
|
+
* Fetch the HA energy dashboard preferences.
|
|
50
|
+
*
|
|
51
|
+
* Returns `null` when no energy dashboard is configured (HA raises an error
|
|
52
|
+
* for `energy/get_prefs` in that case); callers treat `null` as "not
|
|
53
|
+
* configured".
|
|
54
|
+
*/
|
|
55
|
+
export declare function fetchEnergyPreferences(connection: EnergyConnection): Promise<EnergyPreferences | null>;
|
|
56
|
+
export {};
|
package/dist/history/query.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface HistoryPoint {
|
|
|
7
7
|
lastUpdated: number;
|
|
8
8
|
lastChanged?: number;
|
|
9
9
|
}
|
|
10
|
+
/** Hard cap on retained points per entity timeline. */
|
|
11
|
+
export declare const MAX_HISTORY_POINTS = 5760;
|
|
10
12
|
/** Check if an entity has active history tracking. */
|
|
11
13
|
export declare function isHistoryTracked(entityId: EntityId): boolean;
|
|
12
14
|
/** Start tracking history for an entity. Fetches initial backfill. */
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Long-term Statistics Fetching
|
|
3
|
+
*
|
|
4
|
+
* Reads HA's recorder long-term statistics via `recorder/statistics_during_period`.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
import type { SyncLayerConnection } from "../connection/types";
|
|
9
|
+
/**
|
|
10
|
+
* A single statistics bucket for one statistic id.
|
|
11
|
+
*
|
|
12
|
+
* HA 2023.6+ omits columns that were not requested and may omit null
|
|
13
|
+
* columns, so every value field is optional. `start`/`end` are normalized
|
|
14
|
+
* to ms epoch numbers regardless of the version's wire format.
|
|
15
|
+
*/
|
|
16
|
+
export interface StatisticValue {
|
|
17
|
+
/** Bucket start (ms epoch) */
|
|
18
|
+
start: number;
|
|
19
|
+
/** Bucket end (ms epoch) */
|
|
20
|
+
end: number;
|
|
21
|
+
mean?: number;
|
|
22
|
+
sum?: number;
|
|
23
|
+
min?: number;
|
|
24
|
+
max?: number;
|
|
25
|
+
state?: number;
|
|
26
|
+
change?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Aggregation period for statistics buckets. */
|
|
29
|
+
export type StatisticsPeriod = "5minute" | "hour" | "day" | "week" | "month";
|
|
30
|
+
/**
|
|
31
|
+
* Options for {@link fetchStatisticsDuringPeriod}.
|
|
32
|
+
*/
|
|
33
|
+
export interface StatisticsQueryOptions {
|
|
34
|
+
/** Start time (required) */
|
|
35
|
+
startTime: Date;
|
|
36
|
+
/** End time (optional, defaults to now on the HA side) */
|
|
37
|
+
endTime?: Date;
|
|
38
|
+
/** Aggregation period */
|
|
39
|
+
period: StatisticsPeriod;
|
|
40
|
+
}
|
|
41
|
+
/** Connection surface used by statistics fetching. */
|
|
42
|
+
type StatisticsConnection = Pick<SyncLayerConnection, "sendMessagePromise">;
|
|
43
|
+
/**
|
|
44
|
+
* HA returns start/end as ISO strings or ms numbers depending on version.
|
|
45
|
+
* Normalize both shapes to ms epoch numbers; unparseable values become NaN.
|
|
46
|
+
*/
|
|
47
|
+
export declare function normalizeStatisticTime(value: unknown): number;
|
|
48
|
+
/**
|
|
49
|
+
* Fetch long-term statistics for one or more statistic ids during a period.
|
|
50
|
+
*
|
|
51
|
+
* Statistic ids with no data are returned as empty arrays so callers never
|
|
52
|
+
* hit `undefined`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function fetchStatisticsDuringPeriod(connection: StatisticsConnection, statisticIds: string[], options: StatisticsQueryOptions): Promise<Record<string, StatisticValue[]>>;
|
|
55
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -26,14 +26,18 @@ export { resetStore, setState, state } from "./core/store";
|
|
|
26
26
|
export type { CallServiceRequest, CoreEventType, Domain, EntityCategory, EntityRegistryEntry, EventStateChangedData, HAEvent, RegistryEntryDisabler, RegistryEntryHider, ServiceCall, ServiceName, WsCommandType, WsResult, } from "@glasshome/ha-types";
|
|
27
27
|
export { fetchStream, fetchStreamData } from "./camera/fetch";
|
|
28
28
|
export { getStream, refreshStream } from "./camera/query";
|
|
29
|
+
export { fetchEnergyPreferences } from "./energy/prefs";
|
|
30
|
+
export type { EnergyDeviceConsumption, EnergyPreferences, EnergySource, } from "./energy/prefs";
|
|
29
31
|
export type { CameraStream, CameraStreamData, CameraStreams, EntityStreamQueryOptions, StreamFormat, StreamQueryOptions, StreamResult, } from "./camera/types";
|
|
30
32
|
export { getWebRtcClientConfig, sendWebRtcCandidate, startWebRtcSession, } from "./camera/webrtc";
|
|
31
33
|
export { buildAreaView, buildDeviceView, getAreaView, getAreaViews, } from "./entities/area-views";
|
|
32
34
|
export { buildEntityView, entityViewEquals, getAllEntityViews, getEntityView, getEntityViews, } from "./entities/views";
|
|
33
35
|
export { DOMAINS_USE_LAST_UPDATED, entityIdHistoryNeedsAttributes, LINE_ATTRIBUTES_TO_KEEP, NEED_ATTRIBUTE_DOMAINS, } from "./history/constants";
|
|
34
36
|
export { fetchEntityHistory, fetchHistory, historyStateToTimeline } from "./history/fetch";
|
|
35
|
-
export { appendHistoryPoint, bulkAppendHistoryPoints, isHistoryTracked, trackEntityHistory, untrackEntityHistory, } from "./history/query";
|
|
37
|
+
export { appendHistoryPoint, bulkAppendHistoryPoints, isHistoryTracked, MAX_HISTORY_POINTS, trackEntityHistory, untrackEntityHistory, } from "./history/query";
|
|
36
38
|
export type { HistoryPoint } from "./history/query";
|
|
39
|
+
export { fetchStatisticsDuringPeriod, normalizeStatisticTime, } from "./history/statistics";
|
|
40
|
+
export type { StatisticsPeriod, StatisticsQueryOptions, StatisticValue, } from "./history/statistics";
|
|
37
41
|
export type { EntityHistoryData, EntityHistoryQueryOptions, EntityHistoryResult, EntityHistoryState, HistoryData, HistoryQueryOptions, HistoryResult, HistoryStates, TimelineState, } from "./history/types";
|
|
38
42
|
export { fetchForecast, fetchForecastData, fetchForecasts } from "./weather/fetch";
|
|
39
43
|
export { getForecast, getForecasts, refreshForecast, refreshForecasts } from "./weather/query";
|
package/dist/index.js
CHANGED
|
@@ -807,6 +807,8 @@ function historyStateToTimeline(s) {
|
|
|
807
807
|
|
|
808
808
|
//#endregion
|
|
809
809
|
//#region src/history/query.ts
|
|
810
|
+
/** Hard cap on retained points per entity timeline. */
|
|
811
|
+
const MAX_HISTORY_POINTS = 5760;
|
|
810
812
|
/** Set of entity IDs with active history tracking. */
|
|
811
813
|
const trackedEntities = /* @__PURE__ */ new Set();
|
|
812
814
|
/** Check if an entity has active history tracking. */
|
|
@@ -860,6 +862,7 @@ function appendHistoryPoint(entityId, stateValue, attributes, lastUpdated, lastC
|
|
|
860
862
|
function bulkAppendHistoryPoints(points) {
|
|
861
863
|
if (points.length === 0) return;
|
|
862
864
|
setState(produce((s) => {
|
|
865
|
+
const touched = /* @__PURE__ */ new Set();
|
|
863
866
|
for (const { entityId, stateValue, attributes, lastUpdated, lastChanged } of points) {
|
|
864
867
|
const history = s.history[entityId];
|
|
865
868
|
if (!history) continue;
|
|
@@ -876,6 +879,13 @@ function bulkAppendHistoryPoints(points) {
|
|
|
876
879
|
lastChanged,
|
|
877
880
|
lastUpdated
|
|
878
881
|
});
|
|
882
|
+
touched.add(entityId);
|
|
883
|
+
}
|
|
884
|
+
for (const entityId of touched) {
|
|
885
|
+
const history = s.history[entityId];
|
|
886
|
+
if (!history) continue;
|
|
887
|
+
if (history.entityHistory.length > MAX_HISTORY_POINTS) history.entityHistory.splice(0, history.entityHistory.length - MAX_HISTORY_POINTS);
|
|
888
|
+
if (history.timeline.length > MAX_HISTORY_POINTS) history.timeline.splice(0, history.timeline.length - MAX_HISTORY_POINTS);
|
|
879
889
|
}
|
|
880
890
|
}));
|
|
881
891
|
}
|
|
@@ -2164,6 +2174,35 @@ async function refreshStream(entityId, format = "hls") {
|
|
|
2164
2174
|
return getStream(entityId, { format });
|
|
2165
2175
|
}
|
|
2166
2176
|
|
|
2177
|
+
//#endregion
|
|
2178
|
+
//#region src/energy/prefs.ts
|
|
2179
|
+
function isRecord(value) {
|
|
2180
|
+
return typeof value === "object" && value !== null;
|
|
2181
|
+
}
|
|
2182
|
+
/**
|
|
2183
|
+
* Fetch the HA energy dashboard preferences.
|
|
2184
|
+
*
|
|
2185
|
+
* Returns `null` when no energy dashboard is configured (HA raises an error
|
|
2186
|
+
* for `energy/get_prefs` in that case); callers treat `null` as "not
|
|
2187
|
+
* configured".
|
|
2188
|
+
*/
|
|
2189
|
+
async function fetchEnergyPreferences(connection) {
|
|
2190
|
+
let response;
|
|
2191
|
+
try {
|
|
2192
|
+
response = await connection.sendMessagePromise({ type: "energy/get_prefs" });
|
|
2193
|
+
} catch {
|
|
2194
|
+
return null;
|
|
2195
|
+
}
|
|
2196
|
+
if (!isRecord(response)) return null;
|
|
2197
|
+
const sources = Array.isArray(response.energy_sources) ? response.energy_sources.filter(isRecord) : [];
|
|
2198
|
+
const devices = Array.isArray(response.device_consumption) ? response.device_consumption.filter(isRecord) : [];
|
|
2199
|
+
return {
|
|
2200
|
+
...response,
|
|
2201
|
+
energy_sources: sources,
|
|
2202
|
+
device_consumption: devices
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2167
2206
|
//#endregion
|
|
2168
2207
|
//#region src/camera/webrtc.ts
|
|
2169
2208
|
/**
|
|
@@ -2248,6 +2287,70 @@ async function sendWebRtcCandidate(entityId, sessionId, candidate) {
|
|
|
2248
2287
|
});
|
|
2249
2288
|
}
|
|
2250
2289
|
|
|
2290
|
+
//#endregion
|
|
2291
|
+
//#region src/history/statistics.ts
|
|
2292
|
+
const STATISTIC_TYPES = [
|
|
2293
|
+
"mean",
|
|
2294
|
+
"sum",
|
|
2295
|
+
"min",
|
|
2296
|
+
"max",
|
|
2297
|
+
"state",
|
|
2298
|
+
"change"
|
|
2299
|
+
];
|
|
2300
|
+
/**
|
|
2301
|
+
* HA returns start/end as ISO strings or ms numbers depending on version.
|
|
2302
|
+
* Normalize both shapes to ms epoch numbers; unparseable values become NaN.
|
|
2303
|
+
*/
|
|
2304
|
+
function normalizeStatisticTime(value) {
|
|
2305
|
+
if (typeof value === "number") return value;
|
|
2306
|
+
if (typeof value === "string") return new Date(value).getTime();
|
|
2307
|
+
return NaN;
|
|
2308
|
+
}
|
|
2309
|
+
function normalizeNumber(value) {
|
|
2310
|
+
return typeof value === "number" ? value : void 0;
|
|
2311
|
+
}
|
|
2312
|
+
function normalizeStatisticValue(raw) {
|
|
2313
|
+
const value = {
|
|
2314
|
+
start: normalizeStatisticTime(raw.start),
|
|
2315
|
+
end: normalizeStatisticTime(raw.end)
|
|
2316
|
+
};
|
|
2317
|
+
const mean = normalizeNumber(raw.mean);
|
|
2318
|
+
if (mean !== void 0) value.mean = mean;
|
|
2319
|
+
const sum = normalizeNumber(raw.sum);
|
|
2320
|
+
if (sum !== void 0) value.sum = sum;
|
|
2321
|
+
const min = normalizeNumber(raw.min);
|
|
2322
|
+
if (min !== void 0) value.min = min;
|
|
2323
|
+
const max = normalizeNumber(raw.max);
|
|
2324
|
+
if (max !== void 0) value.max = max;
|
|
2325
|
+
const stateVal = normalizeNumber(raw.state);
|
|
2326
|
+
if (stateVal !== void 0) value.state = stateVal;
|
|
2327
|
+
const change = normalizeNumber(raw.change);
|
|
2328
|
+
if (change !== void 0) value.change = change;
|
|
2329
|
+
return value;
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Fetch long-term statistics for one or more statistic ids during a period.
|
|
2333
|
+
*
|
|
2334
|
+
* Statistic ids with no data are returned as empty arrays so callers never
|
|
2335
|
+
* hit `undefined`.
|
|
2336
|
+
*/
|
|
2337
|
+
async function fetchStatisticsDuringPeriod(connection, statisticIds, options) {
|
|
2338
|
+
const { startTime, endTime, period } = options;
|
|
2339
|
+
const params = {
|
|
2340
|
+
type: "recorder/statistics_during_period",
|
|
2341
|
+
start_time: startTime.toISOString(),
|
|
2342
|
+
statistic_ids: statisticIds,
|
|
2343
|
+
period,
|
|
2344
|
+
types: STATISTIC_TYPES
|
|
2345
|
+
};
|
|
2346
|
+
if (endTime) params.end_time = endTime.toISOString();
|
|
2347
|
+
const response = await connection.sendMessagePromise(params);
|
|
2348
|
+
const result = {};
|
|
2349
|
+
for (const id of statisticIds) result[id] = [];
|
|
2350
|
+
if (response && typeof response === "object") for (const [id, buckets] of Object.entries(response)) result[id] = Array.isArray(buckets) ? buckets.map(normalizeStatisticValue) : [];
|
|
2351
|
+
return result;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2251
2354
|
//#endregion
|
|
2252
2355
|
//#region src/weather/fetch.ts
|
|
2253
2356
|
/**
|
|
@@ -2510,4 +2613,4 @@ async function refreshForecasts(entityId, types) {
|
|
|
2510
2613
|
const VERSION = "0.1.0";
|
|
2511
2614
|
|
|
2512
2615
|
//#endregion
|
|
2513
|
-
export { DOMAINS_USE_LAST_UPDATED, LINE_ATTRIBUTES_TO_KEEP, NEED_ATTRIBUTE_DOMAINS, VERSION, addDebugIncomingMessageListener, addDebugOutgoingMessageListener, appendHistoryPoint, applyDemoServiceCall, area, areas, authenticateWithOAuth, authenticateWithToken, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, bulkAppendHistoryPoints, callService, createDemoFixtures, disconnect, entities, entitiesByAreaAndDomain, entitiesWhere, entity, entityIdHistoryNeedsAttributes, entityViewEquals, extractDomain, fetchEntityHistory, fetchForecast, fetchForecastData, fetchForecasts, fetchHistory, fetchStream, fetchStreamData, getAllEntityViews, getAreaView, getAreaViews, getConnection, getConnectionState, getEntityView, getEntityViews, getForecast, getForecasts, getStream, getWebRtcClientConfig, historyStateToTimeline, initConnection, isAuthValid, isConnected, isDemoMode, isHistoryTracked, loadDemoData, refreshAuth, refreshForecast, refreshForecasts, refreshStream, registerEntity, resetStore, searchEntities, sendCommand, sendWebRtcCandidate, setState, startWebRtcSession, state, toggle, trackEntityHistory, turnOff, turnOn, unloadDemoData, untrackEntityHistory, updateEntity };
|
|
2616
|
+
export { DOMAINS_USE_LAST_UPDATED, LINE_ATTRIBUTES_TO_KEEP, MAX_HISTORY_POINTS, NEED_ATTRIBUTE_DOMAINS, VERSION, addDebugIncomingMessageListener, addDebugOutgoingMessageListener, appendHistoryPoint, applyDemoServiceCall, area, areas, authenticateWithOAuth, authenticateWithToken, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, bulkAppendHistoryPoints, callService, createDemoFixtures, disconnect, entities, entitiesByAreaAndDomain, entitiesWhere, entity, entityIdHistoryNeedsAttributes, entityViewEquals, extractDomain, fetchEnergyPreferences, fetchEntityHistory, fetchForecast, fetchForecastData, fetchForecasts, fetchHistory, fetchStatisticsDuringPeriod, fetchStream, fetchStreamData, getAllEntityViews, getAreaView, getAreaViews, getConnection, getConnectionState, getEntityView, getEntityViews, getForecast, getForecasts, getStream, getWebRtcClientConfig, historyStateToTimeline, initConnection, isAuthValid, isConnected, isDemoMode, isHistoryTracked, loadDemoData, normalizeStatisticTime, refreshAuth, refreshForecast, refreshForecasts, refreshStream, registerEntity, resetStore, searchEntities, sendCommand, sendWebRtcCandidate, setState, startWebRtcSession, state, toggle, trackEntityHistory, turnOff, turnOn, unloadDemoData, untrackEntityHistory, updateEntity };
|
package/dist/solid/hooks.d.ts
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @packageDocumentation
|
|
12
12
|
*/
|
|
13
|
-
import type { Accessor } from "solid-js";
|
|
14
|
-
import type { AreaView, CameraStreamData, ConnectionState, EntityHistoryData, EntityView, GlassHomeState, HassEntity, WeatherForecastsData } from "@glasshome/sync-layer";
|
|
13
|
+
import type { Accessor, Resource } from "solid-js";
|
|
14
|
+
import type { AreaView, CameraStreamData, ConnectionState, EntityHistoryData, EntityView, GlassHomeState, HassEntity, StatisticsQueryOptions, StatisticValue, WeatherForecastsData } from "@glasshome/sync-layer";
|
|
15
15
|
import { callService, toggle, turnOff, turnOn } from "@glasshome/sync-layer";
|
|
16
16
|
/**
|
|
17
17
|
* Get a reactive EntityView for an entity ID.
|
|
@@ -71,6 +71,14 @@ export declare function useArea(areaId: Accessor<string> | string): Accessor<Are
|
|
|
71
71
|
* this hook simply reads it reactively.
|
|
72
72
|
*/
|
|
73
73
|
export declare function useEntityHistory(entityId: Accessor<string> | string): Accessor<EntityHistoryData | undefined>;
|
|
74
|
+
/**
|
|
75
|
+
* Get reactive long-term statistics for a statistic id.
|
|
76
|
+
*
|
|
77
|
+
* Fetches via `recorder/statistics_during_period` and re-fetches whenever the
|
|
78
|
+
* statistic id or options change. The returned Resource exposes `.loading`
|
|
79
|
+
* and `.error` alongside the value accessor.
|
|
80
|
+
*/
|
|
81
|
+
export declare function useEntityStatistics(statisticId: Accessor<string> | string, options: Accessor<StatisticsQueryOptions> | StatisticsQueryOptions): Resource<StatisticValue[]>;
|
|
74
82
|
/**
|
|
75
83
|
* Get reactive camera stream data.
|
|
76
84
|
*/
|
package/dist/solid/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/
|
|
9
|
-
export { useArea, useAreas, useCamera, useConnection, useEntities, useEntity, useEntityHistory, useEntityState, useForecast, useService, useStore, useToggle, useTurnOff, useTurnOn, } from "./hooks";
|
|
9
|
+
export { useArea, useAreas, useCamera, useConnection, useEntities, useEntity, useEntityHistory, useEntityState, useEntityStatistics, useForecast, useService, useStore, useToggle, useTurnOff, useTurnOn, } from "./hooks";
|
|
10
10
|
export { allEntityIds, byArea, byDevice, byDomain, byFloor, byLabel, } from "./indices";
|
package/dist/solid/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createEffect, createMemo, onCleanup } from "solid-js";
|
|
2
|
-
import { buildAreaView, callService, entityViewEquals, extractDomain, getEntityView, registerEntity, state, toggle, turnOff, turnOn } from "@glasshome/sync-layer";
|
|
1
|
+
import { createEffect, createMemo, createResource, onCleanup } from "solid-js";
|
|
2
|
+
import { buildAreaView, callService, entityViewEquals, extractDomain, fetchStatisticsDuringPeriod, getEntityView, registerEntity, state, toggle, turnOff, turnOn } from "@glasshome/sync-layer";
|
|
3
3
|
|
|
4
4
|
//#region src/solid/hooks.ts
|
|
5
5
|
/**
|
|
@@ -152,6 +152,28 @@ function useEntityHistory(entityId) {
|
|
|
152
152
|
return createMemo(() => state.history[getId()]);
|
|
153
153
|
}
|
|
154
154
|
/**
|
|
155
|
+
* Get reactive long-term statistics for a statistic id.
|
|
156
|
+
*
|
|
157
|
+
* Fetches via `recorder/statistics_during_period` and re-fetches whenever the
|
|
158
|
+
* statistic id or options change. The returned Resource exposes `.loading`
|
|
159
|
+
* and `.error` alongside the value accessor.
|
|
160
|
+
*/
|
|
161
|
+
function useEntityStatistics(statisticId, options) {
|
|
162
|
+
const getId = typeof statisticId === "function" ? statisticId : () => statisticId;
|
|
163
|
+
const getOptions = typeof options === "function" ? options : () => options;
|
|
164
|
+
const [data] = createResource(() => ({
|
|
165
|
+
id: getId(),
|
|
166
|
+
options: getOptions()
|
|
167
|
+
}), async ({ id, options: opts }) => {
|
|
168
|
+
if (!id) return [];
|
|
169
|
+
const conn = state.conn;
|
|
170
|
+
if (!conn) return [];
|
|
171
|
+
const result = await fetchStatisticsDuringPeriod(conn, [id], opts);
|
|
172
|
+
return result[id] ?? [];
|
|
173
|
+
});
|
|
174
|
+
return data;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
155
177
|
* Get reactive camera stream data.
|
|
156
178
|
*/
|
|
157
179
|
function useCamera(entityId) {
|
|
@@ -179,9 +201,6 @@ function useStore(selector) {
|
|
|
179
201
|
|
|
180
202
|
//#endregion
|
|
181
203
|
//#region src/solid/indices.ts
|
|
182
|
-
/**
|
|
183
|
-
* Group items by a key function
|
|
184
|
-
*/
|
|
185
204
|
function groupBy(items, keyFn) {
|
|
186
205
|
const result = {};
|
|
187
206
|
for (const item of items) {
|
|
@@ -190,26 +209,14 @@ function groupBy(items, keyFn) {
|
|
|
190
209
|
}
|
|
191
210
|
return result;
|
|
192
211
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Create a lazily-initialized memo. The actual createMemo runs on first
|
|
195
|
-
* access, inside whichever Solid root is current at that point.
|
|
196
|
-
* This avoids top-level createMemo calls that execute outside any root.
|
|
197
|
-
*/
|
|
198
|
-
function lazyMemo(fn) {
|
|
199
|
-
let memo;
|
|
200
|
-
return () => {
|
|
201
|
-
if (!memo) memo = createMemo(fn);
|
|
202
|
-
return memo();
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
212
|
/** Entity IDs grouped by domain (e.g., "light" -> ["light.living_room", ...]) */
|
|
206
|
-
const byDomain =
|
|
213
|
+
const byDomain = () => groupBy(Object.keys(state.entities), (id) => extractDomain(id));
|
|
207
214
|
/** Entity IDs grouped by area (via entity registry area_id) */
|
|
208
|
-
const byArea =
|
|
215
|
+
const byArea = () => groupBy(Object.keys(state.entityRegistry), (id) => state.entityRegistry[id]?.area_id ?? null);
|
|
209
216
|
/** Entity IDs grouped by device (via entity registry device_id) */
|
|
210
|
-
const byDevice =
|
|
217
|
+
const byDevice = () => groupBy(Object.keys(state.entityRegistry), (id) => state.entityRegistry[id]?.device_id ?? null);
|
|
211
218
|
/** Entity IDs grouped by floor (device -> area -> floor chain) */
|
|
212
|
-
const byFloor =
|
|
219
|
+
const byFloor = () => {
|
|
213
220
|
const result = {};
|
|
214
221
|
for (const [entityId, reg] of Object.entries(state.entityRegistry)) if (reg.device_id) {
|
|
215
222
|
const device = state.devices[reg.device_id];
|
|
@@ -219,15 +226,15 @@ const byFloor = lazyMemo(() => {
|
|
|
219
226
|
}
|
|
220
227
|
}
|
|
221
228
|
return result;
|
|
222
|
-
}
|
|
229
|
+
};
|
|
223
230
|
/** Entity IDs grouped by label */
|
|
224
|
-
const byLabel =
|
|
231
|
+
const byLabel = () => {
|
|
225
232
|
const result = {};
|
|
226
233
|
for (const [entityId, reg] of Object.entries(state.entityRegistry)) for (const label of reg.labels ?? []) (result[label] ??= []).push(entityId);
|
|
227
234
|
return result;
|
|
228
|
-
}
|
|
235
|
+
};
|
|
229
236
|
/** All entity IDs currently in the store */
|
|
230
|
-
const allEntityIds =
|
|
237
|
+
const allEntityIds = () => Object.keys(state.entities);
|
|
231
238
|
|
|
232
239
|
//#endregion
|
|
233
|
-
export { allEntityIds, byArea, byDevice, byDomain, byFloor, byLabel, useArea, useAreas, useCamera, useConnection, useEntities, useEntity, useEntityHistory, useEntityState, useForecast, useService, useStore, useToggle, useTurnOff, useTurnOn };
|
|
240
|
+
export { allEntityIds, byArea, byDevice, byDomain, byFloor, byLabel, useArea, useAreas, useCamera, useConnection, useEntities, useEntity, useEntityHistory, useEntityState, useEntityStatistics, useForecast, useService, useStore, useToggle, useTurnOff, useTurnOn };
|
package/dist/solid/indices.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/** Reactive derived entity indices
|
|
1
|
+
/** Reactive derived entity indices. Each call reads from the SolidJS store directly,
|
|
2
|
+
* so consumers wrap in createMemo within their own reactive root for memoization. */
|
|
2
3
|
import type { Accessor } from "solid-js";
|
|
3
4
|
/** Entity IDs grouped by domain (e.g., "light" -> ["light.living_room", ...]) */
|
|
4
5
|
export declare const byDomain: Accessor<Record<string, string[]>>;
|