Compare commits

...

18 commits
v0.5.0 ... main

Author SHA1 Message Date
066e9f7bf8
Bump version 2024-12-25 20:52:15 +01:00
e923ecb9d8
Add tests and fix is_on calculation 2024-12-25 01:54:16 +01:00
2f5bb538b2
Remove unwrap when getting active schedule 2024-10-28 02:17:38 +01:00
41cc9e0622
Remove logging file from config (was never implemented) 2024-06-11 17:46:35 +02:00
d4ff664f74
Add relay view to faster load controller_uid 2024-06-11 14:10:25 +02:00
277b159200
Add setting to change "midnight" of day 2024-05-30 02:37:37 +02:00
ce7a79d1de
Remove guessing of active_schedule 2024-05-29 15:51:53 +02:00
929985c64a
Improve handling of override_schedule 2024-05-28 21:17:21 +02:00
473832f58a
Rename active_schedule to override_schedule and add EmgauwaNow 2024-05-26 22:48:22 +02:00
9326b66007
Simplify schedule in macro action 2024-05-13 19:17:35 +02:00
f26e66d687
Add parameter for db pool size 2024-05-10 17:43:29 +02:00
b14049b3f6
Add request model for GET tagged schedule 2024-05-06 16:28:10 +02:00
228b366320
Move relay drivers from common to controller 2024-05-05 23:46:49 +02:00
e9b09cd709
Fix minor issues 2024-05-04 18:32:47 +02:00
cacd740bd9
Fix wrong table in macro update 2024-05-02 22:15:19 +02:00
98db89ce03
Fix is_on function for Periods 2024-05-02 20:12:43 +02:00
fc4c1df09a
Revert "Add sql transactions"
This caused the error "locked database".
This reverts commit 19e2ea003b.
2024-05-02 19:35:22 +02:00
19e2ea003b
Add sql transactions 2024-05-02 13:30:47 +02:00
36 changed files with 1434 additions and 1196 deletions

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE macros SET name = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "2b34934e10005378c331f489751dcc4dc5cc79a52299cb74018e36212809288a"
}

View file

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT * FROM relays WHERE controller_id = ?", "query": "SELECT * FROM v_relays WHERE id = ?",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -22,6 +22,11 @@
"name": "controller_id", "name": "controller_id",
"ordinal": 3, "ordinal": 3,
"type_info": "Int64" "type_info": "Int64"
},
{
"name": "controller_uid",
"ordinal": 4,
"type_info": "Blob"
} }
], ],
"parameters": { "parameters": {
@ -31,8 +36,9 @@
false, false,
false, false,
false, false,
false,
false false
] ]
}, },
"hash": "c9437ff0c3014b269dcb21304fbad12237b9cb69ea6aa4686df6d5262065faa2" "hash": "2b5ac2227f48be1483f4097da6f890be8091daa97b0af548b6ebf60cdc03dfba"
} }

View file

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT relays.* FROM relays INNER JOIN junction_relay_schedule\n\t\t\tON junction_relay_schedule.relay_id = relays.id\n\t\t\tWHERE junction_relay_schedule.schedule_id = ?\n\t\t\tORDER BY junction_relay_schedule.weekday", "query": "SELECT v_relays.* FROM v_relays INNER JOIN junction_tag ON junction_tag.relay_id = v_relays.id WHERE junction_tag.tag_id = ?",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -22,6 +22,11 @@
"name": "controller_id", "name": "controller_id",
"ordinal": 3, "ordinal": 3,
"type_info": "Int64" "type_info": "Int64"
},
{
"name": "controller_uid",
"ordinal": 4,
"type_info": "Blob"
} }
], ],
"parameters": { "parameters": {
@ -31,8 +36,9 @@
false, false,
false, false,
false, false,
false,
false false
] ]
}, },
"hash": "2551c285e3e223311cff8e32022d8b11e95d56b2f166326301a0b6722fc1fd44" "hash": "493ad91be9ce523e9d0f03f5caa9b3255a5426d54901f4f3aa96ad152b05ffd0"
} }

View file

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT relay.* FROM relays AS relay INNER JOIN junction_tag ON junction_tag.relay_id = relay.id WHERE junction_tag.tag_id = ?", "query": "SELECT * FROM v_relays WHERE v_relays.controller_id = ?",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -22,6 +22,11 @@
"name": "controller_id", "name": "controller_id",
"ordinal": 3, "ordinal": 3,
"type_info": "Int64" "type_info": "Int64"
},
{
"name": "controller_uid",
"ordinal": 4,
"type_info": "Blob"
} }
], ],
"parameters": { "parameters": {
@ -31,8 +36,9 @@
false, false,
false, false,
false, false,
false,
false false
] ]
}, },
"hash": "e94ef5bc8b267d493375bb371dcfb7b09f6355ecbc8b6e1085d5f2f9a08cac3f" "hash": "4a99db9678cf8d1bdb082c4a13a1f5cdd699bfe7600389e37ca980b6fad12bb5"
} }

View file

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT * FROM relays", "query": "SELECT * FROM v_relays",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -22,6 +22,11 @@
"name": "controller_id", "name": "controller_id",
"ordinal": 3, "ordinal": 3,
"type_info": "Int64" "type_info": "Int64"
},
{
"name": "controller_uid",
"ordinal": 4,
"type_info": "Blob"
} }
], ],
"parameters": { "parameters": {
@ -31,8 +36,9 @@
false, false,
false, false,
false, false,
false,
false false
] ]
}, },
"hash": "ee7da56331bece2efe21b55dbd5f420d3abb08358a1abe301dc7e08693fbef4d" "hash": "5056b625241d9cbe63d98e00ac39085677c09be8be903804120c2d52579afdbb"
} }

View file

@ -1,38 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO relays (name, number, controller_id) VALUES (?, ?, ?) RETURNING *",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "number",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "controller_id",
"ordinal": 3,
"type_info": "Int64"
}
],
"parameters": {
"Right": 3
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "5865f27b97487b6dfd956a3d260b9bbb0e6c203b721d29cf9149f60bfdd93465"
}

View file

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT * FROM relays WHERE controller_id = ? AND number = ?", "query": "SELECT * FROM v_relays WHERE v_relays.controller_id = ? AND v_relays.number = ?",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -22,6 +22,11 @@
"name": "controller_id", "name": "controller_id",
"ordinal": 3, "ordinal": 3,
"type_info": "Int64" "type_info": "Int64"
},
{
"name": "controller_uid",
"ordinal": 4,
"type_info": "Blob"
} }
], ],
"parameters": { "parameters": {
@ -31,8 +36,9 @@
false, false,
false, false,
false, false,
false,
false false
] ]
}, },
"hash": "b41855e635ac409559fa63cba4c1285034c573b86e3193da3995606dee412153" "hash": "9224ad423f2c86f3d95f2b0b7d99a27f690020f89958dfc8dd6044a31afdb31d"
} }

View file

@ -0,0 +1,44 @@
{
"db_name": "SQLite",
"query": "SELECT v_relays.* FROM v_relays INNER JOIN junction_relay_schedule\n\t\t\tON junction_relay_schedule.relay_id = v_relays.id\n\t\t\tWHERE junction_relay_schedule.schedule_id = ?\n\t\t\tORDER BY junction_relay_schedule.weekday",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "name",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "number",
"ordinal": 2,
"type_info": "Int64"
},
{
"name": "controller_id",
"ordinal": 3,
"type_info": "Int64"
},
{
"name": "controller_uid",
"ordinal": 4,
"type_info": "Blob"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "adbce2c94ac0b54d0826b28f99fe63322d3bb1579e52d0f053307e24bd039ef9"
}

View file

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "SELECT * FROM relays WHERE id = ?", "query": "SELECT * FROM v_relays WHERE v_relays.id = ?",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -22,6 +22,11 @@
"name": "controller_id", "name": "controller_id",
"ordinal": 3, "ordinal": 3,
"type_info": "Int64" "type_info": "Int64"
},
{
"name": "controller_uid",
"ordinal": 4,
"type_info": "Blob"
} }
], ],
"parameters": { "parameters": {
@ -31,8 +36,9 @@
false, false,
false, false,
false, false,
false,
false false
] ]
}, },
"hash": "4f5408e64f5e6a8dd923c3b147f993ce9e4cafc90204b06977481130ec06d111" "hash": "d57c388bf6c26fe6cadad35d0f254ca2ef93958f9975c585c6de3c437782995d"
} }

View file

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO relays (name, number, controller_id) VALUES (?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "f85f0a96bb98d20e47677b0679d552812362c3141738b60bc63d673a7f552506"
}

1897
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "emgauwa-common" name = "emgauwa-common"
version = "0.5.0" version = "0.5.1"
edition = "2021" edition = "2021"
authors = ["Tobias Reisinger <tobias@msrg.cc>"] authors = ["Tobias Reisinger <tobias@msrg.cc>"]
@ -8,25 +8,21 @@ authors = ["Tobias Reisinger <tobias@msrg.cc>"]
[dependencies] [dependencies]
actix = "0.13" actix = "0.13"
actix-web = "4.4" actix-web = "4.4"
actix-web-actors = "4.2"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_with = "3.8"
simple_logger = "4.2" simple_logger = "5.0"
log = "0.4" log = "0.4"
config = "0.13" config = "0.14"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
sqlx = { version = "0.7", features = ["sqlite", "runtime-tokio", "macros", "chrono"] } sqlx = { version = "0.7", features = ["sqlite", "runtime-tokio", "macros"] }
libsqlite3-sys = { version = "*", features = ["bundled"] } libsqlite3-sys = { version = "*", features = ["bundled"] }
uuid = "1.6" uuid = { version = "1.8", features = ["v4"] }
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"

View file

@ -1,3 +1,5 @@
export DATABASE_URL=sqlite://${PWD}/emgauwa-dev.sqlite
sqlx: sqlx:
cargo sqlx database drop -y cargo sqlx database drop -y
cargo sqlx database create cargo sqlx database create

View file

@ -0,0 +1 @@
DROP VIEW v_relays;

View file

@ -0,0 +1,8 @@
CREATE VIEW v_relays
AS
SELECT
relays.*,
controllers.uid AS controller_uid
FROM
relays
INNER JOIN controllers ON controllers.id = relays.controller_id;

View file

@ -158,7 +158,7 @@ impl DbController {
) -> Result<Vec<DbRelay>, DatabaseError> { ) -> Result<Vec<DbRelay>, DatabaseError> {
sqlx::query_as!( sqlx::query_as!(
DbRelay, DbRelay,
"SELECT * FROM relays WHERE controller_id = ?", "SELECT * FROM v_relays WHERE v_relays.controller_id = ?",
self.id self.id
) )
.fetch_all(conn.deref_mut()) .fetch_all(conn.deref_mut())

View file

@ -5,11 +5,10 @@ use sqlx::Sqlite;
use crate::db::{DbRelay, DbSchedule}; use crate::db::{DbRelay, DbSchedule};
use crate::errors::DatabaseError; use crate::errors::DatabaseError;
use crate::types::Weekday;
pub struct DbJunctionRelaySchedule { pub struct DbJunctionRelaySchedule {
pub id: i64, pub id: i64,
pub weekday: Weekday, pub weekday: i64,
pub relay_id: i64, pub relay_id: i64,
pub schedule_id: i64, pub schedule_id: i64,
} }
@ -32,7 +31,7 @@ impl DbJunctionRelaySchedule {
pub async fn get_junction_by_relay_and_weekday( pub async fn get_junction_by_relay_and_weekday(
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
relay: &DbRelay, relay: &DbRelay,
weekday: Weekday, weekday: i64,
) -> Result<Option<DbJunctionRelaySchedule>, DatabaseError> { ) -> Result<Option<DbJunctionRelaySchedule>, DatabaseError> {
sqlx::query_as!( sqlx::query_as!(
DbJunctionRelaySchedule, DbJunctionRelaySchedule,
@ -51,8 +50,8 @@ impl DbJunctionRelaySchedule {
) -> Result<Vec<DbRelay>, DatabaseError> { ) -> Result<Vec<DbRelay>, DatabaseError> {
sqlx::query_as!( sqlx::query_as!(
DbRelay, DbRelay,
r#"SELECT relays.* FROM relays INNER JOIN junction_relay_schedule r#"SELECT v_relays.* FROM v_relays INNER JOIN junction_relay_schedule
ON junction_relay_schedule.relay_id = relays.id ON junction_relay_schedule.relay_id = v_relays.id
WHERE junction_relay_schedule.schedule_id = ? WHERE junction_relay_schedule.schedule_id = ?
ORDER BY junction_relay_schedule.weekday"#, ORDER BY junction_relay_schedule.weekday"#,
schedule.id schedule.id
@ -65,7 +64,7 @@ impl DbJunctionRelaySchedule {
pub async fn get_schedule( pub async fn get_schedule(
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
relay: &DbRelay, relay: &DbRelay,
weekday: Weekday, weekday: i64,
) -> Result<Option<DbSchedule>, DatabaseError> { ) -> Result<Option<DbSchedule>, DatabaseError> {
sqlx::query_as!( sqlx::query_as!(
DbSchedule, DbSchedule,
@ -101,7 +100,7 @@ impl DbJunctionRelaySchedule {
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
relay: &DbRelay, relay: &DbRelay,
schedule: &DbSchedule, schedule: &DbSchedule,
weekday: Weekday, weekday: i64,
) -> Result<DbJunctionRelaySchedule, DatabaseError> { ) -> Result<DbJunctionRelaySchedule, DatabaseError> {
match Self::get_junction_by_relay_and_weekday(conn, relay, weekday).await? { match Self::get_junction_by_relay_and_weekday(conn, relay, weekday).await? {
None => sqlx::query_as!( None => sqlx::query_as!(
@ -139,7 +138,7 @@ impl DbJunctionRelaySchedule {
schedules: Vec<&DbSchedule>, schedules: Vec<&DbSchedule>,
) -> Result<(), DatabaseError> { ) -> Result<(), DatabaseError> {
for (weekday, schedule) in schedules.iter().enumerate() { for (weekday, schedule) in schedules.iter().enumerate() {
Self::set_schedule(conn, relay, schedule, weekday as Weekday).await?; Self::set_schedule(conn, relay, schedule, weekday as i64).await?;
} }
Ok(()) Ok(())
} }

View file

@ -97,7 +97,7 @@ impl DbMacro {
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
new_name: &str, new_name: &str,
) -> Result<DbMacro, DatabaseError> { ) -> Result<DbMacro, DatabaseError> {
sqlx::query!("UPDATE relays SET name = ? WHERE id = ?", new_name, self.id,) sqlx::query!("UPDATE macros SET name = ? WHERE id = ?", new_name, self.id,)
.execute(conn.deref_mut()) .execute(conn.deref_mut())
.await?; .await?;

View file

@ -23,6 +23,9 @@ pub use relays::DbRelay;
pub use schedules::{DbPeriods, DbSchedule}; pub use schedules::{DbPeriods, DbSchedule};
pub use tag::DbTag; pub use tag::DbTag;
#[cfg(test)]
pub(crate) use model_utils::Period;
use crate::errors::{DatabaseError, EmgauwaError}; use crate::errors::{DatabaseError, EmgauwaError};
static MIGRATOR: Migrator = sqlx::migrate!(); // defaults to "./migrations" static MIGRATOR: Migrator = sqlx::migrate!(); // defaults to "./migrations"
@ -33,14 +36,14 @@ pub async fn run_migrations(pool: &Pool<Sqlite>) -> Result<(), EmgauwaError> {
Ok(()) Ok(())
} }
pub async fn init(db: &str) -> Result<Pool<Sqlite>, EmgauwaError> { pub async fn init(db: &str, pool_size: u32) -> Result<Pool<Sqlite>, EmgauwaError> {
let options = SqliteConnectOptions::from_str(db)? let options = SqliteConnectOptions::from_str(db)?
.create_if_missing(true) .create_if_missing(true)
.log_statements(log::LevelFilter::Trace); .log_statements(log::LevelFilter::Trace);
let pool: Pool<Sqlite> = SqlitePoolOptions::new() let pool: Pool<Sqlite> = SqlitePoolOptions::new()
.acquire_timeout(std::time::Duration::from_secs(1)) .acquire_timeout(std::time::Duration::from_secs(1))
.max_connections(5) .max_connections(pool_size)
.connect_with(options) .connect_with(options)
.await?; .await?;

View file

@ -51,13 +51,34 @@ impl Period {
} }
} }
pub fn is_always_on(&self) -> bool {
self.start.eq(&self.end)
}
pub fn is_on(&self, now: &NaiveTime) -> bool { pub fn is_on(&self, now: &NaiveTime) -> bool {
self.start.eq(&self.end) || (self.start.le(now) && self.end.gt(now)) if self.is_always_on() {
return true;
}
let start_after_now = self.start.gt(now);
// add check for end time being 00:00 because end being 00:00 would cause end_after_now to always be false
// this will handle end like 24:00 and end_after_now will be true
// same for start_before_end
let end_after_now = self.end.gt(now) || self.end.eq(&NaiveTime::MIN);
let start_before_end = self.start.lt(&self.end) || self.end.eq(&NaiveTime::MIN);
match (start_after_now, end_after_now, start_before_end) {
(false, false, true) => false, // both before now; start before end means "normal" period before now
(false, false, false) => true, // both before now; end before start means "inverse" period around now
(true, false, _) => false, // only start after now
(false, true, _) => true, // only end after now
(true, true, true) => false, // both after now but start first
(true, true, false) => true, // both after now but end first
}
} }
pub fn get_next_time(&self, now: &NaiveTime) -> Option<NaiveTime> { pub fn get_next_time(&self, now: &NaiveTime) -> Option<NaiveTime> {
if self.start.eq(&self.end) { if self.is_always_on() {
// this period is always on
return None; return None;
} }

View file

@ -4,10 +4,9 @@ use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection; use sqlx::pool::PoolConnection;
use sqlx::Sqlite; use sqlx::Sqlite;
use crate::db::{DbController, DbJunctionRelaySchedule, DbJunctionTag, DbSchedule, DbTag}; use crate::db::{DbController, DbJunctionTag, DbTag};
use crate::errors::DatabaseError; use crate::errors::DatabaseError;
use crate::types::Weekday; use crate::types::EmgauwaUid;
use crate::utils;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DbRelay { pub struct DbRelay {
@ -15,13 +14,15 @@ pub struct DbRelay {
pub id: i64, pub id: i64,
pub name: String, pub name: String,
pub number: i64, pub number: i64,
#[serde(rename = "controller_id")]
pub controller_uid: EmgauwaUid,
#[serde(skip)] #[serde(skip)]
pub controller_id: i64, pub controller_id: i64,
} }
impl DbRelay { impl DbRelay {
pub async fn get_all(conn: &mut PoolConnection<Sqlite>) -> Result<Vec<DbRelay>, DatabaseError> { pub async fn get_all(conn: &mut PoolConnection<Sqlite>) -> Result<Vec<DbRelay>, DatabaseError> {
sqlx::query_as!(DbRelay, "SELECT * FROM relays") sqlx::query_as!(DbRelay, "SELECT * FROM v_relays")
.fetch_all(conn.deref_mut()) .fetch_all(conn.deref_mut())
.await .await
.map_err(DatabaseError::from) .map_err(DatabaseError::from)
@ -31,7 +32,7 @@ impl DbRelay {
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
id: i64, id: i64,
) -> Result<Option<DbRelay>, DatabaseError> { ) -> Result<Option<DbRelay>, DatabaseError> {
sqlx::query_as!(DbRelay, "SELECT * FROM relays WHERE id = ?", id) sqlx::query_as!(DbRelay, "SELECT * FROM v_relays WHERE v_relays.id = ?", id)
.fetch_optional(conn.deref_mut()) .fetch_optional(conn.deref_mut())
.await .await
.map_err(DatabaseError::from) .map_err(DatabaseError::from)
@ -44,7 +45,7 @@ impl DbRelay {
) -> Result<Option<DbRelay>, DatabaseError> { ) -> Result<Option<DbRelay>, DatabaseError> {
sqlx::query_as!( sqlx::query_as!(
DbRelay, DbRelay,
"SELECT * FROM relays WHERE controller_id = ? AND number = ?", "SELECT * FROM v_relays WHERE v_relays.controller_id = ? AND v_relays.number = ?",
controller.id, controller.id,
number number
) )
@ -72,7 +73,7 @@ impl DbRelay {
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
tag: &DbTag, tag: &DbTag,
) -> Result<Vec<DbRelay>, DatabaseError> { ) -> Result<Vec<DbRelay>, DatabaseError> {
sqlx::query_as!(DbRelay, "SELECT relay.* FROM relays AS relay INNER JOIN junction_tag ON junction_tag.relay_id = relay.id WHERE junction_tag.tag_id = ?", tag.id) sqlx::query_as!(DbRelay, "SELECT v_relays.* FROM v_relays INNER JOIN junction_tag ON junction_tag.relay_id = v_relays.id WHERE junction_tag.tag_id = ?", tag.id)
.fetch_all(conn.deref_mut()) .fetch_all(conn.deref_mut())
.await .await
.map_err(DatabaseError::from) .map_err(DatabaseError::from)
@ -84,16 +85,25 @@ impl DbRelay {
new_number: i64, new_number: i64,
new_controller: &DbController, new_controller: &DbController,
) -> Result<DbRelay, DatabaseError> { ) -> Result<DbRelay, DatabaseError> {
sqlx::query_as!( let result = sqlx::query!(
DbRelay, "INSERT INTO relays (name, number, controller_id) VALUES (?, ?, ?)",
"INSERT INTO relays (name, number, controller_id) VALUES (?, ?, ?) RETURNING *",
new_name, new_name,
new_number, new_number,
new_controller.id, new_controller.id,
) )
.fetch_optional(conn.deref_mut()) .execute(conn.deref_mut())
.await? .await?;
.ok_or(DatabaseError::InsertGetError)
let last_insert_id = result.last_insert_rowid();
sqlx::query_as!(
DbRelay,
"SELECT * FROM v_relays WHERE id = ?",
last_insert_id
)
.fetch_one(conn.deref_mut())
.await
.map_err(DatabaseError::from)
} }
pub async fn delete(&self, conn: &mut PoolConnection<Sqlite>) -> Result<(), DatabaseError> { pub async fn delete(&self, conn: &mut PoolConnection<Sqlite>) -> Result<(), DatabaseError> {
@ -163,14 +173,4 @@ impl DbRelay {
.await? .await?
.ok_or(DatabaseError::NotFound) .ok_or(DatabaseError::NotFound)
} }
pub async fn get_active_schedule(
&self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<DbSchedule, DatabaseError> {
let weekday = utils::get_weekday();
DbJunctionRelaySchedule::get_schedule(conn, self, weekday as Weekday)
.await?
.ok_or(DatabaseError::NotFound)
}
} }

View file

@ -1,35 +0,0 @@
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
}
}

View file

@ -1,19 +0,0 @@
mod gpio;
mod null;
mod piface;
pub use gpio::GpioDriver;
pub use null::NullDriver;
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;
}

View file

@ -1,26 +0,0 @@
use crate::drivers::RelayDriver;
use crate::errors::EmgauwaError;
pub struct NullDriver {
pub pin: u8,
}
impl NullDriver {
pub fn new(pin: u8) -> Self {
Self { pin }
}
}
impl RelayDriver for NullDriver {
fn set(&mut self, _value: bool) -> Result<(), EmgauwaError> {
Ok(())
}
fn get_pin(&self) -> u8 {
self.pin
}
fn get_inverted(&self) -> bool {
false
}
}

View file

@ -1,52 +0,0 @@
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
}
}

View file

@ -6,9 +6,6 @@ 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};
@ -50,7 +47,9 @@ impl From<&EmgauwaError> for String {
EmgauwaError::Database(err) => String::from(err), EmgauwaError::Database(err) => String::from(err),
EmgauwaError::Uid(_) => String::from("the uid is in a bad format"), EmgauwaError::Uid(_) => String::from("the uid is in a bad format"),
EmgauwaError::Internal(_) => String::from("internal error"), EmgauwaError::Internal(_) => String::from("internal error"),
EmgauwaError::Connection(_) => String::from("the target controller is not connected"), EmgauwaError::Connection(uid) => {
format!("unable to connect to controller with uid: {}", uid)
}
EmgauwaError::Other(err) => format!("other error: {}", err), EmgauwaError::Other(err) => format!("other error: {}", err),
EmgauwaError::Hardware(err) => format!("hardware error: {}", err), EmgauwaError::Hardware(err) => format!("hardware error: {}", err),
} }
@ -99,25 +98,6 @@ 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)
@ -139,7 +119,7 @@ impl Serialize for EmgauwaError {
{ {
let mut s = serializer.serialize_struct("error", 2)?; let mut s = serializer.serialize_struct("error", 2)?;
s.serialize_field("code", &self.get_code().as_u16())?; s.serialize_field("code", &self.get_code().as_u16())?;
s.serialize_field("description", &String::from(self))?; s.serialize_field("message", &String::from(self))?;
s.end() s.end()
} }
} }

View file

@ -1,8 +1,72 @@
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;
pub mod types; pub mod types;
pub mod utils; pub mod utils;
#[cfg(test)]
mod periods {
use chrono::NaiveTime;
use crate::db::Period;
use crate::types::EmgauwaNow;
const MIDNIGHT: NaiveTime = NaiveTime::MIN;
fn new_time(hour: u32, minute: u32) -> NaiveTime {
NaiveTime::from_hms_opt(hour, minute, 0).expect("Failed to create NaiveTime")
}
fn new_period(start_hour: u32, start_minute: u32, end_hour: u32, end_minute: u32) -> Period {
Period {
start: new_time(start_hour, start_minute),
end: new_time(end_hour, end_minute),
}
}
#[test]
fn always_on() {
let period = Period::new_on();
let now: EmgauwaNow = EmgauwaNow::now(&MIDNIGHT);
assert_eq!(period.is_always_on(), true);
assert_eq!(period.is_on(&MIDNIGHT), true);
assert_eq!(period.is_on(&new_time(12, 00)), true);
assert_eq!(period.is_on(&now.time), true);
}
#[test]
fn simple_period() {
let period = new_period(11, 00, 13, 00);
assert_eq!(period.is_always_on(), false);
assert_eq!(period.is_on(&MIDNIGHT), false);
assert_eq!(period.is_on(&new_time(10, 00)), false);
assert_eq!(period.is_on(&new_time(11, 00)), true);
assert_eq!(period.is_on(&new_time(12, 00)), true);
assert_eq!(period.is_on(&new_time(13, 00)), false);
assert_eq!(period.is_on(&new_time(14, 00)), false);
}
#[test]
fn to_midnight_period() {
let period = new_period(22, 00, 00, 00);
assert_eq!(period.is_always_on(), false);
assert_eq!(period.is_on(&MIDNIGHT), false);
assert_eq!(period.is_on(&new_time(21, 00)), false);
assert_eq!(period.is_on(&new_time(22, 00)), true);
assert_eq!(period.is_on(&new_time(23, 00)), true);
assert_eq!(period.is_on(&new_time(00, 00)), false);
assert_eq!(period.is_on(&new_time(01, 00)), false);
}
#[test]
fn from_midnight_period() {
let period = new_period(00, 00, 02, 00);
assert_eq!(period.is_always_on(), false);
assert_eq!(period.is_on(&MIDNIGHT), true);
assert_eq!(period.is_on(&new_time(23, 00)), false);
assert_eq!(period.is_on(&new_time(00, 00)), true);
assert_eq!(period.is_on(&new_time(01, 00)), true);
assert_eq!(period.is_on(&new_time(02, 00)), false);
assert_eq!(period.is_on(&new_time(03, 00)), false);
}
}

View file

@ -9,8 +9,8 @@ use sqlx::Sqlite;
use crate::db::DbController; use crate::db::DbController;
use crate::errors::{DatabaseError, EmgauwaError}; use crate::errors::{DatabaseError, EmgauwaError};
use crate::models::{convert_db_list_cache, FromDbModel, Relay}; use crate::models::{convert_db_list, FromDbModel, Relay};
use crate::types::RelayStates; use crate::types::{EmgauwaNow, RelayState, RelayStates};
#[derive(Serialize, Deserialize, Debug, Clone, MessageResponse)] #[derive(Serialize, Deserialize, Debug, Clone, MessageResponse)]
pub struct Controller { pub struct Controller {
@ -28,7 +28,7 @@ impl FromDbModel for Controller {
db_model: Self::DbModel, db_model: Self::DbModel,
) -> Result<Self, DatabaseError> { ) -> Result<Self, DatabaseError> {
let relays_db = block_on(db_model.get_relays(conn))?; let relays_db = block_on(db_model.get_relays(conn))?;
let cache = convert_db_list_cache(conn, relays_db, db_model.clone())?; let cache = convert_db_list(conn, relays_db)?;
Self::from_db_model_cache(conn, db_model, cache) Self::from_db_model_cache(conn, db_model, cache)
} }
@ -57,19 +57,20 @@ impl Controller {
self.relays self.relays
.iter_mut() .iter_mut()
.zip(relay_states.iter()) .zip(relay_states.iter())
.for_each(|(relay, is_on)| { .for_each(|(relay, state)| relay.apply_state(state));
relay.is_on = *is_on;
});
} }
pub fn get_relay_states(&self) -> RelayStates { pub fn get_relay_states(&self) -> RelayStates {
self.relays.iter().map(|r| r.is_on).collect() self.relays.iter().map(RelayState::from).collect()
} }
pub fn get_next_time(&self, now: &NaiveTime) -> Option<NaiveTime> { pub fn check_next_time(&mut self, now: &EmgauwaNow) -> Option<NaiveTime> {
self.relays self.relays
.iter() .iter_mut()
.filter_map(|r| r.active_schedule.get_next_time(now)) .filter_map(|r| {
r.reload_active_schedule(now.weekday);
r.get_next_time(&now.time)
})
.min() .min()
} }
@ -80,6 +81,8 @@ impl Controller {
.find(|r| r.r.number == relay_num) .find(|r| r.r.number == relay_num)
.ok_or(EmgauwaError::Other(String::from("Relay not found")))?; .ok_or(EmgauwaError::Other(String::from("Relay not found")))?;
log::debug!("Pulsing relay {} until {:?}", relay_num, until);
relay.pulsing = Some(until); relay.pulsing = Some(until);
Ok(()) Ok(())
} }

View file

@ -3,13 +3,13 @@ use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection; use sqlx::pool::PoolConnection;
use sqlx::Sqlite; use sqlx::Sqlite;
use crate::db::{DbJunctionRelaySchedule, DbMacroAction}; use crate::db::{DbJunctionRelaySchedule, DbMacroAction, DbSchedule};
use crate::errors::{DatabaseError, EmgauwaError}; use crate::errors::{DatabaseError, EmgauwaError};
use crate::models::{FromDbModel, Relay, Schedule}; use crate::models::{FromDbModel, Relay};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MacroAction { pub struct MacroAction {
pub schedule: Schedule, pub schedule: DbSchedule,
pub relay: Relay, pub relay: Relay,
pub weekday: i64, pub weekday: i64,
} }
@ -30,8 +30,7 @@ impl FromDbModel for MacroAction {
db_model: Self::DbModel, db_model: Self::DbModel,
_cache: Self::DbModelCache, _cache: Self::DbModelCache,
) -> Result<Self, DatabaseError> { ) -> Result<Self, DatabaseError> {
let schedule_db = block_on(db_model.get_schedule(conn))?; let schedule = block_on(db_model.get_schedule(conn))?;
let schedule = Schedule::from_db_model(conn, schedule_db)?;
let relay_db = block_on(db_model.get_relay(conn))?; let relay_db = block_on(db_model.get_relay(conn))?;
let relay = Relay::from_db_model(conn, relay_db)?; let relay = Relay::from_db_model(conn, relay_db)?;
@ -48,7 +47,7 @@ impl FromDbModel for MacroAction {
impl MacroAction { impl MacroAction {
pub async fn execute(&self, conn: &mut PoolConnection<Sqlite>) -> Result<(), EmgauwaError> { pub async fn execute(&self, conn: &mut PoolConnection<Sqlite>) -> Result<(), EmgauwaError> {
DbJunctionRelaySchedule::set_schedule(conn, &self.relay.r, &self.schedule.s, self.weekday) DbJunctionRelaySchedule::set_schedule(conn, &self.relay.r, &self.schedule, self.weekday)
.await?; .await?;
Ok(()) Ok(())
} }

View file

@ -1,66 +1,65 @@
use std::time::Instant; use std::time::Instant;
use chrono::NaiveTime; use chrono::{NaiveTime, Weekday};
use futures::executor::block_on; use futures::executor::block_on;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use sqlx::pool::PoolConnection; use sqlx::pool::PoolConnection;
use sqlx::Sqlite; use sqlx::Sqlite;
use crate::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbSchedule}; use crate::db::{DbJunctionRelaySchedule, DbRelay, DbSchedule};
use crate::errors::DatabaseError; use crate::errors::DatabaseError;
use crate::models::FromDbModel; use crate::models::FromDbModel;
use crate::types::EmgauwaUid; use crate::types::RelayState;
use crate::utils;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Relay { pub struct Relay {
#[serde(flatten)] #[serde(flatten)]
pub r: DbRelay, pub r: DbRelay,
pub controller: DbController,
pub controller_id: EmgauwaUid,
pub schedules: Vec<DbSchedule>, pub schedules: Vec<DbSchedule>,
pub active_schedule: DbSchedule, pub active_schedule: Option<DbSchedule>,
pub override_schedule: Option<DbSchedule>,
pub is_on: Option<bool>, pub is_on: Option<bool>,
pub tags: Vec<String>, pub tags: Vec<String>,
// for internal use only. // for internal use only.
#[serde(skip)] #[serde(skip)]
pub pulsing: Option<Instant>, pub pulsing: Option<Instant>,
#[serde(skip, default = "utils::default_weekday")]
pub override_schedule_weekday: Weekday,
} }
impl FromDbModel for Relay { impl FromDbModel for Relay {
type DbModel = DbRelay; type DbModel = DbRelay;
type DbModelCache = DbController; type DbModelCache = ();
fn from_db_model( fn from_db_model(
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
db_model: Self::DbModel, db_model: Self::DbModel,
) -> Result<Self, DatabaseError> { ) -> Result<Self, DatabaseError> {
let cache = block_on(db_model.get_controller(conn))?; Self::from_db_model_cache(conn, db_model, ())
Self::from_db_model_cache(conn, db_model, cache)
} }
fn from_db_model_cache( fn from_db_model_cache(
conn: &mut PoolConnection<Sqlite>, conn: &mut PoolConnection<Sqlite>,
db_model: Self::DbModel, db_model: Self::DbModel,
cache: Self::DbModelCache, _cache: Self::DbModelCache,
) -> Result<Self, DatabaseError> { ) -> Result<Self, DatabaseError> {
let tags = block_on(db_model.get_tags(conn))?; let tags = block_on(db_model.get_tags(conn))?;
let controller_id = cache.uid.clone();
let schedules = block_on(DbJunctionRelaySchedule::get_schedules(conn, &db_model))?; let schedules = block_on(DbJunctionRelaySchedule::get_schedules(conn, &db_model))?;
let active_schedule = block_on(db_model.get_active_schedule(conn))?;
let is_on = None; let is_on = None;
Ok(Relay { Ok(Relay {
r: db_model, r: db_model,
controller: cache,
controller_id,
schedules, schedules,
active_schedule, active_schedule: None,
override_schedule: None,
is_on, is_on,
tags, tags,
pulsing: None, pulsing: None,
override_schedule_weekday: utils::default_weekday(),
}) })
} }
} }
@ -69,25 +68,24 @@ impl Relay {
pub fn reload(&mut self, conn: &mut PoolConnection<Sqlite>) -> Result<(), DatabaseError> { pub fn reload(&mut self, conn: &mut PoolConnection<Sqlite>) -> Result<(), DatabaseError> {
self.r = block_on(self.r.reload(conn))?; self.r = block_on(self.r.reload(conn))?;
self.schedules = block_on(DbJunctionRelaySchedule::get_schedules(conn, &self.r))?; self.schedules = block_on(DbJunctionRelaySchedule::get_schedules(conn, &self.r))?;
self.reload_active_schedule(conn)?;
Ok(()) Ok(())
} }
pub fn reload_active_schedule(
&mut self,
conn: &mut PoolConnection<Sqlite>,
) -> Result<(), DatabaseError> {
self.active_schedule = block_on(self.r.get_active_schedule(conn))?;
Ok(())
}
pub fn is_on(&self, now: &NaiveTime) -> bool { pub fn is_on(&self, now: &NaiveTime) -> bool {
self.active_schedule.is_on(now) if let Some(active_schedule) = &self.active_schedule {
active_schedule.is_on(now)
} else {
false
}
} }
pub fn get_next_time(&self, now: &NaiveTime) -> Option<NaiveTime> { pub fn get_next_time(&self, now: &NaiveTime) -> Option<NaiveTime> {
self.active_schedule.get_next_time(now) if let Some(active_schedule) = &self.active_schedule {
active_schedule.get_next_time(now)
} else {
None
}
} }
pub fn check_pulsing(&mut self, now: &Instant) -> Option<Instant> { pub fn check_pulsing(&mut self, now: &Instant) -> Option<Instant> {
@ -103,4 +101,32 @@ impl Relay {
None => None, None => None,
} }
} }
pub fn reload_active_schedule(&mut self, weekday: Weekday) {
if let Some(schedule) = &self.override_schedule {
if self.override_schedule_weekday == weekday {
self.active_schedule = Some(schedule.clone());
return;
}
if self.override_schedule_weekday != weekday {
self.override_schedule = None;
}
}
if let Some(schedule) = self.schedules.get(weekday as usize) {
self.active_schedule = Some(schedule.clone());
}
}
pub fn apply_state(&mut self, state: &RelayState) {
self.active_schedule.clone_from(&state.active_schedule);
self.override_schedule.clone_from(&state.override_schedule);
self.is_on = state.is_on;
}
pub fn find_and_apply_state(&mut self, stated_relays: &[Relay]) {
if let Some(stated_relay) = stated_relays.iter().find(|r| r.r.id == self.r.id) {
self.apply_state(&stated_relay.into());
}
}
} }

View file

@ -16,7 +16,6 @@ pub struct Server {
#[allow(unused)] #[allow(unused)]
pub struct Logging { pub struct Logging {
pub level: String, pub level: String,
pub file: String,
} }
#[derive(Clone, Debug, Deserialize, Default)] #[derive(Clone, Debug, Deserialize, Default)]
@ -40,7 +39,6 @@ impl Default for Logging {
fn default() -> Self { fn default() -> Self {
Logging { Logging {
level: String::from("info"), level: String::from("info"),
file: String::from("stdout"),
} }
} }
} }

27
src/types/emgauwa_now.rs Normal file
View file

@ -0,0 +1,27 @@
use std::time::Instant;
use chrono::{Local, NaiveTime, Timelike, Weekday};
use crate::utils;
pub struct EmgauwaNow {
pub time: NaiveTime,
pub instant: Instant,
pub weekday: Weekday,
pub midnight: NaiveTime,
}
impl EmgauwaNow {
pub fn now(midnight: &NaiveTime) -> EmgauwaNow {
EmgauwaNow {
time: Local::now().time(),
instant: Instant::now(),
weekday: utils::get_weekday(midnight),
midnight: *midnight,
}
}
pub fn num_seconds_from_midnight(&self) -> u32 {
self.time.num_seconds_from_midnight()
}
}

View file

@ -1,9 +1,13 @@
mod emgauwa_now;
mod emgauwa_uid; mod emgauwa_uid;
mod request; mod request;
mod schedule_uid; mod schedule_uid;
mod relay_state;
use actix::Message; use actix::Message;
pub use emgauwa_now::EmgauwaNow;
pub use emgauwa_uid::EmgauwaUid; pub use emgauwa_uid::EmgauwaUid;
pub use relay_state::{RelayState, RelayStates};
pub use request::*; pub use request::*;
pub use schedule_uid::ScheduleUid; pub use schedule_uid::ScheduleUid;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@ -12,10 +16,6 @@ use crate::db::DbSchedule;
use crate::errors::EmgauwaError; use crate::errors::EmgauwaError;
use crate::models::{Controller, Relay}; use crate::models::{Controller, Relay};
pub type Weekday = i64;
pub type RelayStates = Vec<Option<bool>>;
#[derive(Debug, Serialize, Deserialize, Message)] #[derive(Debug, Serialize, Deserialize, Message)]
#[rtype(result = "Result<(), EmgauwaError>")] #[rtype(result = "Result<(), EmgauwaError>")]
pub enum ControllerWsAction { pub enum ControllerWsAction {

22
src/types/relay_state.rs Normal file
View file

@ -0,0 +1,22 @@
use serde_derive::{Deserialize, Serialize};
use crate::db::DbSchedule;
use crate::models::Relay;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelayState {
pub active_schedule: Option<DbSchedule>,
pub override_schedule: Option<DbSchedule>,
pub is_on: Option<bool>
}
pub type RelayStates = Vec<RelayState>;
impl From<&Relay> for RelayState {
fn from(relay: &Relay) -> Self {
RelayState {
active_schedule: relay.active_schedule.clone(),
override_schedule: relay.override_schedule.clone(),
is_on: relay.is_on
}
}
}

View file

@ -23,11 +23,21 @@ pub struct RequestScheduleUpdate {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct RequestRelayUpdate { pub struct RequestRelayUpdate {
pub name: Option<String>, pub name: Option<String>,
pub active_schedule: Option<RequestScheduleId>, #[serde(
default, // <- important for deserialization
skip_serializing_if = "Option::is_none", // <- important for serialization
with = "::serde_with::rust::double_option",
)]
pub override_schedule: Option<Option<RequestScheduleId>>,
pub schedules: Option<Vec<RequestScheduleId>>, pub schedules: Option<Vec<RequestScheduleId>>,
pub tags: Option<Vec<String>>, pub tags: Option<Vec<String>>,
} }
#[derive(Debug, Deserialize)]
pub struct RequestScheduleGetTagged {
pub strict: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct RequestRelayPulse { pub struct RequestRelayPulse {
pub duration: Option<u32>, pub duration: Option<u32>,

View file

@ -2,17 +2,17 @@ use std::ffi::CString;
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
use std::str::FromStr; use std::str::FromStr;
use chrono::Datelike; use chrono::{Datelike, NaiveTime, Weekday};
use log::LevelFilter; use log::LevelFilter;
use simple_logger::SimpleLogger; use simple_logger::SimpleLogger;
use crate::errors::EmgauwaError; use crate::errors::EmgauwaError;
use crate::settings::Permissions; use crate::settings::{Logging, Permissions};
use crate::types::{RelayStates, Weekday}; use crate::types::RelayStates;
pub fn init_logging(level: &str) -> Result<(), EmgauwaError> { pub fn init_logging(logging: &Logging) -> Result<(), EmgauwaError> {
let log_level: LevelFilter = LevelFilter::from_str(level) let log_level: LevelFilter = LevelFilter::from_str(&logging.level)
.map_err(|_| EmgauwaError::Other(format!("Invalid log level: {}", level)))?; .map_err(|_| EmgauwaError::Other(format!("Invalid log level: {}", logging.level)))?;
log::trace!("Log level set to {:?}", log_level); log::trace!("Log level set to {:?}", log_level);
SimpleLogger::new() SimpleLogger::new()
@ -92,18 +92,24 @@ fn drop_privileges_user(user: &str) -> Result<(), Error> {
Ok(()) Ok(())
} }
pub fn get_weekday() -> Weekday { pub fn get_weekday(midnight: &NaiveTime) -> Weekday {
(chrono::offset::Local::now() let dt = chrono::offset::Local::now().naive_local();
.date_naive() let weekday = dt.weekday();
.weekday() if dt.time().lt(midnight) {
.number_from_monday() weekday.pred()
- 1) as Weekday } else {
weekday
}
}
pub fn default_weekday() -> Weekday {
Weekday::Mon
} }
pub fn printable_relay_states(relay_states: &RelayStates) -> String { pub fn printable_relay_states(relay_states: &RelayStates) -> String {
let mut relay_debug = String::new(); let mut relay_debug = String::new();
relay_states.iter().for_each(|state| { relay_states.iter().for_each(|state| {
relay_debug.push_str(match state { relay_debug.push_str(match state.is_on {
Some(true) => "+", Some(true) => "+",
Some(false) => "-", Some(false) => "-",
None => "?", None => "?",