feat(homecore-server): wire HAP runtime and registry APIs

This commit is contained in:
ruv
2026-07-27 14:41:25 -04:00
parent 5b5c7f323d
commit fbd5cfa242
8 changed files with 337 additions and 30 deletions
+6 -1
View File
@@ -382,7 +382,12 @@ pub async fn compatibility(
"events": "implemented",
"render_template": "implemented",
"feature_negotiation_and_panels": "implemented",
"registries": "requires_registry_backend",
"registry_lists": {
"entity": "implemented",
"device": "implemented",
"area": "implemented_empty",
"mutations": "requires_persistent_registry_backend"
},
"lovelace_media": "integration_dependent"
}
})))
+18
View File
@@ -284,6 +284,24 @@ impl Connection {
let payload = serde_json::to_value(by_domain).unwrap();
self.ack(tx, cmd.id, true, Some(payload));
}
"config/entity_registry/list" | "get_entity_registry" => {
let entries = self.state.homecore().entities().all().await;
let payload =
serde_json::to_value(entries).unwrap_or_else(|_| serde_json::json!([]));
self.ack(tx, cmd.id, true, Some(payload));
}
"config/device_registry/list" | "get_device_registry" => {
let entries = self.state.homecore().devices().all().await;
let payload =
serde_json::to_value(entries).unwrap_or_else(|_| serde_json::json!([]));
self.ack(tx, cmd.id, true, Some(payload));
}
"config/area_registry/list" | "get_area_registry" => {
// HOMECORE does not yet model named areas. Returning the valid
// empty-list shape lets clients distinguish that from an
// unsupported command.
self.ack(tx, cmd.id, true, Some(serde_json::json!([])));
}
"call_service" => {
let (Some(domain), Some(service)) = (cmd.domain.clone(), cmd.service.clone())
else {
@@ -338,3 +338,52 @@ async fn real_state_change_uses_client_subscription_id() {
assert_eq!(unsub["id"], 42);
assert_eq!(unsub["success"], true);
}
#[tokio::test]
async fn registry_list_commands_return_ha_result_shapes() {
use homecore::{EntityEntry, EntityId};
let (addr, hc) = spawn_server_returning_homecore("good_token_abc").await;
hc.entities()
.register(EntityEntry {
entity_id: EntityId::parse("light.registry_probe").unwrap(),
unique_id: Some("probe-1".into()),
platform: "test".into(),
name: Some("Registry probe".into()),
disabled_by: None,
area_id: None,
device_id: None,
entity_category: None,
config_entry_id: None,
})
.await;
let url = format!("ws://{addr}/api/websocket");
let (mut ws, _response) = connect_async(&url).await.unwrap();
let _ = next_json(&mut ws).await;
ws.send(Message::Text(
serde_json::json!({"type":"auth","access_token":"good_token_abc"}).to_string(),
))
.await
.unwrap();
let _ = next_json(&mut ws).await;
for (id, command) in [
(71, "config/entity_registry/list"),
(72, "config/device_registry/list"),
(73, "config/area_registry/list"),
] {
ws.send(Message::Text(
serde_json::json!({"id": id, "type": command}).to_string(),
))
.await
.unwrap();
let reply = next_json(&mut ws).await;
assert_eq!(reply["id"], id);
assert_eq!(reply["success"], true);
assert!(reply["result"].is_array());
if command == "config/entity_registry/list" {
assert_eq!(reply["result"][0]["entity_id"], "light.registry_probe");
}
}
}