125 lines
5.2 KiB
Rust
125 lines
5.2 KiB
Rust
impl GreeClient {
|
|
pub fn new(
|
|
controller_id: String,
|
|
interface: Option<String>,
|
|
debug_events: Option<broadcast::Sender<ApiEvent>>,
|
|
debug_gree_frames: Arc<AtomicBool>,
|
|
) -> Self {
|
|
Self {
|
|
controller_id,
|
|
interface,
|
|
debug_events,
|
|
debug_gree_frames,
|
|
received_frames_total: Arc::new(AtomicU64::new(0)),
|
|
received_frames_by_device: Arc::new(Mutex::new(HashMap::new())),
|
|
buzzer_unsupported: Arc::new(Mutex::new(HashSet::new())),
|
|
quiet_unsupported: Arc::new(Mutex::new(HashSet::new())),
|
|
sleep_unsupported: Arc::new(Mutex::new(HashSet::new())),
|
|
}
|
|
}
|
|
|
|
pub fn received_frame_stats(&self) -> (u64, HashMap<String, u64>) {
|
|
let total = self.received_frames_total.load(Ordering::Relaxed);
|
|
let by_device = self.received_frames_by_device.lock()
|
|
.map(|counts| counts.clone())
|
|
.unwrap_or_default();
|
|
(total, by_device)
|
|
}
|
|
|
|
fn record_received_frame(&self, device: &Device) {
|
|
let total = self.received_frames_total.fetch_add(1, Ordering::Relaxed).saturating_add(1);
|
|
let device_count = self.received_frames_by_device.lock().ok().map(|mut counts| {
|
|
let count = counts.entry(device.id.clone()).or_insert(0);
|
|
*count = (*count).saturating_add(1);
|
|
*count
|
|
}).unwrap_or(0);
|
|
if let Some(events) = &self.debug_events {
|
|
let _ = events.send(ApiEvent {
|
|
event: "gree.frame_received".into(),
|
|
timestamp: Utc::now(),
|
|
data: json!({
|
|
"device_id": device.id,
|
|
"device_name": device.name,
|
|
"total": total,
|
|
"device_count": device_count,
|
|
}),
|
|
});
|
|
}
|
|
}
|
|
|
|
fn debug_frame(&self, direction: &str, device: &Device, target: SocketAddr, protocol: u8, payload: &Value) {
|
|
if !self.debug_gree_frames.load(Ordering::Relaxed) { return; }
|
|
let Some(events) = &self.debug_events else { return; };
|
|
let mut safe = payload.clone();
|
|
if let Some(object) = safe.as_object_mut() {
|
|
if object.contains_key("key") { object.insert("key".into(), json!("***")); }
|
|
}
|
|
let _ = events.send(ApiEvent {
|
|
event: "gree.frame".into(),
|
|
timestamp: Utc::now(),
|
|
data: json!({
|
|
"direction": direction,
|
|
"device_id": device.id,
|
|
"device_name": device.name,
|
|
"target": target.to_string(),
|
|
"protocol_version": protocol,
|
|
"payload": safe,
|
|
}),
|
|
});
|
|
}
|
|
|
|
async fn udp_socket(&self, broadcast: bool, target_hint: Option<Ipv4Addr>) -> Result<UdpSocket> {
|
|
let socket = if let Some(interface) = self.interface.as_deref() {
|
|
let ip = interface_ipv4(interface)?;
|
|
UdpSocket::bind(SocketAddrV4::new(ip, 0)).await
|
|
.with_context(|| format!("cannot bind GREE UDP socket to {ip} from interface {interface}"))?
|
|
} else if let Some(target) = target_hint {
|
|
if let Some(config) = local_ipv4_config_for_target(target)? {
|
|
tracing::debug!(
|
|
target = %target,
|
|
interface = %config.interface,
|
|
local_ip = %config.ip,
|
|
"Automatically selected local interface for GREE UDP"
|
|
);
|
|
UdpSocket::bind(SocketAddrV4::new(config.ip, 0)).await
|
|
.with_context(|| format!("cannot bind GREE UDP socket to {} on {}", config.ip, config.interface))?
|
|
} else {
|
|
UdpSocket::bind("0.0.0.0:0").await?
|
|
}
|
|
} else {
|
|
UdpSocket::bind("0.0.0.0:0").await?
|
|
};
|
|
socket.set_broadcast(broadcast)?;
|
|
Ok(socket)
|
|
}
|
|
|
|
fn bind_scan_target(&self, target: SocketAddr) -> Result<SocketAddr> {
|
|
let SocketAddr::V4(target_v4) = target else { return Ok(target); };
|
|
let broadcast = if let Some(interface) = self.interface.as_deref() {
|
|
let (_, broadcast) = interface_ipv4_config(interface)?;
|
|
Some(broadcast)
|
|
} else {
|
|
local_ipv4_config_for_target(*target_v4.ip())?.map(|config| config.broadcast)
|
|
};
|
|
Ok(broadcast
|
|
.map(|ip| SocketAddr::V4(SocketAddrV4::new(ip, target_v4.port())))
|
|
.unwrap_or(target))
|
|
}
|
|
|
|
fn discovery_target(&self, configured: &str) -> Result<SocketAddr> {
|
|
let value = configured.trim();
|
|
if value.eq_ignore_ascii_case("auto") || value.to_ascii_lowercase().starts_with("auto:") {
|
|
let port = value.split_once(':')
|
|
.map(|(_, port)| port.parse::<u16>().context("invalid automatic discovery port"))
|
|
.transpose()?
|
|
.unwrap_or(7000);
|
|
let interface = self.interface.as_deref()
|
|
.ok_or_else(|| anyhow!("automatic discovery broadcast requires GREE_CONTROLLER_GREE_INTERFACE"))?;
|
|
let (_, broadcast) = interface_ipv4_config(interface)?;
|
|
return Ok(SocketAddr::V4(SocketAddrV4::new(broadcast, port)));
|
|
}
|
|
value.parse().context("invalid discovery broadcast address")
|
|
}
|
|
|
|
}
|