@glasshome/sync-layer 0.1.1 → 0.1.3
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/commands/service.d.ts +1 -1
- package/dist/core/store.d.ts +0 -4
- package/dist/demo/demo-provider.js +2 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +319 -4
- package/dist/solid/hooks.d.ts +2 -6
- package/dist/solid/index.js +1 -3
- package/dist/testing/index.js +1 -1
- package/package.json +1 -1
- package/dist/area-views-B-m8vwce.js +0 -321
- /package/dist/{demo-data-D8JXWRpw.js → demo-data-Bir7Px_e.js} +0 -0
- /package/dist/{types-Csog0nqk.js → types-CxMcKkJQ.js} +0 -0
|
@@ -11,7 +11,7 @@ import type { EntityUpdateFields, ServiceTarget } from "./types";
|
|
|
11
11
|
/**
|
|
12
12
|
* Call a Home Assistant service
|
|
13
13
|
*/
|
|
14
|
-
export declare function callService(domain:
|
|
14
|
+
export declare function callService<D extends Domain, S extends ServiceName<D>>(domain: D, service: S, serviceData?: ServiceCall<D, S>, target?: ServiceTarget): Promise<void>;
|
|
15
15
|
/**
|
|
16
16
|
* Turn on entity or entities
|
|
17
17
|
*/
|
package/dist/core/store.d.ts
CHANGED
|
@@ -58,7 +58,3 @@ export declare function resetStore(): void;
|
|
|
58
58
|
* Creates a new, independent store instance for test isolation.
|
|
59
59
|
*/
|
|
60
60
|
export declare function createStoreInstance(): [GlassHomeState, SetStoreFunction<GlassHomeState>];
|
|
61
|
-
/**
|
|
62
|
-
* Store tuple type for dependency injection
|
|
63
|
-
*/
|
|
64
|
-
export type Store = [get: GlassHomeState, set: SetStoreFunction<GlassHomeState>];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { setState, state } from "../store-B1sbyexX.js";
|
|
2
|
-
import { extractDomain } from "../types-
|
|
3
|
-
import { createDemoFixtures } from "../demo-data-
|
|
2
|
+
import { extractDomain } from "../types-CxMcKkJQ.js";
|
|
3
|
+
import { createDemoFixtures } from "../demo-data-Bir7Px_e.js";
|
|
4
4
|
import { produce, reconcile } from "solid-js/store";
|
|
5
5
|
|
|
6
6
|
//#region src/demo/demo-provider.ts
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,8 @@ export type { AreaQueryBuilder, EntitiesQueryBuilder, EntityQueryBuilder, Unsubs
|
|
|
19
19
|
export { batchEntityUpdates, batchServiceCalls, callService, sendCommand, toggle, turnOff, turnOn, updateEntity, } from "./commands/service";
|
|
20
20
|
export type { CommandOptions, CommandResult, EntityUpdateFields, ServiceCallContext, ServiceTarget, } from "./commands/types";
|
|
21
21
|
export type { GlassHomeState } from "./core/store";
|
|
22
|
-
export type { AreaId, AreaView, DeviceId, DeviceView, EntityDomain, EntityId, EntityView, HassEntity, } from "./core/types";
|
|
22
|
+
export type { AreaId, AreaView, ConnectionState, DeviceId, DeviceView, EntityDomain, EntityId, EntityView, HassEntity, } from "./core/types";
|
|
23
|
+
export { extractDomain } from "./core/types";
|
|
23
24
|
export { resetStore, setState, state } from "./core/store";
|
|
24
25
|
export type { CallServiceRequest, CoreEventType, Domain, EntityCategory, EntityRegistryEntry, EventStateChangedData, HAEvent, RegistryEntryDisabler, RegistryEntryHider, ServiceCall, ServiceName, WsCommandType, WsResult, } from "@glasshome/ha-types";
|
|
25
26
|
export { fetchStream, fetchStreamData } from "./camera/fetch";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { resetStore, setState, state } from "./store-B1sbyexX.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { createConnection, createLongLivedTokenAuth, getAuth } from "home-assistant-js-websocket";
|
|
2
|
+
import { extractDomain } from "./types-CxMcKkJQ.js";
|
|
3
|
+
import { callService as callService$1, createConnection, createLongLivedTokenAuth, getAuth } from "home-assistant-js-websocket";
|
|
5
4
|
import { produce } from "solid-js/store";
|
|
6
5
|
|
|
7
6
|
//#region src/connection/adapter.ts
|
|
@@ -396,6 +395,96 @@ async function refreshAuth(auth) {
|
|
|
396
395
|
return auth;
|
|
397
396
|
}
|
|
398
397
|
|
|
398
|
+
//#endregion
|
|
399
|
+
//#region src/commands/service.ts
|
|
400
|
+
/**
|
|
401
|
+
* Call a Home Assistant service
|
|
402
|
+
*/
|
|
403
|
+
async function callService(domain, service, serviceData = {}, target = {}) {
|
|
404
|
+
const connection = state.conn;
|
|
405
|
+
if (!connection) throw new Error("Not connected to Home Assistant. Call initConnection() first.");
|
|
406
|
+
try {
|
|
407
|
+
const mockConnection = connection;
|
|
408
|
+
if (mockConnection && typeof mockConnection.callService === "function") await mockConnection.callService(domain, service, serviceData, target);
|
|
409
|
+
else await callService$1(connection, domain, service, serviceData, target);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
throw new Error(`Service call failed: ${error.message || String(error)}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Turn on entity or entities
|
|
416
|
+
*/
|
|
417
|
+
async function turnOn(entityId, serviceData = {}) {
|
|
418
|
+
const firstEntityId = Array.isArray(entityId) ? entityId[0] : entityId;
|
|
419
|
+
if (!firstEntityId) throw new Error("Entity ID is required");
|
|
420
|
+
const domain = firstEntityId.split(".")[0];
|
|
421
|
+
if (!domain) throw new Error(`Invalid entity ID: ${firstEntityId}`);
|
|
422
|
+
await callService(domain, "turn_on", serviceData, { entity_id: entityId });
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Turn off entity or entities
|
|
426
|
+
*/
|
|
427
|
+
async function turnOff(entityId, serviceData = {}) {
|
|
428
|
+
const firstEntityId = Array.isArray(entityId) ? entityId[0] : entityId;
|
|
429
|
+
if (!firstEntityId) throw new Error("Entity ID is required");
|
|
430
|
+
const domain = firstEntityId.split(".")[0];
|
|
431
|
+
if (!domain) throw new Error(`Invalid entity ID: ${firstEntityId}`);
|
|
432
|
+
await callService(domain, "turn_off", serviceData, { entity_id: entityId });
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Toggle entity or entities
|
|
436
|
+
*/
|
|
437
|
+
async function toggle(entityId, serviceData = {}) {
|
|
438
|
+
const firstEntityId = Array.isArray(entityId) ? entityId[0] : entityId;
|
|
439
|
+
if (!firstEntityId) throw new Error("Entity ID is required");
|
|
440
|
+
const domain = firstEntityId.split(".")[0];
|
|
441
|
+
if (!domain) throw new Error(`Invalid entity ID: ${firstEntityId}`);
|
|
442
|
+
await callService(domain, "toggle", serviceData, { entity_id: entityId });
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Update entity registry entry
|
|
446
|
+
*/
|
|
447
|
+
async function updateEntity(entityId, updates) {
|
|
448
|
+
const connection = state.conn;
|
|
449
|
+
if (!connection) throw new Error("Not connected to Home Assistant. Call initConnection() first.");
|
|
450
|
+
try {
|
|
451
|
+
const result = await connection.sendMessagePromise({
|
|
452
|
+
type: "config/entity_registry/update",
|
|
453
|
+
entity_id: entityId,
|
|
454
|
+
...updates
|
|
455
|
+
});
|
|
456
|
+
if (result && typeof result === "object" && "_mockError" in result) throw new Error(`Entity registry entry not found: ${entityId}`);
|
|
457
|
+
return result;
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (error?.message && error.message.includes("Entity registry entry not found")) throw error;
|
|
460
|
+
throw new Error(`Entity update failed: ${error.message || String(error)}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Send a generic WebSocket command
|
|
465
|
+
*/
|
|
466
|
+
async function sendCommand(command) {
|
|
467
|
+
const connection = state.conn;
|
|
468
|
+
if (!connection) throw new Error("Not connected");
|
|
469
|
+
try {
|
|
470
|
+
return await connection.sendMessagePromise(command);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
throw new Error(`Command failed: ${error.message || String(error)}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Execute multiple service calls in parallel
|
|
477
|
+
*/
|
|
478
|
+
async function batchServiceCalls(calls) {
|
|
479
|
+
await Promise.all(calls.map((call) => callService(call.domain, call.service, call.serviceData, call.target)));
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Execute multiple entity updates in parallel
|
|
483
|
+
*/
|
|
484
|
+
async function batchEntityUpdates(updates) {
|
|
485
|
+
return Promise.all(updates.map((u) => updateEntity(u.entityId, u.updates)));
|
|
486
|
+
}
|
|
487
|
+
|
|
399
488
|
//#endregion
|
|
400
489
|
//#region src/history/constants.ts
|
|
401
490
|
const DOMAINS_USE_LAST_UPDATED = [
|
|
@@ -897,6 +986,232 @@ async function loadInitialData(conn) {
|
|
|
897
986
|
}
|
|
898
987
|
}
|
|
899
988
|
|
|
989
|
+
//#endregion
|
|
990
|
+
//#region src/entities/index.ts
|
|
991
|
+
/**
|
|
992
|
+
* Get all entity IDs
|
|
993
|
+
*/
|
|
994
|
+
function getAllEntityIds() {
|
|
995
|
+
return Object.keys(state.entities);
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Get entities by domain
|
|
999
|
+
*/
|
|
1000
|
+
function getEntitiesByDomain(domain) {
|
|
1001
|
+
return Object.keys(state.entities).filter((id) => extractDomain(id) === domain);
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Get entities via devices in area
|
|
1005
|
+
*
|
|
1006
|
+
* Only includes entities that inherit area from device (entity.area_id is null).
|
|
1007
|
+
*/
|
|
1008
|
+
function getEntitiesViaDevices(areaId) {
|
|
1009
|
+
const devicesInArea = Object.values(state.devices).filter((device) => device.area_id === areaId);
|
|
1010
|
+
const entityIds = [];
|
|
1011
|
+
for (const device of devicesInArea) {
|
|
1012
|
+
const deviceEntities = getEntitiesByDevice(device.id);
|
|
1013
|
+
for (const entityId of deviceEntities) {
|
|
1014
|
+
const registry = state.entityRegistry[entityId];
|
|
1015
|
+
if (!registry?.area_id) entityIds.push(entityId);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return entityIds;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Get entities by area
|
|
1022
|
+
*
|
|
1023
|
+
* Returns entities with direct area_id assignments and entities that inherit their area from devices.
|
|
1024
|
+
*/
|
|
1025
|
+
function getEntitiesByArea(areaId) {
|
|
1026
|
+
const directEntityIds = [];
|
|
1027
|
+
for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.area_id === areaId) directEntityIds.push(entityId);
|
|
1028
|
+
const deviceEntityIds = getEntitiesViaDevices(areaId);
|
|
1029
|
+
return Array.from(new Set([...directEntityIds, ...deviceEntityIds]));
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Get entities by device
|
|
1033
|
+
*/
|
|
1034
|
+
function getEntitiesByDevice(deviceId) {
|
|
1035
|
+
const entityIds = [];
|
|
1036
|
+
for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.device_id === deviceId) entityIds.push(entityId);
|
|
1037
|
+
return entityIds;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Get entities by floor
|
|
1041
|
+
*/
|
|
1042
|
+
function getEntitiesByFloor(floorId) {
|
|
1043
|
+
const entityIds = [];
|
|
1044
|
+
const areasOnFloor = Object.values(state.areas).filter((area$1) => area$1.floor_id === floorId);
|
|
1045
|
+
for (const area$1 of areasOnFloor) {
|
|
1046
|
+
const areaEntities = getEntitiesByArea(area$1.id);
|
|
1047
|
+
entityIds.push(...areaEntities);
|
|
1048
|
+
}
|
|
1049
|
+
return entityIds;
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Get entities by label
|
|
1053
|
+
*/
|
|
1054
|
+
function getEntitiesByLabel(labelId) {
|
|
1055
|
+
const entityIds = [];
|
|
1056
|
+
for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.labels?.includes(labelId)) entityIds.push(entityId);
|
|
1057
|
+
return entityIds;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
//#endregion
|
|
1061
|
+
//#region src/entities/views.ts
|
|
1062
|
+
/**
|
|
1063
|
+
* Get icon for an entity from registry or attributes
|
|
1064
|
+
*/
|
|
1065
|
+
function resolveIcon(entity$1, registry) {
|
|
1066
|
+
if (registry?.icon) return {
|
|
1067
|
+
icon: registry.icon,
|
|
1068
|
+
source: "registry"
|
|
1069
|
+
};
|
|
1070
|
+
if (entity$1.attributes?.icon) return {
|
|
1071
|
+
icon: entity$1.attributes.icon,
|
|
1072
|
+
source: "attribute"
|
|
1073
|
+
};
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Build an EntityView from runtime state and registry metadata
|
|
1078
|
+
*/
|
|
1079
|
+
function buildEntityView(entity$1, registry) {
|
|
1080
|
+
const domain = extractDomain(entity$1.entity_id);
|
|
1081
|
+
const friendlyName = entity$1.attributes.friendly_name ?? registry?.name ?? entity$1.entity_id;
|
|
1082
|
+
const name = registry?.name ?? friendlyName;
|
|
1083
|
+
const iconResolution = resolveIcon(entity$1, registry);
|
|
1084
|
+
let areaId = registry?.area_id ?? null;
|
|
1085
|
+
if (areaId === null && registry?.device_id) {
|
|
1086
|
+
const device = state.devices[registry.device_id];
|
|
1087
|
+
if (device) areaId = device.area_id ?? null;
|
|
1088
|
+
}
|
|
1089
|
+
return {
|
|
1090
|
+
id: entity$1.entity_id,
|
|
1091
|
+
domain,
|
|
1092
|
+
state: entity$1.state,
|
|
1093
|
+
attributes: entity$1.attributes,
|
|
1094
|
+
lastChanged: new Date(entity$1.last_changed),
|
|
1095
|
+
lastUpdated: new Date(entity$1.last_updated),
|
|
1096
|
+
context: {
|
|
1097
|
+
id: entity$1.context.id,
|
|
1098
|
+
parentId: entity$1.context.parent_id,
|
|
1099
|
+
userId: entity$1.context.user_id
|
|
1100
|
+
},
|
|
1101
|
+
name,
|
|
1102
|
+
friendlyName,
|
|
1103
|
+
areaId,
|
|
1104
|
+
deviceId: registry?.device_id ?? null,
|
|
1105
|
+
platform: registry?.platform ?? "unknown",
|
|
1106
|
+
uniqueId: registry?.unique_id ?? null,
|
|
1107
|
+
isDisabled: registry?.disabled_by !== null && registry?.disabled_by !== void 0,
|
|
1108
|
+
isHidden: registry?.hidden_by !== null && registry?.hidden_by !== void 0,
|
|
1109
|
+
icon: iconResolution?.icon ?? null,
|
|
1110
|
+
iconSource: iconResolution?.source ?? "default",
|
|
1111
|
+
entityCategory: registry?.entity_category ?? null,
|
|
1112
|
+
labels: registry?.labels ?? [],
|
|
1113
|
+
aliases: registry?.aliases ?? [],
|
|
1114
|
+
deviceClass: registry?.device_class ?? entity$1.attributes.device_class ?? null,
|
|
1115
|
+
unitOfMeasurement: registry?.unit_of_measurement ?? entity$1.attributes.unit_of_measurement ?? null,
|
|
1116
|
+
supportedFeatures: registry?.supported_features ?? entity$1.attributes.supported_features
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Get or build an EntityView
|
|
1121
|
+
*/
|
|
1122
|
+
function getEntityView(entityId) {
|
|
1123
|
+
const entity$1 = state.entities[entityId];
|
|
1124
|
+
if (!entity$1) return void 0;
|
|
1125
|
+
const registry = state.entityRegistry[entityId];
|
|
1126
|
+
return buildEntityView(entity$1, registry);
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Get multiple entity views
|
|
1130
|
+
*/
|
|
1131
|
+
function getEntityViews(entityIds) {
|
|
1132
|
+
const views = [];
|
|
1133
|
+
for (const id of entityIds) {
|
|
1134
|
+
const view = getEntityView(id);
|
|
1135
|
+
if (view) views.push(view);
|
|
1136
|
+
}
|
|
1137
|
+
return views;
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Get all entity views
|
|
1141
|
+
*/
|
|
1142
|
+
function getAllEntityViews() {
|
|
1143
|
+
return getEntityViews(Object.keys(state.entities));
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
//#endregion
|
|
1147
|
+
//#region src/entities/area-views.ts
|
|
1148
|
+
/**
|
|
1149
|
+
* Build a DeviceView from registry data
|
|
1150
|
+
*/
|
|
1151
|
+
function buildDeviceView(device) {
|
|
1152
|
+
return {
|
|
1153
|
+
id: device.id,
|
|
1154
|
+
name: device.name ?? "",
|
|
1155
|
+
nameByUser: device.name_by_user ?? null,
|
|
1156
|
+
manufacturer: device.manufacturer ?? null,
|
|
1157
|
+
model: device.model ?? null,
|
|
1158
|
+
swVersion: device.sw_version ?? null,
|
|
1159
|
+
areaId: device.area_id ?? null,
|
|
1160
|
+
configEntries: device.config_entries ?? [],
|
|
1161
|
+
connections: device.connections ?? [],
|
|
1162
|
+
identifiers: device.identifiers ?? [],
|
|
1163
|
+
disabledBy: device.disabled_by ?? null
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Build an AreaView from registry data using query engine
|
|
1168
|
+
*/
|
|
1169
|
+
function buildAreaView(areaId) {
|
|
1170
|
+
const area$1 = state.areas[areaId];
|
|
1171
|
+
if (!area$1) throw new Error(`Area ${areaId} not found`);
|
|
1172
|
+
const directEntityIds = [];
|
|
1173
|
+
for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.area_id === areaId) directEntityIds.push(entityId);
|
|
1174
|
+
const deviceEntityIds = getEntitiesViaDevices(areaId);
|
|
1175
|
+
const allEntityIds = Array.from(new Set([...directEntityIds, ...deviceEntityIds]));
|
|
1176
|
+
const areaEntities = allEntityIds.length > 0 ? getEntityViews(allEntityIds) : [];
|
|
1177
|
+
const devicesInArea = Object.values(state.devices).filter((device) => device.area_id === areaId);
|
|
1178
|
+
const devices = devicesInArea.map((device) => buildDeviceView(device));
|
|
1179
|
+
return {
|
|
1180
|
+
id: areaId,
|
|
1181
|
+
name: area$1.name,
|
|
1182
|
+
normalizedName: area$1.normalized_name ?? area$1.name.toLowerCase(),
|
|
1183
|
+
aliases: Array.isArray(area$1.aliases) ? area$1.aliases : [],
|
|
1184
|
+
floorId: area$1.floor_id ?? null,
|
|
1185
|
+
icon: area$1.icon ?? null,
|
|
1186
|
+
picture: area$1.picture ?? null,
|
|
1187
|
+
labels: Array.isArray(area$1.labels) ? area$1.labels : [],
|
|
1188
|
+
temperatureEntityId: area$1.temperature_entity_id ?? null,
|
|
1189
|
+
humidityEntityId: area$1.humidity_entity_id ?? null,
|
|
1190
|
+
createdAt: area$1.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1191
|
+
modifiedAt: area$1.modified_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1192
|
+
entities: areaEntities,
|
|
1193
|
+
devices,
|
|
1194
|
+
entityIds: allEntityIds,
|
|
1195
|
+
deviceIds: devices.map((d) => d.id)
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Get all area views
|
|
1200
|
+
*/
|
|
1201
|
+
function getAreaViews() {
|
|
1202
|
+
return Object.keys(state.areas).map((areaId) => buildAreaView(areaId));
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Get area view
|
|
1206
|
+
*/
|
|
1207
|
+
function getAreaView(areaId) {
|
|
1208
|
+
try {
|
|
1209
|
+
return buildAreaView(areaId);
|
|
1210
|
+
} catch {
|
|
1211
|
+
return void 0;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
900
1215
|
//#endregion
|
|
901
1216
|
//#region src/query/area-builder.ts
|
|
902
1217
|
var AreaQueryBuilderImpl = class AreaQueryBuilderImpl {
|
|
@@ -1744,4 +2059,4 @@ async function refreshForecasts(entityId, types) {
|
|
|
1744
2059
|
const VERSION = "0.1.0";
|
|
1745
2060
|
|
|
1746
2061
|
//#endregion
|
|
1747
|
-
export { DOMAINS_USE_LAST_UPDATED, LINE_ATTRIBUTES_TO_KEEP, NEED_ATTRIBUTE_DOMAINS, VERSION, addDebugIncomingMessageListener, addDebugOutgoingMessageListener, appendHistoryPoint, area, areas, authenticateWithOAuth, authenticateWithToken, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, callService, disconnect, entities, entitiesByAreaAndDomain, entitiesWhere, entity, entityIdHistoryNeedsAttributes, fetchEntityHistory, fetchForecast, fetchForecastData, fetchForecasts, fetchHistory, fetchStream, fetchStreamData, getAllEntityViews, getAreaView, getAreaViews, getConnection, getEntityView, getEntityViews, getForecast, getForecasts, getStream, getWebRtcClientConfig, historyStateToTimeline, initConnection, isAuthValid, isConnected, isHistoryTracked, refreshAuth, refreshForecast, refreshForecasts, refreshStream, resetStore, searchEntities, sendCommand, sendWebRtcCandidate, setState, startWebRtcSession, state, toggle, trackEntityHistory, turnOff, turnOn, untrackEntityHistory, updateEntity };
|
|
2062
|
+
export { DOMAINS_USE_LAST_UPDATED, LINE_ATTRIBUTES_TO_KEEP, NEED_ATTRIBUTE_DOMAINS, VERSION, addDebugIncomingMessageListener, addDebugOutgoingMessageListener, appendHistoryPoint, area, areas, authenticateWithOAuth, authenticateWithToken, batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, callService, disconnect, entities, entitiesByAreaAndDomain, entitiesWhere, entity, entityIdHistoryNeedsAttributes, extractDomain, fetchEntityHistory, fetchForecast, fetchForecastData, fetchForecasts, fetchHistory, fetchStream, fetchStreamData, getAllEntityViews, getAreaView, getAreaViews, getConnection, getEntityView, getEntityViews, getForecast, getForecasts, getStream, getWebRtcClientConfig, historyStateToTimeline, initConnection, isAuthValid, isConnected, isHistoryTracked, refreshAuth, refreshForecast, refreshForecasts, refreshStream, resetStore, searchEntities, sendCommand, sendWebRtcCandidate, setState, startWebRtcSession, state, toggle, trackEntityHistory, turnOff, turnOn, untrackEntityHistory, updateEntity };
|
package/dist/solid/hooks.d.ts
CHANGED
|
@@ -11,12 +11,8 @@
|
|
|
11
11
|
* @packageDocumentation
|
|
12
12
|
*/
|
|
13
13
|
import type { Accessor } from "solid-js";
|
|
14
|
-
import type { CameraStreamData } from "
|
|
15
|
-
import { callService, toggle, turnOff, turnOn } from "
|
|
16
|
-
import type { GlassHomeState } from "../core/store";
|
|
17
|
-
import type { AreaView, ConnectionState, EntityView, HassEntity } from "../core/types";
|
|
18
|
-
import type { EntityHistoryData } from "../history/types";
|
|
19
|
-
import type { WeatherForecastsData } from "../weather/types";
|
|
14
|
+
import type { AreaView, CameraStreamData, ConnectionState, EntityHistoryData, EntityView, GlassHomeState, HassEntity, WeatherForecastsData } from "@glasshome/sync-layer";
|
|
15
|
+
import { callService, toggle, turnOff, turnOn } from "@glasshome/sync-layer";
|
|
20
16
|
/**
|
|
21
17
|
* Get a reactive EntityView for an entity ID.
|
|
22
18
|
*
|
package/dist/solid/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { state } from "../store-B1sbyexX.js";
|
|
2
|
-
import { buildAreaView, callService, getEntityView, toggle, turnOff, turnOn } from "../area-views-B-m8vwce.js";
|
|
3
|
-
import { extractDomain } from "../types-Csog0nqk.js";
|
|
4
1
|
import { createMemo } from "solid-js";
|
|
2
|
+
import { buildAreaView, callService, extractDomain, getEntityView, state, toggle, turnOff, turnOn } from "@glasshome/sync-layer";
|
|
5
3
|
|
|
6
4
|
//#region src/solid/hooks.ts
|
|
7
5
|
/**
|
package/dist/testing/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
import { state } from "./store-B1sbyexX.js";
|
|
2
|
-
import { extractDomain } from "./types-Csog0nqk.js";
|
|
3
|
-
import { callService } from "home-assistant-js-websocket";
|
|
4
|
-
|
|
5
|
-
//#region src/commands/service.ts
|
|
6
|
-
/**
|
|
7
|
-
* Call a Home Assistant service
|
|
8
|
-
*/
|
|
9
|
-
async function callService$1(domain, service, serviceData = {}, target = {}) {
|
|
10
|
-
const connection = state.conn;
|
|
11
|
-
if (!connection) throw new Error("Not connected to Home Assistant. Call initConnection() first.");
|
|
12
|
-
try {
|
|
13
|
-
const mockConnection = connection;
|
|
14
|
-
if (mockConnection && typeof mockConnection.callService === "function") await mockConnection.callService(domain, service, serviceData, target);
|
|
15
|
-
else await callService(connection, domain, service, serviceData, target);
|
|
16
|
-
} catch (error) {
|
|
17
|
-
throw new Error(`Service call failed: ${error.message || String(error)}`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Turn on entity or entities
|
|
22
|
-
*/
|
|
23
|
-
async function turnOn(entityId, serviceData = {}) {
|
|
24
|
-
const firstEntityId = Array.isArray(entityId) ? entityId[0] : entityId;
|
|
25
|
-
if (!firstEntityId) throw new Error("Entity ID is required");
|
|
26
|
-
const domain = firstEntityId.split(".")[0];
|
|
27
|
-
if (!domain) throw new Error(`Invalid entity ID: ${firstEntityId}`);
|
|
28
|
-
await callService$1(domain, "turn_on", serviceData, { entity_id: entityId });
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Turn off entity or entities
|
|
32
|
-
*/
|
|
33
|
-
async function turnOff(entityId, serviceData = {}) {
|
|
34
|
-
const firstEntityId = Array.isArray(entityId) ? entityId[0] : entityId;
|
|
35
|
-
if (!firstEntityId) throw new Error("Entity ID is required");
|
|
36
|
-
const domain = firstEntityId.split(".")[0];
|
|
37
|
-
if (!domain) throw new Error(`Invalid entity ID: ${firstEntityId}`);
|
|
38
|
-
await callService$1(domain, "turn_off", serviceData, { entity_id: entityId });
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Toggle entity or entities
|
|
42
|
-
*/
|
|
43
|
-
async function toggle(entityId, serviceData = {}) {
|
|
44
|
-
const firstEntityId = Array.isArray(entityId) ? entityId[0] : entityId;
|
|
45
|
-
if (!firstEntityId) throw new Error("Entity ID is required");
|
|
46
|
-
const domain = firstEntityId.split(".")[0];
|
|
47
|
-
if (!domain) throw new Error(`Invalid entity ID: ${firstEntityId}`);
|
|
48
|
-
await callService$1(domain, "toggle", serviceData, { entity_id: entityId });
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Update entity registry entry
|
|
52
|
-
*/
|
|
53
|
-
async function updateEntity(entityId, updates) {
|
|
54
|
-
const connection = state.conn;
|
|
55
|
-
if (!connection) throw new Error("Not connected to Home Assistant. Call initConnection() first.");
|
|
56
|
-
try {
|
|
57
|
-
const result = await connection.sendMessagePromise({
|
|
58
|
-
type: "config/entity_registry/update",
|
|
59
|
-
entity_id: entityId,
|
|
60
|
-
...updates
|
|
61
|
-
});
|
|
62
|
-
if (result && typeof result === "object" && "_mockError" in result) throw new Error(`Entity registry entry not found: ${entityId}`);
|
|
63
|
-
return result;
|
|
64
|
-
} catch (error) {
|
|
65
|
-
if (error?.message && error.message.includes("Entity registry entry not found")) throw error;
|
|
66
|
-
throw new Error(`Entity update failed: ${error.message || String(error)}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Send a generic WebSocket command
|
|
71
|
-
*/
|
|
72
|
-
async function sendCommand(command) {
|
|
73
|
-
const connection = state.conn;
|
|
74
|
-
if (!connection) throw new Error("Not connected");
|
|
75
|
-
try {
|
|
76
|
-
return await connection.sendMessagePromise(command);
|
|
77
|
-
} catch (error) {
|
|
78
|
-
throw new Error(`Command failed: ${error.message || String(error)}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Execute multiple service calls in parallel
|
|
83
|
-
*/
|
|
84
|
-
async function batchServiceCalls(calls) {
|
|
85
|
-
await Promise.all(calls.map((call) => callService$1(call.domain, call.service, call.serviceData, call.target)));
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Execute multiple entity updates in parallel
|
|
89
|
-
*/
|
|
90
|
-
async function batchEntityUpdates(updates) {
|
|
91
|
-
return Promise.all(updates.map((u) => updateEntity(u.entityId, u.updates)));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
//#endregion
|
|
95
|
-
//#region src/entities/index.ts
|
|
96
|
-
/**
|
|
97
|
-
* Get all entity IDs
|
|
98
|
-
*/
|
|
99
|
-
function getAllEntityIds() {
|
|
100
|
-
return Object.keys(state.entities);
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Get entities by domain
|
|
104
|
-
*/
|
|
105
|
-
function getEntitiesByDomain(domain) {
|
|
106
|
-
return Object.keys(state.entities).filter((id) => extractDomain(id) === domain);
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Get entities via devices in area
|
|
110
|
-
*
|
|
111
|
-
* Only includes entities that inherit area from device (entity.area_id is null).
|
|
112
|
-
*/
|
|
113
|
-
function getEntitiesViaDevices(areaId) {
|
|
114
|
-
const devicesInArea = Object.values(state.devices).filter((device) => device.area_id === areaId);
|
|
115
|
-
const entityIds = [];
|
|
116
|
-
for (const device of devicesInArea) {
|
|
117
|
-
const deviceEntities = getEntitiesByDevice(device.id);
|
|
118
|
-
for (const entityId of deviceEntities) {
|
|
119
|
-
const registry = state.entityRegistry[entityId];
|
|
120
|
-
if (!registry?.area_id) entityIds.push(entityId);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return entityIds;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Get entities by area
|
|
127
|
-
*
|
|
128
|
-
* Returns entities with direct area_id assignments and entities that inherit their area from devices.
|
|
129
|
-
*/
|
|
130
|
-
function getEntitiesByArea(areaId) {
|
|
131
|
-
const directEntityIds = [];
|
|
132
|
-
for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.area_id === areaId) directEntityIds.push(entityId);
|
|
133
|
-
const deviceEntityIds = getEntitiesViaDevices(areaId);
|
|
134
|
-
return Array.from(new Set([...directEntityIds, ...deviceEntityIds]));
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Get entities by device
|
|
138
|
-
*/
|
|
139
|
-
function getEntitiesByDevice(deviceId) {
|
|
140
|
-
const entityIds = [];
|
|
141
|
-
for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.device_id === deviceId) entityIds.push(entityId);
|
|
142
|
-
return entityIds;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Get entities by floor
|
|
146
|
-
*/
|
|
147
|
-
function getEntitiesByFloor(floorId) {
|
|
148
|
-
const entityIds = [];
|
|
149
|
-
const areasOnFloor = Object.values(state.areas).filter((area) => area.floor_id === floorId);
|
|
150
|
-
for (const area of areasOnFloor) {
|
|
151
|
-
const areaEntities = getEntitiesByArea(area.id);
|
|
152
|
-
entityIds.push(...areaEntities);
|
|
153
|
-
}
|
|
154
|
-
return entityIds;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Get entities by label
|
|
158
|
-
*/
|
|
159
|
-
function getEntitiesByLabel(labelId) {
|
|
160
|
-
const entityIds = [];
|
|
161
|
-
for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.labels?.includes(labelId)) entityIds.push(entityId);
|
|
162
|
-
return entityIds;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
//#endregion
|
|
166
|
-
//#region src/entities/views.ts
|
|
167
|
-
/**
|
|
168
|
-
* Get icon for an entity from registry or attributes
|
|
169
|
-
*/
|
|
170
|
-
function resolveIcon(entity, registry) {
|
|
171
|
-
if (registry?.icon) return {
|
|
172
|
-
icon: registry.icon,
|
|
173
|
-
source: "registry"
|
|
174
|
-
};
|
|
175
|
-
if (entity.attributes?.icon) return {
|
|
176
|
-
icon: entity.attributes.icon,
|
|
177
|
-
source: "attribute"
|
|
178
|
-
};
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* Build an EntityView from runtime state and registry metadata
|
|
183
|
-
*/
|
|
184
|
-
function buildEntityView(entity, registry) {
|
|
185
|
-
const domain = extractDomain(entity.entity_id);
|
|
186
|
-
const friendlyName = entity.attributes.friendly_name ?? registry?.name ?? entity.entity_id;
|
|
187
|
-
const name = registry?.name ?? friendlyName;
|
|
188
|
-
const iconResolution = resolveIcon(entity, registry);
|
|
189
|
-
let areaId = registry?.area_id ?? null;
|
|
190
|
-
if (areaId === null && registry?.device_id) {
|
|
191
|
-
const device = state.devices[registry.device_id];
|
|
192
|
-
if (device) areaId = device.area_id ?? null;
|
|
193
|
-
}
|
|
194
|
-
return {
|
|
195
|
-
id: entity.entity_id,
|
|
196
|
-
domain,
|
|
197
|
-
state: entity.state,
|
|
198
|
-
attributes: entity.attributes,
|
|
199
|
-
lastChanged: new Date(entity.last_changed),
|
|
200
|
-
lastUpdated: new Date(entity.last_updated),
|
|
201
|
-
context: {
|
|
202
|
-
id: entity.context.id,
|
|
203
|
-
parentId: entity.context.parent_id,
|
|
204
|
-
userId: entity.context.user_id
|
|
205
|
-
},
|
|
206
|
-
name,
|
|
207
|
-
friendlyName,
|
|
208
|
-
areaId,
|
|
209
|
-
deviceId: registry?.device_id ?? null,
|
|
210
|
-
platform: registry?.platform ?? "unknown",
|
|
211
|
-
uniqueId: registry?.unique_id ?? null,
|
|
212
|
-
isDisabled: registry?.disabled_by !== null && registry?.disabled_by !== void 0,
|
|
213
|
-
isHidden: registry?.hidden_by !== null && registry?.hidden_by !== void 0,
|
|
214
|
-
icon: iconResolution?.icon ?? null,
|
|
215
|
-
iconSource: iconResolution?.source ?? "default",
|
|
216
|
-
entityCategory: registry?.entity_category ?? null,
|
|
217
|
-
labels: registry?.labels ?? [],
|
|
218
|
-
aliases: registry?.aliases ?? [],
|
|
219
|
-
deviceClass: registry?.device_class ?? entity.attributes.device_class ?? null,
|
|
220
|
-
unitOfMeasurement: registry?.unit_of_measurement ?? entity.attributes.unit_of_measurement ?? null,
|
|
221
|
-
supportedFeatures: registry?.supported_features ?? entity.attributes.supported_features
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Get or build an EntityView
|
|
226
|
-
*/
|
|
227
|
-
function getEntityView(entityId) {
|
|
228
|
-
const entity = state.entities[entityId];
|
|
229
|
-
if (!entity) return void 0;
|
|
230
|
-
const registry = state.entityRegistry[entityId];
|
|
231
|
-
return buildEntityView(entity, registry);
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Get multiple entity views
|
|
235
|
-
*/
|
|
236
|
-
function getEntityViews(entityIds) {
|
|
237
|
-
const views = [];
|
|
238
|
-
for (const id of entityIds) {
|
|
239
|
-
const view = getEntityView(id);
|
|
240
|
-
if (view) views.push(view);
|
|
241
|
-
}
|
|
242
|
-
return views;
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Get all entity views
|
|
246
|
-
*/
|
|
247
|
-
function getAllEntityViews() {
|
|
248
|
-
return getEntityViews(Object.keys(state.entities));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
//#endregion
|
|
252
|
-
//#region src/entities/area-views.ts
|
|
253
|
-
/**
|
|
254
|
-
* Build a DeviceView from registry data
|
|
255
|
-
*/
|
|
256
|
-
function buildDeviceView(device) {
|
|
257
|
-
return {
|
|
258
|
-
id: device.id,
|
|
259
|
-
name: device.name ?? "",
|
|
260
|
-
nameByUser: device.name_by_user ?? null,
|
|
261
|
-
manufacturer: device.manufacturer ?? null,
|
|
262
|
-
model: device.model ?? null,
|
|
263
|
-
swVersion: device.sw_version ?? null,
|
|
264
|
-
areaId: device.area_id ?? null,
|
|
265
|
-
configEntries: device.config_entries ?? [],
|
|
266
|
-
connections: device.connections ?? [],
|
|
267
|
-
identifiers: device.identifiers ?? [],
|
|
268
|
-
disabledBy: device.disabled_by ?? null
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Build an AreaView from registry data using query engine
|
|
273
|
-
*/
|
|
274
|
-
function buildAreaView(areaId) {
|
|
275
|
-
const area = state.areas[areaId];
|
|
276
|
-
if (!area) throw new Error(`Area ${areaId} not found`);
|
|
277
|
-
const directEntityIds = [];
|
|
278
|
-
for (const [entityId, entry] of Object.entries(state.entityRegistry)) if (entry.area_id === areaId) directEntityIds.push(entityId);
|
|
279
|
-
const deviceEntityIds = getEntitiesViaDevices(areaId);
|
|
280
|
-
const allEntityIds = Array.from(new Set([...directEntityIds, ...deviceEntityIds]));
|
|
281
|
-
const areaEntities = allEntityIds.length > 0 ? getEntityViews(allEntityIds) : [];
|
|
282
|
-
const devicesInArea = Object.values(state.devices).filter((device) => device.area_id === areaId);
|
|
283
|
-
const devices = devicesInArea.map((device) => buildDeviceView(device));
|
|
284
|
-
return {
|
|
285
|
-
id: areaId,
|
|
286
|
-
name: area.name,
|
|
287
|
-
normalizedName: area.normalized_name ?? area.name.toLowerCase(),
|
|
288
|
-
aliases: Array.isArray(area.aliases) ? area.aliases : [],
|
|
289
|
-
floorId: area.floor_id ?? null,
|
|
290
|
-
icon: area.icon ?? null,
|
|
291
|
-
picture: area.picture ?? null,
|
|
292
|
-
labels: Array.isArray(area.labels) ? area.labels : [],
|
|
293
|
-
temperatureEntityId: area.temperature_entity_id ?? null,
|
|
294
|
-
humidityEntityId: area.humidity_entity_id ?? null,
|
|
295
|
-
createdAt: area.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
296
|
-
modifiedAt: area.modified_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
297
|
-
entities: areaEntities,
|
|
298
|
-
devices,
|
|
299
|
-
entityIds: allEntityIds,
|
|
300
|
-
deviceIds: devices.map((d) => d.id)
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
/**
|
|
304
|
-
* Get all area views
|
|
305
|
-
*/
|
|
306
|
-
function getAreaViews() {
|
|
307
|
-
return Object.keys(state.areas).map((areaId) => buildAreaView(areaId));
|
|
308
|
-
}
|
|
309
|
-
/**
|
|
310
|
-
* Get area view
|
|
311
|
-
*/
|
|
312
|
-
function getAreaView(areaId) {
|
|
313
|
-
try {
|
|
314
|
-
return buildAreaView(areaId);
|
|
315
|
-
} catch {
|
|
316
|
-
return void 0;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
//#endregion
|
|
321
|
-
export { batchEntityUpdates, batchServiceCalls, buildAreaView, buildDeviceView, buildEntityView, callService$1 as callService, getAllEntityIds, getAllEntityViews, getAreaView, getAreaViews, getEntitiesByArea, getEntitiesByDevice, getEntitiesByDomain, getEntitiesByFloor, getEntitiesByLabel, getEntityView, getEntityViews, sendCommand, toggle, turnOff, turnOn, updateEntity };
|
|
File without changes
|
|
File without changes
|