Skip to content

Commit 0979fac

Browse files
committed
feat(homecore-server): seed 10 default entities on boot (--no-seed-entities to opt out)
Companion to the seed_default_services() commit. Dashboard + States pages now have content on every fresh --db :memory: boot, not just after `bash scripts/homecore-seed.sh`. Adds: - new CLI flag `--no-seed-entities` (default: enabled) - `seed_default_entities(hc)` mirroring the bash script's 10-entity set (4 RuView sensing-derived + 6 conventional HA fixtures) - Boot log: Service registry seeded with 13 default service(s) State machine seeded with 10 default entities Two seeds stay in sync — integrations overwrite the same entity_ids via /api/states/<id> POST. Run with --no-seed-entities when wiring real plugins that populate the state machine themselves. Empirical (after rebuild + fresh restart): GET /api/states → 10 entities GET /api/services → 6 domains, 13 services homecore-server --db :memory: is now enough for the web UI to be fully populated on first paint. Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent 75f984e commit 0979fac

1 file changed

Lines changed: 83 additions & 1 deletion

File tree

  • v2/crates/homecore-server/src

v2/crates/homecore-server/src/main.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use anyhow::Result;
2525
use clap::Parser;
2626
use tracing::{info, warn};
2727

28-
use homecore::{HomeCore, ServiceCall, ServiceError, ServiceName};
28+
use homecore::{Context, EntityId, HomeCore, ServiceCall, ServiceError, ServiceName};
2929
use homecore::service::FnHandler;
3030
use homecore_api::{router, LongLivedTokenStore, SharedState};
3131
use homecore_assist::pipeline::default_pipeline;
@@ -53,6 +53,12 @@ struct Cli {
5353
/// Disable the SQLite recorder for low-resource deployments.
5454
#[arg(long)]
5555
no_recorder: bool,
56+
57+
/// Skip the boot-time entity seeding (10 demo entities including
58+
/// 4 RuView-derived sensors). Use this when wiring real
59+
/// integrations that will populate the state machine themselves.
60+
#[arg(long)]
61+
no_seed_entities: bool,
5662
}
5763

5864
#[tokio::main]
@@ -74,6 +80,16 @@ async fn main() -> Result<()> {
7480
// by registering the same ServiceName later.
7581
seed_default_services(&hc).await;
7682

83+
// Seed 10 representative entities so the web UI's Dashboard +
84+
// States pages have content out of the box. Operators registering
85+
// real integrations / plugins overwrite these by writing the same
86+
// entity_id with new values. Opt out with `--no-seed-entities`.
87+
if !cli.no_seed_entities {
88+
seed_default_entities(&hc);
89+
} else {
90+
info!("Entity seeding disabled by --no-seed-entities");
91+
}
92+
7793
// ── 2. Recorder (optional) ──────────────────────────────────────
7894
if !cli.no_recorder {
7995
match Recorder::open(&cli.db).await {
@@ -209,3 +225,69 @@ async fn seed_default_services(hc: &HomeCore) {
209225
let _ = ServiceError::NotRegistered { domain: String::new(), service: String::new() };
210226
info!("Service registry seeded with {} default service(s)", count);
211227
}
228+
229+
/// Register 10 representative entities so a fresh `--db :memory:`
230+
/// boot has content for the web UI. Mirrors `scripts/homecore-seed.sh`
231+
/// — when both are run the script just overwrites these values, so
232+
/// they stay in sync.
233+
fn seed_default_entities(hc: &HomeCore) {
234+
let entities: Vec<(&str, &str, serde_json::Value)> = vec![
235+
("sensor.living_room_presence", "false", serde_json::json!({
236+
"friendly_name": "Living Room Presence", "device_class": "occupancy",
237+
"source": "RuView ESP32-C6 BFLD"
238+
})),
239+
("sensor.living_room_motion_score", "0.0", serde_json::json!({
240+
"friendly_name": "Living Room Motion Score", "unit_of_measurement": "score",
241+
"icon": "mdi:motion-sensor"
242+
})),
243+
("sensor.bedroom_breathing_rate", "14.5", serde_json::json!({
244+
"friendly_name": "Bedroom Breathing Rate", "unit_of_measurement": "BPM",
245+
"device_class": "frequency", "source": "Seeed MR60BHA2 mmWave"
246+
})),
247+
("sensor.bedroom_heart_rate", "68.0", serde_json::json!({
248+
"friendly_name": "Bedroom Heart Rate", "unit_of_measurement": "BPM",
249+
"device_class": "frequency", "source": "Seeed MR60BHA2 mmWave"
250+
})),
251+
("light.kitchen_ceiling", "on", serde_json::json!({
252+
"friendly_name": "Kitchen Ceiling", "brightness": 230,
253+
"color_temp_kelvin": 4000, "supported_color_modes": ["color_temp"]
254+
})),
255+
("light.living_room_lamp", "off", serde_json::json!({
256+
"friendly_name": "Living Room Lamp", "brightness": 0,
257+
"supported_color_modes": ["brightness"]
258+
})),
259+
("switch.coffee_maker", "off", serde_json::json!({
260+
"friendly_name": "Coffee Maker", "device_class": "outlet"
261+
})),
262+
("binary_sensor.front_door", "off", serde_json::json!({
263+
"friendly_name": "Front Door", "device_class": "door"
264+
})),
265+
("climate.thermostat", "heat", serde_json::json!({
266+
"friendly_name": "Thermostat", "current_temperature": 21.5,
267+
"temperature": 22.0, "hvac_modes": ["off", "heat", "cool", "auto"],
268+
"supported_features": 387
269+
})),
270+
("sensor.air_quality_index", "42", serde_json::json!({
271+
"friendly_name": "Air Quality Index", "unit_of_measurement": "AQI",
272+
"device_class": "aqi"
273+
})),
274+
];
275+
276+
for (id, state, attrs) in entities {
277+
match EntityId::parse(id) {
278+
Ok(eid) => {
279+
hc.states().set(eid, state, attrs, Context::new());
280+
}
281+
Err(e) => warn!("seed_default_entities: bad entity_id {id}: {e}"),
282+
}
283+
}
284+
285+
let _ = ServiceCall {
286+
name: ServiceName::new("homecore", "noop"),
287+
data: serde_json::json!({}),
288+
context: Context::new(),
289+
};
290+
let total = hc.states().all().len();
291+
info!("State machine seeded with {} default entit{}", total,
292+
if total == 1 { "y" } else { "ies" });
293+
}

0 commit comments

Comments
 (0)