Add drivers for gpio and piface
This commit is contained in:
parent
61a3c6093b
commit
4ed1cd3182
14 changed files with 211 additions and 17 deletions
BIN
Cargo.lock
generated
BIN
Cargo.lock
generated
Binary file not shown.
5
Cross.toml
Normal file
5
Cross.toml
Normal file
|
@ -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"
|
||||||
|
]
|
|
@ -8,8 +8,8 @@ server {
|
||||||
database = "sqlite://emgauwa-controller.sqlite"
|
database = "sqlite://emgauwa-controller.sqlite"
|
||||||
|
|
||||||
permissions {
|
permissions {
|
||||||
user = read("env:USER")
|
user = "" // read("env:USER")
|
||||||
group = read("env:USER")
|
group = "" // read("env:USER")
|
||||||
}
|
}
|
||||||
|
|
||||||
logging {
|
logging {
|
||||||
|
@ -20,37 +20,32 @@ logging {
|
||||||
relays {
|
relays {
|
||||||
new {
|
new {
|
||||||
driver = "gpio"
|
driver = "gpio"
|
||||||
pin = 5
|
pin = 24
|
||||||
inverted = true
|
inverted = true
|
||||||
}
|
}
|
||||||
new {
|
new {
|
||||||
driver = "gpio"
|
driver = "gpio"
|
||||||
pin = 4
|
pin = 23
|
||||||
inverted = true
|
inverted = true
|
||||||
}
|
}
|
||||||
new {
|
new {
|
||||||
driver = "gpio"
|
driver = "gpio"
|
||||||
pin = 3
|
pin = 22
|
||||||
inverted = true
|
inverted = true
|
||||||
}
|
}
|
||||||
new {
|
new {
|
||||||
driver = "gpio"
|
driver = "gpio"
|
||||||
pin = 2
|
pin = 27
|
||||||
inverted = true
|
inverted = true
|
||||||
}
|
}
|
||||||
new {
|
new {
|
||||||
driver = "gpio"
|
driver = "gpio"
|
||||||
pin = 1
|
pin = 18
|
||||||
inverted = true
|
inverted = true
|
||||||
}
|
}
|
||||||
new {
|
new {
|
||||||
driver = "gpio"
|
driver = "gpio"
|
||||||
pin = 0
|
pin = 17
|
||||||
inverted = true
|
|
||||||
}
|
|
||||||
new {
|
|
||||||
driver = "gpio"
|
|
||||||
pin = 16
|
|
||||||
inverted = true
|
inverted = true
|
||||||
}
|
}
|
||||||
new {
|
new {
|
||||||
|
@ -58,6 +53,11 @@ relays {
|
||||||
pin = 15
|
pin = 15
|
||||||
inverted = true
|
inverted = true
|
||||||
}
|
}
|
||||||
|
new {
|
||||||
|
driver = "gpio"
|
||||||
|
pin = 14
|
||||||
|
inverted = true
|
||||||
|
}
|
||||||
new {
|
new {
|
||||||
driver = "piface"
|
driver = "piface"
|
||||||
pin = 1
|
pin = 1
|
||||||
|
|
|
@ -8,8 +8,8 @@ server {
|
||||||
database = "sqlite://emgauwa-core.sqlite"
|
database = "sqlite://emgauwa-core.sqlite"
|
||||||
|
|
||||||
permissions {
|
permissions {
|
||||||
user = read("env:USER")
|
user = "" // read("env:USER")
|
||||||
group = read("env:USER")
|
group = "" // read("env:USER")
|
||||||
}
|
}
|
||||||
|
|
||||||
logging {
|
logging {
|
||||||
|
|
|
@ -26,3 +26,7 @@ sqlx = { version = "0.7", features = ["sqlite", "runtime-tokio", "macros", "chro
|
||||||
|
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-channel = "0.3"
|
futures-channel = "0.3"
|
||||||
|
|
||||||
|
rppal = "0.17"
|
||||||
|
rppal-pfd = "0.0.5"
|
||||||
|
rppal-mcp23s17 = "0.0.3"
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use actix::{Actor, Context, Handler, Message};
|
use actix::{Actor, Context, Handler, Message};
|
||||||
use emgauwa_lib::constants;
|
use emgauwa_lib::constants;
|
||||||
|
use emgauwa_lib::drivers::RelayDriver;
|
||||||
use emgauwa_lib::errors::EmgauwaError;
|
use emgauwa_lib::errors::EmgauwaError;
|
||||||
use emgauwa_lib::models::Controller;
|
use emgauwa_lib::models::Controller;
|
||||||
use emgauwa_lib::types::RelayStates;
|
use emgauwa_lib::types::RelayStates;
|
||||||
|
@ -45,16 +46,23 @@ pub struct AppState {
|
||||||
pub pool: Pool<Sqlite>,
|
pub pool: Pool<Sqlite>,
|
||||||
pub this: Controller,
|
pub this: Controller,
|
||||||
pub settings: Settings,
|
pub settings: Settings,
|
||||||
|
pub drivers: Vec<Box<dyn RelayDriver>>,
|
||||||
pub controller_notifier: Arc<Notify>,
|
pub controller_notifier: Arc<Notify>,
|
||||||
pub relay_notifier: Arc<Notify>,
|
pub relay_notifier: Arc<Notify>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn new(pool: Pool<Sqlite>, this: Controller, settings: Settings) -> AppState {
|
pub fn new(
|
||||||
|
pool: Pool<Sqlite>,
|
||||||
|
this: Controller,
|
||||||
|
settings: Settings,
|
||||||
|
drivers: Vec<Box<dyn RelayDriver>>,
|
||||||
|
) -> AppState {
|
||||||
AppState {
|
AppState {
|
||||||
pool,
|
pool,
|
||||||
this,
|
this,
|
||||||
settings,
|
settings,
|
||||||
|
drivers,
|
||||||
controller_notifier: Arc::new(Notify::new()),
|
controller_notifier: Arc::new(Notify::new()),
|
||||||
relay_notifier: Arc::new(Notify::new()),
|
relay_notifier: Arc::new(Notify::new()),
|
||||||
}
|
}
|
||||||
|
@ -93,6 +101,14 @@ impl Handler<UpdateRelayStates> for AppState {
|
||||||
|
|
||||||
fn handle(&mut self, msg: UpdateRelayStates, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: UpdateRelayStates, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
self.this.apply_relay_states(&msg.relay_states);
|
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();
|
self.notify_relay_change();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use emgauwa_lib::errors::EmgauwaError;
|
||||||
use emgauwa_lib::models::{Controller, FromDbModel};
|
use emgauwa_lib::models::{Controller, FromDbModel};
|
||||||
use emgauwa_lib::types::ControllerUid;
|
use emgauwa_lib::types::ControllerUid;
|
||||||
use emgauwa_lib::utils::{drop_privileges, init_logging};
|
use emgauwa_lib::utils::{drop_privileges, init_logging};
|
||||||
|
use rppal_pfd::PiFaceDigital;
|
||||||
use sqlx::pool::PoolConnection;
|
use sqlx::pool::PoolConnection;
|
||||||
use sqlx::Sqlite;
|
use sqlx::Sqlite;
|
||||||
|
|
||||||
|
@ -64,6 +65,9 @@ async fn main() -> Result<(), std::io::Error> {
|
||||||
|
|
||||||
init_logging(&settings.logging.level)?;
|
init_logging(&settings.logging.level)?;
|
||||||
|
|
||||||
|
let mut pfd: Option<PiFaceDigital> = None;
|
||||||
|
let drivers = settings.relays_make_drivers(&mut pfd)?;
|
||||||
|
|
||||||
let pool = db::init(&settings.database)
|
let pool = db::init(&settings.database)
|
||||||
.await
|
.await
|
||||||
.map_err(EmgauwaError::from)?;
|
.map_err(EmgauwaError::from)?;
|
||||||
|
@ -109,7 +113,7 @@ async fn main() -> Result<(), std::io::Error> {
|
||||||
settings.server.host, settings.server.port
|
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!(
|
let _ = tokio::join!(
|
||||||
tokio::spawn(run_relays_loop(app_state.clone())),
|
tokio::spawn(run_relays_loop(app_state.clone())),
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
use emgauwa_lib::drivers::{GpioDriver, PifaceDriver, RelayDriver};
|
||||||
use emgauwa_lib::errors::EmgauwaError;
|
use emgauwa_lib::errors::EmgauwaError;
|
||||||
use emgauwa_lib::settings;
|
use emgauwa_lib::settings;
|
||||||
|
use rppal_pfd::PiFaceDigital;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
|
|
||||||
use crate::driver::Driver;
|
use crate::driver::Driver;
|
||||||
|
@ -72,4 +74,33 @@ impl Settings {
|
||||||
pub fn get_relay(&self, number: i64) -> Option<&Relay> {
|
pub fn get_relay(&self, number: i64) -> Option<&Relay> {
|
||||||
self.relays.iter().find(|r| r.number == Some(number))
|
self.relays.iter().find(|r| r.number == Some(number))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn relays_make_drivers(
|
||||||
|
&self,
|
||||||
|
pfd: &mut Option<PiFaceDigital>,
|
||||||
|
) -> Result<Vec<Box<dyn RelayDriver>>, 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<PiFaceDigital>,
|
||||||
|
) -> Result<Box<dyn RelayDriver>, EmgauwaError> {
|
||||||
|
let driver: Box<dyn RelayDriver> = 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,3 +26,7 @@ libsqlite3-sys = { version = "*", features = ["bundled"] }
|
||||||
uuid = "1.6"
|
uuid = "1.6"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
|
rppal = "0.17"
|
||||||
|
rppal-pfd = "0.0.5"
|
||||||
|
rppal-mcp23s17 = "0.0.3"
|
||||||
|
|
35
emgauwa-lib/src/drivers/gpio.rs
Normal file
35
emgauwa-lib/src/drivers/gpio.rs
Normal file
|
@ -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<Self, EmgauwaError> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
17
emgauwa-lib/src/drivers/mod.rs
Normal file
17
emgauwa-lib/src/drivers/mod.rs
Normal file
|
@ -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;
|
||||||
|
}
|
52
emgauwa-lib/src/drivers/piface.rs
Normal file
52
emgauwa-lib/src/drivers/piface.rs
Normal file
|
@ -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<PiFaceDigital>) -> Result<Self, EmgauwaError> {
|
||||||
|
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<PiFaceDigital, EmgauwaError> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,9 @@ use actix::MailboxError;
|
||||||
use actix_web::http::StatusCode;
|
use actix_web::http::StatusCode;
|
||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
use config::ConfigError;
|
use config::ConfigError;
|
||||||
|
use rppal::gpio;
|
||||||
|
use rppal_mcp23s17::Mcp23s17Error;
|
||||||
|
use rppal_pfd::PiFaceDigitalError;
|
||||||
use serde::ser::SerializeStruct;
|
use serde::ser::SerializeStruct;
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
|
|
||||||
|
@ -21,6 +24,7 @@ pub enum EmgauwaError {
|
||||||
Other(String),
|
Other(String),
|
||||||
Internal(String),
|
Internal(String),
|
||||||
Connection(ControllerUid),
|
Connection(ControllerUid),
|
||||||
|
Hardware(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmgauwaError {
|
impl EmgauwaError {
|
||||||
|
@ -33,6 +37,7 @@ impl EmgauwaError {
|
||||||
EmgauwaError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
EmgauwaError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
EmgauwaError::Connection(_) => StatusCode::GATEWAY_TIMEOUT,
|
EmgauwaError::Connection(_) => StatusCode::GATEWAY_TIMEOUT,
|
||||||
EmgauwaError::Other(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
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::Internal(_) => String::from("internal error"),
|
||||||
EmgauwaError::Connection(_) => String::from("the target controller is not connected"),
|
EmgauwaError::Connection(_) => String::from("the target controller is not connected"),
|
||||||
EmgauwaError::Other(err) => format!("other error: {}", err),
|
EmgauwaError::Other(err) => format!("other error: {}", err),
|
||||||
|
EmgauwaError::Hardware(err) => format!("hardware error: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,6 +99,25 @@ impl From<ConfigError> for EmgauwaError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<gpio::Error> for EmgauwaError {
|
||||||
|
fn from(value: gpio::Error) -> Self {
|
||||||
|
Self::Hardware(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PiFaceDigitalError> 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 {
|
impl From<&EmgauwaError> for HttpResponse {
|
||||||
fn from(err: &EmgauwaError) -> Self {
|
fn from(err: &EmgauwaError) -> Self {
|
||||||
HttpResponse::build(err.get_code()).json(err)
|
HttpResponse::build(err.get_code()).json(err)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
pub mod drivers;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|
Loading…
Reference in a new issue