v0.8.19
This commit is contained in:
+37
-3
@@ -42,6 +42,9 @@ async fn get_group(State(state): State<AppState>, Path(id): Path<String>) -> Res
|
||||
}
|
||||
|
||||
async fn create_group(State(state): State<AppState>, Json(input): Json<GroupInput>) -> Result<(StatusCode, Json<ClimateGroup>), AppError> {
|
||||
let _configuration_guard = state.lock_configuration_operation().await;
|
||||
let _reference_guard = state.lock_automation_operation().await;
|
||||
let _house_guard = state.lock_house_operation().await;
|
||||
let zone_ids = validate_group_input(&state, &input)?;
|
||||
let now = Utc::now();
|
||||
let group = ClimateGroup {
|
||||
@@ -59,6 +62,12 @@ async fn create_group(State(state): State<AppState>, Json(input): Json<GroupInpu
|
||||
}
|
||||
|
||||
async fn update_group(State(state): State<AppState>, Path(id): Path<String>, Json(input): Json<GroupInput>) -> Result<Json<ClimateGroup>, AppError> {
|
||||
let _configuration_guard = state.lock_configuration_operation().await;
|
||||
// Membership changes alter the target set of group automations, so serialize them with
|
||||
// automation execution/reference validation before taking the group lock.
|
||||
let _automation_guard = state.lock_automation_operation().await;
|
||||
let _house_guard = state.lock_house_operation().await;
|
||||
let _group_guard = state.lock_group_operation(&id).await;
|
||||
let existing = state.db.get_group(&id)?.ok_or_else(|| AppError::NotFound(format!("group {id}")))?;
|
||||
let zone_ids = validate_group_input(&state, &input)?;
|
||||
let group = ClimateGroup {
|
||||
@@ -76,6 +85,10 @@ async fn update_group(State(state): State<AppState>, Path(id): Path<String>, Jso
|
||||
}
|
||||
|
||||
async fn delete_group(State(state): State<AppState>, Path(id): Path<String>) -> Result<StatusCode, AppError> {
|
||||
let _configuration_guard = state.lock_configuration_operation().await;
|
||||
let _automation_guard = state.lock_automation_operation().await;
|
||||
let _house_guard = state.lock_house_operation().await;
|
||||
let _group_guard = state.lock_group_operation(&id).await;
|
||||
if state.db.list_automations()?.iter().any(|item| item.action_group_id.as_deref() == Some(id.as_str())) {
|
||||
return Err(AppError::BadRequest("group is used by an automation; remove or retarget that automation first".into()));
|
||||
}
|
||||
@@ -99,9 +112,14 @@ fn ensure_zone_removal_safe(state: &AppState, zone_ids: &std::collections::HashS
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_zone_ids_from_groups(state: &AppState, zone_ids: &std::collections::HashSet<String>) -> Result<(), AppError> {
|
||||
async fn remove_zone_ids_from_groups_locked(state: &AppState, zone_ids: &std::collections::HashSet<String>) -> Result<(), AppError> {
|
||||
if zone_ids.is_empty() { return Ok(()); }
|
||||
for mut group in state.db.list_groups()? {
|
||||
let mut group_ids: Vec<String> = state.db.list_groups()?.into_iter().map(|group| group.id).collect();
|
||||
group_ids.sort();
|
||||
group_ids.dedup();
|
||||
for group_id in group_ids {
|
||||
let _group_guard = state.lock_group_operation(&group_id).await;
|
||||
let Some(mut group) = state.db.get_group(&group_id)? else { continue; };
|
||||
let before = group.zone_ids.len();
|
||||
group.zone_ids.retain(|zone_id| !zone_ids.contains(zone_id));
|
||||
if group.zone_ids.len() == before { continue; }
|
||||
@@ -141,7 +159,7 @@ 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") {
|
||||
if !matches!(current, "auto" | "comfort" | "sleep" | "away" | "custom") {
|
||||
return "mixed".into();
|
||||
}
|
||||
if let Some(previous) = value {
|
||||
@@ -153,6 +171,21 @@ fn home_assistant_group_preset(zones: &[&Zone]) -> String {
|
||||
value.unwrap_or("mixed").to_string()
|
||||
}
|
||||
|
||||
|
||||
fn home_assistant_group_custom_setpoint(zones: &[&Zone]) -> Option<f64> {
|
||||
let mut value: Option<f64> = None;
|
||||
for zone in zones {
|
||||
if zone.manual_preset.as_deref() != Some("custom") { return None; }
|
||||
let current = zone.manual_setpoint.or(zone.effective_setpoint)?;
|
||||
if let Some(previous) = value {
|
||||
if (previous - current).abs() > 0.05 { return None; }
|
||||
} else {
|
||||
value = Some(current);
|
||||
}
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
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()?;
|
||||
@@ -223,6 +256,7 @@ async fn list_home_assistant_groups(State(state): State<AppState>) -> Result<Jso
|
||||
"effective_power": settings.house_power_enabled && group.power_enabled,
|
||||
"mode": home_assistant_group_mode(&members),
|
||||
"preset": home_assistant_group_preset(&members),
|
||||
"custom_setpoint": home_assistant_group_custom_setpoint(&members),
|
||||
"house_mode": settings.house_mode,
|
||||
"zone_count": members.len(),
|
||||
"enabled_zones": members.iter().filter(|zone| zone.enabled).count(),
|
||||
|
||||
Reference in New Issue
Block a user