diff --git a/Cargo.lock b/Cargo.lock index eb44971..346f9a4 100644 Binary files a/Cargo.lock and b/Cargo.lock differ diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..2b8c82d --- /dev/null +++ b/Cross.toml @@ -0,0 +1,5 @@ +[build] +pre-build = [ + "curl -Lo /usr/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64", + "chmod +x /usr/bin/yq" +] diff --git a/config/emgauwa-controller.pkl b/config/emgauwa-controller.pkl index 272d03a..327c1bd 100644 --- a/config/emgauwa-controller.pkl +++ b/config/emgauwa-controller.pkl @@ -8,8 +8,8 @@ server { database = "sqlite://emgauwa-controller.sqlite" permissions { - user = read("env:USER") - group = read("env:USER") + user = "" // read("env:USER") + group = "" // read("env:USER") } logging { @@ -20,37 +20,32 @@ logging { relays { new { driver = "gpio" - pin = 5 + pin = 24 inverted = true } new { driver = "gpio" - pin = 4 + pin = 23 inverted = true } new { driver = "gpio" - pin = 3 + pin = 22 inverted = true } new { driver = "gpio" - pin = 2 + pin = 27 inverted = true } new { driver = "gpio" - pin = 1 + pin = 18 inverted = true } new { driver = "gpio" - pin = 0 - inverted = true - } - new { - driver = "gpio" - pin = 16 + pin = 17 inverted = true } new { @@ -58,6 +53,11 @@ relays { pin = 15 inverted = true } + new { + driver = "gpio" + pin = 14 + inverted = true + } new { driver = "piface" pin = 1 diff --git a/config/emgauwa-core.pkl b/config/emgauwa-core.pkl index 667b669..3e6caa4 100644 --- a/config/emgauwa-core.pkl +++ b/config/emgauwa-core.pkl @@ -8,8 +8,8 @@ server { database = "sqlite://emgauwa-core.sqlite" permissions { - user = read("env:USER") - group = read("env:USER") + user = "" // read("env:USER") + group = "" // read("env:USER") } logging { diff --git a/emgauwa-controller/Cargo.toml b/emgauwa-controller/Cargo.toml index 5a95670..4f31798 100644 --- a/emgauwa-controller/Cargo.toml +++ b/emgauwa-controller/Cargo.toml @@ -26,3 +26,7 @@ sqlx = { version = "0.7", features = ["sqlite", "runtime-tokio", "macros", "chro futures = "0.3" futures-channel = "0.3" + +rppal = "0.17" +rppal-pfd = "0.0.5" +rppal-mcp23s17 = "0.0.3" diff --git a/emgauwa-controller/src/app_state.rs b/emgauwa-controller/src/app_state.rs index 8eb56fb..a6b3bbf 100644 --- a/emgauwa-controller/src/app_state.rs +++ b/emgauwa-controller/src/app_state.rs @@ -3,6 +3,7 @@ use std::time::{Duration, Instant}; use actix::{Actor, Context, Handler, Message}; use emgauwa_lib::constants; +use emgauwa_lib::drivers::RelayDriver; use emgauwa_lib::errors::EmgauwaError; use emgauwa_lib::models::Controller; use emgauwa_lib::types::RelayStates; @@ -45,16 +46,23 @@ pub struct AppState { pub pool: Pool, pub this: Controller, pub settings: Settings, + pub drivers: Vec>, pub controller_notifier: Arc, pub relay_notifier: Arc, } impl AppState { - pub fn new(pool: Pool, this: Controller, settings: Settings) -> AppState { + pub fn new( + pool: Pool, + this: Controller, + settings: Settings, + drivers: Vec>, + ) -> AppState { AppState { pool, this, settings, + drivers, controller_notifier: Arc::new(Notify::new()), relay_notifier: Arc::new(Notify::new()), } @@ -93,6 +101,14 @@ impl Handler for AppState { fn handle(&mut self, msg: UpdateRelayStates, _ctx: &mut Self::Context) -> Self::Result { self.this.apply_relay_states(&msg.relay_states); + self.drivers + .iter_mut() + .zip(msg.relay_states.iter()) + .for_each(|(driver, state)| { + if let Err(e) = driver.set(state.unwrap_or(false)) { + log::error!("Error setting relay: {}", e); + } + }); self.notify_relay_change(); } diff --git a/emgauwa-controller/src/main.rs b/emgauwa-controller/src/main.rs index e6c329f..bde6b6d 100644 --- a/emgauwa-controller/src/main.rs +++ b/emgauwa-controller/src/main.rs @@ -5,6 +5,7 @@ use emgauwa_lib::errors::EmgauwaError; use emgauwa_lib::models::{Controller, FromDbModel}; use emgauwa_lib::types::ControllerUid; use emgauwa_lib::utils::{drop_privileges, init_logging}; +use rppal_pfd::PiFaceDigital; use sqlx::pool::PoolConnection; use sqlx::Sqlite; @@ -64,6 +65,9 @@ async fn main() -> Result<(), std::io::Error> { init_logging(&settings.logging.level)?; + let mut pfd: Option = None; + let drivers = settings.relays_make_drivers(&mut pfd)?; + let pool = db::init(&settings.database) .await .map_err(EmgauwaError::from)?; @@ -109,7 +113,7 @@ async fn main() -> Result<(), std::io::Error> { settings.server.host, settings.server.port ); - let app_state = app_state::AppState::new(pool.clone(), this, settings).start(); + let app_state = app_state::AppState::new(pool.clone(), this, settings, drivers).start(); let _ = tokio::join!( tokio::spawn(run_relays_loop(app_state.clone())), diff --git a/emgauwa-controller/src/settings.rs b/emgauwa-controller/src/settings.rs index 0c4572e..2eda813 100644 --- a/emgauwa-controller/src/settings.rs +++ b/emgauwa-controller/src/settings.rs @@ -1,5 +1,7 @@ +use emgauwa_lib::drivers::{GpioDriver, PifaceDriver, RelayDriver}; use emgauwa_lib::errors::EmgauwaError; use emgauwa_lib::settings; +use rppal_pfd::PiFaceDigital; use serde_derive::Deserialize; use crate::driver::Driver; @@ -72,4 +74,33 @@ impl Settings { pub fn get_relay(&self, number: i64) -> Option<&Relay> { self.relays.iter().find(|r| r.number == Some(number)) } + + pub fn relays_make_drivers( + &self, + pfd: &mut Option, + ) -> Result>, EmgauwaError> { + let mut drivers = Vec::new(); + for relay in &self.relays { + drivers.push(relay.make_driver(pfd)?); + } + Ok(drivers) + } +} + +impl Relay { + pub fn make_driver( + &self, + pfd: &mut Option, + ) -> Result, EmgauwaError> { + let driver: Box = match self.driver { + Driver::Gpio => Box::new(GpioDriver::new(self.pin, self.inverted)?), + Driver::Piface => { + if pfd.is_none() { + *pfd = Some(PifaceDriver::init_piface()?); + } + Box::new(PifaceDriver::new(self.pin, pfd)?) + } + }; + Ok(driver) + } } diff --git a/emgauwa-lib/Cargo.toml b/emgauwa-lib/Cargo.toml index d4ffd05..f34aa44 100644 --- a/emgauwa-lib/Cargo.toml +++ b/emgauwa-lib/Cargo.toml @@ -26,3 +26,7 @@ libsqlite3-sys = { version = "*", features = ["bundled"] } uuid = "1.6" futures = "0.3" libc = "0.2" + +rppal = "0.17" +rppal-pfd = "0.0.5" +rppal-mcp23s17 = "0.0.3" diff --git a/emgauwa-lib/src/drivers/gpio.rs b/emgauwa-lib/src/drivers/gpio.rs new file mode 100644 index 0000000..7847d43 --- /dev/null +++ b/emgauwa-lib/src/drivers/gpio.rs @@ -0,0 +1,35 @@ +use rppal::gpio::{Gpio, OutputPin}; + +use crate::drivers::RelayDriver; +use crate::errors::EmgauwaError; + +pub struct GpioDriver { + pub gpio: OutputPin, + pub inverted: bool, +} + +impl GpioDriver { + pub fn new(pin: u8, inverted: bool) -> Result { + let gpio = Gpio::new()?.get(pin)?.into_output(); + Ok(Self { gpio, inverted }) + } +} + +impl RelayDriver for GpioDriver { + fn set(&mut self, value: bool) -> Result<(), EmgauwaError> { + if self.get_high(value) { + self.gpio.set_high(); + } else { + self.gpio.set_low(); + } + Ok(()) + } + + fn get_pin(&self) -> u8 { + self.gpio.pin() + } + + fn get_inverted(&self) -> bool { + self.inverted + } +} diff --git a/emgauwa-lib/src/drivers/mod.rs b/emgauwa-lib/src/drivers/mod.rs new file mode 100644 index 0000000..71b1cfa --- /dev/null +++ b/emgauwa-lib/src/drivers/mod.rs @@ -0,0 +1,17 @@ +mod gpio; +mod piface; + +pub use gpio::GpioDriver; +pub use piface::PifaceDriver; + +use crate::errors::EmgauwaError; + +pub trait RelayDriver { + fn get_high(&self, value: bool) -> bool { + value ^ self.get_inverted() + } + + fn set(&mut self, value: bool) -> Result<(), EmgauwaError>; + fn get_pin(&self) -> u8; + fn get_inverted(&self) -> bool; +} diff --git a/emgauwa-lib/src/drivers/piface.rs b/emgauwa-lib/src/drivers/piface.rs new file mode 100644 index 0000000..74c7857 --- /dev/null +++ b/emgauwa-lib/src/drivers/piface.rs @@ -0,0 +1,52 @@ +use rppal_pfd::{ + ChipSelect, HardwareAddress, OutputPin, PiFaceDigital, PiFaceDigitalError, SpiBus, SpiMode, +}; + +use crate::drivers::RelayDriver; +use crate::errors::EmgauwaError; + +pub struct PifaceDriver { + pub pfd_pin: OutputPin, +} + +impl PifaceDriver { + pub fn new(pin: u8, pfd: &Option) -> Result { + let pfd = pfd.as_ref().ok_or(EmgauwaError::Hardware(String::from( + "PiFaceDigital not initialized", + )))?; + let pfd_pin = pfd.get_output_pin(pin)?; + Ok(Self { pfd_pin }) + } + + pub fn init_piface() -> Result { + let mut pfd = PiFaceDigital::new( + HardwareAddress::new(0)?, + SpiBus::Spi0, + ChipSelect::Cs0, + 100_000, + SpiMode::Mode0, + )?; + pfd.init()?; + + Ok(pfd) + } +} + +impl RelayDriver for PifaceDriver { + fn set(&mut self, value: bool) -> Result<(), EmgauwaError> { + if self.get_high(value) { + self.pfd_pin.set_high().map_err(PiFaceDigitalError::from)?; + } else { + self.pfd_pin.set_low().map_err(PiFaceDigitalError::from)?; + } + Ok(()) + } + + fn get_pin(&self) -> u8 { + self.pfd_pin.get_pin_number() + } + + fn get_inverted(&self) -> bool { + false + } +} diff --git a/emgauwa-lib/src/errors/emgauwa_error.rs b/emgauwa-lib/src/errors/emgauwa_error.rs index b947c16..3cf72d5 100644 --- a/emgauwa-lib/src/errors/emgauwa_error.rs +++ b/emgauwa-lib/src/errors/emgauwa_error.rs @@ -6,6 +6,9 @@ use actix::MailboxError; use actix_web::http::StatusCode; use actix_web::HttpResponse; use config::ConfigError; +use rppal::gpio; +use rppal_mcp23s17::Mcp23s17Error; +use rppal_pfd::PiFaceDigitalError; use serde::ser::SerializeStruct; use serde::{Serialize, Serializer}; @@ -21,6 +24,7 @@ pub enum EmgauwaError { Other(String), Internal(String), Connection(ControllerUid), + Hardware(String), } impl EmgauwaError { @@ -33,6 +37,7 @@ impl EmgauwaError { EmgauwaError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, EmgauwaError::Connection(_) => StatusCode::GATEWAY_TIMEOUT, EmgauwaError::Other(_) => StatusCode::INTERNAL_SERVER_ERROR, + EmgauwaError::Hardware(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } @@ -47,6 +52,7 @@ impl From<&EmgauwaError> for String { EmgauwaError::Internal(_) => String::from("internal error"), EmgauwaError::Connection(_) => String::from("the target controller is not connected"), EmgauwaError::Other(err) => format!("other error: {}", err), + EmgauwaError::Hardware(err) => format!("hardware error: {}", err), } } } @@ -93,6 +99,25 @@ impl From for EmgauwaError { } } +impl From for EmgauwaError { + fn from(value: gpio::Error) -> Self { + Self::Hardware(value.to_string()) + } +} + +impl From for EmgauwaError { + fn from(value: PiFaceDigitalError) -> Self { + match value { + PiFaceDigitalError::Mcp23s17Error { source } => match source { + Mcp23s17Error::SpiError { source } => Self::Hardware(source.to_string()), + _ => Self::Hardware(source.to_string()), + }, + PiFaceDigitalError::GpioError { source } => Self::Hardware(source.to_string()), + _ => Self::Hardware(value.to_string()), + } + } +} + impl From<&EmgauwaError> for HttpResponse { fn from(err: &EmgauwaError) -> Self { HttpResponse::build(err.get_code()).json(err) diff --git a/emgauwa-lib/src/lib.rs b/emgauwa-lib/src/lib.rs index f939d0c..14609b9 100644 --- a/emgauwa-lib/src/lib.rs +++ b/emgauwa-lib/src/lib.rs @@ -1,5 +1,6 @@ pub mod constants; pub mod db; +pub mod drivers; pub mod errors; pub mod models; pub mod settings;