@glasshome/sync-layer 0.1.14 → 0.1.16
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/core/store.d.ts +2 -0
- package/dist/demo/demo-data.d.ts +1 -1
- package/dist/demo/demo-provider.d.ts +7 -0
- package/dist/demo/energy-sim.d.ts +106 -0
- package/dist/demo-data-CavpxBw3.js +1186 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +102 -8
- package/dist/solid/hooks.d.ts +11 -0
- package/dist/solid/index.d.ts +1 -1
- package/dist/solid/index.js +31 -4
- package/dist/testing/index.js +23 -2
- package/dist/testing/mock.d.ts +6 -1
- package/package.json +3 -3
- package/dist/demo-data-D1ofmMa4.js +0 -608
package/dist/index.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export type { GlassHomeState } from "./core/store";
|
|
|
23
23
|
export type { AreaId, AreaView, ConnectionState, DeviceId, DeviceView, EntityDomain, EntityId, EntityView, HassEntity, } from "./core/types";
|
|
24
24
|
export { extractDomain } from "./core/types";
|
|
25
25
|
export { resetStore, setState, state } from "./core/store";
|
|
26
|
-
export type { CallServiceRequest, CoreEventType, Domain, EntityCategory, EntityRegistryEntry, EventStateChangedData, HAEvent, RegistryEntryDisabler, RegistryEntryHider, ServiceCall, ServiceName, WsCommandType, WsResult, } from "@glasshome/ha-types";
|
|
26
|
+
export type { CallServiceRequest, CoreEventType, Domain, EntityCategory, EntityRegistryEntry, EventStateChangedData, HAEvent, HassConfig, HassUnitSystem, 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
29
|
export { fetchEnergyPreferences } from "./energy/prefs";
|
|
@@ -42,5 +42,5 @@ export type { EntityHistoryData, EntityHistoryQueryOptions, EntityHistoryResult,
|
|
|
42
42
|
export { fetchForecast, fetchForecastData, fetchForecasts } from "./weather/fetch";
|
|
43
43
|
export { getForecast, getForecasts, refreshForecast, refreshForecasts } from "./weather/query";
|
|
44
44
|
export type { EntityForecastQueryOptions, ForecastQueryOptions, ForecastResult, ForecastsResult, ForecastType, WeatherForecast, WeatherForecastData, WeatherForecasts, WeatherForecastsData, } from "./weather/types";
|
|
45
|
-
export { applyDemoServiceCall, createDemoFixtures, isDemoMode, loadDemoData, unloadDemoData, } from "./demo/demo-provider";
|
|
45
|
+
export { applyDemoServiceCall, createDemoFixtures, isDemoMode, loadDemoData, startDemoEnergyTicker, stopDemoEnergyTicker, unloadDemoData, } from "./demo/demo-provider";
|
|
46
46
|
export declare const VERSION = "0.1.0";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createDemoFixtures, resetStore, setState, state } from "./demo-data-
|
|
1
|
+
import { ENERGY_ENTITY_IDS, createDemoFixtures, demoEnergyPreferences, energyEntityValue, formatEnergyState, isEnergyEntity, isSunUp, resetStore, setState, simulateEnergy, state, sunEvents, synthesizeEnergyHistory, synthesizeEnergyStatistics } from "./demo-data-CavpxBw3.js";
|
|
2
2
|
import { callService as callService$1, createConnection, createLongLivedTokenAuth, getAuth } from "home-assistant-js-websocket";
|
|
3
3
|
import { produce, reconcile } from "solid-js/store";
|
|
4
4
|
|
|
@@ -524,11 +524,13 @@ async function loadDemoData() {
|
|
|
524
524
|
setState("hassUrl", "https://demo.home-assistant.local");
|
|
525
525
|
setState("connectionState", "connected");
|
|
526
526
|
_isDemoMode = true;
|
|
527
|
+
startDemoEnergyTicker();
|
|
527
528
|
}
|
|
528
529
|
/**
|
|
529
530
|
* Unload demo data and reset to disconnected state.
|
|
530
531
|
*/
|
|
531
532
|
function unloadDemoData() {
|
|
533
|
+
stopDemoEnergyTicker();
|
|
532
534
|
setState("entities", reconcile({}));
|
|
533
535
|
setState("entityRegistry", reconcile({}));
|
|
534
536
|
setState("areas", reconcile({}));
|
|
@@ -537,6 +539,54 @@ function unloadDemoData() {
|
|
|
537
539
|
setState("connectionState", "disconnected");
|
|
538
540
|
_isDemoMode = false;
|
|
539
541
|
}
|
|
542
|
+
const ENERGY_TICK_MS = 5e3;
|
|
543
|
+
let _energyTicker = null;
|
|
544
|
+
/** Write the current energy sample into the store as string states (like real HA). */
|
|
545
|
+
function tickEnergy() {
|
|
546
|
+
const nowMs = Date.now();
|
|
547
|
+
const sample = simulateEnergy(nowMs);
|
|
548
|
+
const sun = sunEvents(nowMs);
|
|
549
|
+
const nowIso = new Date(nowMs).toISOString();
|
|
550
|
+
setState(produce((s) => {
|
|
551
|
+
for (const entityId of ENERGY_ENTITY_IDS) {
|
|
552
|
+
const e = s.entities[entityId];
|
|
553
|
+
if (!e) continue;
|
|
554
|
+
const value = energyEntityValue(entityId, sample);
|
|
555
|
+
if (value === void 0) continue;
|
|
556
|
+
const next = formatEnergyState(entityId, value);
|
|
557
|
+
if (e.state !== next) e.last_changed = nowIso;
|
|
558
|
+
e.state = next;
|
|
559
|
+
e.last_updated = nowIso;
|
|
560
|
+
}
|
|
561
|
+
const sunEntity = s.entities["sun.sun"];
|
|
562
|
+
if (sunEntity) {
|
|
563
|
+
const nextState = isSunUp(nowMs) ? "above_horizon" : "below_horizon";
|
|
564
|
+
if (sunEntity.state !== nextState) sunEntity.last_changed = nowIso;
|
|
565
|
+
sunEntity.state = nextState;
|
|
566
|
+
sunEntity.last_updated = nowIso;
|
|
567
|
+
sunEntity.attributes.elevation = sun.elevation;
|
|
568
|
+
sunEntity.attributes.next_rising = sun.nextRising;
|
|
569
|
+
sunEntity.attributes.next_setting = sun.nextSetting;
|
|
570
|
+
}
|
|
571
|
+
}));
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Start the demo energy ticker. Updates the energy sensors and `sun.sun`
|
|
575
|
+
* every 5 seconds. Idempotent: clears any prior interval first. SSR-safe.
|
|
576
|
+
*/
|
|
577
|
+
function startDemoEnergyTicker() {
|
|
578
|
+
stopDemoEnergyTicker();
|
|
579
|
+
if (typeof setInterval !== "function") return;
|
|
580
|
+
tickEnergy();
|
|
581
|
+
_energyTicker = setInterval(tickEnergy, ENERGY_TICK_MS);
|
|
582
|
+
}
|
|
583
|
+
/** Stop the demo energy ticker if running. */
|
|
584
|
+
function stopDemoEnergyTicker() {
|
|
585
|
+
if (_energyTicker !== null) {
|
|
586
|
+
clearInterval(_energyTicker);
|
|
587
|
+
_energyTicker = null;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
540
590
|
/**
|
|
541
591
|
* Apply an optimistic state update for demo mode.
|
|
542
592
|
*
|
|
@@ -564,10 +614,10 @@ function applyDemoServiceCall(_domain, service, _serviceData = {}, target = {})
|
|
|
564
614
|
else if (service === "unlock") e.state = "unlocked";
|
|
565
615
|
else if (service === "open_cover") {
|
|
566
616
|
e.state = "open";
|
|
567
|
-
e.attributes.current_position = 100;
|
|
617
|
+
if ("current_position" in e.attributes) e.attributes.current_position = 100;
|
|
568
618
|
} else if (service === "close_cover") {
|
|
569
619
|
e.state = "closed";
|
|
570
|
-
e.attributes.current_position = 0;
|
|
620
|
+
if ("current_position" in e.attributes) e.attributes.current_position = 0;
|
|
571
621
|
} else if (service === "set_cover_position" && _serviceData.position != null) {
|
|
572
622
|
e.attributes.current_position = _serviceData.position;
|
|
573
623
|
e.state = _serviceData.position > 0 ? "open" : "closed";
|
|
@@ -585,7 +635,7 @@ function applyTurnOn(e, domain, serviceData = {}) {
|
|
|
585
635
|
} else if (domain === "media_player") e.state = "playing";
|
|
586
636
|
else if (domain === "cover") {
|
|
587
637
|
e.state = "open";
|
|
588
|
-
e.attributes.current_position = 100;
|
|
638
|
+
if ("current_position" in e.attributes) e.attributes.current_position = 100;
|
|
589
639
|
} else e.state = "on";
|
|
590
640
|
}
|
|
591
641
|
function applyTurnOff(e, domain) {
|
|
@@ -595,7 +645,7 @@ function applyTurnOff(e, domain) {
|
|
|
595
645
|
} else if (domain === "media_player") e.state = "paused";
|
|
596
646
|
else if (domain === "cover") {
|
|
597
647
|
e.state = "closed";
|
|
598
|
-
e.attributes.current_position = 0;
|
|
648
|
+
if ("current_position" in e.attributes) e.attributes.current_position = 0;
|
|
599
649
|
} else e.state = "off";
|
|
600
650
|
}
|
|
601
651
|
|
|
@@ -734,6 +784,19 @@ function entityIdHistoryNeedsAttributes(entityId) {
|
|
|
734
784
|
*/
|
|
735
785
|
async function fetchHistory(options) {
|
|
736
786
|
const { startTime, endTime, entityIds = [], includeStartTimeState = true, significantChangesOnly = true, minimalResponse = true, noAttributes: noAttributesOverride } = options;
|
|
787
|
+
if (isDemoMode()) {
|
|
788
|
+
const endMs = (endTime ?? /* @__PURE__ */ new Date()).getTime();
|
|
789
|
+
const result = {};
|
|
790
|
+
for (const id of entityIds) {
|
|
791
|
+
if (!isEnergyEntity(id)) continue;
|
|
792
|
+
result[id] = synthesizeEnergyHistory(id, startTime.getTime(), endMs).map((p) => ({
|
|
793
|
+
s: p.s,
|
|
794
|
+
a: {},
|
|
795
|
+
lu: p.lu
|
|
796
|
+
}));
|
|
797
|
+
}
|
|
798
|
+
return result;
|
|
799
|
+
}
|
|
737
800
|
const startTimeStr = startTime.toISOString();
|
|
738
801
|
const endTimeStr = endTime?.toISOString();
|
|
739
802
|
const noAttributes = noAttributesOverride ?? (entityIds.length === 0 ? false : !entityIds.some((entityId) => entityIdHistoryNeedsAttributes(entityId)));
|
|
@@ -969,6 +1032,10 @@ async function subscribeToUpdates(conn$1) {
|
|
|
969
1032
|
handleStatisticsUpdate(event);
|
|
970
1033
|
}, "recorder_5min_statistics_generated");
|
|
971
1034
|
registrySubscriptions.push(() => statisticsSub());
|
|
1035
|
+
const coreConfigSub = await conn$1.subscribeEvents(() => {
|
|
1036
|
+
handleCoreConfigUpdate();
|
|
1037
|
+
}, "core_config_updated");
|
|
1038
|
+
registrySubscriptions.push(() => coreConfigSub());
|
|
972
1039
|
}
|
|
973
1040
|
/**
|
|
974
1041
|
* Clean up all active subscriptions
|
|
@@ -978,6 +1045,16 @@ async function cleanupSubscriptions() {
|
|
|
978
1045
|
for (const unsub of subscriptions) await unsub();
|
|
979
1046
|
registrySubscriptions = [];
|
|
980
1047
|
}
|
|
1048
|
+
async function handleCoreConfigUpdate() {
|
|
1049
|
+
const conn$1 = state.conn;
|
|
1050
|
+
if (!conn$1) return;
|
|
1051
|
+
try {
|
|
1052
|
+
const config = await conn$1.sendMessagePromise({ type: "get_config" });
|
|
1053
|
+
if (config) setState("config", config);
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
console.error("Error refreshing core config:", error);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
981
1058
|
async function handleRegistryUpdate(event) {
|
|
982
1059
|
const { action, entity_id } = event.data;
|
|
983
1060
|
if (action === "create" || action === "update") {
|
|
@@ -1345,16 +1422,18 @@ function setupEventHandlers(conn$1, options) {
|
|
|
1345
1422
|
}
|
|
1346
1423
|
async function loadInitialData(conn$1) {
|
|
1347
1424
|
try {
|
|
1348
|
-
const [states, entityRegistry, deviceRegistry, areaRegistry, floorRegistry, labelRegistry] = await Promise.all([
|
|
1425
|
+
const [states, entityRegistry, deviceRegistry, areaRegistry, floorRegistry, labelRegistry, config] = await Promise.all([
|
|
1349
1426
|
conn$1.sendMessagePromise({ type: "get_states" }),
|
|
1350
1427
|
conn$1.sendMessagePromise({ type: "config/entity_registry/list" }),
|
|
1351
1428
|
conn$1.sendMessagePromise({ type: "config/device_registry/list" }),
|
|
1352
1429
|
conn$1.sendMessagePromise({ type: "config/area_registry/list" }),
|
|
1353
1430
|
conn$1.sendMessagePromise({ type: "config/floor_registry/list" }),
|
|
1354
|
-
conn$1.sendMessagePromise({ type: "config/label_registry/list" })
|
|
1431
|
+
conn$1.sendMessagePromise({ type: "config/label_registry/list" }),
|
|
1432
|
+
conn$1.sendMessagePromise({ type: "get_config" })
|
|
1355
1433
|
]);
|
|
1356
1434
|
bulkUpdateEntities(states);
|
|
1357
1435
|
bulkUpdateEntityRegistry(entityRegistry);
|
|
1436
|
+
setState("config", config);
|
|
1358
1437
|
setState(produce((s) => {
|
|
1359
1438
|
for (const device of deviceRegistry) s.devices[device.id] = device;
|
|
1360
1439
|
for (const areaApi of areaRegistry) {
|
|
@@ -2187,6 +2266,14 @@ function isRecord(value) {
|
|
|
2187
2266
|
* configured".
|
|
2188
2267
|
*/
|
|
2189
2268
|
async function fetchEnergyPreferences(connection) {
|
|
2269
|
+
if (isDemoMode()) {
|
|
2270
|
+
const prefs = demoEnergyPreferences();
|
|
2271
|
+
return {
|
|
2272
|
+
...prefs,
|
|
2273
|
+
energy_sources: prefs.energy_sources,
|
|
2274
|
+
device_consumption: prefs.device_consumption
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2190
2277
|
let response;
|
|
2191
2278
|
try {
|
|
2192
2279
|
response = await connection.sendMessagePromise({ type: "energy/get_prefs" });
|
|
@@ -2336,6 +2423,13 @@ function normalizeStatisticValue(raw) {
|
|
|
2336
2423
|
*/
|
|
2337
2424
|
async function fetchStatisticsDuringPeriod(connection, statisticIds, options) {
|
|
2338
2425
|
const { startTime, endTime, period } = options;
|
|
2426
|
+
if (isDemoMode()) {
|
|
2427
|
+
const result$1 = {};
|
|
2428
|
+
const statPeriod = period === "hour" || period === "day" ? period : "day";
|
|
2429
|
+
const endMs = (endTime ?? /* @__PURE__ */ new Date()).getTime();
|
|
2430
|
+
for (const id of statisticIds) result$1[id] = isEnergyEntity(id) ? synthesizeEnergyStatistics(id, startTime.getTime(), endMs, statPeriod) : [];
|
|
2431
|
+
return result$1;
|
|
2432
|
+
}
|
|
2339
2433
|
const params = {
|
|
2340
2434
|
type: "recorder/statistics_during_period",
|
|
2341
2435
|
start_time: startTime.toISOString(),
|
|
@@ -2613,4 +2707,4 @@ async function refreshForecasts(entityId, types) {
|
|
|
2613
2707
|
const VERSION = "0.1.0";
|
|
2614
2708
|
|
|
2615
2709
|
//#endregion
|
|
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 };
|
|
2710
|
+
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, startDemoEnergyTicker, startWebRtcSession, state, stopDemoEnergyTicker, toggle, trackEntityHistory, turnOff, turnOn, unloadDemoData, untrackEntityHistory, updateEntity };
|
package/dist/solid/hooks.d.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @packageDocumentation
|
|
12
12
|
*/
|
|
13
|
+
import type { HassConfig, HassUnitSystem } from "@glasshome/ha-types";
|
|
13
14
|
import type { Accessor, Resource } from "solid-js";
|
|
14
15
|
import type { AreaView, CameraStreamData, ConnectionState, EntityHistoryData, EntityView, GlassHomeState, HassEntity, StatisticsQueryOptions, StatisticValue, WeatherForecastsData } from "@glasshome/sync-layer";
|
|
15
16
|
import { callService, toggle, turnOff, turnOn } from "@glasshome/sync-layer";
|
|
@@ -39,6 +40,16 @@ export declare function useConnection(): {
|
|
|
39
40
|
status: Accessor<ConnectionState>;
|
|
40
41
|
isConnected: Accessor<boolean>;
|
|
41
42
|
};
|
|
43
|
+
/** Reactive Home Assistant core config (null until loaded). */
|
|
44
|
+
export declare function useHassConfig(): Accessor<HassConfig | null>;
|
|
45
|
+
/** Reactive HA unit system (length/mass/temperature/volume), or null until loaded. */
|
|
46
|
+
export declare function useUnitSystem(): Accessor<HassUnitSystem | null>;
|
|
47
|
+
/** Reactive temperature unit from HA config, including the degree sign. Defaults to "°C". */
|
|
48
|
+
export declare function useTemperatureUnit(): Accessor<string>;
|
|
49
|
+
/** Reactive UI locale (BCP 47 tag) from HA config. Defaults to "en". */
|
|
50
|
+
export declare function useLocale(): Accessor<string>;
|
|
51
|
+
/** Reactive currency (ISO 4217) from HA config. Defaults to "USD". */
|
|
52
|
+
export declare function useCurrency(): Accessor<string>;
|
|
42
53
|
/**
|
|
43
54
|
* Get all service call functions.
|
|
44
55
|
*/
|
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, useEntityStatistics, useForecast, useService, useStore, useToggle, useTurnOff, useTurnOn, } from "./hooks";
|
|
9
|
+
export { useArea, useAreas, useCamera, useConnection, useCurrency, useEntities, useEntity, useEntityHistory, useEntityState, useEntityStatistics, useForecast, useHassConfig, useLocale, useService, useStore, useTemperatureUnit, useToggle, useTurnOff, useTurnOn, useUnitSystem, } from "./hooks";
|
|
10
10
|
export { allEntityIds, byArea, byDevice, byDomain, byFloor, byLabel, } from "./indices";
|
package/dist/solid/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
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";
|
|
2
|
+
import { buildAreaView, callService, entityViewEquals, extractDomain, fetchStatisticsDuringPeriod, getEntityView, isDemoMode, registerEntity, state, toggle, turnOff, turnOn } from "@glasshome/sync-layer";
|
|
3
3
|
|
|
4
4
|
//#region src/solid/hooks.ts
|
|
5
5
|
/**
|
|
@@ -69,6 +69,26 @@ function useConnection() {
|
|
|
69
69
|
isConnected
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
+
/** Reactive Home Assistant core config (null until loaded). */
|
|
73
|
+
function useHassConfig() {
|
|
74
|
+
return createMemo(() => state.config);
|
|
75
|
+
}
|
|
76
|
+
/** Reactive HA unit system (length/mass/temperature/volume), or null until loaded. */
|
|
77
|
+
function useUnitSystem() {
|
|
78
|
+
return createMemo(() => state.config?.unit_system ?? null);
|
|
79
|
+
}
|
|
80
|
+
/** Reactive temperature unit from HA config, including the degree sign. Defaults to "°C". */
|
|
81
|
+
function useTemperatureUnit() {
|
|
82
|
+
return createMemo(() => state.config?.unit_system.temperature ?? "°C");
|
|
83
|
+
}
|
|
84
|
+
/** Reactive UI locale (BCP 47 tag) from HA config. Defaults to "en". */
|
|
85
|
+
function useLocale() {
|
|
86
|
+
return createMemo(() => state.config?.language || "en");
|
|
87
|
+
}
|
|
88
|
+
/** Reactive currency (ISO 4217) from HA config. Defaults to "USD". */
|
|
89
|
+
function useCurrency() {
|
|
90
|
+
return createMemo(() => state.config?.currency || "USD");
|
|
91
|
+
}
|
|
72
92
|
/**
|
|
73
93
|
* Get all service call functions.
|
|
74
94
|
*/
|
|
@@ -152,6 +172,13 @@ function useEntityHistory(entityId) {
|
|
|
152
172
|
return createMemo(() => state.history[getId()]);
|
|
153
173
|
}
|
|
154
174
|
/**
|
|
175
|
+
* Connection stub for demo mode: the statistics fetcher's demo branch never
|
|
176
|
+
* sends a message, so this only satisfies the signature.
|
|
177
|
+
*/
|
|
178
|
+
const DEMO_STATS_CONNECTION = { sendMessagePromise() {
|
|
179
|
+
return Promise.reject(new Error("demo mode: no connection"));
|
|
180
|
+
} };
|
|
181
|
+
/**
|
|
155
182
|
* Get reactive long-term statistics for a statistic id.
|
|
156
183
|
*
|
|
157
184
|
* Fetches via `recorder/statistics_during_period` and re-fetches whenever the
|
|
@@ -167,8 +194,8 @@ function useEntityStatistics(statisticId, options) {
|
|
|
167
194
|
}), async ({ id, options: opts }) => {
|
|
168
195
|
if (!id) return [];
|
|
169
196
|
const conn = state.conn;
|
|
170
|
-
if (!conn) return [];
|
|
171
|
-
const result = await fetchStatisticsDuringPeriod(conn, [id], opts);
|
|
197
|
+
if (!conn && !isDemoMode()) return [];
|
|
198
|
+
const result = await fetchStatisticsDuringPeriod(conn ?? DEMO_STATS_CONNECTION, [id], opts);
|
|
172
199
|
return result[id] ?? [];
|
|
173
200
|
});
|
|
174
201
|
return data;
|
|
@@ -237,4 +264,4 @@ const byLabel = () => {
|
|
|
237
264
|
const allEntityIds = () => Object.keys(state.entities);
|
|
238
265
|
|
|
239
266
|
//#endregion
|
|
240
|
-
export { allEntityIds, byArea, byDevice, byDomain, byFloor, byLabel, useArea, useAreas, useCamera, useConnection, useEntities, useEntity, useEntityHistory, useEntityState, useEntityStatistics, useForecast, useService, useStore, useToggle, useTurnOff, useTurnOn };
|
|
267
|
+
export { allEntityIds, byArea, byDevice, byDomain, byFloor, byLabel, useArea, useAreas, useCamera, useConnection, useCurrency, useEntities, useEntity, useEntityHistory, useEntityState, useEntityStatistics, useForecast, useHassConfig, useLocale, useService, useStore, useTemperatureUnit, useToggle, useTurnOff, useTurnOn, useUnitSystem };
|
package/dist/testing/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createDemoFixtures, state } from "../demo-data-
|
|
1
|
+
import { createDemoFixtures, state } from "../demo-data-CavpxBw3.js";
|
|
2
2
|
|
|
3
3
|
//#region src/testing/mock-behaviors/base.ts
|
|
4
4
|
/**
|
|
@@ -601,6 +601,7 @@ var MockConnection = class {
|
|
|
601
601
|
messageIdCounter = 1;
|
|
602
602
|
autoReact;
|
|
603
603
|
reactionDelay;
|
|
604
|
+
configOverride;
|
|
604
605
|
serviceCalls = [];
|
|
605
606
|
sentCommands = [];
|
|
606
607
|
constructor(fixtures, latency = 0, options) {
|
|
@@ -608,6 +609,25 @@ var MockConnection = class {
|
|
|
608
609
|
this.latency = latency;
|
|
609
610
|
this.autoReact = options?.autoReact ?? true;
|
|
610
611
|
this.reactionDelay = options?.reactionDelay ?? 0;
|
|
612
|
+
this.configOverride = options?.config ?? {};
|
|
613
|
+
}
|
|
614
|
+
/** Replace the core config and fire core_config_updated (simulates HA Region change). */
|
|
615
|
+
setConfig(config) {
|
|
616
|
+
this.configOverride = {
|
|
617
|
+
...this.configOverride,
|
|
618
|
+
...config
|
|
619
|
+
};
|
|
620
|
+
this.emitEvent({
|
|
621
|
+
event_type: "core_config_updated",
|
|
622
|
+
data: {},
|
|
623
|
+
origin: "LOCAL",
|
|
624
|
+
time_fired: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
625
|
+
context: {
|
|
626
|
+
id: "mock",
|
|
627
|
+
parent_id: null,
|
|
628
|
+
user_id: null
|
|
629
|
+
}
|
|
630
|
+
});
|
|
611
631
|
}
|
|
612
632
|
/**
|
|
613
633
|
* Get connected state
|
|
@@ -807,7 +827,8 @@ var MockConnection = class {
|
|
|
807
827
|
internal_url: null,
|
|
808
828
|
currency: "USD",
|
|
809
829
|
country: null,
|
|
810
|
-
language: "en"
|
|
830
|
+
language: "en",
|
|
831
|
+
...this.configOverride
|
|
811
832
|
};
|
|
812
833
|
case "get_services": return {};
|
|
813
834
|
case "fire_event": {
|
package/dist/testing/mock.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
-
import type { EntityRegistryEntry, HAEvent } from "@glasshome/ha-types";
|
|
8
|
+
import type { EntityRegistryEntry, HassConfig, HAEvent } from "@glasshome/ha-types";
|
|
9
9
|
import type { SyncLayerConnection } from "../connection/types";
|
|
10
10
|
import type { HassEntity } from "../core/types";
|
|
11
11
|
import type { AreaRegistryEntry, DeviceRegistryEntry, Fixtures } from "./types";
|
|
@@ -30,6 +30,7 @@ export declare class MockConnection implements SyncLayerConnection {
|
|
|
30
30
|
private messageIdCounter;
|
|
31
31
|
private autoReact;
|
|
32
32
|
private reactionDelay;
|
|
33
|
+
private configOverride;
|
|
33
34
|
serviceCalls: Array<{
|
|
34
35
|
domain: string;
|
|
35
36
|
service: string;
|
|
@@ -47,7 +48,11 @@ export declare class MockConnection implements SyncLayerConnection {
|
|
|
47
48
|
autoReact?: boolean;
|
|
48
49
|
/** Delay before emitting entity update events after service calls in ms (default: 0) */
|
|
49
50
|
reactionDelay?: number;
|
|
51
|
+
/** Override fields of the get_config response (unit_system, language, currency, etc.) */
|
|
52
|
+
config?: Partial<HassConfig>;
|
|
50
53
|
});
|
|
54
|
+
/** Replace the core config and fire core_config_updated (simulates HA Region change). */
|
|
55
|
+
setConfig(config: Partial<HassConfig>): void;
|
|
51
56
|
/**
|
|
52
57
|
* Get connected state
|
|
53
58
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glasshome/sync-layer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Type-safe, reactive state synchronization layer for Home Assistant",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"./package.json": "./package.json"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"@glasshome/ha-types": "0.1.
|
|
32
|
+
"@glasshome/ha-types": "0.1.2",
|
|
33
33
|
"solid-js": "^1.9.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"home-assistant-js-websocket": "^9.6.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@glasshome/ha-types": "0.1.
|
|
56
|
+
"@glasshome/ha-types": "0.1.2",
|
|
57
57
|
"@types/bun": "latest",
|
|
58
58
|
"solid-js": "^1.9.0",
|
|
59
59
|
"tsdown": "^0.2.0",
|