homework: upgrade power socket

This commit is contained in:
6 changed files with 137 additions and 28 deletions

View File

@@ -122,9 +122,9 @@
### Описание/Пошаговая инструкция выполнения домашнего задания:
Для типа умной розетки:
- [ ] Функционал не изменяется: включение/выключение + запрос мощности.
- [ ] Взаимодействие организовано синхронно, через TCP.
- [ ] Розетка может использовать как реальный TCP-обмен, так и имитировать реальную работу (для тестов).
- [x] Функционал не изменяется: включение/выключение + запрос мощности.
- [x] Взаимодействие организовано синхронно, через TCP.
- [x] Розетка может использовать как реальный TCP-обмен, так и имитировать реальную работу (для тестов).
Для имитатора умной розетки:
- [ ] Читает адрес для приёма TCP-соединений из аргументов командной строки.

View File

@@ -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 ]");

View File

@@ -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()

View File

@@ -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();
}

View File

@@ -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<dyn PowerSocketHandle>,
}
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<dyn PowerSocketHandle>) -> 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<TcpStream>,
}
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 ]"
);
}
}

View File

@@ -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),
)
}