This commit is contained in:
Mateusz Gruszczyński
2026-09-15 09:04:29 +02:00
parent 0a2fb8c6c7
commit 2ff310bdc5
37 changed files with 731 additions and 141 deletions
+1 -1
View File
@@ -27,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.1".into()));
return Err(AppError::BadRequest("unsupported configuration export version; version 3 is required by GREE Controller 0.14.2".into()));
}
if export.settings.control_strategy != "setpoint" {
return Err(AppError::BadRequest(
+5 -1
View File
@@ -1107,7 +1107,9 @@ fn compile_flow(
&& native_time_range
&& preset_for_schedule != "auto"
&& mode_for_schedule == "auto"
&& flow_bool(&action_node.config, "power").is_none();
&& flow_bool(&action_node.config, "power").is_none()
&& flow_bool(&action_node.config, "swing_vertical").is_none()
&& flow_bool(&action_node.config, "swing_horizontal").is_none();
if schedule_only {
let zone_id = flow_string(&action_node.config, "zone_id")
.ok_or_else(|| AppError::BadRequest("thermostat block needs a zone".into()))?;
@@ -1285,6 +1287,8 @@ fn compile_flow(
}
item.action.mode = Some(mode);
}
item.action.swing_vertical = flow_bool(&action_node.config, "swing_vertical");
item.action.swing_horizontal = flow_bool(&action_node.config, "swing_horizontal");
}
"device_action" => {
let id = flow_string(&action_node.config, "device_id")
+78 -1
View File
@@ -20,12 +20,81 @@ struct SystemInfoResponse {
gree_received_frames_by_device: std::collections::HashMap<String, u64>,
}
#[derive(Debug, Clone, serde::Serialize)]
struct DeviceGroupEnergySnapshot {
total_kwh: f64,
timestamp: Option<chrono::DateTime<Utc>>,
source: String,
origin: String,
}
fn device_group_energy_snapshot(
state: &AppState,
group: &DeviceGroup,
devices: &[Device],
) -> Result<Option<DeviceGroupEnergySnapshot>, AppError> {
let selected_source = match group.energy_source {
EnergySourcePreference::GreeCloud => Some("gree_cloud"),
EnergySourcePreference::HomeAssistant => Some("home_assistant"),
EnergySourcePreference::Auto if group.energy_device_id.is_some() => Some("gree_cloud"),
EnergySourcePreference::Auto if group.ha_energy_entity_id.is_some() => Some("home_assistant"),
EnergySourcePreference::Auto => None,
};
match selected_source {
Some("gree_cloud") => {
let Some(device_id) = group.energy_device_id.as_deref() else {
return Ok(None);
};
if let Some(device) = devices.iter().find(|device| device.id == device_id) {
if let Some(total_kwh) = device
.total_energy_kwh
.filter(|value| value.is_finite() && *value >= 0.0)
{
return Ok(Some(DeviceGroupEnergySnapshot {
total_kwh,
timestamp: device
.last_cloud_sync
.clone()
.or_else(|| device.last_seen.clone()),
source: "gree_cloud".into(),
origin: "cloud".into(),
}));
}
}
Ok(state
.db
.last_energy_reading(device_id, "gree_cloud")?
.map(|reading| DeviceGroupEnergySnapshot {
total_kwh: reading.normalized_meter_kwh,
timestamp: Some(reading.timestamp),
source: "gree_cloud".into(),
origin: "database".into(),
}))
}
Some("home_assistant") => {
let storage_id = format!("group:{}", group.id);
Ok(state
.db
.last_energy_reading(&storage_id, "home_assistant")?
.map(|reading| DeviceGroupEnergySnapshot {
total_kwh: reading.normalized_meter_kwh,
timestamp: Some(reading.timestamp),
source: "home_assistant".into(),
origin: "database".into(),
}))
}
_ => Ok(None),
}
}
#[derive(Debug, serde::Serialize)]
struct BootstrapResponse {
devices: Vec<Device>,
zones: Vec<Zone>,
groups: Vec<ClimateGroup>,
device_groups: Vec<DeviceGroup>,
device_group_energy: std::collections::HashMap<String, DeviceGroupEnergySnapshot>,
schedules: Vec<Schedule>,
automations: Vec<Automation>,
flows: Vec<Flow>,
@@ -59,6 +128,13 @@ async fn build_bootstrap(state: &AppState) -> Result<BootstrapResponse, AppError
(settings_snapshot(&settings), settings.house_mode.clone())
};
let devices = state.db.list_devices()?;
let device_groups = state.db.list_device_groups()?;
let mut device_group_energy = std::collections::HashMap::new();
for group in &device_groups {
if let Some(snapshot) = device_group_energy_snapshot(state, group, &devices)? {
device_group_energy.insert(group.id.clone(), snapshot);
}
}
let system = build_system_info(state, &devices);
let control_plan = engine::get_control_plan_snapshot(state).await?;
@@ -66,7 +142,8 @@ async fn build_bootstrap(state: &AppState) -> Result<BootstrapResponse, AppError
devices,
zones: state.db.list_zones()?,
groups: state.db.list_groups()?,
device_groups: state.db.list_device_groups()?,
device_groups,
device_group_energy,
schedules: state.db.list_schedules()?,
automations: state.db.list_automations()?,
flows: state.db.list_flows()?,