v0.7.2
This commit is contained in:
+123
@@ -80,6 +80,8 @@ pub fn router(state: AppState) -> Router {
|
||||
.route("/api/integrations/home-assistant/devices", get(list_devices))
|
||||
.route("/api/integrations/home-assistant/devices/:id/command", post(command_device))
|
||||
.route("/api/integrations/home-assistant/control-plan", get(control_plan))
|
||||
.route("/api/integrations/home-assistant/groups", get(list_home_assistant_groups))
|
||||
.route("/api/integrations/home-assistant/groups/:id/control", post(update_home_assistant_group_control))
|
||||
.route("/api/integrations/home-assistant/house/control", post(update_house_control))
|
||||
.route("/api/integrations/home-assistant/house/preset", post(update_house_preset))
|
||||
.route("/api/integrations/home-assistant/house/power", post(update_house_power))
|
||||
@@ -737,6 +739,127 @@ async fn update_group_control(State(state): State<AppState>, Path(id): Path<Stri
|
||||
Ok(Json(engine::control_group(&state, &id, patch, "group.quick_control").await?))
|
||||
}
|
||||
|
||||
fn home_assistant_group_mode(zones: &[&Zone]) -> String {
|
||||
let mut value: Option<&str> = None;
|
||||
for zone in zones {
|
||||
let current = if zone.inherit_house_mode { "house" } else { zone.mode.as_str() };
|
||||
if !matches!(current, "house" | "cool" | "heat") {
|
||||
return "mixed".into();
|
||||
}
|
||||
if let Some(previous) = value {
|
||||
if previous != current { return "mixed".into(); }
|
||||
} else {
|
||||
value = Some(current);
|
||||
}
|
||||
}
|
||||
value.unwrap_or("mixed").to_string()
|
||||
}
|
||||
|
||||
fn home_assistant_group_preset(zones: &[&Zone]) -> String {
|
||||
let mut value: Option<&str> = None;
|
||||
for zone in zones {
|
||||
let current = zone.manual_preset.as_deref().unwrap_or("auto");
|
||||
if !matches!(current, "auto" | "comfort" | "sleep" | "away") {
|
||||
return "mixed".into();
|
||||
}
|
||||
if let Some(previous) = value {
|
||||
if previous != current { return "mixed".into(); }
|
||||
} else {
|
||||
value = Some(current);
|
||||
}
|
||||
}
|
||||
value.unwrap_or("mixed").to_string()
|
||||
}
|
||||
|
||||
async fn list_home_assistant_groups(State(state): State<AppState>) -> Result<Json<Vec<Value>>, AppError> {
|
||||
let groups = state.db.list_groups()?;
|
||||
let zones = state.db.list_zones()?;
|
||||
let devices = state.db.list_devices()?;
|
||||
let plan = engine::build_control_plan(&state).await?;
|
||||
let settings = state.settings.read().await.clone();
|
||||
let mut output = Vec::with_capacity(groups.len());
|
||||
|
||||
for group in groups {
|
||||
let members = zones.iter()
|
||||
.filter(|zone| group.zone_ids.iter().any(|zone_id| zone_id == &zone.id))
|
||||
.collect::<Vec<_>>();
|
||||
let planned_members = plan.zones.iter()
|
||||
.filter(|zone| group.zone_ids.iter().any(|zone_id| zone_id == &zone.zone_id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let zone_names = members.iter().map(|zone| zone.name.clone()).collect::<Vec<_>>();
|
||||
let member_device_ids = members.iter().map(|zone| zone.device_id.as_str()).collect::<std::collections::HashSet<_>>();
|
||||
let online_devices = devices.iter()
|
||||
.filter(|device| member_device_ids.contains(device.id.as_str()) && device.online)
|
||||
.count();
|
||||
let current_temperatures = planned_members.iter()
|
||||
.filter_map(|zone| zone.current_temperature)
|
||||
.collect::<Vec<_>>();
|
||||
let current_temperature = if current_temperatures.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(current_temperatures.iter().sum::<f64>() / current_temperatures.len() as f64)
|
||||
};
|
||||
let mut next_events = Vec::new();
|
||||
for zone in &planned_members {
|
||||
for event in &zone.next_events {
|
||||
let mut event = event.clone();
|
||||
event.label = format!("{}: {}", zone.zone_name, event.label);
|
||||
next_events.push(event);
|
||||
}
|
||||
}
|
||||
next_events.sort_by_key(|event| event.at);
|
||||
next_events.truncate(8);
|
||||
let member_states = planned_members.iter().map(|zone| json!({
|
||||
"zone_id": zone.zone_id,
|
||||
"zone_name": zone.zone_name,
|
||||
"device_id": zone.device_id,
|
||||
"device_name": zone.device_name,
|
||||
"enabled": zone.enabled,
|
||||
"mode": zone.mode,
|
||||
"configured_mode": zone.configured_mode,
|
||||
"inherit_house_mode": zone.inherit_house_mode,
|
||||
"preset": zone.preset,
|
||||
"current_temperature": zone.current_temperature,
|
||||
"target_temperature": zone.target_temperature,
|
||||
"demand": zone.demand,
|
||||
"control_source": zone.control_source,
|
||||
"current_schedule": zone.current_schedule_name,
|
||||
})).collect::<Vec<_>>();
|
||||
|
||||
output.push(json!({
|
||||
"id": group.id,
|
||||
"name": group.name,
|
||||
"zone_ids": group.zone_ids,
|
||||
"zone_names": zone_names,
|
||||
"power_enabled": group.power_enabled,
|
||||
"effective_power": settings.house_power_enabled && group.power_enabled,
|
||||
"mode": home_assistant_group_mode(&members),
|
||||
"preset": home_assistant_group_preset(&members),
|
||||
"house_mode": settings.house_mode,
|
||||
"zone_count": members.len(),
|
||||
"enabled_zones": members.iter().filter(|zone| zone.enabled).count(),
|
||||
"active_zones": planned_members.iter().filter(|zone| zone.enabled).count(),
|
||||
"demanding_zones": planned_members.iter().filter(|zone| zone.demand).count(),
|
||||
"device_count": member_device_ids.len(),
|
||||
"online_devices": online_devices,
|
||||
"current_temperature": current_temperature,
|
||||
"members": member_states,
|
||||
"next_events": next_events,
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(Json(output))
|
||||
}
|
||||
|
||||
async fn update_home_assistant_group_control(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<String>,
|
||||
Json(patch): Json<GroupControlPatch>,
|
||||
) -> Result<Json<Value>, AppError> {
|
||||
Ok(Json(engine::control_group(&state, &id, patch, "home_assistant.group_control").await?))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct HouseControlPatch { mode: String }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user