v0.5.2
This commit is contained in:
+65
-5
@@ -23,6 +23,7 @@ pub struct GreeClient {
|
||||
debug_events: Option<broadcast::Sender<ApiEvent>>,
|
||||
debug_gree_frames: Arc<AtomicBool>,
|
||||
buzzer_unsupported: Arc<Mutex<HashSet<String>>>,
|
||||
quiet_unsupported: Arc<Mutex<HashSet<String>>>,
|
||||
}
|
||||
|
||||
impl GreeClient {
|
||||
@@ -38,6 +39,7 @@ impl GreeClient {
|
||||
debug_events,
|
||||
debug_gree_frames,
|
||||
buzzer_unsupported: Arc::new(Mutex::new(HashSet::new())),
|
||||
quiet_unsupported: Arc::new(Mutex::new(HashSet::new())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,17 +416,25 @@ impl GreeClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn command(&self, device: &Device, command: &DeviceCommand, suppress_beep: bool) -> Result<Value> {
|
||||
let key = device.key.as_deref().ok_or_else(|| anyhow!("device is not bound"))?;
|
||||
pub fn quiet_command_supported(&self, device_id: &str) -> bool {
|
||||
self.quiet_unsupported.lock().map(|items| !items.contains(device_id)).unwrap_or(true)
|
||||
}
|
||||
|
||||
async fn request_command_with_buzzer_fallback(
|
||||
&self,
|
||||
device: &Device,
|
||||
key: &str,
|
||||
command: &DeviceCommand,
|
||||
suppress_beep: bool,
|
||||
) -> Result<Value> {
|
||||
let try_buzzer_suppression = suppress_beep
|
||||
&& self.buzzer_unsupported.lock().map(|items| !items.contains(&device.id)).unwrap_or(true);
|
||||
let inner = Self::command_payload(command, try_buzzer_suppression)?;
|
||||
match self.request(device, &inner, key, false, device.protocol_version).await {
|
||||
Ok(value) => Ok(value),
|
||||
Err(first_err) if try_buzzer_suppression => {
|
||||
// Some firmwares reject unknown command properties instead of ignoring them.
|
||||
// Retry the exact state change without buzzer fields; only remember the device
|
||||
// as incompatible after that fallback succeeds.
|
||||
// Some firmwares reject unknown buzzer properties instead of ignoring them.
|
||||
// Retry the exact state change without buzzer fields and remember the fallback.
|
||||
let fallback = Self::command_payload(command, false)?;
|
||||
match self.request(device, &fallback, key, false, device.protocol_version).await {
|
||||
Ok(value) => {
|
||||
@@ -439,6 +449,38 @@ impl GreeClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn command(&self, device: &Device, command: &DeviceCommand, suppress_beep: bool) -> Result<DeviceCommand> {
|
||||
let key = device.key.as_deref().ok_or_else(|| anyhow!("device is not bound"))?;
|
||||
let mut effective = command.clone();
|
||||
if effective.quiet.is_some() && !self.quiet_command_supported(&device.id) {
|
||||
effective.quiet = None;
|
||||
}
|
||||
if effective.is_empty() {
|
||||
return Ok(effective);
|
||||
}
|
||||
|
||||
match self.request_command_with_buzzer_fallback(device, key, &effective, suppress_beep).await {
|
||||
Ok(_) => Ok(effective),
|
||||
Err(first_err) if effective.quiet.is_some() => {
|
||||
// Quiet is not implemented by every GREE indoor unit. If a combined thermostat
|
||||
// command is rejected, retry the same setpoint/fan change without Quiet. Once
|
||||
// that succeeds, remember the unit so future thermostat frames omit Quiet.
|
||||
let mut fallback = effective.clone();
|
||||
fallback.quiet = None;
|
||||
if fallback.is_empty() { return Err(first_err); }
|
||||
match self.request_command_with_buzzer_fallback(device, key, &fallback, suppress_beep).await {
|
||||
Ok(_) => {
|
||||
if let Ok(mut items) = self.quiet_unsupported.lock() { items.insert(device.id.clone()); }
|
||||
tracing::warn!(device=%device.id, "GREE Quiet command is unsupported; thermostat will use Low fan without Quiet for this device");
|
||||
Ok(fallback)
|
||||
}
|
||||
Err(_) => Err(first_err),
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn command_payload(command: &DeviceCommand, suppress_beep: bool) -> Result<Value> {
|
||||
let mut opt = Vec::<&str>::new();
|
||||
let mut values = Vec::<Value>::new();
|
||||
@@ -683,3 +725,21 @@ pub fn merge_discovered(existing: Option<Device>, discovered: Device) -> Device
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn thermostat_standby_setpoint_low_fan_and_quiet_share_one_frame() {
|
||||
let payload = GreeClient::command_payload(&DeviceCommand {
|
||||
target_temperature: Some(19.0),
|
||||
fan_speed: Some(1),
|
||||
quiet: Some(true),
|
||||
..DeviceCommand::default()
|
||||
}, false).expect("thermostat command payload");
|
||||
|
||||
assert_eq!(payload.get("opt").cloned(), Some(serde_json::json!(["SetTem", "WdSpd", "Quiet"])));
|
||||
assert_eq!(payload.get("p").cloned(), Some(serde_json::json!([19, 1, 1])));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user