From 9898b79efa1572a8adeb69da39ce5cc096028b3d Mon Sep 17 00:00:00 2001 From: Alexander Baranov Date: Mon, 23 Feb 2026 15:31:31 +0300 Subject: [PATCH] homework: upgrade power socket --- smart-house/README.md | 6 +- smart-house/src/device.rs | 5 +- smart-house/src/house.rs | 6 +- smart-house/src/main.rs | 13 ++-- smart-house/src/power_socket.rs | 132 ++++++++++++++++++++++++++++---- smart-house/src/room.rs | 3 +- 6 files changed, 137 insertions(+), 28 deletions(-) diff --git a/smart-house/README.md b/smart-house/README.md index b6a2022..18fbc7b 100644 --- a/smart-house/README.md +++ b/smart-house/README.md @@ -122,9 +122,9 @@ ### Описание/Пошаговая инструкция выполнения домашнего задания: Для типа умной розетки: -- [ ] Функционал не изменяется: включение/выключение + запрос мощности. -- [ ] Взаимодействие организовано синхронно, через TCP. -- [ ] Розетка может использовать как реальный TCP-обмен, так и имитировать реальную работу (для тестов). +- [x] Функционал не изменяется: включение/выключение + запрос мощности. +- [x] Взаимодействие организовано синхронно, через TCP. +- [x] Розетка может использовать как реальный TCP-обмен, так и имитировать реальную работу (для тестов). Для имитатора умной розетки: - [ ] Читает адрес для приёма TCP-соединений из аргументов командной строки. diff --git a/smart-house/src/device.rs b/smart-house/src/device.rs index 097614b..d2a4736 100644 --- a/smart-house/src/device.rs +++ b/smart-house/src/device.rs @@ -41,12 +41,13 @@ impl PrintStatus for Device { #[cfg(test)] mod tests { use super::*; + use crate::power_socket::PowerSocketStub; use crate::{PowerSocket, Thermometer}; #[test] fn smoke_test() { let dev_thermometer = Device::Thermometer(Thermometer::new(20.1)); - let dev_power_socket = Device::PowerSocket(PowerSocket::new(11.2, false)); + let dev_power_socket = Device::PowerSocket(PowerSocket::stub(11.2, false)); dev_thermometer.print_status(); dev_power_socket.print_status(); @@ -63,7 +64,7 @@ mod tests { #[test] fn display_test() { let dev_thermometer = Device::Thermometer(Thermometer::new(20.1)); - let dev_power_socket = Device::PowerSocket(PowerSocket::new(11.2, false)); + let dev_power_socket = Device::PowerSocket(PowerSocket::new(Box::new(PowerSocketStub::new(11.2, false)))); assert_eq!(format!("{}", dev_thermometer.display()), "DEV:Thermometer[ 20.1 ]"); assert_eq!(format!("{}", dev_power_socket.display()), "DEV:PowerSocket[ OFF : 0.0 ]"); diff --git a/smart-house/src/house.rs b/smart-house/src/house.rs index 6065d67..72ab80a 100644 --- a/smart-house/src/house.rs +++ b/smart-house/src/house.rs @@ -62,8 +62,8 @@ mod tests { Room::new( [ ("ThermA".to_string(), Thermometer::new(20.0).into()), - ("PSocA".to_string(), PowerSocket::new(12.34, false).into()), - ("PSocB".to_string(), PowerSocket::new(10.01, true).into()), + ("PSocA".to_string(), PowerSocket::stub(12.34, false).into()), + ("PSocB".to_string(), PowerSocket::stub(10.01, true).into()), ] .into_iter() .collect(), @@ -73,7 +73,7 @@ mod tests { "bedroom".to_string(), Room::new( [ - ("PSocC".to_string(), PowerSocket::new(11.11, true).into()), + ("PSocC".to_string(), PowerSocket::stub(11.11, true).into()), ("ThermB".to_string(), Thermometer::new(17.99).into()), ] .into_iter() diff --git a/smart-house/src/main.rs b/smart-house/src/main.rs index 48d7356..c81b8ad 100644 --- a/smart-house/src/main.rs +++ b/smart-house/src/main.rs @@ -25,23 +25,23 @@ fn create_house_demo() -> House { ( "Hall".to_string(), room!( - "PSocA" => PowerSocket::new(9.5, true), + "PSocA" => PowerSocket::stub(9.5, true), "ThermA" => Thermometer::new(20.1), ), ), ( "Main".to_string(), room!( - "PSocB" => PowerSocket::new(11.2, true), + "PSocB" => PowerSocket::stub(11.2, true), "ThermB" => Thermometer::new(24.5), - "PSocC" => PowerSocket::new(10.4, true), + "PSocC" => PowerSocket::stub(10.4, true), ), ), ( "Bedroom".to_string(), room!( "ThermC" => Thermometer::new(19.3), - "PSocD" => PowerSocket::new(12.1, true), + "PSocD" => PowerSocket::stub(12.1, true), ), ), ] @@ -76,7 +76,10 @@ fn add_new_room_in_house_demo(house: &mut House) { fn add_power_socket_to_closet_room_demo(house: &mut House) { println!("# Add power socket to 'Closet' room"); - house.get_room_mut("Closet").unwrap().insert_device("PSocE", PowerSocket::new(8.0, true).into()); + house + .get_room_mut("Closet") + .unwrap() + .insert_device("PSocE", PowerSocket::stub(8.0, true).into()); house.print_status(); } diff --git a/smart-house/src/power_socket.rs b/smart-house/src/power_socket.rs index 3b1be09..f0abaad 100644 --- a/smart-house/src/power_socket.rs +++ b/smart-house/src/power_socket.rs @@ -1,26 +1,41 @@ -use std::fmt::Display; +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 { - power_rate: f32, - on: bool, + handle: Box, } impl PowerSocket { - pub fn new(power_rate: f32, on: bool) -> Self { - Self { power_rate, on } + pub fn stub(power_rate: f32, on: bool) -> Self { + Self { + handle: Box::new(PowerSocketStub::new(power_rate, on)), + } + } + + pub fn connect(addr: &str) -> Self { + Self { + handle: Box::new(PowerSocketClient::new(addr)), + } + } + + pub fn new(handle: Box) -> Self { + Self { handle } } pub fn is_on(&self) -> bool { - self.on + self.handle.is_on() } pub fn set_on(&mut self, on: bool) { - self.on = on + self.handle.set_on(on) } pub fn get_power(&self) -> f32 { - if self.on { self.power_rate } else { 0.0 } + self.handle.get_power() } pub fn display(&self) -> impl Display { @@ -29,15 +44,95 @@ impl PowerSocket { } } +pub trait PowerSocketHandle: Debug { + fn is_on(&self) -> bool; + + fn set_on(&mut self, on: bool); + + fn get_power(&self) -> f32; +} + +#[derive(Debug)] +pub 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) -> bool { + self.on + } + + fn set_on(&mut self, on: bool) { + self.on = on + } + + fn get_power(&self) -> f32 { + if self.on { self.power_rate } else { 0.0 } + } +} + +const TIMEOUT: Duration = Duration::from_secs(5); + +#[derive(Debug)] +pub struct PowerSocketClient { + stream: RefCell, +} + +impl PowerSocketClient { + pub fn new(addr: &str) -> Self { + let addr: SocketAddr = addr.parse().expect("addr should be valid tcp address"); + let stream = TcpStream::connect_timeout(&addr, TIMEOUT).expect("address should be available to connect"); + stream.set_write_timeout(Some(TIMEOUT)).expect("write timeout should be set"); + stream.set_read_timeout(Some(TIMEOUT)).expect("read timeout should be set"); + 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) -> bool { + let mut buf = [CMD_GET_ON; 1]; + self.stream.borrow_mut().write_all(&buf).expect("CMD_GET_ON request should be sent"); + self.stream.borrow_mut().read_exact(&mut buf).expect("CMD_GET_ON response should be received"); + !matches!(buf, [0]) + } + + fn set_on(&mut self, on: bool) { + let cmd = if on { CMD_TURN_ON } else { CMD_TURN_OFF }; + let mut buf = [cmd; 1]; + self.stream.borrow_mut().write_all(&buf).expect("change state request should be sent"); + self.stream.borrow_mut().read_exact(&mut buf).expect("change state response should be received"); + } + + fn get_power(&self) -> f32 { + let mut buf = [CMD_GET_POWER; 4]; + self.stream.borrow_mut().write_all(&buf[0..1]).expect("CMD_GET_POWER request should be sent"); + self.stream + .borrow_mut() + .read_exact(&mut buf) + .expect("CMD_GET_POWER response should be received"); + f32::from_le_bytes(buf) + } +} + #[cfg(test)] mod tests { use super::*; #[test] fn smoke_test() { - let mut power_socket = PowerSocket::new(12.4, false); - assert_eq!(power_socket.power_rate, 12.4); - assert!(!power_socket.on); + let mut power_socket = PowerSocket::new(Box::new(PowerSocketStub::new(12.4, false))); assert!(!power_socket.is_on()); assert_eq!(power_socket.get_power(), 0.0); @@ -48,8 +143,17 @@ mod tests { #[test] fn display_test() { - assert_eq!(format!("{}", PowerSocket::new(11.549, false).display()), "PowerSocket[ OFF : 0.0 ]"); - assert_eq!(format!("{}", PowerSocket::new(11.549, true).display()), "PowerSocket[ ON : 11.5 ]"); - assert_eq!(format!("{}", PowerSocket::new(11.550, true).display()), "PowerSocket[ ON : 11.6 ]"); + assert_eq!( + format!("{}", PowerSocket::new(Box::new(PowerSocketStub::new(11.549, false))).display()), + "PowerSocket[ OFF : 0.0 ]" + ); + assert_eq!( + format!("{}", PowerSocket::new(Box::new(PowerSocketStub::new(11.549, true))).display()), + "PowerSocket[ ON : 11.5 ]" + ); + assert_eq!( + format!("{}", PowerSocket::new(Box::new(PowerSocketStub::new(11.550, true))).display()), + "PowerSocket[ ON : 11.6 ]" + ); } } diff --git a/smart-house/src/room.rs b/smart-house/src/room.rs index 5a36303..c8e2d75 100644 --- a/smart-house/src/room.rs +++ b/smart-house/src/room.rs @@ -49,11 +49,12 @@ macro_rules! room { #[cfg(test)] mod tests { use super::*; + use crate::power_socket::PowerSocketStub; use crate::{PowerSocket, Thermometer}; fn create_test_room() -> Room { room!( - "PSoc" => PowerSocket::new(12.34, false), + "PSoc" => PowerSocket::new(Box::new(PowerSocketStub::new(12.34, false))), "Therm" => Thermometer::new(21.56), ) }