This commit is contained in:
Mateusz Gruszczyński
2026-08-24 14:05:43 +02:00
parent eb02f66056
commit 04fc91b9f4
30 changed files with 2257 additions and 175 deletions
+159
View File
@@ -26,6 +26,10 @@ fn default_cool_away() -> f64 { 27.0 }
fn default_heat_comfort() -> f64 { 21.0 }
fn default_heat_sleep() -> f64 { 19.0 }
fn default_heat_away() -> f64 { 17.0 }
fn default_history_retention_days() -> u32 { 30 }
fn default_influx_version() -> String { "2".into() }
fn default_influx_database() -> String { "gree_controller".into() }
fn default_influx_threshold_days() -> u32 { 30 }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Device {
@@ -148,6 +152,27 @@ pub struct DeviceCommand {
}
impl DeviceCommand {
pub fn is_empty(&self) -> bool {
self.power.is_none() && self.mode.is_none() && self.target_temperature.is_none()
&& self.fan_speed.is_none() && self.swing_vertical.is_none() && self.swing_horizontal.is_none()
&& self.quiet.is_none() && self.turbo.is_none() && self.light.is_none()
}
/// Return only fields that differ from the last known device state.
pub fn changed_from(&self, device: &Device) -> Self {
Self {
power: self.power.filter(|value| *value != device.power),
mode: self.mode.as_ref().filter(|value| value.as_str() != device.mode.as_str()).cloned(),
target_temperature: self.target_temperature.filter(|value| value.clamp(8.0, 30.0).round() != device.target_temperature.clamp(8.0, 30.0).round()),
fan_speed: self.fan_speed.filter(|value| (*value).min(5) != device.fan_speed),
swing_vertical: self.swing_vertical.filter(|value| *value != device.swing_vertical),
swing_horizontal: self.swing_horizontal.filter(|value| *value != device.swing_horizontal),
quiet: self.quiet.filter(|value| *value != device.quiet),
turbo: self.turbo.filter(|value| *value != device.turbo),
light: self.light.filter(|value| *value != device.light),
}
}
pub fn apply(&self, device: &mut Device) {
if let Some(v) = self.power { device.power = v; }
if let Some(v) = &self.mode { device.mode = v.clone(); }
@@ -386,6 +411,128 @@ pub struct HomeAssistantSettings {
pub allow_invalid_tls: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InfluxDbSettings {
#[serde(default)]
pub enabled: bool,
/// InfluxDB API generation: `1` or `2`.
#[serde(default = "default_influx_version")]
pub version: String,
#[serde(default)]
pub url: String,
/// InfluxDB 1.x database name.
#[serde(default = "default_influx_database")]
pub database: String,
#[serde(default)]
pub username: String,
#[serde(default)]
pub password: String,
/// InfluxDB 2.x organization.
#[serde(default)]
pub org: String,
/// InfluxDB 2.x bucket.
#[serde(default = "default_influx_database")]
pub bucket: String,
#[serde(default)]
pub token: String,
/// Queries older than this age are read from InfluxDB when it is enabled.
#[serde(default = "default_influx_threshold_days")]
pub history_threshold_days: u32,
}
impl Default for InfluxDbSettings {
fn default() -> Self {
Self {
enabled: false,
version: default_influx_version(),
url: String::new(),
database: default_influx_database(),
username: String::new(),
password: String::new(),
org: String::new(),
bucket: default_influx_database(),
token: String::new(),
history_threshold_days: default_influx_threshold_days(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DebugSettings {
#[serde(default)]
pub overlay_enabled: bool,
#[serde(default)]
pub gree_frames: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigurationExport {
pub format_version: u32,
pub exported_at: DateTime<Utc>,
pub settings: RuntimeSettings,
pub devices: Vec<Device>,
pub zones: Vec<Zone>,
pub schedules: Vec<Schedule>,
pub automations: Vec<Automation>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ControlPlanEvent {
pub at: DateTime<Utc>,
pub kind: String,
pub label: String,
pub preset: Option<String>,
pub target_temperature: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ZoneControlPlan {
pub zone_id: String,
pub zone_name: String,
pub device_id: String,
pub device_name: String,
pub enabled: bool,
pub mode: String,
pub preset: String,
pub current_temperature: Option<f64>,
pub target_temperature: Option<f64>,
pub device_setpoint: Option<f64>,
pub demand: bool,
pub control_source: String,
pub manual_override_until: Option<DateTime<Utc>>,
pub current_schedule_id: Option<String>,
pub current_schedule_name: Option<String>,
pub next_events: Vec<ControlPlanEvent>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutomationPlanRule {
pub id: String,
pub name: String,
pub enabled: bool,
pub trigger_kind: String,
pub trigger_device_id: Option<String>,
pub trigger_device_name: Option<String>,
pub threshold: Option<f64>,
pub at_time: Option<String>,
pub action_device_id: String,
pub action_device_name: String,
pub action: DeviceCommand,
pub last_fired_at: Option<DateTime<Utc>>,
pub next_ready_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ControlPlan {
pub generated_at: DateTime<Utc>,
pub house_mode: String,
pub outdoor_temperature: Option<f64>,
pub control_strategy: String,
pub next_events: Vec<ControlPlanEvent>,
pub zones: Vec<ZoneControlPlan>,
pub rules: Vec<AutomationPlanRule>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeSettings {
pub controller_id: String,
@@ -402,6 +549,18 @@ pub struct RuntimeSettings {
pub control_strategy: String,
#[serde(default = "default_true")]
pub outdoor_assist_enabled: bool,
/// Keep recent metrics locally; older history may live in InfluxDB.
#[serde(default = "default_history_retention_days")]
pub history_retention_days: u32,
#[serde(default = "default_true")]
pub history_compaction_enabled: bool,
/// Add protocol-specific buzzer suppression fields to command frames.
#[serde(default)]
pub suppress_device_beep: bool,
#[serde(default)]
pub influxdb: InfluxDbSettings,
#[serde(default)]
pub debug: DebugSettings,
pub home_assistant: HomeAssistantSettings,
}