use std::cell::RefCell; use std::fmt::{Debug, Display}; use std::io::{Read, Write}; use std::net::{SocketAddr, TcpStream}; use std::time::Duration; #[derive(Debug)] pub struct PowerSocket { handle: Box, } impl PowerSocket { pub fn stub(power_rate: f32, on: bool) -> Self { Self { handle: Box::new(PowerSocketStub::new(power_rate, on)), } } pub fn connect(addr: &str) -> Result { let handle = PowerSocketClient::new(addr)?; Ok(Self { handle: Box::new(handle) }) } pub fn is_on(&self) -> Result { self.handle.is_on() } pub fn set_on(&mut self, on: bool) -> Result<(), std::io::Error> { self.handle.set_on(on) } pub fn get_power(&self) -> Result { self.handle.get_power() } pub fn display(&self) -> impl Display { const ERR: &str = "PowerSocket[ ERR ]"; let power = match self.get_power() { Ok(power) => power, Err(e) => { eprintln!("error on get power: {:?}", e); return ERR.to_string(); } }; let on = match self.is_on() { Ok(state) => state, Err(e) => { eprintln!("error on get state: {:?}", e); return ERR.to_string(); } }; let state = if on { "ON" } else { "OFF" }; format!("PowerSocket[ {} : {:02.1} ]", state, power) } } trait PowerSocketHandle: Debug { fn is_on(&self) -> Result; fn set_on(&mut self, on: bool) -> Result<(), std::io::Error>; fn get_power(&self) -> Result; } #[derive(Debug)] struct PowerSocketStub { power_rate: f32, on: bool, } impl PowerSocketStub { pub fn new(power_rate: f32, on: bool) -> Self { Self { power_rate, on } } } impl PowerSocketHandle for PowerSocketStub { fn is_on(&self) -> Result { Ok(self.on) } fn set_on(&mut self, on: bool) -> Result<(), std::io::Error> { self.on = on; Ok(()) } fn get_power(&self) -> Result { Ok(if self.on { self.power_rate } else { 0.0 }) } } const TIMEOUT: Duration = Duration::from_secs(5); #[derive(Debug)] struct PowerSocketClient { stream: RefCell, } impl PowerSocketClient { fn new(addr: &str) -> Result { let addr: SocketAddr = addr.parse().map_err(std::io::Error::other)?; let stream = TcpStream::connect_timeout(&addr, TIMEOUT)?; stream.set_write_timeout(Some(TIMEOUT))?; stream.set_read_timeout(Some(TIMEOUT))?; Ok(Self { stream: RefCell::new(stream) }) } } const CMD_GET_ON: u8 = 1; const CMD_TURN_ON: u8 = 2; const CMD_TURN_OFF: u8 = 3; const CMD_GET_POWER: u8 = 4; impl PowerSocketHandle for PowerSocketClient { fn is_on(&self) -> Result { let mut buf = [CMD_GET_ON; 1]; self.stream .borrow_mut() .write_all(&buf) .map_err(|e| std::io::Error::other(format!("CMD_GET_ON request error: {:?}", e)))?; self.stream .borrow_mut() .read_exact(&mut buf) .map_err(|e| std::io::Error::other(format!("CMD_GET_ON response error: {:?}", e)))?; Ok(!matches!(buf, [0])) } fn set_on(&mut self, on: bool) -> Result<(), std::io::Error> { let cmd = if on { CMD_TURN_ON } else { CMD_TURN_OFF }; let mut buf = [cmd; 1]; self.stream .borrow_mut() .write_all(&buf) .map_err(|e| std::io::Error::other(format!("change state request error: {:?}", e)))?; self.stream .borrow_mut() .read_exact(&mut buf) .map_err(|e| std::io::Error::other(format!("change state response error: {:?}", e)))?; Ok(()) } fn get_power(&self) -> Result { let mut buf = [CMD_GET_POWER; 4]; self.stream .borrow_mut() .write_all(&buf[0..1]) .map_err(|e| std::io::Error::other(format!("CMD_GET_POWER request error: {:?}", e)))?; self.stream .borrow_mut() .read_exact(&mut buf) .map_err(|e| std::io::Error::other(format!("CMD_GET_POWER response error: {:?}", e)))?; Ok(f32::from_le_bytes(buf)) } } #[cfg(test)] mod tests { use super::*; #[test] fn smoke_test() { let mut power_socket = PowerSocket::stub(12.4, false); assert!(!power_socket.is_on().unwrap()); assert_eq!(power_socket.get_power().unwrap(), 0.0); power_socket.set_on(true).unwrap(); assert!(power_socket.is_on().unwrap()); assert_eq!(power_socket.get_power().unwrap(), 12.4); } #[test] fn display_test() { assert_eq!(format!("{}", PowerSocket::stub(11.549, false).display()), "PowerSocket[ OFF : 0.0 ]"); assert_eq!(format!("{}", PowerSocket::stub(11.549, true).display()), "PowerSocket[ ON : 11.5 ]"); assert_eq!(format!("{}", PowerSocket::stub(11.550, true).display()), "PowerSocket[ ON : 11.6 ]"); } }