@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.
Files changed (89) hide show
  1. package/dist/hass.module.d.mts +23 -1
  2. package/dist/hass.module.mjs +26 -1
  3. package/dist/hass.module.mjs.map +1 -1
  4. package/dist/helpers/fetch.mjs +0 -1
  5. package/dist/helpers/fetch.mjs.map +1 -1
  6. package/dist/helpers/utility.d.mts +1 -0
  7. package/dist/helpers/utility.mjs +4 -0
  8. package/dist/helpers/utility.mjs.map +1 -1
  9. package/dist/mock_assistant/mock-assistant.module.d.mts +22 -0
  10. package/dist/services/area.service.mjs +9 -1
  11. package/dist/services/area.service.mjs.map +1 -1
  12. package/dist/services/call-proxy.service.mjs +10 -2
  13. package/dist/services/call-proxy.service.mjs.map +1 -1
  14. package/dist/services/config.service.mjs +15 -13
  15. package/dist/services/config.service.mjs.map +1 -1
  16. package/dist/services/device.service.mjs +3 -1
  17. package/dist/services/device.service.mjs.map +1 -1
  18. package/dist/services/diagnostics.service.d.mts +26 -0
  19. package/dist/services/diagnostics.service.mjs +41 -0
  20. package/dist/services/diagnostics.service.mjs.map +1 -0
  21. package/dist/services/entity.service.mjs +12 -1
  22. package/dist/services/entity.service.mjs.map +1 -1
  23. package/dist/services/fetch-api.service.mjs +8 -2
  24. package/dist/services/fetch-api.service.mjs.map +1 -1
  25. package/dist/services/floor.service.mjs +3 -1
  26. package/dist/services/floor.service.mjs.map +1 -1
  27. package/dist/services/id-by.service.d.mts +1 -1
  28. package/dist/services/id-by.service.mjs +20 -13
  29. package/dist/services/id-by.service.mjs.map +1 -1
  30. package/dist/services/index.d.mts +1 -0
  31. package/dist/services/index.mjs +1 -0
  32. package/dist/services/index.mjs.map +1 -1
  33. package/dist/services/label.service.mjs +3 -1
  34. package/dist/services/label.service.mjs.map +1 -1
  35. package/dist/services/reference.service.mjs +15 -4
  36. package/dist/services/reference.service.mjs.map +1 -1
  37. package/dist/services/registry.service.mjs +8 -8
  38. package/dist/services/registry.service.mjs.map +1 -1
  39. package/dist/services/websocket-api.service.mjs +10 -2
  40. package/dist/services/websocket-api.service.mjs.map +1 -1
  41. package/dist/services/zone.service.mjs +3 -1
  42. package/dist/services/zone.service.mjs.map +1 -1
  43. package/dist/testing/area.spec.mjs +141 -132
  44. package/dist/testing/area.spec.mjs.map +1 -1
  45. package/dist/testing/device.spec.mjs +17 -0
  46. package/dist/testing/device.spec.mjs.map +1 -1
  47. package/dist/testing/entity.spec.mjs +167 -0
  48. package/dist/testing/entity.spec.mjs.map +1 -1
  49. package/dist/testing/fetch.spec.d.mts +1 -0
  50. package/dist/testing/fetch.spec.mjs +45 -0
  51. package/dist/testing/fetch.spec.mjs.map +1 -0
  52. package/dist/testing/floor.spec.mjs +17 -0
  53. package/dist/testing/floor.spec.mjs.map +1 -1
  54. package/dist/testing/id-by.spec.mjs +93 -5
  55. package/dist/testing/id-by.spec.mjs.map +1 -1
  56. package/dist/testing/label.spec.mjs +17 -0
  57. package/dist/testing/label.spec.mjs.map +1 -1
  58. package/dist/testing/ref-by.spec.mjs +1 -1
  59. package/dist/testing/ref-by.spec.mjs.map +1 -1
  60. package/dist/testing/zone.spec.mjs +24 -5
  61. package/dist/testing/zone.spec.mjs.map +1 -1
  62. package/package.json +35 -31
  63. package/src/hass.module.mts +29 -0
  64. package/src/helpers/fetch.mts +1 -1
  65. package/src/helpers/utility.mts +5 -0
  66. package/src/services/area.service.mts +9 -0
  67. package/src/services/call-proxy.service.mts +16 -9
  68. package/src/services/config.service.mts +21 -16
  69. package/src/services/device.service.mts +3 -0
  70. package/src/services/diagnostics.service.mts +45 -0
  71. package/src/services/entity.service.mts +12 -0
  72. package/src/services/fetch-api.service.mts +11 -2
  73. package/src/services/floor.service.mts +3 -0
  74. package/src/services/id-by.service.mts +25 -15
  75. package/src/services/index.mts +1 -0
  76. package/src/services/label.service.mts +3 -0
  77. package/src/services/reference.service.mts +15 -3
  78. package/src/services/registry.service.mts +8 -8
  79. package/src/services/websocket-api.service.mts +10 -2
  80. package/src/services/zone.service.mts +3 -0
  81. package/src/testing/area.spec.mts +153 -140
  82. package/src/testing/device.spec.mts +22 -0
  83. package/src/testing/entity.spec.mts +201 -0
  84. package/src/testing/fetch.spec.mts +54 -0
  85. package/src/testing/floor.spec.mts +22 -0
  86. package/src/testing/id-by.spec.mts +100 -5
  87. package/src/testing/label.spec.mts +22 -0
  88. package/src/testing/ref-by.spec.mts +1 -1
  89. 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>(domain: DOMAIN) {
38
- const MASTER_STATE = hass.entity._masterState();
39
- return Object.keys(MASTER_STATE[domain] ?? {}).map(
40
- id => `${domain}.${id}` as PICK_ENTITY<DOMAIN>,
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: trimmed.map(i => i.entity_id), unique_id },
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
- hass.entity.registry.current
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 fromEntity = hass.entity.registry.current
117
- .filter(i => i.area_id === area)
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 = hass.entity.registry.current
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
- hass.entity.registry.current
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
- hass.entity.registry.current
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
- hass.entity.registry.current
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,
@@ -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
- return new Proxy(thing, {
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
- return await hass.call[entity_domain][property]({
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 ManifestList() {
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 UpdateCore(options: UpdateCoreOptions) {
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 GetConfig() {
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 GetConfigEntries() {
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: GetConfig,
40
- getConfigEntries: GetConfigEntries,
41
- manifestList: ManifestList,
42
- updateCore: 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
- return await hass.socket.sendMessage({
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
  });