@glasshome/sync-layer 0.1.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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/area-views-B-m8vwce.js +321 -0
- package/dist/camera/fetch.d.ts +15 -0
- package/dist/camera/query.d.ts +15 -0
- package/dist/camera/types.d.ts +85 -0
- package/dist/camera/webrtc.d.ts +31 -0
- package/dist/commands/service.d.ts +53 -0
- package/dist/commands/types.d.ts +113 -0
- package/dist/connection/adapter.d.ts +48 -0
- package/dist/connection/auth.d.ts +126 -0
- package/dist/connection/manager.d.ts +42 -0
- package/dist/connection/subscriptions.d.ts +25 -0
- package/dist/connection/types.d.ts +161 -0
- package/dist/core/reducers.d.ts +99 -0
- package/dist/core/store.d.ts +64 -0
- package/dist/core/types.d.ts +336 -0
- package/dist/demo/demo-data.d.ts +24 -0
- package/dist/demo/demo-provider.d.ts +33 -0
- package/dist/demo/demo-provider.js +103 -0
- package/dist/demo-data-D8JXWRpw.js +419 -0
- package/dist/entities/area-views.d.ts +25 -0
- package/dist/entities/index.d.ts +54 -0
- package/dist/entities/views.d.ts +30 -0
- package/dist/history/constants.d.ts +16 -0
- package/dist/history/fetch.d.ts +19 -0
- package/dist/history/query.d.ts +13 -0
- package/dist/history/types.d.ts +146 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +1747 -0
- package/dist/query/area-builder.d.ts +25 -0
- package/dist/query/builder.d.ts +18 -0
- package/dist/query/helpers.d.ts +21 -0
- package/dist/query/memoization.d.ts +18 -0
- package/dist/query/projections.d.ts +74 -0
- package/dist/query/types.d.ts +260 -0
- package/dist/solid/hooks.d.ts +88 -0
- package/dist/solid/index.d.ts +10 -0
- package/dist/solid/index.js +176 -0
- package/dist/solid/indices.d.ts +14 -0
- package/dist/store-B1sbyexX.js +54 -0
- package/dist/testing/fixtures.d.ts +161 -0
- package/dist/testing/helpers.d.ts +160 -0
- package/dist/testing/index.d.ts +13 -0
- package/dist/testing/index.js +2047 -0
- package/dist/testing/mock-behaviors/base.d.ts +73 -0
- package/dist/testing/mock-behaviors/climate.d.ts +22 -0
- package/dist/testing/mock-behaviors/cover.d.ts +22 -0
- package/dist/testing/mock-behaviors/index.d.ts +11 -0
- package/dist/testing/mock-behaviors/light.d.ts +20 -0
- package/dist/testing/mock-behaviors/lock.d.ts +20 -0
- package/dist/testing/mock-behaviors/media_player.d.ts +42 -0
- package/dist/testing/mock-behaviors/registry.d.ts +55 -0
- package/dist/testing/mock-behaviors/switch.d.ts +17 -0
- package/dist/testing/mock.d.ts +191 -0
- package/dist/testing/types.d.ts +87 -0
- package/dist/types-Csog0nqk.js +10 -0
- package/dist/weather/fetch.d.ts +19 -0
- package/dist/weather/query.d.ts +23 -0
- package/dist/weather/types.d.ts +126 -0
- package/package.json +85 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for @glasshome/sync-layer
|
|
3
|
+
*
|
|
4
|
+
* This file defines types that are:
|
|
5
|
+
* 1. **Used from ha-types**: WebSocket commands, events, registry entries
|
|
6
|
+
* 2. **Runtime data structures**: Entity states, device/area/floor/label registries
|
|
7
|
+
* (These should eventually be moved to ha-types but are defined here for now
|
|
8
|
+
* as ha-types currently only has WebSocket command types)
|
|
9
|
+
* 3. **Sync-layer specific**: EntityView, views
|
|
10
|
+
*
|
|
11
|
+
* @packageDocumentation
|
|
12
|
+
*/
|
|
13
|
+
import type { AreaEntry, DeviceEntry, EntityCategory, EntityRegistryEntry } from "@glasshome/ha-types";
|
|
14
|
+
/**
|
|
15
|
+
* Unique entity identifier (e.g., "light.living_room")
|
|
16
|
+
*/
|
|
17
|
+
export type EntityId = string;
|
|
18
|
+
/**
|
|
19
|
+
* Domain extracted from entity_id (e.g., "light")
|
|
20
|
+
*/
|
|
21
|
+
export type EntityDomain = string;
|
|
22
|
+
/**
|
|
23
|
+
* Unique device identifier (UUID)
|
|
24
|
+
*/
|
|
25
|
+
export type DeviceId = string;
|
|
26
|
+
/**
|
|
27
|
+
* Unique area identifier
|
|
28
|
+
*/
|
|
29
|
+
export type AreaId = string;
|
|
30
|
+
/**
|
|
31
|
+
* Floor identifier
|
|
32
|
+
*/
|
|
33
|
+
export type FloorId = string;
|
|
34
|
+
/**
|
|
35
|
+
* Label identifier
|
|
36
|
+
*/
|
|
37
|
+
export type LabelId = string;
|
|
38
|
+
/**
|
|
39
|
+
* Runtime entity state from Home Assistant
|
|
40
|
+
*
|
|
41
|
+
* This represents the live state of an entity as received
|
|
42
|
+
* from the WebSocket API's `get_states` command or `subscribe_entities` subscription.
|
|
43
|
+
*/
|
|
44
|
+
export interface HassEntity {
|
|
45
|
+
/** Unique entity identifier */
|
|
46
|
+
entity_id: EntityId;
|
|
47
|
+
/** Current state value (e.g., "on", "off", "23.5") */
|
|
48
|
+
state: string;
|
|
49
|
+
/** Entity attributes (brightness, temperature, etc.) */
|
|
50
|
+
attributes: Record<string, any>;
|
|
51
|
+
/** ISO 8601 timestamp of last state change */
|
|
52
|
+
last_changed: string;
|
|
53
|
+
/** ISO 8601 timestamp of last update */
|
|
54
|
+
last_updated: string;
|
|
55
|
+
/** Event context */
|
|
56
|
+
context: {
|
|
57
|
+
/** Context ID */
|
|
58
|
+
id: string;
|
|
59
|
+
/** Parent context ID */
|
|
60
|
+
parent_id: string | null;
|
|
61
|
+
/** User ID that triggered the change */
|
|
62
|
+
user_id: string | null;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Device registry entry
|
|
67
|
+
*
|
|
68
|
+
* @alias DeviceEntry
|
|
69
|
+
*/
|
|
70
|
+
export type DeviceRegistryEntry = DeviceEntry;
|
|
71
|
+
/**
|
|
72
|
+
* Area registry entry
|
|
73
|
+
*
|
|
74
|
+
* @alias AreaEntry
|
|
75
|
+
* @note AreaEntry uses 'id' field, not 'area_id'
|
|
76
|
+
*/
|
|
77
|
+
export type AreaRegistryEntry = AreaEntry;
|
|
78
|
+
/**
|
|
79
|
+
* Floor registry entry
|
|
80
|
+
*
|
|
81
|
+
* Response structure from `config/floor_registry/list`
|
|
82
|
+
*/
|
|
83
|
+
export interface FloorRegistryEntry {
|
|
84
|
+
/** Unique floor identifier */
|
|
85
|
+
floor_id: FloorId;
|
|
86
|
+
/** Floor name */
|
|
87
|
+
name: string;
|
|
88
|
+
/** Floor level (integer, 0 = ground floor) */
|
|
89
|
+
level: number;
|
|
90
|
+
/** Floor icon */
|
|
91
|
+
icon: string | null;
|
|
92
|
+
/** Floor aliases */
|
|
93
|
+
aliases: string[];
|
|
94
|
+
/** Created timestamp */
|
|
95
|
+
created_at: string;
|
|
96
|
+
/** Modified timestamp */
|
|
97
|
+
modified_at: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Label registry entry
|
|
101
|
+
*
|
|
102
|
+
* Response structure from `config/label_registry/list`
|
|
103
|
+
*/
|
|
104
|
+
export interface LabelRegistryEntry {
|
|
105
|
+
/** Unique label identifier */
|
|
106
|
+
label_id: LabelId;
|
|
107
|
+
/** Label name */
|
|
108
|
+
name: string;
|
|
109
|
+
/** Label color (hex) */
|
|
110
|
+
color: string | null;
|
|
111
|
+
/** Label icon */
|
|
112
|
+
icon: string | null;
|
|
113
|
+
/** Label description */
|
|
114
|
+
description: string | null;
|
|
115
|
+
/** Created timestamp */
|
|
116
|
+
created_at: string;
|
|
117
|
+
/** Modified timestamp */
|
|
118
|
+
modified_at: string;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Statistics metadata
|
|
122
|
+
*
|
|
123
|
+
* Response structure from `recorder/get_statistics_metadata`
|
|
124
|
+
*/
|
|
125
|
+
export interface StatisticMetadata {
|
|
126
|
+
/** Statistic ID */
|
|
127
|
+
statistic_id: string;
|
|
128
|
+
/** Data source */
|
|
129
|
+
source: string;
|
|
130
|
+
/** Unit of measurement */
|
|
131
|
+
unit_of_measurement: string | null;
|
|
132
|
+
/** Whether statistic has mean values */
|
|
133
|
+
has_mean: boolean;
|
|
134
|
+
/** Whether statistic has sum values */
|
|
135
|
+
has_sum: boolean;
|
|
136
|
+
/** Display name */
|
|
137
|
+
name: string | null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Statistics data point
|
|
141
|
+
*
|
|
142
|
+
* Response structure from `recorder/statistics_during_period`
|
|
143
|
+
*/
|
|
144
|
+
export interface StatisticData {
|
|
145
|
+
/** Statistic ID */
|
|
146
|
+
statistic_id: string;
|
|
147
|
+
/** Period start (ISO 8601) */
|
|
148
|
+
start: string;
|
|
149
|
+
/** Period end (ISO 8601) */
|
|
150
|
+
end: string;
|
|
151
|
+
/** Mean value */
|
|
152
|
+
mean?: number;
|
|
153
|
+
/** Minimum value */
|
|
154
|
+
min?: number;
|
|
155
|
+
/** Maximum value */
|
|
156
|
+
max?: number;
|
|
157
|
+
/** Last reset timestamp */
|
|
158
|
+
last_reset?: string;
|
|
159
|
+
/** State value */
|
|
160
|
+
state?: number;
|
|
161
|
+
/** Sum value */
|
|
162
|
+
sum?: number;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Unified entity view combining runtime state and registry metadata
|
|
166
|
+
*
|
|
167
|
+
* This is the primary interface for working with entities in the sync-layer.
|
|
168
|
+
* It combines live state data with registry metadata to provide a complete
|
|
169
|
+
* view of an entity.
|
|
170
|
+
*/
|
|
171
|
+
export interface EntityView {
|
|
172
|
+
/** Unique entity identifier */
|
|
173
|
+
id: EntityId;
|
|
174
|
+
/** Entity domain (e.g., "light", "sensor") */
|
|
175
|
+
domain: EntityDomain;
|
|
176
|
+
/** Current state value */
|
|
177
|
+
state: string;
|
|
178
|
+
/** Entity attributes */
|
|
179
|
+
attributes: Record<string, any>;
|
|
180
|
+
/** Last state change timestamp */
|
|
181
|
+
lastChanged: Date;
|
|
182
|
+
/** Last update timestamp */
|
|
183
|
+
lastUpdated: Date;
|
|
184
|
+
/** Event context */
|
|
185
|
+
context: {
|
|
186
|
+
id: string;
|
|
187
|
+
parentId: string | null;
|
|
188
|
+
userId: string | null;
|
|
189
|
+
};
|
|
190
|
+
/** Entity name (from registry or friendly_name) */
|
|
191
|
+
name: string;
|
|
192
|
+
/** Friendly display name */
|
|
193
|
+
friendlyName: string;
|
|
194
|
+
/** Area ID where entity is located */
|
|
195
|
+
areaId: AreaId | null;
|
|
196
|
+
/** Device ID this entity belongs to */
|
|
197
|
+
deviceId: DeviceId | null;
|
|
198
|
+
/** Platform/integration that provides this entity */
|
|
199
|
+
platform: string;
|
|
200
|
+
/** Unique ID from integration */
|
|
201
|
+
uniqueId: string | null;
|
|
202
|
+
/** Whether entity is disabled */
|
|
203
|
+
isDisabled: boolean;
|
|
204
|
+
/** Whether entity is hidden */
|
|
205
|
+
isHidden: boolean;
|
|
206
|
+
/** Resolved icon (registry icon, attribute icon, or domain default) */
|
|
207
|
+
icon: string | null;
|
|
208
|
+
/** Icon source (registry, attribute, or default) */
|
|
209
|
+
iconSource: "registry" | "attribute" | "default";
|
|
210
|
+
/** Entity category (config, diagnostic, or null) */
|
|
211
|
+
entityCategory: EntityCategory | null;
|
|
212
|
+
/** Labels applied to this entity */
|
|
213
|
+
labels: LabelId[];
|
|
214
|
+
/** Entity aliases */
|
|
215
|
+
aliases: string[];
|
|
216
|
+
/** Device class (e.g., "temperature", "humidity") */
|
|
217
|
+
deviceClass?: string | null;
|
|
218
|
+
/** Unit of measurement */
|
|
219
|
+
unitOfMeasurement?: string | null;
|
|
220
|
+
/** Supported features bitmask */
|
|
221
|
+
supportedFeatures?: number;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Device view combining registry data with computed properties
|
|
225
|
+
*/
|
|
226
|
+
export interface DeviceView {
|
|
227
|
+
/** Unique device identifier */
|
|
228
|
+
id: DeviceId;
|
|
229
|
+
/** Device name */
|
|
230
|
+
name: string;
|
|
231
|
+
/** User-friendly name */
|
|
232
|
+
nameByUser: string | null;
|
|
233
|
+
/** Manufacturer */
|
|
234
|
+
manufacturer: string | null;
|
|
235
|
+
/** Model */
|
|
236
|
+
model: string | null;
|
|
237
|
+
/** Software version */
|
|
238
|
+
swVersion: string | null;
|
|
239
|
+
/** Area ID where device is located */
|
|
240
|
+
areaId: AreaId | null;
|
|
241
|
+
/** Configuration entry IDs */
|
|
242
|
+
configEntries: string[];
|
|
243
|
+
/** Device connections */
|
|
244
|
+
connections: Array<[string, string]>;
|
|
245
|
+
/** Device identifiers */
|
|
246
|
+
identifiers: Array<[string, string]>;
|
|
247
|
+
/** Whether device is disabled */
|
|
248
|
+
disabledBy: string | null;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Area view combining registry data with computed entities and devices
|
|
252
|
+
*/
|
|
253
|
+
export interface AreaView {
|
|
254
|
+
/** Unique area identifier */
|
|
255
|
+
id: AreaId;
|
|
256
|
+
/** Area name */
|
|
257
|
+
name: string;
|
|
258
|
+
/** Normalized area name (for searching) */
|
|
259
|
+
normalizedName: string;
|
|
260
|
+
/** Area aliases */
|
|
261
|
+
aliases: string[];
|
|
262
|
+
/** Floor ID this area belongs to */
|
|
263
|
+
floorId: FloorId | null;
|
|
264
|
+
/** Area icon */
|
|
265
|
+
icon: string | null;
|
|
266
|
+
/** Area picture */
|
|
267
|
+
picture: string | null;
|
|
268
|
+
/** Labels applied to this area */
|
|
269
|
+
labels: LabelId[];
|
|
270
|
+
/** Temperature entity ID for this area */
|
|
271
|
+
temperatureEntityId: EntityId | null;
|
|
272
|
+
/** Humidity entity ID for this area */
|
|
273
|
+
humidityEntityId: EntityId | null;
|
|
274
|
+
/** Created timestamp */
|
|
275
|
+
createdAt: string;
|
|
276
|
+
/** Last modified timestamp */
|
|
277
|
+
modifiedAt: string;
|
|
278
|
+
/** Entities in this area (direct + via devices) - uses query engine */
|
|
279
|
+
entities: EntityView[];
|
|
280
|
+
/** Devices in this area */
|
|
281
|
+
devices: DeviceView[];
|
|
282
|
+
/** Entity IDs for fast lookups */
|
|
283
|
+
entityIds: EntityId[];
|
|
284
|
+
/** Device IDs for fast lookups */
|
|
285
|
+
deviceIds: DeviceId[];
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* WebSocket connection state
|
|
289
|
+
*/
|
|
290
|
+
export type ConnectionState = "connecting" | "connected" | "disconnected" | "reconnecting" | "error";
|
|
291
|
+
/**
|
|
292
|
+
* Unsubscribe function returned by subscriptions
|
|
293
|
+
*/
|
|
294
|
+
export type UnsubscribeFunc = () => void;
|
|
295
|
+
/**
|
|
296
|
+
* Generic callback type
|
|
297
|
+
*/
|
|
298
|
+
export type Callback<T> = (data: T) => void;
|
|
299
|
+
/**
|
|
300
|
+
* Predicate function for filtering
|
|
301
|
+
*/
|
|
302
|
+
export type PredicateFunc<T> = (item: T) => boolean;
|
|
303
|
+
/**
|
|
304
|
+
* Comparison function for sorting
|
|
305
|
+
*/
|
|
306
|
+
export type CompareFunc<T> = (a: T, b: T) => number;
|
|
307
|
+
/**
|
|
308
|
+
* Mapping function for transformations
|
|
309
|
+
*/
|
|
310
|
+
export type MapFunc<T, R> = (item: T) => R;
|
|
311
|
+
/**
|
|
312
|
+
* Registry update event data
|
|
313
|
+
*/
|
|
314
|
+
export interface RegistryUpdateEvent {
|
|
315
|
+
action: "create" | "update" | "remove";
|
|
316
|
+
entity_id: EntityId;
|
|
317
|
+
entity?: EntityRegistryEntry;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Statistics update event data
|
|
321
|
+
*/
|
|
322
|
+
export interface StatisticsUpdateEvent {
|
|
323
|
+
statistic_ids: string[];
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Check if a value is a valid EntityId
|
|
327
|
+
*/
|
|
328
|
+
export declare function isEntityId(value: unknown): value is EntityId;
|
|
329
|
+
/**
|
|
330
|
+
* Extract domain from entity ID
|
|
331
|
+
*/
|
|
332
|
+
export declare function extractDomain(entityId: EntityId): EntityDomain;
|
|
333
|
+
/**
|
|
334
|
+
* Check if entity is in a specific domain
|
|
335
|
+
*/
|
|
336
|
+
export declare function isDomain(entityId: EntityId, domain: EntityDomain): boolean;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo Mode Fixtures
|
|
3
|
+
*
|
|
4
|
+
* ~30 curated apartment entities for showcasing the dashboard without
|
|
5
|
+
* a real Home Assistant instance. Covers all widget-relevant domains.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
import type { EntityRegistryEntry } from "@glasshome/ha-types";
|
|
10
|
+
import type { AreaRegistryEntry, DeviceRegistryEntry, HassEntity } from "../core/types";
|
|
11
|
+
export interface DemoFixtures {
|
|
12
|
+
entities: Record<string, HassEntity>;
|
|
13
|
+
entityRegistry: Record<string, EntityRegistryEntry>;
|
|
14
|
+
areas: Record<string, AreaRegistryEntry>;
|
|
15
|
+
devices: Record<string, DeviceRegistryEntry>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create ~30 curated apartment entity fixtures.
|
|
19
|
+
*
|
|
20
|
+
* Entities span lights, sensors, binary sensors, climate, covers,
|
|
21
|
+
* locks, switches, media player, weather, camera, scenes, and buttons
|
|
22
|
+
* across 4 areas: living_room, bedroom, kitchen, entry.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createDemoFixtures(): DemoFixtures;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Demo Mode Provider
|
|
3
|
+
*
|
|
4
|
+
* Loads demo fixtures into the SolidJS store and provides
|
|
5
|
+
* optimistic local state updates for service calls (toggle, turnOn, turnOff).
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Whether demo mode is currently active.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isDemoMode(): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Load demo fixtures into the SolidJS store.
|
|
15
|
+
*
|
|
16
|
+
* Populates entities, entity registry, areas, and devices.
|
|
17
|
+
* Sets connection state to "connected" (pretend connected).
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadDemoData(): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Unload demo data and reset to disconnected state.
|
|
22
|
+
*/
|
|
23
|
+
export declare function unloadDemoData(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Apply an optimistic state update for demo mode.
|
|
26
|
+
*
|
|
27
|
+
* When a service call is made in demo mode, this updates the entity
|
|
28
|
+
* state locally (no WebSocket message sent). State resets on page refresh.
|
|
29
|
+
*/
|
|
30
|
+
export declare function applyDemoServiceCall(domain: string, service: string, _serviceData?: Record<string, any>, target?: {
|
|
31
|
+
entity_id?: string | string[];
|
|
32
|
+
}): void;
|
|
33
|
+
export { createDemoFixtures } from "./demo-data";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { setState, state } from "../store-B1sbyexX.js";
|
|
2
|
+
import { extractDomain } from "../types-Csog0nqk.js";
|
|
3
|
+
import { createDemoFixtures } from "../demo-data-D8JXWRpw.js";
|
|
4
|
+
import { produce, reconcile } from "solid-js/store";
|
|
5
|
+
|
|
6
|
+
//#region src/demo/demo-provider.ts
|
|
7
|
+
let _isDemoMode = false;
|
|
8
|
+
/**
|
|
9
|
+
* Whether demo mode is currently active.
|
|
10
|
+
*/
|
|
11
|
+
function isDemoMode() {
|
|
12
|
+
return _isDemoMode;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Load demo fixtures into the SolidJS store.
|
|
16
|
+
*
|
|
17
|
+
* Populates entities, entity registry, areas, and devices.
|
|
18
|
+
* Sets connection state to "connected" (pretend connected).
|
|
19
|
+
*/
|
|
20
|
+
async function loadDemoData() {
|
|
21
|
+
const fixtures = createDemoFixtures();
|
|
22
|
+
setState("entities", reconcile(fixtures.entities));
|
|
23
|
+
setState("entityRegistry", reconcile(fixtures.entityRegistry));
|
|
24
|
+
setState("areas", reconcile(fixtures.areas));
|
|
25
|
+
setState("devices", reconcile(fixtures.devices));
|
|
26
|
+
setState("connectionState", "connected");
|
|
27
|
+
_isDemoMode = true;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Unload demo data and reset to disconnected state.
|
|
31
|
+
*/
|
|
32
|
+
function unloadDemoData() {
|
|
33
|
+
setState("entities", reconcile({}));
|
|
34
|
+
setState("entityRegistry", reconcile({}));
|
|
35
|
+
setState("areas", reconcile({}));
|
|
36
|
+
setState("devices", reconcile({}));
|
|
37
|
+
setState("connectionState", "disconnected");
|
|
38
|
+
_isDemoMode = false;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Apply an optimistic state update for demo mode.
|
|
42
|
+
*
|
|
43
|
+
* When a service call is made in demo mode, this updates the entity
|
|
44
|
+
* state locally (no WebSocket message sent). State resets on page refresh.
|
|
45
|
+
*/
|
|
46
|
+
function applyDemoServiceCall(domain, service, _serviceData = {}, target = {}) {
|
|
47
|
+
if (!_isDemoMode) return;
|
|
48
|
+
const entityIds = Array.isArray(target.entity_id) ? target.entity_id : target.entity_id ? [target.entity_id] : [];
|
|
49
|
+
for (const entityId of entityIds) {
|
|
50
|
+
const entity = state.entities[entityId];
|
|
51
|
+
if (!entity) continue;
|
|
52
|
+
const entityDomain = extractDomain(entityId);
|
|
53
|
+
setState(produce((s) => {
|
|
54
|
+
const e = s.entities[entityId];
|
|
55
|
+
if (!e) return;
|
|
56
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
57
|
+
e.last_changed = now;
|
|
58
|
+
e.last_updated = now;
|
|
59
|
+
if (service === "turn_on") applyTurnOn(e, entityDomain, _serviceData);
|
|
60
|
+
else if (service === "turn_off") applyTurnOff(e, entityDomain);
|
|
61
|
+
else if (service === "toggle") if (e.state === "on" || e.state === "playing" || e.state === "open") applyTurnOff(e, entityDomain);
|
|
62
|
+
else applyTurnOn(e, entityDomain, _serviceData);
|
|
63
|
+
else if (service === "lock") e.state = "locked";
|
|
64
|
+
else if (service === "unlock") e.state = "unlocked";
|
|
65
|
+
else if (service === "open_cover") {
|
|
66
|
+
e.state = "open";
|
|
67
|
+
e.attributes.current_position = 100;
|
|
68
|
+
} else if (service === "close_cover") {
|
|
69
|
+
e.state = "closed";
|
|
70
|
+
e.attributes.current_position = 0;
|
|
71
|
+
} else if (service === "set_cover_position" && _serviceData.position != null) {
|
|
72
|
+
e.attributes.current_position = _serviceData.position;
|
|
73
|
+
e.state = _serviceData.position > 0 ? "open" : "closed";
|
|
74
|
+
} else if (service === "set_temperature" && _serviceData.temperature != null) e.attributes.temperature = _serviceData.temperature;
|
|
75
|
+
else if (service === "set_hvac_mode" && _serviceData.hvac_mode != null) e.state = _serviceData.hvac_mode;
|
|
76
|
+
else if (service === "volume_set" && _serviceData.volume_level != null) e.attributes.volume_level = _serviceData.volume_level;
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function applyTurnOn(e, domain, serviceData = {}) {
|
|
81
|
+
if (domain === "light") {
|
|
82
|
+
e.state = "on";
|
|
83
|
+
e.attributes.brightness = serviceData.brightness ?? e.attributes.brightness ?? 255;
|
|
84
|
+
if (serviceData.color_temp_kelvin) e.attributes.color_temp_kelvin = serviceData.color_temp_kelvin;
|
|
85
|
+
} else if (domain === "media_player") e.state = "playing";
|
|
86
|
+
else if (domain === "cover") {
|
|
87
|
+
e.state = "open";
|
|
88
|
+
e.attributes.current_position = 100;
|
|
89
|
+
} else e.state = "on";
|
|
90
|
+
}
|
|
91
|
+
function applyTurnOff(e, domain) {
|
|
92
|
+
if (domain === "light") {
|
|
93
|
+
e.state = "off";
|
|
94
|
+
delete e.attributes.brightness;
|
|
95
|
+
} else if (domain === "media_player") e.state = "paused";
|
|
96
|
+
else if (domain === "cover") {
|
|
97
|
+
e.state = "closed";
|
|
98
|
+
e.attributes.current_position = 0;
|
|
99
|
+
} else e.state = "off";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
export { applyDemoServiceCall, createDemoFixtures, isDemoMode, loadDemoData, unloadDemoData };
|