Commit cebbe00
authored
feat(home-assistant): add REST coverage for config CRUD, history, and calendars (#239)
## Summary
- add automation, script, and scene config CRUD, reached over REST at
`/api/config/<component>/config/<key>`
- add history, logbook, calendar, and error log actions from Home
Assistant's published REST API
- fix `buildHomeAssistantUrl` dropping the instance base path
Fifteen actions in total, all REST. No WebSocket code is touched.
## Problem
Two separate gaps, both in the REST surface.
**The editable config store was missing entirely.** Home Assistant
serves automation, script, and scene configuration over REST, but at
`/api/config/<component>/config/<key>` rather than under the endpoints
listed in the published REST API reference. The provider could therefore
run an automation but never write one. That endpoint family is absent
from the reference page, which is the most likely reason it was never
covered — the eight actions the provider started with map exactly onto
that page and nothing outside it.
**There was no time dimension.** `/api/states` answers "what is the
temperature now"; nothing answered "how did it get there" or "what
tripped that switch overnight". `history`, `logbook`, `calendars`, and
`error_log` are all on the published reference page and were simply
never implemented.
## Config CRUD
`automation`, `script`, and `scene` differ only in three respects: the
path segment, the input field carrying the key, and whether Home
Assistant validates that key as a slug (`cv.slug` for script,
`cv.string` for automation and scene). One descriptor captures those
differences and parameterises shared read/write/delete handlers, rather
than nine near-identical copies.
The key travels in the path and Home Assistant injects it into the
stored entry itself, so the request body is the bare config object.
`POST` is create-or-update: writing to an unused key creates the entry,
which is how the UI creates automations.
Both endpoints require an admin token, and they only see entries Home
Assistant manages itself — an automation defined in a separate YAML file
or a package is not visible and returns not found. The action
descriptions say so, because a non-admin token fails at the provider
rather than at input validation.
This pairs with `validate_config` from #227 to close the loop an agent
needs for automation work: discover what a device can do, validate a
candidate config, write it, then read the logbook to confirm it fired.
`followUpActions` wires that sequence into the catalog so it is
discoverable rather than something the caller has to know.
## History and diagnostics
`get_history` exposes `minimalResponse` and `noAttributes` because a
full-attribute history across several entities is large enough to matter
for an agent's context budget. Against a real instance, one entity over
24 hours returned 7219 points; the compact form drops each row from the
full state object to `{"state", "last_changed"}`.
Home Assistant reads `minimal_response`, `no_attributes`, and
`skip_initial_state` by presence rather than by value, so `false` omits
the parameter instead of sending `0`. `significant_changes_only` is read
by value and defaults to true, so it is encoded normally.
`get_error_log` needs one caveat: Home Assistant registers that view
only when the instance runs with file logging, so it can report not
found on an otherwise healthy instance. Rather than surface a bare "Not
Found", the handler maps that case to a message explaining why, and the
action description repeats it.
## Base path fix
`normalizeBaseUrl` accepts an instance URL with a path, so a Home
Assistant behind a reverse proxy at `/ha` is a supported configuration.
`buildHomeAssistantUrl` assigned the endpoint path over `url.pathname`,
which dropped that prefix and sent every request to `/api/...` at the
origin instead of `/ha/api/...`.
It now appends to the base path. The WebSocket URL builder added in #227
already did this, so the two transports agree. This is a separate
commit; it predates both PRs, and the fifteen new paths here would have
multiplied it.
## Actions
| Action | Endpoint |
| --- | --- |
| `get_automation_config` / `save_automation_config` /
`delete_automation_config` | `GET`/`POST`/`DELETE
/api/config/automation/config/{id}` |
| `get_script_config` / `save_script_config` / `delete_script_config` |
`GET`/`POST`/`DELETE /api/config/script/config/{key}` |
| `get_scene_config` / `save_scene_config` / `delete_scene_config` |
`GET`/`POST`/`DELETE /api/config/scene/config/{id}` |
| `check_config` | `POST /api/config/core/check_config` |
| `get_history` | `GET /api/history/period/{timestamp}` |
| `get_logbook` | `GET /api/logbook/{timestamp}` |
| `list_calendars` | `GET /api/calendars` |
| `list_calendar_events` | `GET /api/calendars/{entity_id}` |
| `get_error_log` | `GET /api/error_log` |
Reference: https://developers.home-assistant.io/docs/api/rest
## Verification
- `npm run fix-check`, `npm test` (584 passing)
- read paths against a real instance: `check_config` returned valid;
`get_history` returned 7219 points for one entity over 24 hours with the
compact form visibly applied; `get_logbook` returned 4417 entries;
`list_calendars` returned an empty list on an instance with no calendar
integration; `get_error_log` returned the explanatory 404 described
above
- write paths against the same instance, creating and then removing an
`oc_smoke_test` entry in each domain: create returned `ok`, the
read-back showed the key injected into the stored entry, delete returned
`ok`, and each subsequent read reported not found. A non-slug script key
was rejected at the provider before any request. A following scan of
3830 entities found nothing left behind.1 parent af9090f commit cebbe00
4 files changed
Lines changed: 542 additions & 30 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
25 | 35 | | |
26 | 36 | | |
27 | 37 | | |
| |||
69 | 79 | | |
70 | 80 | | |
71 | 81 | | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
72 | 113 | | |
73 | 114 | | |
74 | 115 | | |
| |||
177 | 218 | | |
178 | 219 | | |
179 | 220 | | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
180 | 340 | | |
181 | 341 | | |
182 | 342 | | |
| |||
229 | 389 | | |
230 | 390 | | |
231 | 391 | | |
| 392 | + | |
232 | 393 | | |
233 | 394 | | |
234 | 395 | | |
| |||
272 | 433 | | |
273 | 434 | | |
274 | 435 | | |
| 436 | + | |
275 | 437 | | |
276 | 438 | | |
277 | 439 | | |
| |||
293 | 455 | | |
294 | 456 | | |
295 | 457 | | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
296 | 544 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| |||
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
17 | | - | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
18 | 23 | | |
19 | 24 | | |
20 | 25 | | |
| |||
0 commit comments