@digital-alchemy/hass 25.3.2 → 25.5.2
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/hass.module.d.mts +23 -1
- package/dist/hass.module.mjs +26 -1
- package/dist/hass.module.mjs.map +1 -1
- package/dist/helpers/fetch.mjs +0 -1
- package/dist/helpers/fetch.mjs.map +1 -1
- package/dist/helpers/utility.d.mts +1 -0
- package/dist/helpers/utility.mjs +4 -0
- package/dist/helpers/utility.mjs.map +1 -1
- package/dist/mock_assistant/mock-assistant.module.d.mts +22 -0
- package/dist/services/area.service.mjs +9 -1
- package/dist/services/area.service.mjs.map +1 -1
- package/dist/services/call-proxy.service.mjs +10 -2
- package/dist/services/call-proxy.service.mjs.map +1 -1
- package/dist/services/config.service.mjs +15 -13
- package/dist/services/config.service.mjs.map +1 -1
- package/dist/services/device.service.mjs +3 -1
- package/dist/services/device.service.mjs.map +1 -1
- package/dist/services/diagnostics.service.d.mts +26 -0
- package/dist/services/diagnostics.service.mjs +41 -0
- package/dist/services/diagnostics.service.mjs.map +1 -0
- package/dist/services/entity.service.mjs +12 -1
- package/dist/services/entity.service.mjs.map +1 -1
- package/dist/services/fetch-api.service.mjs +8 -2
- package/dist/services/fetch-api.service.mjs.map +1 -1
- package/dist/services/floor.service.mjs +3 -1
- package/dist/services/floor.service.mjs.map +1 -1
- package/dist/services/id-by.service.d.mts +1 -1
- package/dist/services/id-by.service.mjs +20 -13
- package/dist/services/id-by.service.mjs.map +1 -1
- package/dist/services/index.d.mts +1 -0
- package/dist/services/index.mjs +1 -0
- package/dist/services/index.mjs.map +1 -1
- package/dist/services/label.service.mjs +3 -1
- package/dist/services/label.service.mjs.map +1 -1
- package/dist/services/reference.service.mjs +15 -4
- package/dist/services/reference.service.mjs.map +1 -1
- package/dist/services/registry.service.mjs +8 -8
- package/dist/services/registry.service.mjs.map +1 -1
- package/dist/services/websocket-api.service.mjs +10 -2
- package/dist/services/websocket-api.service.mjs.map +1 -1
- package/dist/services/zone.service.mjs +3 -1
- package/dist/services/zone.service.mjs.map +1 -1
- package/dist/testing/area.spec.mjs +141 -132
- package/dist/testing/area.spec.mjs.map +1 -1
- package/dist/testing/device.spec.mjs +17 -0
- package/dist/testing/device.spec.mjs.map +1 -1
- package/dist/testing/entity.spec.mjs +167 -0
- package/dist/testing/entity.spec.mjs.map +1 -1
- package/dist/testing/fetch.spec.d.mts +1 -0
- package/dist/testing/fetch.spec.mjs +45 -0
- package/dist/testing/fetch.spec.mjs.map +1 -0
- package/dist/testing/floor.spec.mjs +17 -0
- package/dist/testing/floor.spec.mjs.map +1 -1
- package/dist/testing/id-by.spec.mjs +93 -5
- package/dist/testing/id-by.spec.mjs.map +1 -1
- package/dist/testing/label.spec.mjs +17 -0
- package/dist/testing/label.spec.mjs.map +1 -1
- package/dist/testing/ref-by.spec.mjs +1 -1
- package/dist/testing/ref-by.spec.mjs.map +1 -1
- package/dist/testing/zone.spec.mjs +24 -5
- package/dist/testing/zone.spec.mjs.map +1 -1
- package/package.json +35 -31
- package/src/hass.module.mts +29 -0
- package/src/helpers/fetch.mts +1 -1
- package/src/helpers/utility.mts +5 -0
- package/src/services/area.service.mts +9 -0
- package/src/services/call-proxy.service.mts +16 -9
- package/src/services/config.service.mts +21 -16
- package/src/services/device.service.mts +3 -0
- package/src/services/diagnostics.service.mts +45 -0
- package/src/services/entity.service.mts +12 -0
- package/src/services/fetch-api.service.mts +11 -2
- package/src/services/floor.service.mts +3 -0
- package/src/services/id-by.service.mts +25 -15
- package/src/services/index.mts +1 -0
- package/src/services/label.service.mts +3 -0
- package/src/services/reference.service.mts +15 -3
- package/src/services/registry.service.mts +8 -8
- package/src/services/websocket-api.service.mts +10 -2
- package/src/services/zone.service.mts +3 -0
- package/src/testing/area.spec.mts +153 -140
- package/src/testing/device.spec.mts +22 -0
- package/src/testing/entity.spec.mts +201 -0
- package/src/testing/fetch.spec.mts +54 -0
- package/src/testing/floor.spec.mts +22 -0
- package/src/testing/id-by.spec.mts +100 -5
- package/src/testing/label.spec.mts +22 -0
- package/src/testing/ref-by.spec.mts +1 -1
- package/src/testing/zone.spec.mts +29 -5
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
ALL_DOMAINS,
|
|
6
6
|
ANY_ENTITY,
|
|
7
7
|
HassUniqueIdMapping,
|
|
8
|
-
PICK_ENTITY,
|
|
9
8
|
PICK_FROM_AREA,
|
|
10
9
|
PICK_FROM_DEVICE,
|
|
11
10
|
PICK_FROM_FLOOR,
|
|
@@ -22,6 +21,7 @@ import {
|
|
|
22
21
|
export function IDByExtension({
|
|
23
22
|
hass,
|
|
24
23
|
logger,
|
|
24
|
+
config,
|
|
25
25
|
internal: {
|
|
26
26
|
utils: { is },
|
|
27
27
|
},
|
|
@@ -33,12 +33,16 @@ export function IDByExtension({
|
|
|
33
33
|
return raw;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
const getEntities = () =>
|
|
37
|
+
config.hass.FILTER_DISABLED_ENTITIES_ID_BY
|
|
38
|
+
? hass.entity.registry.current.filter(i => is.empty(i.disabled_by))
|
|
39
|
+
: hass.entity.registry.current;
|
|
40
|
+
|
|
36
41
|
// * byDomain
|
|
37
|
-
function byDomain<DOMAIN extends ALL_DOMAINS>(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
42
|
+
function byDomain<DOMAIN extends ALL_DOMAINS>(target: DOMAIN) {
|
|
43
|
+
return getEntities()
|
|
44
|
+
.map(i => i.entity_id)
|
|
45
|
+
.filter(i => is.domain(i, target));
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
/**
|
|
@@ -74,8 +78,9 @@ export function IDByExtension({
|
|
|
74
78
|
domain(i.entity_id) !== "update",
|
|
75
79
|
);
|
|
76
80
|
if (trimmed.length > SINGLE) {
|
|
81
|
+
const available_entity_ids = trimmed.map(i => i.entity_id);
|
|
77
82
|
logger.warn(
|
|
78
|
-
{ available_entity_ids
|
|
83
|
+
{ available_entity_ids, unique_id },
|
|
79
84
|
`unique_id collision during lookup (chose first in list)`,
|
|
80
85
|
);
|
|
81
86
|
} else {
|
|
@@ -88,6 +93,12 @@ export function IDByExtension({
|
|
|
88
93
|
list = trimmed;
|
|
89
94
|
}
|
|
90
95
|
const [entity] = list;
|
|
96
|
+
if (config.hass.FILTER_DISABLED_ENTITIES_ID_BY && !is.empty(entity?.disabled_by)) {
|
|
97
|
+
logger.debug(
|
|
98
|
+
{ entity_id: entity?.entity_id, unique_id },
|
|
99
|
+
`access disabled entity by unique_id`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
91
102
|
return entity?.entity_id;
|
|
92
103
|
}
|
|
93
104
|
|
|
@@ -98,7 +109,7 @@ export function IDByExtension({
|
|
|
98
109
|
) {
|
|
99
110
|
hass.entity.warnEarly("label");
|
|
100
111
|
return check(
|
|
101
|
-
|
|
112
|
+
getEntities()
|
|
102
113
|
.filter(i => i.labels.includes(label))
|
|
103
114
|
.map(i => i.entity_id as PICK_FROM_LABEL<LABEL, DOMAIN>),
|
|
104
115
|
domains,
|
|
@@ -113,9 +124,8 @@ export function IDByExtension({
|
|
|
113
124
|
hass.entity.warnEarly("area");
|
|
114
125
|
|
|
115
126
|
// find entities are associated with the area directly
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
.map(i => i.entity_id);
|
|
127
|
+
const entities = getEntities();
|
|
128
|
+
const fromEntity = entities.filter(i => i.area_id === area).map(i => i.entity_id);
|
|
119
129
|
|
|
120
130
|
// identify devices
|
|
121
131
|
const devices = new Set(
|
|
@@ -123,7 +133,7 @@ export function IDByExtension({
|
|
|
123
133
|
);
|
|
124
134
|
|
|
125
135
|
// extract entities associated with device, that have not been assigned to a room
|
|
126
|
-
const fromDevice =
|
|
136
|
+
const fromDevice = entities
|
|
127
137
|
.filter(entity => devices.has(entity.device_id) && is.empty(entity.area_id))
|
|
128
138
|
.map(i => i.entity_id);
|
|
129
139
|
|
|
@@ -141,7 +151,7 @@ export function IDByExtension({
|
|
|
141
151
|
): PICK_FROM_DEVICE<DEVICE, DOMAIN>[] {
|
|
142
152
|
hass.entity.warnEarly("device");
|
|
143
153
|
return check(
|
|
144
|
-
|
|
154
|
+
getEntities()
|
|
145
155
|
.filter(i => i.device_id === device)
|
|
146
156
|
.map(i => i.entity_id as PICK_FROM_DEVICE<DEVICE, DOMAIN>),
|
|
147
157
|
domains,
|
|
@@ -158,7 +168,7 @@ export function IDByExtension({
|
|
|
158
168
|
hass.area.current.filter(i => i.floor_id === floor).map(i => i.area_id),
|
|
159
169
|
);
|
|
160
170
|
return check(
|
|
161
|
-
|
|
171
|
+
getEntities()
|
|
162
172
|
.filter(i => areas.has(i.area_id))
|
|
163
173
|
.map(i => i.entity_id as PICK_FROM_FLOOR<FLOOR, DOMAIN>),
|
|
164
174
|
domains,
|
|
@@ -172,7 +182,7 @@ export function IDByExtension({
|
|
|
172
182
|
): PICK_FROM_PLATFORM<PLATFORM, DOMAIN>[] {
|
|
173
183
|
hass.entity.warnEarly("platform");
|
|
174
184
|
return check(
|
|
175
|
-
|
|
185
|
+
getEntities()
|
|
176
186
|
.filter(i => i.platform === platform)
|
|
177
187
|
.map(i => i.entity_id as PICK_FROM_PLATFORM<PLATFORM, DOMAIN>),
|
|
178
188
|
domains,
|
package/src/services/index.mts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./backup.service.mts";
|
|
|
3
3
|
export * from "./call-proxy.service.mts";
|
|
4
4
|
export * from "./config.service.mts";
|
|
5
5
|
export * from "./device.service.mts";
|
|
6
|
+
export * from "./diagnostics.service.mts";
|
|
6
7
|
export * from "./entity.service.mts";
|
|
7
8
|
export * from "./events.service.mts";
|
|
8
9
|
export * from "./fetch-api.service.mts";
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
LABEL_REGISTRY_UPDATED,
|
|
7
7
|
LabelDefinition,
|
|
8
8
|
LabelOptions,
|
|
9
|
+
perf,
|
|
9
10
|
} from "../helpers/index.mts";
|
|
10
11
|
import { TLabelId } from "../user.mts";
|
|
11
12
|
|
|
@@ -29,10 +30,12 @@ export function Label({
|
|
|
29
30
|
context,
|
|
30
31
|
event_type: "label_registry_updated",
|
|
31
32
|
async exec() {
|
|
33
|
+
const ms = perf();
|
|
32
34
|
await debounce(LABEL_REGISTRY_UPDATED, config.hass.EVENT_DEBOUNCE_MS);
|
|
33
35
|
hass.label.current = await hass.label.list();
|
|
34
36
|
logger.debug(`label registry updated`);
|
|
35
37
|
event.emit(LABEL_REGISTRY_UPDATED);
|
|
38
|
+
hass.diagnostics.label?.registry_update.publish({ ms: ms() });
|
|
36
39
|
},
|
|
37
40
|
});
|
|
38
41
|
});
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
domain,
|
|
9
9
|
ENTITY_STATE,
|
|
10
10
|
HassReferenceService,
|
|
11
|
+
perf,
|
|
11
12
|
RemoveCallback,
|
|
12
13
|
} from "../helpers/index.mts";
|
|
13
14
|
import {
|
|
@@ -164,10 +165,11 @@ export function ReferenceService({
|
|
|
164
165
|
const listeners = new Set<() => void>();
|
|
165
166
|
|
|
166
167
|
// just because you can't do generics properly....
|
|
167
|
-
|
|
168
|
+
const proxy = new Proxy(thing, {
|
|
168
169
|
// things that shouldn't be needed: this extract
|
|
169
170
|
// eslint-disable-next-line sonarjs/function-return-type
|
|
170
171
|
get: (_, property: Extract<keyof ByIdProxy<ENTITY_ID>, string>) => {
|
|
172
|
+
hass.diagnostics.reference?.get_property.publish({ entity_id, property });
|
|
171
173
|
switch (property) {
|
|
172
174
|
// #MARK: onUpdate
|
|
173
175
|
case "onUpdate": {
|
|
@@ -304,7 +306,6 @@ export function ReferenceService({
|
|
|
304
306
|
listeners.add(remove);
|
|
305
307
|
|
|
306
308
|
const complete = (entity: ENTITY_STATE<ENTITY_ID>) => {
|
|
307
|
-
// eslint-disable-next-line sonarjs/different-types-comparison
|
|
308
309
|
if (entity.state !== state) {
|
|
309
310
|
logger.trace(
|
|
310
311
|
{
|
|
@@ -340,11 +341,20 @@ export function ReferenceService({
|
|
|
340
341
|
// #MARK: service calls
|
|
341
342
|
if (hass.configure.isService(entity_domain, property)) {
|
|
342
343
|
return async function (data = {}) {
|
|
344
|
+
const ms = perf();
|
|
343
345
|
// @ts-expect-error i don't care, this is fine
|
|
344
|
-
|
|
346
|
+
const result = await hass.call[entity_domain][property]({
|
|
345
347
|
entity_id,
|
|
346
348
|
...data,
|
|
347
349
|
});
|
|
350
|
+
hass.diagnostics.reference?.call_service.publish({
|
|
351
|
+
data,
|
|
352
|
+
entity_domain,
|
|
353
|
+
entity_id,
|
|
354
|
+
ms: ms(),
|
|
355
|
+
property,
|
|
356
|
+
});
|
|
357
|
+
return result;
|
|
348
358
|
};
|
|
349
359
|
}
|
|
350
360
|
return proxyGetLogic(entity_id, property);
|
|
@@ -392,6 +402,8 @@ export function ReferenceService({
|
|
|
392
402
|
return false;
|
|
393
403
|
},
|
|
394
404
|
});
|
|
405
|
+
hass.diagnostics.reference?.create_proxy.publish({ entity_id, proxy });
|
|
406
|
+
return proxy;
|
|
395
407
|
}
|
|
396
408
|
|
|
397
409
|
// #MARK: <return>
|
|
@@ -10,35 +10,35 @@ import {
|
|
|
10
10
|
} from "../helpers/index.mts";
|
|
11
11
|
|
|
12
12
|
export function Registry({ hass }: TServiceParams): HassRegistryService {
|
|
13
|
-
async function
|
|
13
|
+
async function manifestList() {
|
|
14
14
|
return await hass.socket.sendMessage<ManifestItem[]>({
|
|
15
15
|
type: "manifest/list",
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
async function
|
|
19
|
+
async function updateCore(options: UpdateCoreOptions) {
|
|
20
20
|
await hass.socket.sendMessage<ZoneDetails[]>({
|
|
21
21
|
...options,
|
|
22
22
|
type: "config/core/update",
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
async function
|
|
26
|
+
async function getConfig() {
|
|
27
27
|
return await hass.socket.sendMessage<HassConfig>({
|
|
28
28
|
type: "get_config",
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
async function
|
|
32
|
+
async function getConfigEntries() {
|
|
33
33
|
return await hass.socket.sendMessage<ConfigEntry[]>({
|
|
34
34
|
type: "config_entries/get",
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
return {
|
|
39
|
-
getConfig
|
|
40
|
-
getConfigEntries
|
|
41
|
-
manifestList
|
|
42
|
-
updateCore
|
|
39
|
+
getConfig,
|
|
40
|
+
getConfigEntries,
|
|
41
|
+
manifestList,
|
|
42
|
+
updateCore,
|
|
43
43
|
};
|
|
44
44
|
}
|
|
@@ -73,6 +73,7 @@ export function WebsocketAPI({
|
|
|
73
73
|
// #MARK: setConnectionState
|
|
74
74
|
function setConnectionState(state: ConnectionState) {
|
|
75
75
|
hass.socket.connectionState = state;
|
|
76
|
+
hass.diagnostics.websocket?.set_connection_state.publish({ state });
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
// #MARK: handleUnknownConnectionState
|
|
@@ -87,6 +88,7 @@ export function WebsocketAPI({
|
|
|
87
88
|
// send a ping message to force a pong
|
|
88
89
|
logger.trace({ name }, `emitting ping`);
|
|
89
90
|
lastPingAttempt = now;
|
|
91
|
+
hass.diagnostics.websocket?.send_ping.publish({});
|
|
90
92
|
|
|
91
93
|
// emit a ping, do not wait for reply (inline)
|
|
92
94
|
hass.socket.sendMessage({ type: "ping" }, false);
|
|
@@ -109,6 +111,7 @@ export function WebsocketAPI({
|
|
|
109
111
|
logger.warn({ name }, "failed to receive expected {pong}");
|
|
110
112
|
return;
|
|
111
113
|
}
|
|
114
|
+
hass.diagnostics.websocket?.failed_ping.publish({});
|
|
112
115
|
// 🪦 oof, get rid of the current connection and try again
|
|
113
116
|
await hass.socket.teardown();
|
|
114
117
|
logger.warn({ name }, "hass stopped replying {unknown} => {offline}");
|
|
@@ -215,11 +218,13 @@ export function WebsocketAPI({
|
|
|
215
218
|
|
|
216
219
|
// #MARK: fireEvent
|
|
217
220
|
async function fireEvent(event_type: string, event_data: object = {}) {
|
|
218
|
-
|
|
221
|
+
const result = await hass.socket.sendMessage({
|
|
219
222
|
event_data,
|
|
220
223
|
event_type,
|
|
221
224
|
type: "fire_event",
|
|
222
225
|
});
|
|
226
|
+
hass.diagnostics.websocket?.fire_event.publish({ event_data, event_type, result });
|
|
227
|
+
return result;
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
// #MARK: sendMessage
|
|
@@ -245,6 +250,7 @@ export function WebsocketAPI({
|
|
|
245
250
|
if (data.type !== "auth") {
|
|
246
251
|
data.id = id;
|
|
247
252
|
}
|
|
253
|
+
hass.diagnostics.websocket?.send_message.publish({ data, waitForResponse });
|
|
248
254
|
const json = JSON.stringify(data);
|
|
249
255
|
const sentAt = new Date();
|
|
250
256
|
|
|
@@ -275,6 +281,7 @@ export function WebsocketAPI({
|
|
|
275
281
|
//
|
|
276
282
|
// discard the promise so whatever flow is depending on this can get garbage collected
|
|
277
283
|
waitingCallback.delete(id);
|
|
284
|
+
hass.diagnostics.websocket?.missed_reply.publish({ data, sentAt });
|
|
278
285
|
logger.warn(
|
|
279
286
|
{
|
|
280
287
|
message: data,
|
|
@@ -398,10 +405,11 @@ export function WebsocketAPI({
|
|
|
398
405
|
*/
|
|
399
406
|
async function onMessage(message: SocketMessageDTO) {
|
|
400
407
|
const id = Number(message.id);
|
|
408
|
+
setImmediate(() => hass.diagnostics.websocket?.message_received.publish({ message }));
|
|
401
409
|
switch (message.type) {
|
|
402
410
|
case "auth_required": {
|
|
403
411
|
logger.trace({ name: onMessage }, `sending authentication`);
|
|
404
|
-
hass.socket.sendMessage({ access_token: config.hass.TOKEN, type: "auth" }, false);
|
|
412
|
+
void hass.socket.sendMessage({ access_token: config.hass.TOKEN, type: "auth" }, false);
|
|
405
413
|
return;
|
|
406
414
|
}
|
|
407
415
|
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
EARLY_ON_READY,
|
|
5
5
|
HassZoneService,
|
|
6
6
|
ManifestItem,
|
|
7
|
+
perf,
|
|
7
8
|
ZONE_REGISTRY_UPDATED,
|
|
8
9
|
ZoneDetails,
|
|
9
10
|
ZoneOptions,
|
|
@@ -29,10 +30,12 @@ export function Zone({
|
|
|
29
30
|
context,
|
|
30
31
|
event_type: "zone_registry_updated",
|
|
31
32
|
async exec() {
|
|
33
|
+
const ms = perf();
|
|
32
34
|
await debounce(ZONE_REGISTRY_UPDATED, config.hass.EVENT_DEBOUNCE_MS);
|
|
33
35
|
hass.zone.current = await hass.zone.list();
|
|
34
36
|
logger.debug(`zone registry updated`);
|
|
35
37
|
event.emit(ZONE_REGISTRY_UPDATED);
|
|
38
|
+
hass.diagnostics.zone?.registry_update.publish({ ms: ms() });
|
|
36
39
|
},
|
|
37
40
|
});
|
|
38
41
|
});
|