@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
@@ -1,3 +1,4 @@
1
+ import { subscribe } from "node:diagnostics_channel";
1
2
  import { sleep } from "@digital-alchemy/core";
2
3
  import { ZONE_REGISTRY_UPDATED } from "../helpers/index.mjs";
3
4
  import { hassTestRunner } from "../mock_assistant/index.mjs";
@@ -7,11 +8,13 @@ describe("Zone", () => {
7
8
  vi.restoreAllMocks();
8
9
  });
9
10
  const EXAMPLE_ZONE = {
10
- icon: "",
11
- latitude: 0,
12
- longitude: 0,
13
- name: "Test",
14
- passive: true,
11
+ icon: "mdi:map-marker",
12
+ id: "test",
13
+ latitude: 37.7749,
14
+ longitude: -122.4194,
15
+ name: "Example Zone",
16
+ passive: false,
17
+ radius: 100,
15
18
  };
16
19
  describe("Lifecycle", () => {
17
20
  it("should force values to be available before ready", async () => {
@@ -55,6 +58,22 @@ describe("Zone", () => {
55
58
  });
56
59
  });
57
60
  });
61
+ it("should publish diagnostics on zone registry update", async () => {
62
+ expect.assertions(1);
63
+ hassTestRunner.configure({ hass: { EMIT_DIAGNOSTICS: true } });
64
+ await hassTestRunner.run(({ lifecycle, hass }) => {
65
+ vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
66
+ const spy = vi.fn();
67
+ subscribe(hass.diagnostics.zone.registry_update.name, spy);
68
+ lifecycle.onReady(async () => {
69
+ setImmediate(async () => {
70
+ hass.socket.socketEvents.emit("zone_registry_updated");
71
+ });
72
+ await sleep(100);
73
+ expect(spy).toHaveBeenCalledWith(expect.objectContaining({ ms: expect.any(Number) }), hass.diagnostics.zone.registry_update.name);
74
+ });
75
+ });
76
+ });
58
77
  });
59
78
  describe("Order of operations", () => {
60
79
  it("should debounce updates properly", async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"zone.spec.mjs","sourceRoot":"","sources":["../../src/testing/zone.spec.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"zone.spec.mjs","sourceRoot":"","sources":["../../src/testing/zone.spec.mts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -3,12 +3,13 @@
3
3
  "name": "@digital-alchemy/hass",
4
4
  "repository": "https://github.com/Digital-Alchemy-TS/hass",
5
5
  "homepage": "https://docs.digital-alchemy.app",
6
- "version": "25.3.2",
6
+ "version": "25.5.2",
7
7
  "description": "Typescript APIs for Home Assistant. Includes rest & websocket bindings",
8
8
  "scripts": {
9
9
  "build": "rm -rf dist/; tsc",
10
10
  "lint": "eslint src",
11
11
  "test": "vitest",
12
+ "test:coverage": "vitest run --coverage",
12
13
  "prepublishOnly": "tsc --project ./tsconfig.lib.json",
13
14
  "upgrade": "yarn up '@digital-alchemy/*'"
14
15
  },
@@ -57,50 +58,53 @@
57
58
  },
58
59
  "license": "MIT",
59
60
  "devDependencies": {
60
- "@cspell/eslint-plugin": "^8.17.5",
61
- "@digital-alchemy/core": "^25.2.2",
62
- "@digital-alchemy/synapse": "^25.2.1",
63
- "@digital-alchemy/type-writer": "^25.2.1",
64
- "@eslint/compat": "^1.2.7",
65
- "@eslint/eslintrc": "^3.3.0",
66
- "@eslint/js": "^9.22.0",
67
- "@faker-js/faker": "^9.6.0",
61
+ "@cspell/eslint-plugin": "^9.0.2",
62
+ "@digital-alchemy/core": "^25.5.2",
63
+ "@digital-alchemy/synapse": "^25.3.1",
64
+ "@digital-alchemy/type-writer": "^25.5.1",
65
+ "@eslint/compat": "^1.2.9",
66
+ "@eslint/eslintrc": "^3.3.1",
67
+ "@eslint/js": "^9.28.0",
68
+ "@faker-js/faker": "^9.8.0",
68
69
  "@types/js-yaml": "^4.0.9",
69
- "@types/node": "^22.13.10",
70
+ "@types/node": "^22.15.29",
70
71
  "@types/node-cron": "^3.0.11",
71
- "@types/semver": "^7.5.8",
72
- "@types/ws": "^8.18.0",
73
- "@typescript-eslint/eslint-plugin": "8.27.0",
74
- "@typescript-eslint/parser": "8.27.0",
72
+ "@types/semver": "^7.7.0",
73
+ "@types/ws": "^8.18.1",
74
+ "@typescript-eslint/eslint-plugin": "8.33.0",
75
+ "@typescript-eslint/parser": "8.33.0",
76
+ "@vitest/coverage-v8": "^3.1.4",
75
77
  "dayjs": "^1.11.13",
76
- "dotenv": "^16.4.7",
77
- "eslint": "9.23.0",
78
- "eslint-config-prettier": "10.1.1",
78
+ "dotenv": "^16.5.0",
79
+ "eslint": "9.28.0",
80
+ "eslint-config-prettier": "10.1.5",
79
81
  "eslint-plugin-import": "^2.31.0",
80
- "eslint-plugin-jsonc": "^2.19.1",
82
+ "eslint-plugin-jsonc": "^2.20.1",
81
83
  "eslint-plugin-no-unsanitized": "^4.1.2",
82
- "eslint-plugin-prettier": "^5.2.3",
84
+ "eslint-plugin-prettier": "^5.4.1",
83
85
  "eslint-plugin-security": "^3.0.1",
84
86
  "eslint-plugin-simple-import-sort": "^12.1.1",
85
87
  "eslint-plugin-sonarjs": "^3.0.2",
86
88
  "eslint-plugin-sort-keys-fix": "^1.1.2",
87
- "eslint-plugin-unicorn": "^58.0.0",
88
- "node-cron": "^3.0.3",
89
+ "eslint-plugin-unicorn": "^59.0.1",
90
+ "node-cron": "^4.1.0",
89
91
  "prettier": "^3.5.3",
90
- "semver": "^7.7.1",
91
- "tsx": "^4.19.3",
92
- "typescript": "^5.8.2",
92
+ "semver": "^7.7.2",
93
+ "tsx": "^4.19.4",
94
+ "typescript": "^5.8.3",
93
95
  "uuid": "^11.1.0",
94
- "vitest": "^3.0.8",
95
- "ws": "^8.18.1"
96
+ "vitest": "^3.1.4",
97
+ "ws": "^8.18.2"
98
+ },
99
+ "peerDependencies": {
100
+ "@digital-alchemy/core": "^25.5.1"
96
101
  },
97
102
  "dependencies": {
98
- "@digital-alchemy/core": "^25.2.2",
99
103
  "dayjs": "^1.11.13",
100
- "semver": "^7.7.1",
101
- "type-fest": "^4.37.0",
104
+ "semver": "^7.7.2",
105
+ "type-fest": "^4.41.0",
102
106
  "uuid": "^11.1.0",
103
- "ws": "^8.18.1"
107
+ "ws": "^8.18.2"
104
108
  },
105
- "packageManager": "yarn@4.7.0"
109
+ "packageManager": "yarn@4.9.1"
106
110
  }
@@ -11,6 +11,7 @@ import {
11
11
  FetchAPI,
12
12
  FetchInternals,
13
13
  Floor,
14
+ HassDiagnosticsService,
14
15
  IDByExtension,
15
16
  Label,
16
17
  ReferenceService,
@@ -32,6 +33,18 @@ export const LIB_HASS = CreateLibrary({
32
33
  type: "string",
33
34
  },
34
35
 
36
+ /**
37
+ * Setting this to true will tell hass.diagnostics to create the related channels & start emitting
38
+ */
39
+ EMIT_DIAGNOSTICS: {
40
+ default: false,
41
+ description: [
42
+ "Enable the creation of diagnostics channels",
43
+ "Value read at bootstrap, cannot be set by env or at runtime",
44
+ ],
45
+ type: "boolean",
46
+ },
47
+
35
48
  /**
36
49
  * When adding new integrations, app will receive 1 update event for everything that changes.
37
50
  * This can result in a flood of updates where only a single one is needed at the very end.
@@ -58,6 +71,17 @@ export const LIB_HASS = CreateLibrary({
58
71
  type: "number",
59
72
  },
60
73
 
74
+ /**
75
+ * This is reflected in type-writer, make sure to keep your runtime & types in sync
76
+ *
77
+ * By default disabled entities are removed to help keep file bloat down
78
+ */
79
+ FILTER_DISABLED_ENTITIES_ID_BY: {
80
+ default: true,
81
+ description: "Filter events from disabled entities in id",
82
+ type: "boolean",
83
+ },
84
+
61
85
  /**
62
86
  * General purpose variable, adds delays to things when retrying
63
87
  *
@@ -150,6 +174,11 @@ export const LIB_HASS = CreateLibrary({
150
174
  */
151
175
  device: Device,
152
176
 
177
+ /**
178
+ *
179
+ */
180
+ diagnostics: HassDiagnosticsService,
181
+
153
182
  /**
154
183
  * retrieve and interact with home assistant entities
155
184
  */
@@ -129,7 +129,7 @@ export type FilterValueType = string | boolean | number | Date | RegExp | Record
129
129
  */
130
130
  export enum FILTER_OPERATIONS {
131
131
  // "elemMatch" functionality in mongo
132
- // eslint-disable-next-line unicorn/prevent-abbreviations
132
+
133
133
  elem = "elem",
134
134
  regex = "regex",
135
135
  in = "in",
@@ -98,3 +98,8 @@ export const PostConfigPriorities = {
98
98
  FETCH: 1,
99
99
  VALIDATE: -1,
100
100
  } as const;
101
+
102
+ export const perf = () => {
103
+ const start = performance.now();
104
+ return () => performance.now() - start;
105
+ };
@@ -7,6 +7,7 @@ import {
7
7
  EARLY_ON_READY,
8
8
  ENTITY_REGISTRY_UPDATED,
9
9
  HassAreaService,
10
+ perf,
10
11
  } from "../index.mts";
11
12
  import { ANY_ENTITY, TAreaId } from "../user.mts";
12
13
 
@@ -30,10 +31,12 @@ export function Area({
30
31
  context,
31
32
  event_type: "area_registry_updated",
32
33
  async exec() {
34
+ const ms = perf();
33
35
  await debounce(AREA_REGISTRY_UPDATED, config.hass.EVENT_DEBOUNCE_MS);
34
36
  hass.area.current = await hass.area.list();
35
37
  logger.debug(`area registry updated`);
36
38
  event.emit(AREA_REGISTRY_UPDATED);
39
+ hass.diagnostics.area?.registry_update.publish({ ms: ms() });
37
40
  },
38
41
  });
39
42
  });
@@ -44,6 +47,12 @@ export function Area({
44
47
  });
45
48
  }
46
49
 
50
+ /**
51
+ * 1. emit delete message
52
+ * 2. hass does stuff internally
53
+ * 3. hass emits update message
54
+ * 4. promise resolves
55
+ */
47
56
  async function deleteArea(area_id: TAreaId) {
48
57
  return await new Promise<void>(async done => {
49
58
  event.once(AREA_REGISTRY_UPDATED, done);
@@ -1,6 +1,11 @@
1
1
  import { TServiceParams } from "@digital-alchemy/core";
2
2
 
3
- import { ALL_SERVICE_DOMAINS, PICK_SERVICE, PICK_SERVICE_PARAMETERS } from "../helpers/index.mts";
3
+ import {
4
+ ALL_SERVICE_DOMAINS,
5
+ perf,
6
+ PICK_SERVICE,
7
+ PICK_SERVICE_PARAMETERS,
8
+ } from "../helpers/index.mts";
4
9
  import { iCallService } from "../user.mts";
5
10
 
6
11
  export function CallProxy({
@@ -49,6 +54,7 @@ export function CallProxy({
49
54
  );
50
55
  logger.trace({ name: loadServiceList, services }, `loaded domain [%s]`, value.domain);
51
56
  });
57
+ hass.diagnostics.call?.reload.publish({});
52
58
  }
53
59
 
54
60
  /**
@@ -69,19 +75,20 @@ export function CallProxy({
69
75
  const [domain, service] = serviceName.split(".");
70
76
  // User can just not await this call if they don't care about the "waitForChange"
71
77
 
78
+ const ms = perf();
72
79
  if (!return_response) {
73
- return await hass.socket.sendMessage(
74
- { domain, service, service_data, type: "call_service" },
75
- true,
76
- );
80
+ const payload = { domain, service, service_data, type: "call_service" };
81
+ const out = await hass.socket.sendMessage(payload, true);
82
+ hass.diagnostics.call?.fast.publish({ ms: ms(), payload });
83
+ return out;
77
84
  }
78
- const result = (await hass.socket.sendMessage(
79
- { domain, return_response, service, service_data, type: "call_service" },
80
- true,
81
- )) as { response: unknown };
85
+
86
+ const payload = { domain, return_response, service, service_data, type: "call_service" };
87
+ const result = (await hass.socket.sendMessage(payload, true)) as { response: unknown };
82
88
  if (!result?.response) {
83
89
  logger.warn({ result }, `{%s}.{%s} did not return a response`, domain, service);
84
90
  }
91
+ hass.diagnostics.call?.response.publish({ ms: ms(), payload, result });
85
92
  return result?.response;
86
93
  }
87
94
 
@@ -24,6 +24,9 @@ export function Configure({
24
24
  internal,
25
25
  }: TServiceParams): HassConfigService {
26
26
  const { is } = internal.utils;
27
+ let checkedServices = new Map<string, boolean>();
28
+ let services: HassServiceDTO[];
29
+
27
30
  lifecycle.onPreInit(() => {
28
31
  // HASSIO_TOKEN provided by home assistant to addons
29
32
  // SUPERVISOR_TOKEN used as alias elsewhere
@@ -70,7 +73,6 @@ export function Configure({
70
73
  }
71
74
  }, PostConfigPriorities.VALIDATE);
72
75
 
73
- let services: HassServiceDTO[];
74
76
  async function loadServiceList(recursion = START): Promise<void> {
75
77
  logger.debug({ name: loadServiceList }, `fetching service list`);
76
78
  services = await hass.fetch.listServices();
@@ -85,28 +87,31 @@ export function Configure({
85
87
  recursion,
86
88
  MAX_ATTEMPTS,
87
89
  );
90
+ hass.diagnostics.config?.load_services_failure.publish({ recursion });
88
91
  await sleep(config.hass.RETRY_INTERVAL * SECOND);
89
92
  await loadServiceList(recursion + INCREMENT);
90
- } else {
91
- event.emit(SERVICE_LIST_UPDATED, services);
92
- checkedServices = new Map();
93
+ return;
93
94
  }
95
+ event.emit(SERVICE_LIST_UPDATED, services);
96
+ checkedServices = new Map();
97
+ hass.diagnostics.config?.service_list_updated.publish({ recursion });
98
+ }
99
+
100
+ function isService<DOMAIN extends ALL_SERVICE_DOMAINS>(
101
+ domain: DOMAIN,
102
+ service: string,
103
+ ): service is Extract<keyof iCallService[DOMAIN], string> {
104
+ if (checkedServices.has(service)) {
105
+ return checkedServices.get(service);
106
+ }
107
+ const exists = services.some(i => i.domain === domain && !is.undefined(i.services[service]));
108
+ checkedServices.set(service, exists);
109
+ return exists;
94
110
  }
95
- let checkedServices = new Map<string, boolean>();
96
111
 
97
112
  return {
98
113
  getServices: () => services,
99
- isService: <DOMAIN extends ALL_SERVICE_DOMAINS>(
100
- domain: DOMAIN,
101
- service: string,
102
- ): service is Extract<keyof iCallService[DOMAIN], string> => {
103
- if (checkedServices.has(service)) {
104
- return checkedServices.get(service);
105
- }
106
- const exists = services.some(i => i.domain === domain && !is.undefined(i.services[service]));
107
- checkedServices.set(service, exists);
108
- return exists;
109
- },
114
+ isService,
110
115
  loadServiceList,
111
116
  };
112
117
  }
@@ -5,6 +5,7 @@ import {
5
5
  DeviceDetails,
6
6
  EARLY_ON_READY,
7
7
  HassDeviceService,
8
+ perf,
8
9
  } from "../helpers/index.mts";
9
10
 
10
11
  export function Device({
@@ -28,10 +29,12 @@ export function Device({
28
29
  context,
29
30
  event_type: "device_registry_updated",
30
31
  async exec() {
32
+ const ms = perf();
31
33
  await debounce(DEVICE_REGISTRY_UPDATED, config.hass.EVENT_DEBOUNCE_MS);
32
34
  hass.device.current = await hass.device.list();
33
35
  logger.debug(`device registry updated`);
34
36
  event.emit(DEVICE_REGISTRY_UPDATED);
37
+ hass.diagnostics.device?.registry_update.publish({ ms: ms() });
35
38
  },
36
39
  });
37
40
  });
@@ -0,0 +1,45 @@
1
+ import { TServiceParams } from "@digital-alchemy/core";
2
+ import { channel } from "diagnostics_channel";
3
+
4
+ function createDiagnostics<CHANNEL extends string>(context: string, channels: CHANNEL[]) {
5
+ return Object.fromEntries(channels.map(i => [i, channel(`hass:${context}:${i}`)])) as Record<
6
+ CHANNEL,
7
+ ReturnType<typeof channel>
8
+ >;
9
+ }
10
+ export function HassDiagnosticsService({ config, logger }: TServiceParams) {
11
+ if (!config.hass.EMIT_DIAGNOSTICS) {
12
+ logger.debug("skipping diagnostics channel creation");
13
+ return {};
14
+ }
15
+ logger.info("creating diagnostics channels");
16
+ return {
17
+ area: createDiagnostics("area", ["registry_update"]),
18
+ call: createDiagnostics("call", ["fast", "response", "reload"]),
19
+ config: createDiagnostics("config", ["service_list_updated", "load_services_failure"]),
20
+ device: createDiagnostics("device", ["registry_update"]),
21
+ entity: createDiagnostics("entity", [
22
+ "warn_ready",
23
+ "history_lookup",
24
+ "refresh_entities",
25
+ "entity_updated",
26
+ "registry_updated",
27
+ "entity_remove",
28
+ "entity_add",
29
+ ]),
30
+ fetch: createDiagnostics("fetch", ["fetch"]),
31
+ floor: createDiagnostics("floor", ["registry_update"]),
32
+ label: createDiagnostics("label", ["registry_update"]),
33
+ reference: createDiagnostics("reference", ["create_proxy", "get_property", "call_service"]),
34
+ websocket: createDiagnostics("websocket", [
35
+ "fire_event",
36
+ "message_received",
37
+ "missed_reply",
38
+ "send_message",
39
+ "failed_ping",
40
+ "send_ping",
41
+ "set_connection_state",
42
+ ]),
43
+ zone: createDiagnostics("zone", ["registry_update"]),
44
+ };
45
+ }
@@ -19,6 +19,7 @@ import {
19
19
  EntityHistoryResult,
20
20
  EntityRegistryItem,
21
21
  HassEntityManager,
22
+ perf,
22
23
  TMasterState,
23
24
  } from "../index.mts";
24
25
  import { ALL_DOMAINS, ANY_ENTITY, PICK_ENTITY } from "../user.mts";
@@ -44,6 +45,7 @@ export function EntityManager({
44
45
  const PREVIOUS_STATE = new Map<ANY_ENTITY, ENTITY_STATE<ANY_ENTITY>>();
45
46
  let lastRefresh: Dayjs;
46
47
  function warnEarly(method: string) {
48
+ hass.diagnostics.entity?.warn_ready.publish({ method });
47
49
  if (!init) {
48
50
  lifecycle.onReady(() => {
49
51
  if (config.boilerplate.LOG_LEVEL !== "trace") {
@@ -76,12 +78,14 @@ export function EntityManager({
76
78
  payload: Omit<EntityHistoryDTO<ENTITES>, "type">,
77
79
  ) {
78
80
  logger.trace({ payload }, `looking up entity history`);
81
+ const ms = perf();
79
82
  const result = (await hass.socket.sendMessage({
80
83
  ...payload,
81
84
  end_time: dayjs(payload.end_time).toISOString(),
82
85
  start_time: dayjs(payload.start_time).toISOString(),
83
86
  type: "history/history_during_period",
84
87
  })) as Record<ANY_ENTITY, EntityHistoryItem[]>;
88
+ hass.diagnostics.entity?.history_lookup.publish({ ms: ms(), payload, result });
85
89
 
86
90
  const entities = Object.keys(result) as ANY_ENTITY[];
87
91
  return Object.fromEntries(
@@ -191,6 +195,7 @@ export function EntityManager({
191
195
  internal.utils.object.get(oldState, entity.entity_id),
192
196
  ),
193
197
  );
198
+ hass.diagnostics.entity?.refresh_entities.publish({ emitUpdates });
194
199
  });
195
200
  init = true;
196
201
  }
@@ -201,6 +206,7 @@ export function EntityManager({
201
206
  new_state: ENTITY_STATE<ENTITY>,
202
207
  old_state: ENTITY_STATE<ENTITY>,
203
208
  ) {
209
+ hass.diagnostics.entity?.entity_updated.publish({ entity_id, new_state, old_state });
204
210
  PREVIOUS_STATE.set(entity_id, old_state);
205
211
  if (new_state === null) {
206
212
  logger.warn(
@@ -209,8 +215,12 @@ export function EntityManager({
209
215
  entity_id,
210
216
  );
211
217
  internal.utils.object.del(MASTER_STATE, entity_id);
218
+ hass.diagnostics.entity?.entity_remove.publish({ entity_id });
212
219
  return;
213
220
  }
221
+ if (old_state === null) {
222
+ hass.diagnostics.entity?.entity_add.publish({ entity_id });
223
+ }
214
224
  internal.utils.object.set(MASTER_STATE, entity_id, new_state);
215
225
  if (!hass.socket.pauseMessages) {
216
226
  event.emit(entity_id, new_state, old_state);
@@ -293,10 +303,12 @@ export function EntityManager({
293
303
  context,
294
304
  event_type: "entity_registry_updated",
295
305
  async exec() {
306
+ const ms = perf();
296
307
  await debounce(ENTITY_REGISTRY_UPDATED, config.hass.EVENT_DEBOUNCE_MS);
297
308
  logger.debug("entity registry updated");
298
309
  hass.entity.registry.current = await hass.entity.registry.list();
299
310
  event.emit(ENTITY_REGISTRY_UPDATED);
311
+ hass.diagnostics.entity?.registry_updated.publish({ ms: ms() });
300
312
  },
301
313
  });
302
314
  hass.entity.registry.current = await hass.entity.registry.list();
@@ -13,6 +13,7 @@ import {
13
13
  HassConfig,
14
14
  HassServiceDTO,
15
15
  HomeAssistantServerLogItem,
16
+ perf,
16
17
  PICK_SERVICE,
17
18
  PICK_SERVICE_PARAMETERS,
18
19
  PostConfigPriorities,
@@ -253,6 +254,15 @@ export function FetchAPI({
253
254
  });
254
255
  }
255
256
 
257
+ async function fetch<T, BODY extends TFetchBody = undefined>(
258
+ options: Partial<FetchArguments<BODY>>,
259
+ ) {
260
+ const ms = perf();
261
+ const out = await fetcher.exec<T, BODY>(options);
262
+ hass.diagnostics.fetch?.fetch.publish({ ms: ms(), options, out });
263
+ return out;
264
+ }
265
+
256
266
  return {
257
267
  _fetcher: fetcher,
258
268
  calendarSearch,
@@ -260,8 +270,7 @@ export function FetchAPI({
260
270
  checkConfig,
261
271
  checkCredentials,
262
272
  download,
263
- fetch: async <T, BODY extends TFetchBody = undefined>(options: Partial<FetchArguments<BODY>>) =>
264
- await fetcher.exec<T, BODY>(options),
273
+ fetch,
265
274
  fetchEntityCustomizations,
266
275
  fetchEntityHistory,
267
276
  fireEvent,
@@ -6,6 +6,7 @@ import {
6
6
  FloorCreate,
7
7
  FloorDetails,
8
8
  HassFloorService,
9
+ perf,
9
10
  } from "../helpers/index.mts";
10
11
  import { TFloorId } from "../user.mts";
11
12
 
@@ -29,10 +30,12 @@ export function Floor({
29
30
  context,
30
31
  event_type: "floor_registry_updated",
31
32
  async exec() {
33
+ const ms = perf();
32
34
  await debounce(FLOOR_REGISTRY_UPDATED, config.hass.EVENT_DEBOUNCE_MS);
33
35
  hass.floor.current = await hass.floor.list();
34
36
  logger.debug(`floor registry updated`);
35
37
  event.emit(FLOOR_REGISTRY_UPDATED);
38
+ hass.diagnostics.floor?.registry_update.publish({ ms: ms() });
36
39
  },
37
40
  });
38
41
  });