From 9326b66007f61cb3c92cf3774f2c6a8d238a3b7d Mon Sep 17 00:00:00 2001
From: Tobias Reisinger <tobias@msrg.cc>
Date: Mon, 13 May 2024 19:17:35 +0200
Subject: [PATCH 1/3] Simplify schedule in macro action

---
 src/models/macro_action.rs | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/models/macro_action.rs b/src/models/macro_action.rs
index a36a45a..786bf81 100644
--- a/src/models/macro_action.rs
+++ b/src/models/macro_action.rs
@@ -3,13 +3,13 @@ use serde_derive::{Deserialize, Serialize};
 use sqlx::pool::PoolConnection;
 use sqlx::Sqlite;
 
-use crate::db::{DbJunctionRelaySchedule, DbMacroAction};
+use crate::db::{DbJunctionRelaySchedule, DbMacroAction, DbSchedule};
 use crate::errors::{DatabaseError, EmgauwaError};
-use crate::models::{FromDbModel, Relay, Schedule};
+use crate::models::{FromDbModel, Relay};
 
 #[derive(Serialize, Deserialize, Debug, Clone)]
 pub struct MacroAction {
-	pub schedule: Schedule,
+	pub schedule: DbSchedule,
 	pub relay: Relay,
 	pub weekday: i64,
 }
@@ -30,8 +30,7 @@ impl FromDbModel for MacroAction {
 		db_model: Self::DbModel,
 		_cache: Self::DbModelCache,
 	) -> Result<Self, DatabaseError> {
-		let schedule_db = block_on(db_model.get_schedule(conn))?;
-		let schedule = Schedule::from_db_model(conn, schedule_db)?;
+		let schedule = block_on(db_model.get_schedule(conn))?;
 
 		let relay_db = block_on(db_model.get_relay(conn))?;
 		let relay = Relay::from_db_model(conn, relay_db)?;
@@ -48,7 +47,7 @@ impl FromDbModel for MacroAction {
 
 impl MacroAction {
 	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?;
 		Ok(())
 	}

From 473832f58a258e404d7bba0ca20a3ff58e6ba11c Mon Sep 17 00:00:00 2001
From: Tobias Reisinger <tobias@msrg.cc>
Date: Sun, 26 May 2024 22:48:22 +0200
Subject: [PATCH 2/3] Rename active_schedule to override_schedule and add
 EmgauwaNow

---
 Cargo.lock                  | 117 +++++++++++++++++++++++++++++++++---
 Cargo.toml                  |   1 +
 src/db/model_utils.rs       |  14 ++---
 src/errors/emgauwa_error.rs |   4 +-
 src/models/controller.rs    |  26 +++++---
 src/models/relay.rs         |  46 +++++++++++---
 src/types/emgauwa_now.rs    |  26 ++++++++
 src/types/mod.rs            |  10 ++-
 src/types/request.rs        |   7 ++-
 src/utils.rs                |   2 +-
 10 files changed, 218 insertions(+), 35 deletions(-)
 create mode 100644 src/types/emgauwa_now.rs

diff --git a/Cargo.lock b/Cargo.lock
index 18d357b..1bee2db 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -55,7 +55,7 @@ dependencies = [
  "actix-service",
  "actix-utils",
  "ahash",
- "base64",
+ "base64 0.21.7",
  "bitflags 2.5.0",
  "brotli",
  "bytes",
@@ -355,6 +355,12 @@ version = "0.21.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
 [[package]]
 name = "base64ct"
 version = "1.6.0"
@@ -626,6 +632,41 @@ dependencies = [
  "typenum",
 ]
 
+[[package]]
+name = "darling"
+version = "0.20.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.60",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 2.0.60",
+]
+
 [[package]]
 name = "der"
 version = "0.7.9"
@@ -644,6 +685,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
 dependencies = [
  "powerfmt",
+ "serde",
 ]
 
 [[package]]
@@ -711,6 +753,7 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
+ "serde_with",
  "simple_logger",
  "sqlx",
  "uuid",
@@ -945,13 +988,19 @@ dependencies = [
  "futures-sink",
  "futures-util",
  "http",
- "indexmap",
+ "indexmap 2.2.6",
  "slab",
  "tokio",
  "tokio-util",
  "tracing",
 ]
 
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
 [[package]]
 name = "hashbrown"
 version = "0.13.2"
@@ -1065,6 +1114,12 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
 [[package]]
 name = "idna"
 version = "0.5.0"
@@ -1075,6 +1130,17 @@ dependencies = [
  "unicode-normalization",
 ]
 
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
 [[package]]
 name = "indexmap"
 version = "2.2.6"
@@ -1083,6 +1149,7 @@ checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
 dependencies = [
  "equivalent",
  "hashbrown 0.14.5",
+ "serde",
 ]
 
 [[package]]
@@ -1605,7 +1672,7 @@ version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
 dependencies = [
- "base64",
+ "base64 0.21.7",
  "bitflags 2.5.0",
  "serde",
  "serde_derive",
@@ -1739,6 +1806,36 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_with"
+version = "3.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20"
+dependencies = [
+ "base64 0.22.1",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.2.6",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.60",
+]
+
 [[package]]
 name = "sha1"
 version = "0.10.6"
@@ -1887,7 +1984,7 @@ dependencies = [
  "futures-util",
  "hashlink",
  "hex",
- "indexmap",
+ "indexmap 2.2.6",
  "log",
  "memchr",
  "once_cell",
@@ -1950,7 +2047,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418"
 dependencies = [
  "atoi",
- "base64",
+ "base64 0.21.7",
  "bitflags 2.5.0",
  "byteorder",
  "bytes",
@@ -1992,7 +2089,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
 dependencies = [
  "atoi",
- "base64",
+ "base64 0.21.7",
  "bitflags 2.5.0",
  "byteorder",
  "crc",
@@ -2057,6 +2154,12 @@ dependencies = [
  "unicode-normalization",
 ]
 
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
 [[package]]
 name = "subtle"
 version = "2.5.0"
@@ -2242,7 +2345,7 @@ version = "0.22.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
 dependencies = [
- "indexmap",
+ "indexmap 2.2.6",
  "serde",
  "serde_spanned",
  "toml_datetime",
diff --git a/Cargo.toml b/Cargo.toml
index 30ed02a..464e9b2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,6 +13,7 @@ actix-web-actors = "4.2"
 serde = "1.0"
 serde_json = "1.0"
 serde_derive = "1.0"
+serde_with = "3.8"
 
 simple_logger = "5.0"
 log = "0.4"
diff --git a/src/db/model_utils.rs b/src/db/model_utils.rs
index e178d8e..db845bc 100644
--- a/src/db/model_utils.rs
+++ b/src/db/model_utils.rs
@@ -1,10 +1,10 @@
 use chrono::{NaiveTime, Timelike};
 use serde::{Deserialize, Serialize};
-use sqlx::{Decode, Encode, Sqlite, Type};
 use sqlx::database::HasArguments;
 use sqlx::encode::IsNull;
 use sqlx::error::BoxDynError;
 use sqlx::sqlite::{SqliteTypeInfo, SqliteValueRef};
+use sqlx::{Decode, Encode, Sqlite, Type};
 
 use crate::db::DbPeriods;
 
@@ -67,12 +67,12 @@ impl Period {
 		let start_before_end = self.start.lt(&self.end);
 
 		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 "inversed" 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
+			(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 "inversed" 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
 		}
 	}
 
diff --git a/src/errors/emgauwa_error.rs b/src/errors/emgauwa_error.rs
index f37c38e..a833c78 100644
--- a/src/errors/emgauwa_error.rs
+++ b/src/errors/emgauwa_error.rs
@@ -47,7 +47,9 @@ impl From<&EmgauwaError> for String {
 			EmgauwaError::Database(err) => String::from(err),
 			EmgauwaError::Uid(_) => String::from("the uid is in a bad format"),
 			EmgauwaError::Internal(_) => String::from("internal error"),
-			EmgauwaError::Connection(uid) => format!("unable to connect to controller with uid: {}", uid),
+			EmgauwaError::Connection(uid) => {
+				format!("unable to connect to controller with uid: {}", uid)
+			}
 			EmgauwaError::Other(err) => format!("other error: {}", err),
 			EmgauwaError::Hardware(err) => format!("hardware error: {}", err),
 		}
diff --git a/src/models/controller.rs b/src/models/controller.rs
index 96d7b00..ebc556a 100644
--- a/src/models/controller.rs
+++ b/src/models/controller.rs
@@ -10,7 +10,7 @@ use sqlx::Sqlite;
 use crate::db::DbController;
 use crate::errors::{DatabaseError, EmgauwaError};
 use crate::models::{convert_db_list_cache, FromDbModel, Relay};
-use crate::types::RelayStates;
+use crate::types::{EmgauwaNow, RelayState, RelayStates};
 
 #[derive(Serialize, Deserialize, Debug, Clone, MessageResponse)]
 pub struct Controller {
@@ -57,19 +57,29 @@ impl Controller {
 		self.relays
 			.iter_mut()
 			.zip(relay_states.iter())
-			.for_each(|(relay, is_on)| {
-				relay.is_on = *is_on;
+			.for_each(|(relay, state)| {
+				relay.active_schedule = state.active_schedule.clone();
+				relay.is_on = state.is_on;
 			});
 	}
 
 	pub fn get_relay_states(&self) -> RelayStates {
-		self.relays.iter().map(|r| r.is_on).collect()
-	}
-
-	pub fn get_next_time(&self, now: &NaiveTime) -> Option<NaiveTime> {
 		self.relays
 			.iter()
-			.filter_map(|r| r.active_schedule.get_next_time(now))
+			.map(|r| RelayState {
+				active_schedule: r.active_schedule.clone(),
+				is_on: r.is_on,
+			})
+			.collect()
+	}
+
+	pub fn check_next_time(&mut self, now: &EmgauwaNow) -> Option<NaiveTime> {
+		self.relays
+			.iter_mut()
+			.filter_map(|r| {
+				r.reload_active_schedule(now.weekday);
+				r.active_schedule.get_next_time(&now.time)
+			})
 			.min()
 	}
 
diff --git a/src/models/relay.rs b/src/models/relay.rs
index e6ecd0f..f44b290 100644
--- a/src/models/relay.rs
+++ b/src/models/relay.rs
@@ -9,7 +9,8 @@ use sqlx::Sqlite;
 use crate::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbSchedule};
 use crate::errors::DatabaseError;
 use crate::models::FromDbModel;
-use crate::types::EmgauwaUid;
+use crate::types::{EmgauwaUid, Weekday};
+use crate::utils;
 
 #[derive(Serialize, Deserialize, Debug, Clone)]
 pub struct Relay {
@@ -19,12 +20,23 @@ pub struct Relay {
 	pub controller_id: EmgauwaUid,
 	pub schedules: Vec<DbSchedule>,
 	pub active_schedule: DbSchedule,
+	#[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<DbSchedule>>,
 	pub is_on: Option<bool>,
 	pub tags: Vec<String>,
 
 	// for internal use only.
 	#[serde(skip)]
 	pub pulsing: Option<Instant>,
+	#[serde(
+		skip,
+		default = "utils::get_weekday",
+	)]
+	pub override_schedule_weekday: Weekday,
 }
 
 impl FromDbModel for Relay {
@@ -58,9 +70,11 @@ impl FromDbModel for Relay {
 			controller_id,
 			schedules,
 			active_schedule,
+			override_schedule: None,
 			is_on,
 			tags,
 			pulsing: None,
+			override_schedule_weekday: Weekday::default(),
 		})
 	}
 }
@@ -69,16 +83,9 @@ impl Relay {
 	pub fn reload(&mut self, conn: &mut PoolConnection<Sqlite>) -> Result<(), DatabaseError> {
 		self.r = block_on(self.r.reload(conn))?;
 		self.schedules = block_on(DbJunctionRelaySchedule::get_schedules(conn, &self.r))?;
-		self.reload_active_schedule(conn)?;
 
-		Ok(())
-	}
+		self.reload_active_schedule(utils::get_weekday());
 
-	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(())
 	}
 
@@ -103,4 +110,25 @@ impl Relay {
 			None => None,
 		}
 	}
+
+	pub fn reload_active_schedule(&mut self, weekday: Weekday) {
+		if let Some((Some(schedule), schedule_weekday)) = self.unwrap_override_schedule() {
+			if schedule_weekday == weekday {
+				self.active_schedule = schedule.clone();
+				return;
+			}
+			if schedule_weekday != weekday {
+				self.override_schedule = None;
+			}
+		}
+
+		self.active_schedule = self.schedules.get(weekday as usize).unwrap().clone()
+	}
+
+	pub fn unwrap_override_schedule(&self) -> Option<(&Option<DbSchedule>, Weekday)> {
+		if let Some(schedule) = &self.override_schedule {
+			return Some((schedule, self.override_schedule_weekday));
+		}
+		None
+	}
 }
diff --git a/src/types/emgauwa_now.rs b/src/types/emgauwa_now.rs
new file mode 100644
index 0000000..40f4907
--- /dev/null
+++ b/src/types/emgauwa_now.rs
@@ -0,0 +1,26 @@
+use std::time::Instant;
+
+use chrono::{Local, NaiveTime, Timelike};
+
+use crate::types::Weekday;
+use crate::utils;
+
+pub struct EmgauwaNow {
+	pub time: NaiveTime,
+	pub instant: Instant,
+	pub weekday: Weekday,
+}
+
+impl EmgauwaNow {
+	pub fn now() -> EmgauwaNow {
+		EmgauwaNow {
+			time: Local::now().time(),
+			instant: Instant::now(),
+			weekday: utils::get_weekday(),
+		}
+	}
+
+	pub fn time_in_s(&self) -> u32 {
+		self.time.num_seconds_from_midnight()
+	}
+}
diff --git a/src/types/mod.rs b/src/types/mod.rs
index dbbfb77..24f3c78 100644
--- a/src/types/mod.rs
+++ b/src/types/mod.rs
@@ -1,8 +1,10 @@
+mod emgauwa_now;
 mod emgauwa_uid;
 mod request;
 mod schedule_uid;
 
 use actix::Message;
+pub use emgauwa_now::EmgauwaNow;
 pub use emgauwa_uid::EmgauwaUid;
 pub use request::*;
 pub use schedule_uid::ScheduleUid;
@@ -14,7 +16,13 @@ use crate::models::{Controller, Relay};
 
 pub type Weekday = i64;
 
-pub type RelayStates = Vec<Option<bool>>;
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct RelayState {
+	pub active_schedule: DbSchedule,
+	pub is_on: Option<bool>
+}
+
+pub type RelayStates = Vec<RelayState>;
 
 #[derive(Debug, Serialize, Deserialize, Message)]
 #[rtype(result = "Result<(), EmgauwaError>")]
diff --git a/src/types/request.rs b/src/types/request.rs
index 4956733..d452b31 100644
--- a/src/types/request.rs
+++ b/src/types/request.rs
@@ -23,7 +23,12 @@ pub struct RequestScheduleUpdate {
 #[derive(Debug, Serialize, Deserialize)]
 pub struct RequestRelayUpdate {
 	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 tags: Option<Vec<String>>,
 }
diff --git a/src/utils.rs b/src/utils.rs
index e9ee62e..3b20d9a 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -103,7 +103,7 @@ pub fn get_weekday() -> Weekday {
 pub fn printable_relay_states(relay_states: &RelayStates) -> String {
 	let mut relay_debug = String::new();
 	relay_states.iter().for_each(|state| {
-		relay_debug.push_str(match state {
+		relay_debug.push_str(match state.is_on {
 			Some(true) => "+",
 			Some(false) => "-",
 			None => "?",

From 929985c64af0a69db30c89e3c03d22294fc0935c Mon Sep 17 00:00:00 2001
From: Tobias Reisinger <tobias@msrg.cc>
Date: Tue, 28 May 2024 21:17:21 +0200
Subject: [PATCH 3/3] Improve handling of override_schedule

---
 src/models/controller.rs | 15 ++++-----------
 src/models/relay.rs      | 31 ++++++++++++++++---------------
 src/types/mod.rs         | 10 ++--------
 src/types/relay_state.rs | 22 ++++++++++++++++++++++
 4 files changed, 44 insertions(+), 34 deletions(-)
 create mode 100644 src/types/relay_state.rs

diff --git a/src/models/controller.rs b/src/models/controller.rs
index ebc556a..e1a272b 100644
--- a/src/models/controller.rs
+++ b/src/models/controller.rs
@@ -57,20 +57,11 @@ impl Controller {
 		self.relays
 			.iter_mut()
 			.zip(relay_states.iter())
-			.for_each(|(relay, state)| {
-				relay.active_schedule = state.active_schedule.clone();
-				relay.is_on = state.is_on;
-			});
+			.for_each(|(relay, state)| relay.apply_state(state));
 	}
 
 	pub fn get_relay_states(&self) -> RelayStates {
-		self.relays
-			.iter()
-			.map(|r| RelayState {
-				active_schedule: r.active_schedule.clone(),
-				is_on: r.is_on,
-			})
-			.collect()
+		self.relays.iter().map(RelayState::from).collect()
 	}
 
 	pub fn check_next_time(&mut self, now: &EmgauwaNow) -> Option<NaiveTime> {
@@ -90,6 +81,8 @@ impl Controller {
 			.find(|r| r.r.number == relay_num)
 			.ok_or(EmgauwaError::Other(String::from("Relay not found")))?;
 
+		log::debug!("Pulsing relay {} until {:?}", relay_num, until);
+
 		relay.pulsing = Some(until);
 		Ok(())
 	}
diff --git a/src/models/relay.rs b/src/models/relay.rs
index f44b290..51289ca 100644
--- a/src/models/relay.rs
+++ b/src/models/relay.rs
@@ -9,7 +9,7 @@ use sqlx::Sqlite;
 use crate::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbSchedule};
 use crate::errors::DatabaseError;
 use crate::models::FromDbModel;
-use crate::types::{EmgauwaUid, Weekday};
+use crate::types::{EmgauwaUid, RelayState, Weekday};
 use crate::utils;
 
 #[derive(Serialize, Deserialize, Debug, Clone)]
@@ -20,12 +20,7 @@ pub struct Relay {
 	pub controller_id: EmgauwaUid,
 	pub schedules: Vec<DbSchedule>,
 	pub active_schedule: DbSchedule,
-	#[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<DbSchedule>>,
+	pub override_schedule: Option<DbSchedule>,
 	pub is_on: Option<bool>,
 	pub tags: Vec<String>,
 
@@ -112,12 +107,12 @@ impl Relay {
 	}
 
 	pub fn reload_active_schedule(&mut self, weekday: Weekday) {
-		if let Some((Some(schedule), schedule_weekday)) = self.unwrap_override_schedule() {
-			if schedule_weekday == weekday {
+		if let Some(schedule) = &self.override_schedule {
+			if self.override_schedule_weekday == weekday {
 				self.active_schedule = schedule.clone();
 				return;
 			}
-			if schedule_weekday != weekday {
+			if self.override_schedule_weekday != weekday {
 				self.override_schedule = None;
 			}
 		}
@@ -125,10 +120,16 @@ impl Relay {
 		self.active_schedule = self.schedules.get(weekday as usize).unwrap().clone()
 	}
 
-	pub fn unwrap_override_schedule(&self) -> Option<(&Option<DbSchedule>, Weekday)> {
-		if let Some(schedule) = &self.override_schedule {
-			return Some((schedule, self.override_schedule_weekday));
-		}
-		None
+	pub fn apply_state(&mut self, state: &RelayState) {
+		self.active_schedule = state.active_schedule.clone();
+		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());
+		}
+	}
+
 }
diff --git a/src/types/mod.rs b/src/types/mod.rs
index 24f3c78..44d116c 100644
--- a/src/types/mod.rs
+++ b/src/types/mod.rs
@@ -2,10 +2,12 @@ mod emgauwa_now;
 mod emgauwa_uid;
 mod request;
 mod schedule_uid;
+mod relay_state;
 
 use actix::Message;
 pub use emgauwa_now::EmgauwaNow;
 pub use emgauwa_uid::EmgauwaUid;
+pub use relay_state::{RelayState, RelayStates};
 pub use request::*;
 pub use schedule_uid::ScheduleUid;
 use serde_derive::{Deserialize, Serialize};
@@ -16,14 +18,6 @@ use crate::models::{Controller, Relay};
 
 pub type Weekday = i64;
 
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct RelayState {
-	pub active_schedule: DbSchedule,
-	pub is_on: Option<bool>
-}
-
-pub type RelayStates = Vec<RelayState>;
-
 #[derive(Debug, Serialize, Deserialize, Message)]
 #[rtype(result = "Result<(), EmgauwaError>")]
 pub enum ControllerWsAction {
diff --git a/src/types/relay_state.rs b/src/types/relay_state.rs
new file mode 100644
index 0000000..f4a2e3a
--- /dev/null
+++ b/src/types/relay_state.rs
@@ -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: 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
+        }
+    }
+}
\ No newline at end of file