Files
gree-controller/src/db/climate.rs
T
2026-09-17 08:52:02 +02:00

138 lines
4.6 KiB
Rust

impl Db {
pub fn save_zone(&self, zone: &Zone) -> Result<()> {
let payload = Self::to_json(zone)?;
let conn = self.lock()?;
conn.execute(
queries::UPSERT_ZONE,
params![zone.id, payload, zone.updated_at.to_rfc3339()],
)?;
Ok(())
}
pub fn list_zones(&self) -> Result<Vec<Zone>> {
self.list_payloads(queries::LIST_ZONES)
}
pub fn get_zone(&self, id: &str) -> Result<Option<Zone>> {
self.get_payload(queries::GET_ZONE, id)
}
pub fn delete_zone(&self, id: &str) -> Result<bool> {
let mut conn = self.lock()?;
let tx = conn.transaction()?;
tx.execute(queries::DELETE_SCHEDULES_BY_ZONE_ID, [id])?;
tx.execute(queries::DELETE_ZONE_READINGS_BY_ZONE_ID, [id])?;
let changed = tx.execute(queries::DELETE_ZONE, [id])? > 0;
tx.commit()?;
Ok(changed)
}
pub fn save_group(&self, group: &ClimateGroup) -> Result<()> {
let payload = Self::to_json(group)?;
let conn = self.lock()?;
conn.execute(
queries::UPSERT_GROUP,
params![group.id, payload, group.updated_at.to_rfc3339()],
)?;
Ok(())
}
pub fn list_groups(&self) -> Result<Vec<ClimateGroup>> {
self.list_payloads(queries::LIST_GROUPS)
}
pub fn get_group(&self, id: &str) -> Result<Option<ClimateGroup>> {
self.get_payload(queries::GET_GROUP, id)
}
pub fn delete_group(&self, id: &str) -> Result<bool> {
self.delete_by_id("groups", id)
}
pub fn save_device_group(&self, group: &DeviceGroup) -> Result<()> {
let payload = Self::to_json(group)?;
let conn = self.lock()?;
conn.execute(
queries::UPSERT_DEVICE_GROUP,
params![group.id, payload, group.updated_at.to_rfc3339()],
)?;
Ok(())
}
pub fn list_device_groups(&self) -> Result<Vec<DeviceGroup>> {
self.list_payloads(queries::LIST_DEVICE_GROUPS)
}
pub fn get_device_group(&self, id: &str) -> Result<Option<DeviceGroup>> {
self.get_payload(queries::GET_DEVICE_GROUP, id)
}
pub fn device_group_for_device(&self, device_id: &str) -> Result<Option<DeviceGroup>> {
Ok(self
.list_device_groups()?
.into_iter()
.find(|group| group.device_ids.iter().any(|id| id == device_id)))
}
pub fn delete_device_group(&self, id: &str) -> Result<bool> {
self.delete_by_id("device_groups", id)
}
}
impl Db {
/// Merge device transition timestamps into zone JSON without replacing newer zone state.
/// `updated_at` acts as a compare-and-swap token but is intentionally not changed here:
/// thermostat cycles use it to detect real configuration/ownership updates.
pub fn merge_zone_device_transition_timestamps(
&self,
device_id: &str,
power_changed: bool,
mode_changed: bool,
at: DateTime<Utc>,
) -> Result<Vec<Zone>> {
if !power_changed && !mode_changed {
return Ok(Vec::new());
}
let zone_ids: Vec<String> = self
.list_zones()?
.into_iter()
.filter(|zone| zone.device_id == device_id)
.map(|zone| zone.id)
.collect();
let mut updated = Vec::with_capacity(zone_ids.len());
for zone_id in zone_ids {
let mut saved = false;
for _ in 0..8 {
let Some(mut zone) = self.get_zone(&zone_id)? else {
break;
};
let expected_updated_at = zone.updated_at.to_rfc3339();
if power_changed {
zone.last_power_change_at = Some(at);
}
if mode_changed {
zone.last_mode_change_at = Some(at);
}
let payload = Self::to_json(&zone)?;
let conn = self.lock()?;
let changed = conn.execute(
"UPDATE zones SET payload = ?1 WHERE id = ?2 AND updated_at = ?3",
params![payload, zone.id, expected_updated_at],
)?;
drop(conn);
if changed > 0 {
updated.push(zone);
saved = true;
break;
}
}
if !saved && self.get_zone(&zone_id)?.is_some() {
return Err(anyhow::anyhow!(
"zone {zone_id} kept changing while device transition timestamps were merged"
));
}
}
Ok(updated)
}
}