@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
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAE9C,OAAO,EAAE,qBAAqB,EAAe,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;IACpB,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,MAAM,cAAc,CAAC,QAAQ,EAAE,CAAC;QAChC,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG;QACnB,IAAI,EAAE,EAAE;
|
|
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.
|
|
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": "^
|
|
61
|
-
"@digital-alchemy/core": "^25.
|
|
62
|
-
"@digital-alchemy/synapse": "^25.
|
|
63
|
-
"@digital-alchemy/type-writer": "^25.
|
|
64
|
-
"@eslint/compat": "^1.2.
|
|
65
|
-
"@eslint/eslintrc": "^3.3.
|
|
66
|
-
"@eslint/js": "^9.
|
|
67
|
-
"@faker-js/faker": "^9.
|
|
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.
|
|
70
|
+
"@types/node": "^22.15.29",
|
|
70
71
|
"@types/node-cron": "^3.0.11",
|
|
71
|
-
"@types/semver": "^7.
|
|
72
|
-
"@types/ws": "^8.18.
|
|
73
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
74
|
-
"@typescript-eslint/parser": "8.
|
|
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.
|
|
77
|
-
"eslint": "9.
|
|
78
|
-
"eslint-config-prettier": "10.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.
|
|
82
|
+
"eslint-plugin-jsonc": "^2.20.1",
|
|
81
83
|
"eslint-plugin-no-unsanitized": "^4.1.2",
|
|
82
|
-
"eslint-plugin-prettier": "^5.
|
|
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": "^
|
|
88
|
-
"node-cron": "^
|
|
89
|
+
"eslint-plugin-unicorn": "^59.0.1",
|
|
90
|
+
"node-cron": "^4.1.0",
|
|
89
91
|
"prettier": "^3.5.3",
|
|
90
|
-
"semver": "^7.7.
|
|
91
|
-
"tsx": "^4.19.
|
|
92
|
-
"typescript": "^5.8.
|
|
92
|
+
"semver": "^7.7.2",
|
|
93
|
+
"tsx": "^4.19.4",
|
|
94
|
+
"typescript": "^5.8.3",
|
|
93
95
|
"uuid": "^11.1.0",
|
|
94
|
-
"vitest": "^3.
|
|
95
|
-
"ws": "^8.18.
|
|
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.
|
|
101
|
-
"type-fest": "^4.
|
|
104
|
+
"semver": "^7.7.2",
|
|
105
|
+
"type-fest": "^4.41.0",
|
|
102
106
|
"uuid": "^11.1.0",
|
|
103
|
-
"ws": "^8.18.
|
|
107
|
+
"ws": "^8.18.2"
|
|
104
108
|
},
|
|
105
|
-
"packageManager": "yarn@4.
|
|
109
|
+
"packageManager": "yarn@4.9.1"
|
|
106
110
|
}
|
package/src/hass.module.mts
CHANGED
|
@@ -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
|
*/
|
package/src/helpers/fetch.mts
CHANGED
|
@@ -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
|
-
|
|
132
|
+
|
|
133
133
|
elem = "elem",
|
|
134
134
|
regex = "regex",
|
|
135
135
|
in = "in",
|
package/src/helpers/utility.mts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
});
|