This commit is contained in:
Mateusz Gruszczyński
2026-09-14 23:10:17 +02:00
parent 4bb9c8621a
commit 0a2fb8c6c7
47 changed files with 1337 additions and 372 deletions
+70 -1
View File
@@ -1,6 +1,7 @@
struct ConfigurationIds<'a> {
devices: std::collections::HashSet<&'a str>,
zones: std::collections::HashSet<&'a str>,
device_groups: std::collections::HashSet<&'a str>,
schedules: std::collections::HashSet<&'a str>,
automations: std::collections::HashSet<&'a str>,
flows: std::collections::HashSet<&'a str>,
@@ -26,7 +27,7 @@ async fn export_configuration(
fn validate_configuration_header(export: &ConfigurationExport) -> Result<(), AppError> {
if export.format_version != 3 {
return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.0".into()));
return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.1".into()));
}
if export.settings.control_strategy != "setpoint" {
return Err(AppError::BadRequest(
@@ -49,6 +50,7 @@ fn collect_configuration_ids(
let ids = ConfigurationIds {
devices: export.devices.iter().map(|item| item.id.as_str()).collect(),
zones: export.zones.iter().map(|item| item.id.as_str()).collect(),
device_groups: export.device_groups.iter().map(|item| item.id.as_str()).collect(),
schedules: export
.schedules
.iter()
@@ -63,11 +65,13 @@ fn collect_configuration_ids(
};
let duplicate_or_empty = ids.devices.len() != export.devices.len()
|| ids.zones.len() != export.zones.len()
|| ids.device_groups.len() != export.device_groups.len()
|| ids.schedules.len() != export.schedules.len()
|| ids.automations.len() != export.automations.len()
|| ids.flows.len() != export.flows.len()
|| ids.devices.contains("")
|| ids.zones.contains("")
|| ids.device_groups.contains("")
|| ids.schedules.contains("")
|| ids.automations.contains("")
|| ids.flows.contains("");
@@ -154,6 +158,71 @@ fn validate_configuration_devices_and_zones(
));
}
}
let mut installation_devices = std::collections::HashSet::new();
for group in &export.device_groups {
if group.name.trim().is_empty() || group.device_ids.is_empty() {
return Err(AppError::BadRequest(
"import contains an empty split/multisplit installation".into(),
));
}
if group.kind == DeviceGroupKind::Split && group.device_ids.len() != 1 {
return Err(AppError::BadRequest(
"import contains a split installation with more than one unit".into(),
));
}
for device_id in &group.device_ids {
if !ids.devices.contains(device_id.as_str()) {
return Err(AppError::BadRequest(
"import contains an installation referencing a missing device".into(),
));
}
if !installation_devices.insert(device_id.as_str()) {
return Err(AppError::BadRequest(
"import assigns one device to more than one split/multisplit installation".into(),
));
}
}
if group.energy_device_id.as_deref().is_some_and(|id| !group.device_ids.iter().any(|member| member == id)) {
return Err(AppError::BadRequest(
"import contains an installation energy source outside the installation".into(),
));
}
if group.energy_source == EnergySourcePreference::GreeCloud && group.energy_device_id.is_none() {
return Err(AppError::BadRequest(
"import contains a GREE Cloud installation without an energy source device".into(),
));
}
if group.energy_source == EnergySourcePreference::HomeAssistant && group.ha_energy_entity_id.as_deref().map_or(true, |value| value.trim().is_empty()) {
return Err(AppError::BadRequest(
"import contains a Home Assistant installation without an energy entity".into(),
));
}
if let Some(energy_device_id) = group.energy_device_id.as_deref() {
let energy_device = export.devices.iter().find(|device| device.id == energy_device_id).ok_or_else(|| {
AppError::BadRequest("import contains an installation energy source referencing a missing device".into())
})?;
if energy_device.connection_type != ConnectionType::GreeCloud || !energy_device.capabilities.energy_meter {
return Err(AppError::BadRequest(
"import contains an installation energy source without a GREE Cloud energy meter".into(),
));
}
}
if group.ha_energy_entity_id.as_deref().is_some_and(|value| !value.trim().is_empty()) {
if group.ha_energy_device_class.as_deref() != Some("energy")
|| !matches!(group.ha_energy_state_class.as_deref(), Some("total" | "total_increasing"))
|| !matches!(group.ha_energy_unit.as_deref().map(str::to_ascii_lowercase).as_deref(), Some("wh" | "kwh"))
{
return Err(AppError::BadRequest(
"import contains an invalid Home Assistant cumulative energy entity".into(),
));
}
}
if group.outdoor_temperature_device_id.as_deref().is_some_and(|id| !group.device_ids.iter().any(|member| member == id)) {
return Err(AppError::BadRequest(
"import contains an installation outdoor-temperature source outside the installation".into(),
));
}
}
Ok(())
}