1use log::error;
11
12#[cfg(not(test))]
13use log::warn;
14
15use spin_sleep::SpinSleeper;
16use std::sync::{Arc, Mutex};
17use std::time::{Duration, Instant};
18
19#[cfg(all(target_os = "linux", not(test)))]
20use nix::unistd::gettid;
21
22use crate::database::sql_interface_error::SqlInterfaceError;
23use crate::database::sql_interface_heating_stats::{HeatingStatsEntry, SqlInterfaceHeatingStats};
24use crate::database::thermal_set_value_updater_trait::ThermalSetValueUpdaterTrait;
25use crate::sensors::sensor_manager::SensorManagerSignals;
26use crate::sensors::tank_level_switch::TankLevelSwitchSignals;
27use crate::thermal::heating_channels::HeatingChannels;
28use crate::thermal::heating_config::HeatingConfig;
29use crate::utilities::acknowledge_signal_handler::AcknowledgeSignalHandlerTrait;
30use crate::utilities::channel_content::{ActuatorState, AquariumDevice, InternalCommand};
31use crate::utilities::check_mutex_access_duration::CheckMutexAccessDurationTrait;
32use crate::utilities::common::check_if_mutex_is_blocked;
33use crate::utilities::database_ping_trait::DatabasePingTrait;
34use crate::utilities::logger::log_error_chain;
35use crate::utilities::proc_ext_req::ProcessExternalRequestTrait;
36use crate::utilities::sawtooth_profile::SawToothProfile;
37use crate::utilities::wait_for_termination::WaitForTerminationTrait;
38use crate::{manage_cycle_time_thermal, perform_schedule_check, update_thermal_set_values};
39#[cfg(not(test))]
40use log::info;
41
42const CYCLE_TIME_HEATING_MILLIS: u64 = 500;
43
44const TIME_INCREMENT_HEATING_SECS: f32 = CYCLE_TIME_HEATING_MILLIS as f32 / 1000.0;
45
46const MAX_MUTEX_ACCESS_DURATION_MILLIS: u64 = 10;
48
49pub trait HeatingStatsDataTransferTrait {
52 fn transfer_heating_stats(
70 &mut self,
71 data: HeatingStatsEntry,
72 sql_interface_heating: &mut SqlInterfaceHeatingStats,
73 ) -> Result<(), SqlInterfaceError>;
74}
75
76fn actuate_heater(
88 command: InternalCommand,
89 mutex_device_scheduler_heating: &Arc<Mutex<i32>>,
90 mutex_blocked_during_actuation: &mut bool,
91 heating_channels: &mut HeatingChannels,
92) {
93 *mutex_blocked_during_actuation |= check_if_mutex_is_blocked(mutex_device_scheduler_heating);
95
96 let mut mutex_data = mutex_device_scheduler_heating.lock().unwrap();
98
99 if let Err(e) = heating_channels.send_to_relay_manager(command.clone()) {
101 let error_message = format!("Channel communication to relay manager for {command} failed.");
102 log_error_chain(module_path!(), &error_message, e);
103 } else {
104 if let Err(e) = heating_channels.receive_from_relay_manager() {
106 let error_message =
107 format!("Receiving answer from relay manager for {command} failed.");
108 log_error_chain(module_path!(), &error_message, e);
109 }
110 }
111 *mutex_data = mutex_data.saturating_add(1);
112}
113
114fn switch_on_heater(
123 mutex_device_scheduler_heating: &Arc<Mutex<i32>>,
124 mutex_blocked_during_actuation: &mut bool,
125 heating_channels: &mut HeatingChannels,
126) {
127 actuate_heater(
128 InternalCommand::SwitchOn(AquariumDevice::Heater),
129 mutex_device_scheduler_heating,
130 mutex_blocked_during_actuation,
131 heating_channels,
132 );
133}
134
135fn switch_off_heater(
144 mutex_device_scheduler_heating: &Arc<Mutex<i32>>,
145 mutex_blocked_during_actuation: &mut bool,
146 heating_channels: &mut HeatingChannels,
147) {
148 actuate_heater(
149 InternalCommand::SwitchOff(AquariumDevice::Heater),
150 mutex_device_scheduler_heating,
151 mutex_blocked_during_actuation,
152 heating_channels,
153 );
154}
155
156pub struct HeatingMutexes {
161 pub(crate) mutex_sensor_manager_signals: Arc<Mutex<SensorManagerSignals>>,
163
164 pub(crate) mutex_tank_level_switch_signals: Arc<Mutex<TankLevelSwitchSignals>>,
166
167 pub(crate) mutex_heating_status: Arc<Mutex<bool>>,
169}
170
171#[cfg_attr(doc, aquamarine::aquamarine)]
172pub struct Heating {
189 config: HeatingConfig,
191
192 lock_error_channel_receive_schedule_check: bool,
194
195 lock_error_channel_send_schedule_check: bool,
197
198 pub last_ping_instant: Instant,
200
201 pub database_ping_interval: Duration,
203
204 pub lock_warn_max_mutex_access_duration: bool,
206
207 lock_error_heating_set_value_read_failure: bool,
209
210 #[cfg(test)]
212 pub mutex_access_duration_exceeded: bool,
213
214 pub lock_warn_inapplicable_command_signal_handler: bool,
216
217 pub lock_error_channel_receive_termination: bool,
219
220 pub max_mutex_access_duration: Duration,
222}
223
224impl ProcessExternalRequestTrait for Heating {}
225
226impl Heating {
227 pub fn new(config: HeatingConfig, database_ping_interval: Duration) -> Heating {
246 Self {
247 config,
248 lock_error_channel_send_schedule_check: false,
249 lock_error_channel_receive_schedule_check: false,
250 last_ping_instant: Instant::now(),
251 database_ping_interval,
252 lock_warn_max_mutex_access_duration: false,
253 lock_error_heating_set_value_read_failure: false,
254 #[cfg(test)]
255 mutex_access_duration_exceeded: false,
256 lock_warn_inapplicable_command_signal_handler: false,
257 lock_error_channel_receive_termination: false,
258 max_mutex_access_duration: Duration::from_millis(MAX_MUTEX_ACCESS_DURATION_MILLIS),
259 }
260 }
261
262 fn calc_normalized_control_deviation(&self, measured_value: f32) -> f32 {
279 let target_value_delta: f32 =
280 self.config.switch_off_temperature - self.config.switch_on_temperature;
281 let measured_value_delta: f32 = measured_value - self.config.switch_on_temperature;
282 if target_value_delta > 0.0 {
283 measured_value_delta / target_value_delta
284 } else {
285 0.0 }
287 }
288
289 fn get_duration_until_midnight(
302 &mut self,
303 sql_interface_heating: &mut SqlInterfaceHeatingStats,
304 ) -> i64 {
305 sql_interface_heating
306 .get_duration_until_midnight()
307 .unwrap_or_else(|e| {
308 log_error_chain(module_path!(), "Could not get duration until midnight.", e);
309 24 * 3600 })
311 }
312
313 pub fn execute(
348 &mut self,
349 mutex_device_scheduler_heating: Arc<Mutex<i32>>,
350 heating_channels: &mut HeatingChannels,
351 heating_stats_transfer: &mut impl HeatingStatsDataTransferTrait,
352 heating_set_val_updater: &mut impl ThermalSetValueUpdaterTrait,
353 heating_mutexes: HeatingMutexes,
354 mut sql_interface_heating: SqlInterfaceHeatingStats,
355 ) {
356 #[cfg(all(target_os = "linux", not(test)))]
357 info!(target: module_path!(), "Thread started with TID: {}", gettid());
358
359 let _refill_error = false;
360 let spin_sleeper = SpinSleeper::default();
361 let sleep_duration_hundred_millis = Duration::from_millis(100);
362 let mut saw_tooth_profile = SawToothProfile::new(&self.config.saw_tooth_profile_config);
363 let mut measured_water_temperature = 0.0;
364 let mut measurement_error: bool;
365 let mut measured_ambient_temperature = 0.0;
366 let mut measured_tank_level_switch_position = true;
367 let mut measured_tank_level_switch_invalid = false;
368 let mut loop_counter = 0;
369 let mut heater_state: ActuatorState = ActuatorState::Undefined;
370 let mut schedule_check_result: bool;
371 let mut duration_until_midnight =
372 self.get_duration_until_midnight(&mut sql_interface_heating);
373 let mut tank_level_low_counter = 0;
374 let mut energy_increment: f64 = 0.0;
375 let mut heating_inhibited: bool = false; let mut lock_warn_cycle_time_exceeded: bool = false;
377 let mut actuation_mutex_was_blocked_during_actuation: bool = false;
378 let cycle_time_duration = Duration::from_millis(CYCLE_TIME_HEATING_MILLIS);
379 let midnight_check_interval = Duration::from_secs(1);
380 let mut instant_midnight_check = Instant::now() - midnight_check_interval;
381
382 let mut heating_stats_opt = match sql_interface_heating
383 .get_single_heating_stats_from_database()
384 {
385 Ok(c) => Some(c),
386 Err(_) => {
387 #[cfg(test)]
388 println!(
389 "{}: could not get existing heating stats entry for today from database - creating new one for today",
390 module_path!()
391 );
392
393 Some(HeatingStatsEntry::new(&mut sql_interface_heating.conn))
394 }
395 };
396
397 let mut start_time = Instant::now();
398 loop {
399 let (
400 quit_command_received, start_command_received, stop_command_received, ) = self.process_external_request(
404 &mut heating_channels.rx_heating_from_signal_handler,
405 heating_channels.rx_heating_from_messaging_opt.as_mut(),
406 );
407 if quit_command_received {
408 break;
409 }
410 if stop_command_received {
411 #[cfg(not(test))]
412 info!(
413 target: module_path!(),
414 "received Stop command. inhibiting heating."
415 );
416
417 heating_inhibited = true;
418 }
419 if start_command_received {
420 #[cfg(not(test))]
421 info!(
422 target: module_path!(),
423 "received Start command. restarting heating."
424 );
425
426 heating_inhibited = false;
427 }
428
429 if self.config.active {
430 {
432 match heating_mutexes.mutex_sensor_manager_signals.lock() {
433 Ok(c) => {
434 measured_water_temperature = c.water_temperature;
435 measured_ambient_temperature = c.ambient_temperature;
436 measurement_error = false;
437 }
438 Err(_) => {
439 measurement_error = true;
440 }
441 };
442 }
443
444 {
446 (
447 measured_tank_level_switch_position,
448 measured_tank_level_switch_invalid,
449 ) = match heating_mutexes.mutex_tank_level_switch_signals.lock() {
450 Ok(c) => (c.tank_level_switch_position, c.tank_level_switch_invalid),
451 Err(_) => {
452 (
453 measured_tank_level_switch_position,
454 measured_tank_level_switch_invalid,
455 ) }
457 }
458 }
459
460 if (measured_tank_level_switch_invalid) || (!measured_tank_level_switch_position) {
462 tank_level_low_counter += 1;
463 } else {
464 tank_level_low_counter = 0;
465 }
466
467 perform_schedule_check!(
469 heating_channels,
470 schedule_check_result,
471 self.lock_error_channel_send_schedule_check,
472 self.lock_error_channel_receive_schedule_check,
473 module_path!()
474 );
475
476 update_thermal_set_values!(
478 heating_set_val_updater,
479 self.config.switch_off_temperature,
480 self.config.switch_on_temperature,
481 self.lock_error_heating_set_value_read_failure,
482 module_path!()
483 );
484
485 if self.config.stop_on_refill_error
486 && (tank_level_low_counter > self.config.stop_on_refill_error_delay)
487 {
488 if heater_state != ActuatorState::Off {
491 switch_off_heater(
492 &mutex_device_scheduler_heating,
493 &mut actuation_mutex_was_blocked_during_actuation,
494 heating_channels,
495 );
496 heater_state = ActuatorState::Off;
497 }
498 } else {
499 if schedule_check_result && !heating_inhibited {
501 if (measured_water_temperature >= self.config.switch_on_temperature)
505 && (measured_water_temperature <= self.config.switch_off_temperature)
506 {
507 if (self.calc_normalized_control_deviation(measured_water_temperature)
509 < saw_tooth_profile.level_normalized)
510 && !measurement_error
511 {
512 if heater_state != ActuatorState::On {
514 switch_on_heater(
515 &mutex_device_scheduler_heating,
516 &mut actuation_mutex_was_blocked_during_actuation,
517 heating_channels,
518 );
519 heater_state = ActuatorState::On;
520 }
521 } else {
522 if heater_state != ActuatorState::Off {
524 switch_off_heater(
525 &mutex_device_scheduler_heating,
526 &mut actuation_mutex_was_blocked_during_actuation,
527 heating_channels,
528 );
529 heater_state = ActuatorState::Off;
530 }
531 }
532 } else {
533 if measured_water_temperature > self.config.switch_off_temperature {
535 if heater_state != ActuatorState::Off {
537 switch_off_heater(
538 &mutex_device_scheduler_heating,
539 &mut actuation_mutex_was_blocked_during_actuation,
540 heating_channels,
541 );
542 heater_state = ActuatorState::Off;
543 }
544 }
545 if measured_water_temperature < self.config.switch_on_temperature {
546 if heater_state != ActuatorState::On {
548 switch_on_heater(
549 &mutex_device_scheduler_heating,
550 &mut actuation_mutex_was_blocked_during_actuation,
551 heating_channels,
552 );
553 heater_state = ActuatorState::On;
554 }
555 }
556 }
557 } else if heating_inhibited {
558 match self.config.switch_on_when_external_stop {
560 true => {
561 if heater_state != ActuatorState::On {
563 switch_on_heater(
564 &mutex_device_scheduler_heating,
565 &mut actuation_mutex_was_blocked_during_actuation,
566 heating_channels,
567 );
568 heater_state = ActuatorState::On;
569 }
570 }
571 false => {
572 if heater_state != ActuatorState::Off {
574 switch_off_heater(
575 &mutex_device_scheduler_heating,
576 &mut actuation_mutex_was_blocked_during_actuation,
577 heating_channels,
578 );
579 heater_state = ActuatorState::Off;
580 }
581 }
582 }
583 } else {
584 match self.config.switch_on_when_out_of_schedule {
586 true => {
587 if heater_state != ActuatorState::On {
589 switch_on_heater(
590 &mutex_device_scheduler_heating,
591 &mut actuation_mutex_was_blocked_during_actuation,
592 heating_channels,
593 );
594 heater_state = ActuatorState::On;
595 }
596 }
597 false => {
598 if heater_state != ActuatorState::Off {
600 switch_off_heater(
601 &mutex_device_scheduler_heating,
602 &mut actuation_mutex_was_blocked_during_actuation,
603 heating_channels,
604 );
605 heater_state = ActuatorState::Off;
606 }
607 }
608 }
609 }
610 }
611
612 saw_tooth_profile.execute_with(TIME_INCREMENT_HEATING_SECS);
613
614 if instant_midnight_check.elapsed() >= midnight_check_interval {
616 instant_midnight_check = Instant::now();
618 duration_until_midnight = duration_until_midnight.saturating_sub(1);
619 match heating_stats_opt {
620 Some(ref mut heating_stats) => {
621 let water_temperature_opt = match measurement_error {
623 true => None,
624 false => Some(measured_water_temperature as f64),
625 };
626 let ambient_temperature_opt = match measurement_error {
627 true => None,
628 false => Some(measured_ambient_temperature as f64),
629 };
630 if heater_state == ActuatorState::On {
632 energy_increment = (self.config.heater_power / 3600.0) as f64;
633 }
634 heating_stats.update(
635 water_temperature_opt,
636 ambient_temperature_opt,
637 energy_increment,
638 );
639 }
640 None => {
641 }
643 }
644
645 if duration_until_midnight <= 0 {
647 if let Some(stats) = heating_stats_opt.take() {
649 if let Err(e) = heating_stats_transfer
650 .transfer_heating_stats(stats, &mut sql_interface_heating)
651 {
652 error!("Error occurred when trying to store heating stats data in database: {e:?}");
653 }
654 heating_stats_opt =
656 Some(HeatingStatsEntry::new(&mut sql_interface_heating.conn));
657 duration_until_midnight =
658 self.get_duration_until_midnight(&mut sql_interface_heating);
659 }
660 }
661 }
662 }
663
664 let instant_before_locking_mutex = Instant::now();
665 let mut instant_after_locking_mutex = Instant::now(); {
669 match heating_mutexes.mutex_heating_status.lock() {
670 Ok(mut c) => {
671 instant_after_locking_mutex = Instant::now();
672 *c = match heater_state {
673 ActuatorState::On => true,
674 ActuatorState::Off => false,
675 _ => false,
676 }
677 }
678 Err(_) => {
679 }
681 }
682 }
683
684 self.check_mutex_access_duration(
686 None,
687 instant_after_locking_mutex,
688 instant_before_locking_mutex,
689 );
690
691 loop_counter += 1;
692
693 if loop_counter == 1_000_000 {
695 loop_counter = 0;
696 }
697
698 manage_cycle_time_thermal!(
700 start_time,
701 cycle_time_duration,
702 CYCLE_TIME_HEATING_MILLIS,
703 spin_sleeper,
704 lock_warn_cycle_time_exceeded,
705 actuation_mutex_was_blocked_during_actuation,
706 module_path!()
707 );
708
709 self.check_timing_and_ping_database(&mut sql_interface_heating);
710 }
711
712 if heater_state != ActuatorState::Off {
714 switch_off_heater(
715 &mutex_device_scheduler_heating,
716 &mut actuation_mutex_was_blocked_during_actuation,
717 heating_channels,
718 );
719 }
720
721 if let Some(stats) = heating_stats_opt.take() {
724 if let Err(e) =
725 heating_stats_transfer.transfer_heating_stats(stats, &mut sql_interface_heating)
726 {
727 error!("Error occurred when trying to store heating stats data in database (during termination): {e:?}");
728 }
729 }
730
731 heating_channels.acknowledge_signal_handler();
732
733 self.wait_for_termination(
737 &mut heating_channels.rx_heating_from_signal_handler,
738 sleep_duration_hundred_millis,
739 module_path!(),
740 );
741 }
742}
743
744#[cfg(test)]
745pub mod tests {
746 use all_asserts::{assert_ge, assert_le};
747 use chrono::Local;
748 use mysql::PooledConn;
749 use spin_sleep::SpinSleeper;
750 use std::sync::{Arc, Mutex};
751 use std::thread;
752 use std::time::{Duration, Instant};
753
754 use crate::database::sql_interface_heating_stats::{
755 HeatingStatsEntry, SqlInterfaceHeatingStats,
756 };
757 use crate::database::sql_interface_midnight::SqlInterfaceMidnightCalculatorTrait;
758 use crate::database::{sql_interface::SqlInterface, sql_interface_error::SqlInterfaceError};
759 use crate::launch::channels::Channels;
760 use crate::mocks::mock_heating::tests::MockSqlInterfaceHeatingSetVals;
761 use crate::mocks::mock_relay_manager::tests::mock_relay_manager;
762 use crate::mocks::mock_schedule_check::tests::mock_schedule_check;
763 use crate::sensors::sensor_manager::SensorManagerSignals;
764 use crate::sensors::tank_level_switch::TankLevelSwitchSignals;
765 use crate::thermal::heating::{Heating, HeatingMutexes, HeatingStatsDataTransferTrait};
766 use crate::utilities::channel_content::{AquariumDevice, InternalCommand};
767 use crate::utilities::common::tests::update_min_max_actuation_duration;
768 use crate::utilities::config::{read_config_file_with_test_database, ConfigData};
769 use crate::utilities::sawtooth_profile::SawToothProfileConfig;
770
771 pub struct HeatingStatsMockDataTransfer {
772 heating_stats_entry_previous: HeatingStatsEntry,
773 heating_stats_entry: HeatingStatsEntry,
774 }
775
776 impl HeatingStatsMockDataTransfer {
777 pub fn new(conn: &mut PooledConn) -> HeatingStatsMockDataTransfer {
786 Self {
787 heating_stats_entry_previous: HeatingStatsEntry::new(conn),
788 heating_stats_entry: HeatingStatsEntry::new(conn),
789 }
790 }
791 }
792
793 impl HeatingStatsDataTransferTrait for HeatingStatsMockDataTransfer {
794 fn transfer_heating_stats(
806 &mut self,
807 heating_stats_entry: HeatingStatsEntry,
808 _sql_interface_heating: &mut SqlInterfaceHeatingStats,
809 ) -> Result<(), SqlInterfaceError> {
810 self.heating_stats_entry_previous = self.heating_stats_entry.clone();
811 self.heating_stats_entry = heating_stats_entry.clone();
812 println!(
813 "Heating: Received command to transfer mock data to SQL database: {}",
814 heating_stats_entry,
815 );
816 Ok(())
817 }
818 }
819
820 pub struct SqlInterfaceHeatingMockMidnightCalculator {
821 seconds_to_midnight: i64,
822 }
823
824 impl SqlInterfaceHeatingMockMidnightCalculator {
825 pub fn new(seconds_to_midnight: i64) -> SqlInterfaceHeatingMockMidnightCalculator {
838 Self {
839 seconds_to_midnight,
840 }
841 }
842 }
843
844 impl SqlInterfaceMidnightCalculatorTrait for SqlInterfaceHeatingMockMidnightCalculator {
845 fn get_duration_until_midnight(&mut self) -> Result<i64, SqlInterfaceError> {
856 Ok(self.seconds_to_midnight) }
859 }
860
861 pub fn prepare_heating_tests_mock_midnight_calculator(
891 seconds_to_midnight: i64,
892 test_db_number: u32,
893 saw_tooth_profile_config_opt: Option<SawToothProfileConfig>,
894 ) -> (ConfigData, Heating, SqlInterface, SqlInterfaceHeatingStats) {
895 let mut config: ConfigData = read_config_file_with_test_database(
897 "/config/aquarium_control_test_generic.toml".to_string(),
898 test_db_number,
899 );
900 config.heating.stop_on_refill_error_delay = 2;
901
902 match saw_tooth_profile_config_opt {
903 Some(saw_tooth_profile_config) => {
904 config.heating.saw_tooth_profile_config = saw_tooth_profile_config;
905 }
906 None => { }
907 }
908
909 println!("Testing with database {}", config.sql_interface.db_name);
910 let mut sql_interface = match SqlInterface::new(config.sql_interface.clone()) {
911 Ok(c) => c,
912 Err(e) => {
913 panic!("Could not connect to SQL database: {e:?}");
914 }
915 };
916
917 match SqlInterface::truncate_table(&mut sql_interface, "heatingstats".to_string()) {
919 Ok(_) => {}
920 Err(e) => {
921 panic!("Could not prepare test case: {e:?}")
922 }
923 }
924
925 let sql_interface_heating_midnight_calculator = Box::new(
926 SqlInterfaceHeatingMockMidnightCalculator::new(seconds_to_midnight),
927 );
928 let sql_interface_heating_stats = SqlInterfaceHeatingStats::new(
929 sql_interface.get_connection().unwrap(),
930 config.sql_interface.max_rows_heating_stats,
931 sql_interface_heating_midnight_calculator,
932 )
933 .unwrap();
934
935 println!(
936 "prepare_heating_tests_mock_midnight_calculator: SwitchOff temperature = {}, SwitchOn temperature = {}",
937 config.heating.switch_on_temperature, config.heating.switch_off_temperature
938 );
939 let heating_config = config.heating.clone();
940 (
941 config,
942 Heating::new(heating_config, Duration::from_secs(1000)),
943 sql_interface,
944 sql_interface_heating_stats,
945 )
946 }
947
948 #[test]
949 pub fn test_heating_with_measured_temperature_high() {
953 let sleep_duration_100_millis = Duration::from_millis(100);
954 let spin_sleeper = SpinSleeper::default();
955
956 let (mut config, mut heating, sql_interface, sql_interface_heating_stats) =
957 prepare_heating_tests_mock_midnight_calculator(3600, 11, None);
958
959 config.sensor_manager.replacement_value_water_temperature = 30.0;
961 config.sensor_manager.replacement_value_ambient_temperature = 24.0;
962
963 let mut channels = Channels::new_for_test();
964
965 let mutex_tank_level_switch_signals =
967 Arc::new(Mutex::new(TankLevelSwitchSignals::new(true, true, false)));
968
969 let join_handle_mock_schedule_check = thread::Builder::new()
971 .name("mock_schedule_check".to_string())
972 .spawn(move || {
973 mock_schedule_check(
974 &mut channels.schedule_check.tx_schedule_check_to_heating,
975 &mut channels.schedule_check.rx_schedule_check_from_heating,
976 None,
977 true,
978 );
979 })
980 .unwrap();
981
982 let mutex_device_scheduler_heating = Arc::new(Mutex::new(0));
983
984 let join_handle_mock_relay_manager = thread::Builder::new()
986 .name("mock_relay_manager".to_string())
987 .spawn(move || {
988 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
989 &mut channels.relay_manager.tx_relay_manager_to_heating,
990 &mut channels.relay_manager.rx_relay_manager_from_heating,
991 );
992 assert_eq!(actuation_events.len(), 1);
993 let actuation_event = actuation_events.pop().unwrap();
994 assert_eq!(
995 actuation_event.command,
996 InternalCommand::SwitchOff(AquariumDevice::Heater)
997 );
998 mock_actuator_states.check_terminal_condition_heating();
999 })
1000 .unwrap();
1001
1002 let join_handle_test_environment = thread::Builder::new()
1004 .name("test_environment".to_string())
1005 .spawn(move || {
1006 let sleep_duration_ten_seconds = Duration::from_secs(10);
1007 let spin_sleeper = SpinSleeper::default();
1008 spin_sleeper.sleep(sleep_duration_ten_seconds);
1009 let _ = channels
1010 .signal_handler
1011 .send_to_heating(InternalCommand::Quit);
1012 channels.signal_handler.receive_from_heating().unwrap();
1013 let _ = channels
1014 .signal_handler
1015 .send_to_heating(InternalCommand::Terminate);
1016 })
1017 .unwrap();
1018
1019 spin_sleeper.sleep(sleep_duration_100_millis);
1021
1022 let join_handle_test_object = thread::Builder::new()
1024 .name("test_object".to_string())
1025 .spawn(move || {
1026 let mut tx_heating_to_schedule_check_for_test_case_finish =
1028 channels.heating.tx_heating_to_schedule_check.clone();
1029 let mut tx_heating_to_relay_manager_for_test_case_finish =
1030 channels.heating.tx_heating_to_relay_manager.clone();
1031
1032 let mut heating_stats_transfer =
1033 HeatingStatsMockDataTransfer::new(&mut sql_interface.get_connection().unwrap());
1034 let mut heating_set_value_updater =
1035 MockSqlInterfaceHeatingSetVals::new(false, None, None, None);
1036
1037 let heating_mutexes = HeatingMutexes {
1038 mutex_sensor_manager_signals: Arc::new(Mutex::new(SensorManagerSignals::new(
1039 &config.sensor_manager,
1040 ))),
1041 mutex_tank_level_switch_signals,
1042 mutex_heating_status: Arc::new(Mutex::new(false)),
1043 };
1044
1045 heating.execute(
1046 mutex_device_scheduler_heating.clone(),
1047 &mut channels.heating,
1048 &mut heating_stats_transfer,
1049 &mut heating_set_value_updater,
1050 heating_mutexes,
1051 sql_interface_heating_stats,
1052 );
1053
1054 let _ =
1056 tx_heating_to_schedule_check_for_test_case_finish.send(InternalCommand::Quit);
1057 let _ =
1058 tx_heating_to_relay_manager_for_test_case_finish.send(InternalCommand::Quit);
1059
1060 println!("* [Heating] checking reaction to high temperature succeeded.");
1061 })
1062 .unwrap();
1063
1064 join_handle_mock_schedule_check
1065 .join()
1066 .expect("Mock schedule check did not finish.");
1067 join_handle_mock_relay_manager
1068 .join()
1069 .expect("Mock relay manager thread did not finish.");
1070 join_handle_test_environment
1071 .join()
1072 .expect("Test environment thread did not finish.");
1073 join_handle_test_object
1074 .join()
1075 .expect("Test object thread did not finish.");
1076 }
1077
1078 #[test]
1079 pub fn test_heating_with_measured_temperature_low_without_mutex_blocked() {
1084 let sleep_duration_100_millis = Duration::from_millis(100);
1085 let spin_sleeper = SpinSleeper::default();
1086
1087 let (mut config, mut heating, sql_interface, sql_interface_heating_stats) =
1088 prepare_heating_tests_mock_midnight_calculator(3600, 12, None);
1089
1090 config.sensor_manager.replacement_value_water_temperature = 20.0;
1092 config.sensor_manager.replacement_value_ambient_temperature = 24.0;
1093
1094 let mut channels = Channels::new_for_test();
1095
1096 let mutex_tank_level_switch_signals =
1098 Arc::new(Mutex::new(TankLevelSwitchSignals::new(true, true, false)));
1099
1100 let join_handle_mock_schedule_check = thread::Builder::new()
1102 .name("mock_schedule_check".to_string())
1103 .spawn(move || {
1104 mock_schedule_check(
1105 &mut channels.schedule_check.tx_schedule_check_to_heating,
1106 &mut channels.schedule_check.rx_schedule_check_from_heating,
1107 None,
1108 true,
1109 );
1110 })
1111 .unwrap();
1112
1113 let mutex_device_scheduler_heating = Arc::new(Mutex::new(0));
1114
1115 let join_handle_mock_relay_manager = thread::Builder::new()
1117 .name("mock_relay_manager".to_string())
1118 .spawn(move || {
1119 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
1120 &mut channels.relay_manager.tx_relay_manager_to_heating,
1121 &mut channels.relay_manager.rx_relay_manager_from_heating,
1122 );
1123 assert_eq!(actuation_events.len(), 2);
1124 let last_actuation_event = actuation_events.pop().unwrap();
1125 assert_eq!(
1126 last_actuation_event.command,
1127 InternalCommand::SwitchOff(AquariumDevice::Heater)
1128 );
1129 let first_actuation_event = actuation_events.pop().unwrap();
1130 assert_eq!(
1131 first_actuation_event.command,
1132 InternalCommand::SwitchOn(AquariumDevice::Heater)
1133 );
1134 mock_actuator_states.check_terminal_condition_heating();
1135 })
1136 .unwrap();
1137
1138 let join_handle_test_environment = thread::Builder::new()
1140 .name("test_environment".to_string())
1141 .spawn(move || {
1142 let sleep_duration_ten_seconds = Duration::from_secs(10);
1143 let spin_sleeper = SpinSleeper::default();
1144 spin_sleeper.sleep(sleep_duration_ten_seconds);
1145 let _ = channels
1146 .signal_handler
1147 .send_to_heating(InternalCommand::Quit);
1148 channels.signal_handler.receive_from_heating().unwrap();
1149 let _ = channels
1150 .signal_handler
1151 .send_to_heating(InternalCommand::Terminate);
1152 })
1153 .unwrap();
1154
1155 spin_sleeper.sleep(sleep_duration_100_millis);
1157
1158 let join_handle_test_object = thread::Builder::new()
1160 .name("test_object".to_string())
1161 .spawn(move || {
1162 let mut tx_heating_to_schedule_check_for_test_case_finish =
1164 channels.heating.tx_heating_to_schedule_check.clone();
1165 let mut tx_heating_to_relay_manager_for_test_case_finish =
1166 channels.heating.tx_heating_to_relay_manager.clone();
1167
1168 let mut heating_stats_transfer =
1169 HeatingStatsMockDataTransfer::new(&mut sql_interface.get_connection().unwrap());
1170 let mut heating_set_value_updater =
1171 MockSqlInterfaceHeatingSetVals::new(false, None, None, None);
1172
1173 let heating_mutexes = HeatingMutexes {
1174 mutex_sensor_manager_signals: Arc::new(Mutex::new(SensorManagerSignals::new(
1175 &config.sensor_manager,
1176 ))),
1177 mutex_tank_level_switch_signals,
1178 mutex_heating_status: Arc::new(Mutex::new(false)),
1179 };
1180
1181 heating.execute(
1182 mutex_device_scheduler_heating.clone(),
1183 &mut channels.heating,
1184 &mut heating_stats_transfer,
1185 &mut heating_set_value_updater,
1186 heating_mutexes,
1187 sql_interface_heating_stats,
1188 );
1189
1190 let _ =
1192 tx_heating_to_schedule_check_for_test_case_finish.send(InternalCommand::Quit);
1193 let _ =
1194 tx_heating_to_relay_manager_for_test_case_finish.send(InternalCommand::Quit);
1195
1196 println!("* [Heating] checking reaction to low temperature succeeded.");
1197 })
1198 .unwrap();
1199
1200 join_handle_mock_schedule_check
1201 .join()
1202 .expect("Mock schedule check did not finish.");
1203 join_handle_mock_relay_manager
1204 .join()
1205 .expect("Mock relay manager thread did not finish.");
1206 join_handle_test_environment
1207 .join()
1208 .expect("Test environment thread did not finish.");
1209 join_handle_test_object
1210 .join()
1211 .expect("Test object thread did not finish.");
1212 }
1213
1214 #[test]
1215 pub fn test_heating_with_measured_temperature_50_percent() {
1220 let sleep_duration_100_millis = Duration::from_millis(100);
1221 let spin_sleeper = SpinSleeper::default();
1222
1223 let (mut config, mut heating, sql_interface, sql_interface_heating_stats) =
1224 prepare_heating_tests_mock_midnight_calculator(
1225 3600,
1226 13,
1227 Some(SawToothProfileConfig::new(6, 1, 6, 1)),
1228 );
1229
1230 config.sensor_manager.replacement_value_water_temperature = 25.0;
1232 config.sensor_manager.replacement_value_ambient_temperature = 24.0;
1233
1234 let mut channels = Channels::new_for_test();
1235
1236 let mutex_tank_level_switch_signals =
1238 Arc::new(Mutex::new(TankLevelSwitchSignals::new(true, true, false)));
1239
1240 let join_handle_mock_schedule_check = thread::Builder::new()
1242 .name("mock_schedule_check".to_string())
1243 .spawn(move || {
1244 mock_schedule_check(
1245 &mut channels.schedule_check.tx_schedule_check_to_heating,
1246 &mut channels.schedule_check.rx_schedule_check_from_heating,
1247 None,
1248 true,
1249 );
1250 })
1251 .unwrap();
1252
1253 let mutex_device_scheduler_heating = Arc::new(Mutex::new(0));
1254
1255 let join_handle_mock_relay_manager = thread::Builder::new()
1257 .name("mock_relay_manager".to_string())
1258 .spawn(move || {
1259 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
1260 &mut channels.relay_manager.tx_relay_manager_to_heating,
1261 &mut channels.relay_manager.rx_relay_manager_from_heating,
1262 );
1263 println!(
1264 "Mock relay manager received {} commands:",
1265 actuation_events.len()
1266 );
1267 for actuation_event in actuation_events.clone() {
1268 println!("{}", actuation_event);
1269 }
1270 let _last_actuation_event = actuation_events.pop().unwrap();
1272 let second_last_actuation_event = actuation_events.pop().unwrap();
1273 let third_last_actuation_event = actuation_events.pop().unwrap();
1274 let fourth_last_actuation_event = actuation_events.pop().unwrap();
1275 let fifth_last_actuation_event = actuation_events.pop().unwrap();
1276 let sixth_last_actuation_event = actuation_events.pop().unwrap();
1277 let seventh_last_actuation_event = actuation_events.pop().unwrap();
1278
1279 let mut min_actuation_duration: u128 = 100000;
1280 let mut max_actuation_duration: u128 = 0;
1281
1282 let second_last_actuation_duration = second_last_actuation_event
1283 .time
1284 .duration_since(third_last_actuation_event.time)
1285 .as_millis();
1286 (min_actuation_duration, max_actuation_duration) =
1287 update_min_max_actuation_duration(
1288 min_actuation_duration,
1289 max_actuation_duration,
1290 second_last_actuation_duration,
1291 );
1292 println!(
1293 "second_last_actuation_duration={}",
1294 second_last_actuation_duration
1295 );
1296
1297 let third_last_actuation_duration = third_last_actuation_event
1298 .time
1299 .duration_since(fourth_last_actuation_event.time)
1300 .as_millis();
1301 (min_actuation_duration, max_actuation_duration) =
1302 update_min_max_actuation_duration(
1303 min_actuation_duration,
1304 max_actuation_duration,
1305 second_last_actuation_duration,
1306 );
1307 println!(
1308 "third_last_actuation_duration={}",
1309 third_last_actuation_duration
1310 );
1311
1312 let fourth_last_actuation_duration = fourth_last_actuation_event
1313 .time
1314 .duration_since(fifth_last_actuation_event.time)
1315 .as_millis();
1316 (min_actuation_duration, max_actuation_duration) =
1317 update_min_max_actuation_duration(
1318 min_actuation_duration,
1319 max_actuation_duration,
1320 second_last_actuation_duration,
1321 );
1322 println!(
1323 "fourth_last_actuation_duration={}",
1324 fourth_last_actuation_duration
1325 );
1326
1327 let fifth_last_actuation_duration = fifth_last_actuation_event
1328 .time
1329 .duration_since(sixth_last_actuation_event.time)
1330 .as_millis();
1331 (min_actuation_duration, max_actuation_duration) =
1332 update_min_max_actuation_duration(
1333 min_actuation_duration,
1334 max_actuation_duration,
1335 second_last_actuation_duration,
1336 );
1337 println!(
1338 "fifth_last_actuation_duration={}",
1339 fifth_last_actuation_duration
1340 );
1341
1342 let sixth_last_actuation_duration = sixth_last_actuation_event
1343 .time
1344 .duration_since(seventh_last_actuation_event.time)
1345 .as_millis();
1346 (min_actuation_duration, max_actuation_duration) =
1347 update_min_max_actuation_duration(
1348 min_actuation_duration,
1349 max_actuation_duration,
1350 second_last_actuation_duration,
1351 );
1352 println!(
1353 "sixth_last_actuation_duration={}",
1354 sixth_last_actuation_duration
1355 );
1356
1357 let delta_min_max_duration = max_actuation_duration - min_actuation_duration;
1358
1359 cfg_if::cfg_if! {
1362 if #[cfg(target_os = "linux")] {
1363 assert_le!(second_last_actuation_duration, 7100);
1364 assert_ge!(second_last_actuation_duration, 6400);
1365 assert_le!(third_last_actuation_duration, 7100);
1366 assert_ge!(third_last_actuation_duration, 6400);
1367 assert_le!(fourth_last_actuation_duration, 7100);
1368 assert_ge!(fourth_last_actuation_duration, 6400);
1369 assert_le!(fifth_last_actuation_duration, 7100);
1370 assert_ge!(fifth_last_actuation_duration, 6400);
1371 assert_le!(sixth_last_actuation_duration, 7100);
1372 assert_ge!(sixth_last_actuation_duration, 6400);
1373 }
1374 else {
1375 assert_le!(second_last_actuation_duration, 7400);
1376 assert_ge!(second_last_actuation_duration, 6400);
1377 assert_le!(third_last_actuation_duration, 7400);
1378 assert_ge!(third_last_actuation_duration, 6400);
1379 assert_le!(fourth_last_actuation_duration, 7400);
1380 assert_ge!(fourth_last_actuation_duration, 6400);
1381 assert_le!(fifth_last_actuation_duration, 7400);
1382 assert_ge!(fifth_last_actuation_duration, 6400);
1383 assert_le!(sixth_last_actuation_duration, 7400);
1384 assert_ge!(sixth_last_actuation_duration, 6400);
1385 }
1386 }
1387 assert_le!(delta_min_max_duration, 50);
1388 mock_actuator_states.check_terminal_condition_heating();
1389 })
1390 .unwrap();
1391
1392 let join_handle_test_environment = thread::Builder::new()
1394 .name("test_environment".to_string())
1395 .spawn(move || {
1396 let sleep_duration = Duration::from_secs(59);
1397 let spin_sleeper = SpinSleeper::default();
1398 spin_sleeper.sleep(sleep_duration);
1399 let _ = channels
1400 .signal_handler
1401 .send_to_heating(InternalCommand::Quit);
1402 channels.signal_handler.receive_from_heating().unwrap();
1403 let _ = channels
1404 .signal_handler
1405 .send_to_heating(InternalCommand::Terminate);
1406 })
1407 .unwrap();
1408
1409 spin_sleeper.sleep(sleep_duration_100_millis);
1411
1412 let join_handle_test_object = thread::Builder::new()
1414 .name("test_object".to_string())
1415 .spawn(move || {
1416 let mut tx_heating_to_schedule_check_for_test_case_finish =
1418 channels.heating.tx_heating_to_schedule_check.clone();
1419 let mut tx_heating_to_relay_manager_for_test_case_finish =
1420 channels.heating.tx_heating_to_relay_manager.clone();
1421
1422 let mut heating_stats_transfer =
1423 HeatingStatsMockDataTransfer::new(&mut sql_interface.get_connection().unwrap());
1424 let mut heating_set_value_updater =
1425 MockSqlInterfaceHeatingSetVals::new(false, None, None, None);
1426
1427 let heating_mutexes = HeatingMutexes {
1428 mutex_sensor_manager_signals: Arc::new(Mutex::new(SensorManagerSignals::new(
1429 &config.sensor_manager,
1430 ))),
1431 mutex_tank_level_switch_signals,
1432 mutex_heating_status: Arc::new(Mutex::new(false)),
1433 };
1434
1435 heating.execute(
1436 mutex_device_scheduler_heating.clone(),
1437 &mut channels.heating,
1438 &mut heating_stats_transfer,
1439 &mut heating_set_value_updater,
1440 heating_mutexes,
1441 sql_interface_heating_stats,
1442 );
1443
1444 let _ =
1446 tx_heating_to_schedule_check_for_test_case_finish.send(InternalCommand::Quit);
1447 let _ =
1448 tx_heating_to_relay_manager_for_test_case_finish.send(InternalCommand::Quit);
1449
1450 println!("* [Heating] checking reaction to medium temperature succeeded.");
1451 })
1452 .unwrap();
1453
1454 join_handle_mock_schedule_check
1455 .join()
1456 .expect("Mock schedule check did not finish.");
1457 join_handle_mock_relay_manager
1458 .join()
1459 .expect("Mock relay manager thread did not finish.");
1460 join_handle_test_environment
1461 .join()
1462 .expect("Test environment thread did not finish.");
1463 join_handle_test_object
1464 .join()
1465 .expect("Test object thread did not finish.");
1466 }
1467
1468 #[test]
1469 pub fn test_heating_with_pending_quit_terminate_command() {
1473 let sleep_duration_100_millis = Duration::from_millis(100);
1474 let spin_sleeper = SpinSleeper::default();
1475
1476 let (mut config, mut heating, sql_interface, sql_interface_heating_stats) =
1477 prepare_heating_tests_mock_midnight_calculator(3, 14, None);
1478
1479 config.sensor_manager.replacement_value_water_temperature = 25.0;
1481 config.sensor_manager.replacement_value_ambient_temperature = 24.0;
1482
1483 let mut channels = Channels::new_for_test();
1484
1485 let start_time = Instant::now();
1486
1487 let mutex_tank_level_switch_signals =
1489 Arc::new(Mutex::new(TankLevelSwitchSignals::new(true, true, false)));
1490
1491 let join_handle_mock_schedule_check = thread::Builder::new()
1493 .name("mock_schedule_check".to_string())
1494 .spawn(move || {
1495 mock_schedule_check(
1496 &mut channels.schedule_check.tx_schedule_check_to_heating,
1497 &mut channels.schedule_check.rx_schedule_check_from_heating,
1498 None,
1499 true,
1500 );
1501 })
1502 .unwrap();
1503
1504 let mutex_device_scheduler_heating = Arc::new(Mutex::new(0));
1505
1506 let join_handle_mock_relay_manager = thread::Builder::new()
1508 .name("mock_relay_manager".to_string())
1509 .spawn(move || {
1510 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
1511 &mut channels.relay_manager.tx_relay_manager_to_heating,
1512 &mut channels.relay_manager.rx_relay_manager_from_heating,
1513 );
1514 println!(
1515 "Mock relay manager received {} commands.",
1516 actuation_events.len()
1517 );
1518 assert_eq!(actuation_events.len(), 1);
1519 let actuation_event = actuation_events.pop().unwrap();
1520 assert_eq!(
1521 actuation_event.command,
1522 InternalCommand::SwitchOff(AquariumDevice::Heater)
1523 );
1524 mock_actuator_states.check_terminal_condition_heating();
1525 })
1526 .unwrap();
1527
1528 let join_handle_test_environment = thread::Builder::new()
1530 .name("test_environment".to_string())
1531 .spawn(move || {
1532 let _ = channels
1533 .signal_handler
1534 .send_to_heating(InternalCommand::Quit);
1535 channels.signal_handler.receive_from_heating().unwrap();
1536 let _ = channels
1537 .signal_handler
1538 .send_to_heating(InternalCommand::Terminate);
1539 })
1540 .unwrap();
1541
1542 spin_sleeper.sleep(sleep_duration_100_millis);
1543
1544 let join_handle_test_object = thread::Builder::new()
1546 .name("test_object".to_string())
1547 .spawn(move || {
1548 let mut tx_heating_to_schedule_check_for_test_case_finish =
1550 channels.heating.tx_heating_to_schedule_check.clone();
1551 let mut tx_heating_to_relay_manager_for_test_case_finish =
1552 channels.heating.tx_heating_to_relay_manager.clone();
1553
1554 let mut heating_stats_transfer =
1555 HeatingStatsMockDataTransfer::new(&mut sql_interface.get_connection().unwrap());
1556 let mut heating_set_value_updater = MockSqlInterfaceHeatingSetVals::new(false, None, None, None);
1557
1558 let heating_mutexes = HeatingMutexes {
1559 mutex_sensor_manager_signals: Arc::new(Mutex::new(SensorManagerSignals::new(&config.sensor_manager))),
1560 mutex_tank_level_switch_signals,
1561 mutex_heating_status: Arc::new(Mutex::new(false)),
1562 };
1563
1564 heating.execute(
1565 mutex_device_scheduler_heating.clone(),
1566 &mut channels.heating,
1567 &mut heating_stats_transfer,
1568 &mut heating_set_value_updater,
1569 heating_mutexes,
1570 sql_interface_heating_stats
1571 );
1572
1573 let _ =
1575 tx_heating_to_schedule_check_for_test_case_finish.send(InternalCommand::Quit);
1576 let _ = tx_heating_to_relay_manager_for_test_case_finish.send(InternalCommand::Quit);
1577
1578 let mutex_data = mutex_device_scheduler_heating.lock().unwrap();
1580 assert_eq!(*mutex_data, 1);
1581 let finish_time = Instant::now();
1582 let execution_duration = finish_time.duration_since(start_time);
1583 assert_le!(execution_duration.as_millis(), 650);
1584 println!(
1585 "* [Heating] checking heating execution with pending quit/terminate command (time limit) succeeded."
1586 );
1587 })
1588 .unwrap();
1589
1590 join_handle_mock_schedule_check
1591 .join()
1592 .expect("Mock schedule check did not finish.");
1593 join_handle_mock_relay_manager
1594 .join()
1595 .expect("Mock relay manager thread did not finish.");
1596 join_handle_test_environment
1597 .join()
1598 .expect("Test environment thread did not finish.");
1599 join_handle_test_object
1600 .join()
1601 .expect("Test object thread did not finish.");
1602 }
1603
1604 #[test]
1605 pub fn test_heating_wait_for_midnight_calc_stats_heater_on() {
1608 let sleep_duration_100_millis = Duration::from_millis(100);
1609 let spin_sleeper = SpinSleeper::default();
1610
1611 let (mut config, mut heating, sql_interface, mut sql_interface_heating_stats) =
1612 prepare_heating_tests_mock_midnight_calculator(3, 15, None);
1613
1614 config.sensor_manager.replacement_value_water_temperature = 20.0;
1616 config.sensor_manager.replacement_value_ambient_temperature = 24.0;
1617
1618 let mut channels = Channels::new_for_test();
1619
1620 let mutex_tank_level_switch_signals =
1622 Arc::new(Mutex::new(TankLevelSwitchSignals::new(true, true, false)));
1623
1624 let initial_heating_stats_entry = sql_interface_heating_stats
1626 .get_single_heating_stats_from_database()
1627 .unwrap_or_else(|_| {
1628 HeatingStatsEntry {
1630 date: Local::now().date_naive(),
1631 energy: 0.0,
1632 ambient_temperature_average_value: 0.0,
1633 ambient_temperature_average_counter: 0,
1634 water_temperature_average_value: 0.0,
1635 water_temperature_average_counter: 0,
1636 heating_control_runtime: 0,
1637 }
1638 });
1639
1640 let join_handle_mock_schedule_check = thread::Builder::new()
1642 .name("mock_schedule_check".to_string())
1643 .spawn(move || {
1644 mock_schedule_check(
1645 &mut channels.schedule_check.tx_schedule_check_to_heating,
1646 &mut channels.schedule_check.rx_schedule_check_from_heating,
1647 None,
1648 true,
1649 );
1650 })
1651 .unwrap();
1652
1653 let mutex_device_scheduler_heating = Arc::new(Mutex::new(0));
1654
1655 let join_handle_mock_relay_manager = thread::Builder::new()
1657 .name("mock_relay_manager".to_string())
1658 .spawn(move || {
1659 mock_relay_manager(
1660 &mut channels.relay_manager.tx_relay_manager_to_heating,
1661 &mut channels.relay_manager.rx_relay_manager_from_heating,
1662 );
1663 })
1664 .unwrap();
1665
1666 let join_handle_test_environment = thread::Builder::new()
1668 .name("test_environment".to_string())
1669 .spawn(move || {
1670 let sleep_duration = Duration::from_secs(4);
1671 let spin_sleeper = SpinSleeper::default();
1672 spin_sleeper.sleep(sleep_duration);
1673 let _ = channels
1674 .signal_handler
1675 .send_to_heating(InternalCommand::Quit);
1676 channels.signal_handler.receive_from_heating().unwrap();
1677 let _ = channels
1678 .signal_handler
1679 .send_to_heating(InternalCommand::Terminate);
1680 })
1681 .unwrap();
1682
1683 spin_sleeper.sleep(sleep_duration_100_millis);
1684
1685 let join_handle_test_object = thread::Builder::new()
1687 .name("test_object".to_string())
1688 .spawn(move || {
1689 let mut tx_heating_to_schedule_check_for_test_case_finish =
1691 channels.heating.tx_heating_to_schedule_check.clone();
1692 let mut tx_heating_to_relay_manager_for_test_case_finish =
1693 channels.heating.tx_heating_to_relay_manager.clone();
1694
1695 let mut heating_stats_transfer =
1696 HeatingStatsMockDataTransfer::new(&mut sql_interface.get_connection().unwrap());
1697 let mut heating_set_value_updater = MockSqlInterfaceHeatingSetVals::new(false, None, None, None);
1698
1699 let heating_mutexes = HeatingMutexes {
1700 mutex_sensor_manager_signals: Arc::new(Mutex::new(SensorManagerSignals::new(&config.sensor_manager))),
1701 mutex_tank_level_switch_signals,
1702 mutex_heating_status: Arc::new(Mutex::new(false)),
1703 };
1704
1705 heating.execute(
1706 mutex_device_scheduler_heating.clone(),
1707 &mut channels.heating,
1708 &mut heating_stats_transfer,
1709 &mut heating_set_value_updater,
1710 heating_mutexes,
1711 sql_interface_heating_stats
1712 );
1713
1714 let _ =
1716 tx_heating_to_schedule_check_for_test_case_finish.send(InternalCommand::Quit);
1717 let _ = tx_heating_to_relay_manager_for_test_case_finish.send(InternalCommand::Quit);
1718
1719 println!(
1720 "test_heating_wait_for_midnight_calc_stats_heater_on (previous): {}",
1721 heating_stats_transfer.heating_stats_entry_previous,
1722 );
1723 assert_eq!(
1724 heating_stats_transfer
1725 .heating_stats_entry_previous
1726 .heating_control_runtime
1727 - initial_heating_stats_entry.heating_control_runtime,
1728 3
1729 );
1730 assert_le!(
1731 0.16666666,
1732 heating_stats_transfer.heating_stats_entry_previous.energy
1733 );
1734 assert_ge!(
1735 0.16666667,
1736 heating_stats_transfer.heating_stats_entry_previous.energy
1737 );
1738 assert_le!(
1739 23.999,
1740 heating_stats_transfer
1741 .heating_stats_entry_previous
1742 .ambient_temperature_average_value
1743 );
1744 assert_ge!(
1745 24.001,
1746 heating_stats_transfer
1747 .heating_stats_entry_previous
1748 .ambient_temperature_average_value
1749 );
1750 assert_le!(
1751 19.999,
1752 heating_stats_transfer
1753 .heating_stats_entry_previous
1754 .water_temperature_average_value
1755 );
1756 assert_ge!(
1757 20.001,
1758 heating_stats_transfer
1759 .heating_stats_entry_previous
1760 .water_temperature_average_value
1761 );
1762 assert_eq!(
1763 heating_stats_transfer
1764 .heating_stats_entry_previous
1765 .ambient_temperature_average_counter
1766 - initial_heating_stats_entry.ambient_temperature_average_counter,
1767 3
1768 );
1769 assert_eq!(
1770 heating_stats_transfer
1771 .heating_stats_entry_previous
1772 .water_temperature_average_counter
1773 - initial_heating_stats_entry.water_temperature_average_counter,
1774 3
1775 );
1776 println!(
1777 "test_heating_wait_for_midnight_calc_stats_heater_on (current): {}",
1778 heating_stats_transfer.heating_stats_entry,
1779 );
1780 assert_eq!(
1781 heating_stats_transfer
1782 .heating_stats_entry
1783 .heating_control_runtime
1784 - initial_heating_stats_entry.heating_control_runtime,
1785 1
1786 );
1787 assert_le!(0.0555, heating_stats_transfer.heating_stats_entry.energy);
1788 assert_ge!(0.0556, heating_stats_transfer.heating_stats_entry.energy);
1789 assert_le!(
1790 23.999,
1791 heating_stats_transfer
1792 .heating_stats_entry
1793 .ambient_temperature_average_value
1794 );
1795 assert_ge!(
1796 24.001,
1797 heating_stats_transfer
1798 .heating_stats_entry
1799 .ambient_temperature_average_value
1800 );
1801 assert_le!(
1802 19.999,
1803 heating_stats_transfer
1804 .heating_stats_entry
1805 .water_temperature_average_value
1806 );
1807 assert_ge!(
1808 20.001,
1809 heating_stats_transfer
1810 .heating_stats_entry
1811 .water_temperature_average_value
1812 );
1813 assert_eq!(
1814 heating_stats_transfer
1815 .heating_stats_entry
1816 .ambient_temperature_average_counter
1817 - initial_heating_stats_entry.ambient_temperature_average_counter,
1818 1
1819 );
1820 assert_eq!(
1821 heating_stats_transfer
1822 .heating_stats_entry
1823 .water_temperature_average_counter
1824 - initial_heating_stats_entry.water_temperature_average_counter,
1825 1
1826 );
1827 println!("* [Heating] checking heating waiting for midnight and stats calculation with heater on succeeded.");
1828 })
1829 .unwrap();
1830
1831 join_handle_mock_schedule_check
1832 .join()
1833 .expect("Mock schedule check did not finish.");
1834 join_handle_mock_relay_manager
1835 .join()
1836 .expect("Mock relay manager thread did not finish.");
1837 join_handle_test_environment
1838 .join()
1839 .expect("Test environment thread did not finish.");
1840 join_handle_test_object
1841 .join()
1842 .expect("Test object thread did not finish.");
1843 }
1844
1845 #[test]
1846 pub fn test_heating_wait_for_midnight_calc_stats_heater_off() {
1849 let sleep_duration_100_millis = Duration::from_millis(100);
1850 let spin_sleeper = SpinSleeper::default();
1851
1852 let (mut config, mut heating, sql_interface, mut sql_interface_heating_stats) =
1853 prepare_heating_tests_mock_midnight_calculator(3, 16, None);
1854
1855 config.sensor_manager.replacement_value_water_temperature = 30.0;
1857 config.sensor_manager.replacement_value_ambient_temperature = 24.0;
1858
1859 let mut channels = Channels::new_for_test();
1860
1861 let initial_heating_stats_entry = sql_interface_heating_stats
1863 .get_single_heating_stats_from_database()
1864 .unwrap_or_else(|_| {
1865 HeatingStatsEntry {
1867 date: Local::now().date_naive(),
1868 energy: 0.0,
1869 ambient_temperature_average_value: 0.0,
1870 ambient_temperature_average_counter: 0,
1871 water_temperature_average_value: 0.0,
1872 water_temperature_average_counter: 0,
1873 heating_control_runtime: 0,
1874 }
1875 });
1876
1877 let join_handle_mock_schedule_check = thread::Builder::new()
1879 .name("mock_schedule_check".to_string())
1880 .spawn(move || {
1881 mock_schedule_check(
1882 &mut channels.schedule_check.tx_schedule_check_to_heating,
1883 &mut channels.schedule_check.rx_schedule_check_from_heating,
1884 None,
1885 true,
1886 );
1887 })
1888 .unwrap();
1889
1890 let mutex_tank_level_switch_signals =
1892 Arc::new(Mutex::new(TankLevelSwitchSignals::new(true, true, false)));
1893
1894 let mutex_device_scheduler_heating = Arc::new(Mutex::new(0));
1895
1896 let join_handle_mock_relay_manager = thread::Builder::new()
1898 .name("mock_relay_manager".to_string())
1899 .spawn(move || {
1900 mock_relay_manager(
1901 &mut channels.relay_manager.tx_relay_manager_to_heating,
1902 &mut channels.relay_manager.rx_relay_manager_from_heating,
1903 );
1904 })
1905 .unwrap();
1906
1907 let join_handle_test_environment = thread::Builder::new()
1909 .name("test_environment".to_string())
1910 .spawn(move || {
1911 let sleep_duration = Duration::from_secs(4);
1912 let spin_sleeper = SpinSleeper::default();
1913 spin_sleeper.sleep(sleep_duration);
1914 let _ = channels
1915 .signal_handler
1916 .send_to_heating(InternalCommand::Quit);
1917 channels.signal_handler.receive_from_heating().unwrap();
1918 let _ = channels
1919 .signal_handler
1920 .send_to_heating(InternalCommand::Terminate);
1921 })
1922 .unwrap();
1923
1924 spin_sleeper.sleep(sleep_duration_100_millis);
1925
1926 let join_handle_test_object = thread::Builder::new()
1928 .name("test_object".to_string())
1929 .spawn(move || {
1930 let mut tx_heating_to_schedule_check_for_test_case_finish =
1932 channels.heating.tx_heating_to_schedule_check.clone();
1933 let mut tx_heating_to_relay_manager_for_test_case_finish =
1934 channels.heating.tx_heating_to_relay_manager.clone();
1935
1936 let mut heating_stats_transfer =
1937 HeatingStatsMockDataTransfer::new(&mut sql_interface.get_connection().unwrap());
1938 let mut heating_set_value_updater = MockSqlInterfaceHeatingSetVals::new(false, None, None, None);
1939
1940 let heating_mutexes = HeatingMutexes {
1941 mutex_sensor_manager_signals: Arc::new(Mutex::new(SensorManagerSignals::new(&config.sensor_manager))),
1942 mutex_tank_level_switch_signals,
1943 mutex_heating_status: Arc::new(Mutex::new(false)),
1944 };
1945
1946 heating.execute(
1947 mutex_device_scheduler_heating.clone(),
1948 &mut channels.heating,
1949 &mut heating_stats_transfer,
1950 &mut heating_set_value_updater,
1951 heating_mutexes,
1952 sql_interface_heating_stats
1953 );
1954
1955 let _ =
1957 tx_heating_to_schedule_check_for_test_case_finish.send(InternalCommand::Quit);
1958 let _ = tx_heating_to_relay_manager_for_test_case_finish.send(InternalCommand::Quit);
1959
1960 println!(
1961 "test_heating_wait_for_midnight_calc_stats_heater_on (previous): {}",
1962 heating_stats_transfer.heating_stats_entry_previous,
1963 );
1964 assert_eq!(
1965 heating_stats_transfer
1966 .heating_stats_entry_previous
1967 .heating_control_runtime
1968 - initial_heating_stats_entry.heating_control_runtime,
1969 3
1970 );
1971 assert_le!(
1972 -0.001,
1973 heating_stats_transfer.heating_stats_entry_previous.energy
1974 );
1975 assert_ge!(
1976 0.001,
1977 heating_stats_transfer.heating_stats_entry_previous.energy
1978 );
1979 assert_le!(
1980 23.999,
1981 heating_stats_transfer
1982 .heating_stats_entry_previous
1983 .ambient_temperature_average_value
1984 );
1985 assert_ge!(
1986 24.001,
1987 heating_stats_transfer
1988 .heating_stats_entry_previous
1989 .ambient_temperature_average_value
1990 );
1991 assert_le!(
1992 29.999,
1993 heating_stats_transfer
1994 .heating_stats_entry_previous
1995 .water_temperature_average_value
1996 );
1997 assert_ge!(
1998 30.001,
1999 heating_stats_transfer
2000 .heating_stats_entry_previous
2001 .water_temperature_average_value
2002 );
2003 assert_eq!(
2004 heating_stats_transfer
2005 .heating_stats_entry_previous
2006 .ambient_temperature_average_counter
2007 - initial_heating_stats_entry.ambient_temperature_average_counter,
2008 3
2009 );
2010 assert_eq!(
2011 heating_stats_transfer
2012 .heating_stats_entry_previous
2013 .water_temperature_average_counter
2014 - initial_heating_stats_entry.water_temperature_average_counter,
2015 3
2016 );
2017 println!(
2018 "test_heating_wait_for_midnight_calc_stats_heater_on (current): {}",
2019 heating_stats_transfer.heating_stats_entry,
2020 );
2021 assert_eq!(
2022 heating_stats_transfer
2023 .heating_stats_entry
2024 .heating_control_runtime
2025 - initial_heating_stats_entry.heating_control_runtime,
2026 1
2027 );
2028 assert_le!(-0.001, heating_stats_transfer.heating_stats_entry.energy);
2029 assert_ge!(0.001, heating_stats_transfer.heating_stats_entry.energy);
2030 assert_le!(
2031 23.999,
2032 heating_stats_transfer
2033 .heating_stats_entry
2034 .ambient_temperature_average_value
2035 );
2036 assert_ge!(
2037 24.001,
2038 heating_stats_transfer
2039 .heating_stats_entry
2040 .ambient_temperature_average_value
2041 );
2042 assert_le!(
2043 29.999,
2044 heating_stats_transfer
2045 .heating_stats_entry
2046 .water_temperature_average_value
2047 );
2048 assert_ge!(
2049 30.001,
2050 heating_stats_transfer
2051 .heating_stats_entry
2052 .water_temperature_average_value
2053 );
2054 assert_eq!(
2055 heating_stats_transfer
2056 .heating_stats_entry
2057 .ambient_temperature_average_counter
2058 - initial_heating_stats_entry.ambient_temperature_average_counter,
2059 1
2060 );
2061 assert_eq!(
2062 heating_stats_transfer
2063 .heating_stats_entry
2064 .water_temperature_average_counter
2065 - initial_heating_stats_entry.water_temperature_average_counter,
2066 1
2067 );
2068 println!("* [Heating] checking heating waiting for midnight and stats calculation with heater on succeeded.");
2069 })
2070 .unwrap();
2071
2072 join_handle_mock_schedule_check
2073 .join()
2074 .expect("Mock schedule check did not finish.");
2075 join_handle_mock_relay_manager
2076 .join()
2077 .expect("Mock relay manager thread did not finish.");
2078 join_handle_test_environment
2079 .join()
2080 .expect("Test environment thread did not finish.");
2081 join_handle_test_object
2082 .join()
2083 .expect("Test object thread did not finish.");
2084 }
2085
2086 #[test]
2087 pub fn test_heating_blocked_by_schedule() {
2090 let sleep_duration_100_millis = Duration::from_millis(100);
2091 let spin_sleeper = SpinSleeper::default();
2092
2093 let (mut config, mut heating, sql_interface, sql_interface_heating_stats) =
2094 prepare_heating_tests_mock_midnight_calculator(3600, 17, None);
2095
2096 config.sensor_manager.replacement_value_water_temperature = 20.0;
2098 config.sensor_manager.replacement_value_ambient_temperature = 24.0;
2099
2100 let mut channels = Channels::new_for_test();
2101
2102 let mutex_tank_level_switch_signals =
2104 Arc::new(Mutex::new(TankLevelSwitchSignals::new(true, true, false)));
2105
2106 let join_handle_mock_schedule_check = thread::Builder::new()
2108 .name("mock_schedule_check".to_string())
2109 .spawn(move || {
2110 mock_schedule_check(
2111 &mut channels.schedule_check.tx_schedule_check_to_heating,
2112 &mut channels.schedule_check.rx_schedule_check_from_heating,
2113 None,
2114 false,
2115 );
2116 })
2117 .unwrap();
2118
2119 let mutex_device_scheduler_heating = Arc::new(Mutex::new(0));
2120
2121 let join_handle_mock_relay_manager = thread::Builder::new()
2123 .name("mock_relay_manager".to_string())
2124 .spawn(move || {
2125 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
2126 &mut channels.relay_manager.tx_relay_manager_to_heating,
2127 &mut channels.relay_manager.rx_relay_manager_from_heating,
2128 );
2129 assert_eq!(actuation_events.len(), 1);
2130 let actuation_event = actuation_events.pop().unwrap();
2131 assert_eq!(
2132 actuation_event.command,
2133 InternalCommand::SwitchOff(AquariumDevice::Heater)
2134 );
2135 mock_actuator_states.check_terminal_condition_heating();
2136 })
2137 .unwrap();
2138
2139 let join_handle_test_environment = thread::Builder::new()
2141 .name("test_environment".to_string())
2142 .spawn(move || {
2143 let sleep_duration_ten_seconds = Duration::from_secs(10);
2144 let spin_sleeper = SpinSleeper::default();
2145 spin_sleeper.sleep(sleep_duration_ten_seconds);
2146 let _ = channels
2147 .signal_handler
2148 .send_to_heating(InternalCommand::Quit);
2149 channels.signal_handler.receive_from_heating().unwrap();
2150 let _ = channels
2151 .signal_handler
2152 .send_to_heating(InternalCommand::Terminate);
2153 })
2154 .unwrap();
2155
2156 spin_sleeper.sleep(sleep_duration_100_millis);
2158
2159 let join_handle_test_object = thread::Builder::new()
2161 .name("test_object".to_string())
2162 .spawn(move || {
2163 let mut tx_heating_to_schedule_check_for_test_case_finish =
2165 channels.heating.tx_heating_to_schedule_check.clone();
2166 let mut tx_heating_to_relay_manager_for_test_case_finish =
2167 channels.heating.tx_heating_to_relay_manager.clone();
2168
2169 let mut heating_stats_transfer =
2170 HeatingStatsMockDataTransfer::new(&mut sql_interface.get_connection().unwrap());
2171 let mut heating_set_value_updater =
2172 MockSqlInterfaceHeatingSetVals::new(false, None, None, None);
2173
2174 let heating_mutexes = HeatingMutexes {
2175 mutex_sensor_manager_signals: Arc::new(Mutex::new(SensorManagerSignals::new(
2176 &config.sensor_manager,
2177 ))),
2178 mutex_tank_level_switch_signals,
2179 mutex_heating_status: Arc::new(Mutex::new(false)),
2180 };
2181
2182 heating.execute(
2183 mutex_device_scheduler_heating.clone(),
2184 &mut channels.heating,
2185 &mut heating_stats_transfer,
2186 &mut heating_set_value_updater,
2187 heating_mutexes,
2188 sql_interface_heating_stats,
2189 );
2190
2191 let _ =
2193 tx_heating_to_schedule_check_for_test_case_finish.send(InternalCommand::Quit);
2194 let _ =
2195 tx_heating_to_relay_manager_for_test_case_finish.send(InternalCommand::Quit);
2196 println!("* [Heating] checking if schedule checker can block actuation.");
2197 })
2198 .unwrap();
2199
2200 join_handle_mock_schedule_check
2201 .join()
2202 .expect("Mock schedule check did not finish.");
2203 join_handle_mock_relay_manager
2204 .join()
2205 .expect("Mock relay manager thread did not finish.");
2206 join_handle_test_environment
2207 .join()
2208 .expect("Test environment thread did not finish.");
2209 join_handle_test_object
2210 .join()
2211 .expect("Test object thread did not finish.");
2212 }
2213
2214 #[test]
2215 pub fn test_heating_component_protection_when_tank_level_low() {
2218 let sleep_duration_100_millis = Duration::from_millis(100);
2219 let spin_sleeper = SpinSleeper::default();
2220
2221 let (mut config, mut heating, sql_interface, sql_interface_heating_stats) =
2222 prepare_heating_tests_mock_midnight_calculator(3, 18, None);
2223 config.sensor_manager.replacement_value_water_temperature = 20.0;
2225 config.sensor_manager.replacement_value_ambient_temperature = 24.0;
2226
2227 let mut channels = Channels::new_for_test();
2228
2229 let join_handle_mock_schedule_check = thread::Builder::new()
2231 .name("mock_schedule_check".to_string())
2232 .spawn(move || {
2233 mock_schedule_check(
2234 &mut channels.schedule_check.tx_schedule_check_to_heating,
2235 &mut channels.schedule_check.rx_schedule_check_from_heating,
2236 None,
2237 true,
2238 );
2239 })
2240 .unwrap();
2241
2242 let mutex_tank_level_switch_signals =
2244 Arc::new(Mutex::new(TankLevelSwitchSignals::new(false, false, false)));
2245
2246 let mutex_device_scheduler_heating = Arc::new(Mutex::new(0));
2247
2248 let join_handle_mock_relay_manager = thread::Builder::new()
2250 .name("mock_relay_manager".to_string())
2251 .spawn(move || {
2252 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
2253 &mut channels.relay_manager.tx_relay_manager_to_heating,
2254 &mut channels.relay_manager.rx_relay_manager_from_heating,
2255 );
2256 assert_eq!(actuation_events.len(), 2);
2257 let actuation_event = actuation_events.pop().unwrap();
2258 assert_eq!(
2259 actuation_event.command,
2260 InternalCommand::SwitchOff(AquariumDevice::Heater)
2261 );
2262 let actuation_event = actuation_events.pop().unwrap();
2263 assert_eq!(
2264 actuation_event.command,
2265 InternalCommand::SwitchOn(AquariumDevice::Heater)
2266 );
2267 mock_actuator_states.check_terminal_condition_heating();
2268 })
2269 .unwrap();
2270
2271 let join_handle_test_environment = thread::Builder::new()
2273 .name("test_environment".to_string())
2274 .spawn(move || {
2275 let sleep_duration_ten_seconds = Duration::from_secs(10);
2276 let spin_sleeper = SpinSleeper::default();
2277 spin_sleeper.sleep(sleep_duration_ten_seconds);
2278 let _ = channels
2279 .signal_handler
2280 .send_to_heating(InternalCommand::Quit);
2281 channels.signal_handler.receive_from_heating().unwrap();
2282 let _ = channels
2283 .signal_handler
2284 .send_to_heating(InternalCommand::Terminate);
2285 })
2286 .unwrap();
2287
2288 spin_sleeper.sleep(sleep_duration_100_millis);
2290
2291 let join_handle_test_object = thread::Builder::new()
2293 .name("test_object".to_string())
2294 .spawn(move || {
2295 let mut tx_heating_to_schedule_check_for_test_case_finish =
2297 channels.heating.tx_heating_to_schedule_check.clone();
2298 let mut tx_heating_to_relay_manager_for_test_case_finish =
2299 channels.heating.tx_heating_to_relay_manager.clone();
2300
2301 let mut heating_stats_transfer =
2302 HeatingStatsMockDataTransfer::new(&mut sql_interface.get_connection().unwrap());
2303 let mut heating_set_value_updater =
2304 MockSqlInterfaceHeatingSetVals::new(false, None, None, None);
2305
2306 let heating_mutexes = HeatingMutexes {
2307 mutex_sensor_manager_signals: Arc::new(Mutex::new(SensorManagerSignals::new(
2308 &config.sensor_manager,
2309 ))),
2310 mutex_tank_level_switch_signals,
2311 mutex_heating_status: Arc::new(Mutex::new(false)),
2312 };
2313
2314 heating.execute(
2315 mutex_device_scheduler_heating.clone(),
2316 &mut channels.heating,
2317 &mut heating_stats_transfer,
2318 &mut heating_set_value_updater,
2319 heating_mutexes,
2320 sql_interface_heating_stats,
2321 );
2322
2323 let _ =
2325 tx_heating_to_schedule_check_for_test_case_finish.send(InternalCommand::Quit);
2326 let _ =
2327 tx_heating_to_relay_manager_for_test_case_finish.send(InternalCommand::Quit);
2328
2329 println!("* [Heating] checking if schedule checker can block actuation.");
2330 })
2331 .unwrap();
2332
2333 join_handle_mock_schedule_check
2334 .join()
2335 .expect("Mock schedule check did not finish.");
2336 join_handle_mock_relay_manager
2337 .join()
2338 .expect("Mock relay manager thread did not finish.");
2339 join_handle_test_environment
2340 .join()
2341 .expect("Test environment thread did not finish.");
2342 join_handle_test_object
2343 .join()
2344 .expect("Test object thread did not finish.");
2345 }
2346
2347 #[test]
2348 pub fn test_messaging_stops_starts_heating() {
2354 let (mut config, mut heating, sql_interface, sql_interface_heating_stats) =
2355 prepare_heating_tests_mock_midnight_calculator(3600, 19, None);
2356
2357 config.sensor_manager.replacement_value_water_temperature = 20.0;
2359 config.sensor_manager.replacement_value_ambient_temperature = 24.0;
2360
2361 let mut channels = Channels::new_for_test();
2362
2363 let mutex_device_scheduler_heating = Arc::new(Mutex::new(0));
2364 let mutex_device_scheduler_test_environment = mutex_device_scheduler_heating.clone();
2365
2366 let join_handle_mock_schedule_check = thread::Builder::new()
2368 .name("mock_schedule_check".to_string())
2369 .spawn(move || {
2370 mock_schedule_check(
2371 &mut channels.schedule_check.tx_schedule_check_to_heating,
2372 &mut channels.schedule_check.rx_schedule_check_from_heating,
2373 None,
2374 true,
2375 );
2376 })
2377 .unwrap();
2378
2379 let mutex_tank_level_switch_signals =
2381 Arc::new(Mutex::new(TankLevelSwitchSignals::new(true, true, false)));
2382
2383 let join_handle_mock_relay_manager = thread::Builder::new()
2385 .name("mock_relay_manager".to_string())
2386 .spawn(move || {
2387 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
2388 &mut channels.relay_manager.tx_relay_manager_to_heating,
2389 &mut channels.relay_manager.rx_relay_manager_from_heating,
2390 );
2391 println!("actuation_events:");
2392 for actuation_event in &actuation_events {
2393 println!("{}", actuation_event);
2394 }
2395 assert_eq!(actuation_events.len(), 4);
2396 let actuation_event = actuation_events.pop().unwrap();
2397 assert_eq!(
2398 actuation_event.command,
2399 InternalCommand::SwitchOff(AquariumDevice::Heater)
2400 );
2401 let actuation_event = actuation_events.pop().unwrap();
2402 assert_eq!(
2403 actuation_event.command,
2404 InternalCommand::SwitchOn(AquariumDevice::Heater)
2405 );
2406 let actuation_event = actuation_events.pop().unwrap();
2407 assert_eq!(
2408 actuation_event.command,
2409 InternalCommand::SwitchOff(AquariumDevice::Heater)
2410 );
2411 let actuation_event = actuation_events.pop().unwrap();
2412 assert_eq!(
2413 actuation_event.command,
2414 InternalCommand::SwitchOn(AquariumDevice::Heater)
2415 );
2416 mock_actuator_states.check_terminal_condition_heating();
2417 })
2418 .unwrap();
2419
2420 let join_handle_test_environment = thread::Builder::new()
2422 .name("test_environment".to_string())
2423 .spawn(move || {
2424 let sleep_duration_100_millis = Duration::from_millis(100);
2425 let spin_sleeper = SpinSleeper::default();
2426
2427 for _ in 0..10 {
2429 spin_sleeper.sleep(sleep_duration_100_millis);
2430 }
2431 assert_eq!(*mutex_device_scheduler_test_environment.lock().unwrap(), 1);
2433
2434 match channels
2436 .messaging
2437 .tx_messaging_to_heating
2438 .send(InternalCommand::Stop)
2439 {
2440 Ok(()) => { }
2441 Err(e) => {
2442 panic!(
2443 "{}: error when sending stop command to test object ({e:?})",
2444 module_path!()
2445 );
2446 }
2447 }
2448
2449 for _ in 0..10 {
2451 spin_sleeper.sleep(sleep_duration_100_millis);
2452 }
2453 assert_eq!(*mutex_device_scheduler_test_environment.lock().unwrap(), 2);
2455
2456 match channels
2458 .messaging
2459 .tx_messaging_to_heating
2460 .send(InternalCommand::Start)
2461 {
2462 Ok(()) => { }
2463 Err(e) => {
2464 panic!(
2465 "{}: error when sending start command to test object ({e:?})",
2466 module_path!()
2467 );
2468 }
2469 }
2470
2471 for _ in 0..10 {
2473 spin_sleeper.sleep(sleep_duration_100_millis);
2474 }
2475 assert_eq!(*mutex_device_scheduler_test_environment.lock().unwrap(), 3);
2477
2478 match channels
2480 .messaging
2481 .tx_messaging_to_heating
2482 .send(InternalCommand::Stop)
2483 {
2484 Ok(()) => { }
2485 Err(e) => {
2486 panic!(
2487 "{}: error when sending stop command to test object ({e:?})",
2488 module_path!()
2489 );
2490 }
2491 }
2492
2493 for _ in 0..10 {
2495 spin_sleeper.sleep(sleep_duration_100_millis);
2496 }
2497 assert_eq!(*mutex_device_scheduler_test_environment.lock().unwrap(), 4);
2499
2500 let _ = channels
2502 .signal_handler
2503 .send_to_heating(InternalCommand::Quit);
2504 channels.signal_handler.receive_from_heating().unwrap();
2505
2506 assert_eq!(*mutex_device_scheduler_test_environment.lock().unwrap(), 4);
2508 let _ = channels
2509 .signal_handler
2510 .send_to_heating(InternalCommand::Terminate);
2511 })
2512 .unwrap();
2513
2514 let join_handle_test_object = thread::Builder::new()
2516 .name("test_object".to_string())
2517 .spawn(move || {
2518 let mut tx_heating_to_schedule_check_for_test_case_finish =
2520 channels.heating.tx_heating_to_schedule_check.clone();
2521 let mut tx_heating_to_relay_manager_for_test_case_finish =
2522 channels.heating.tx_heating_to_relay_manager.clone();
2523
2524 let mut heating_stats_transfer =
2525 HeatingStatsMockDataTransfer::new(&mut sql_interface.get_connection().unwrap());
2526 let mut heating_set_value_updater =
2527 MockSqlInterfaceHeatingSetVals::new(false, None, None, None);
2528
2529 let heating_mutexes = HeatingMutexes {
2530 mutex_sensor_manager_signals: Arc::new(Mutex::new(SensorManagerSignals::new(
2531 &config.sensor_manager,
2532 ))),
2533 mutex_tank_level_switch_signals,
2534 mutex_heating_status: Arc::new(Mutex::new(false)),
2535 };
2536
2537 heating.execute(
2538 mutex_device_scheduler_heating.clone(),
2539 &mut channels.heating,
2540 &mut heating_stats_transfer,
2541 &mut heating_set_value_updater,
2542 heating_mutexes,
2543 sql_interface_heating_stats,
2544 );
2545
2546 let _ =
2548 tx_heating_to_schedule_check_for_test_case_finish.send(InternalCommand::Quit);
2549 let _ =
2550 tx_heating_to_relay_manager_for_test_case_finish.send(InternalCommand::Quit);
2551
2552 println!("* [Heating] checking if messaging can block and restart actuation.");
2553 })
2554 .unwrap();
2555
2556 join_handle_mock_schedule_check
2557 .join()
2558 .expect("Mock schedule check did not finish.");
2559 join_handle_mock_relay_manager
2560 .join()
2561 .expect("Mock relay manager thread did not finish.");
2562 join_handle_test_environment
2563 .join()
2564 .expect("Test environment thread did not finish.");
2565 join_handle_test_object
2566 .join()
2567 .expect("Test object thread did not finish.");
2568 }
2569
2570 #[test]
2571 pub fn test_heating_with_increased_set_values() {
2576 let sleep_duration_100_millis = Duration::from_millis(100);
2577 let spin_sleeper = SpinSleeper::default();
2578
2579 let (mut config, mut heating, sql_interface, sql_interface_heating_stats) =
2580 prepare_heating_tests_mock_midnight_calculator(3600, 50, None);
2581 config.sensor_manager.replacement_value_water_temperature = 30.0;
2583 config.sensor_manager.replacement_value_ambient_temperature = 24.0;
2584
2585 let mut channels = Channels::new_for_test();
2586
2587 let mutex_tank_level_switch_signals =
2589 Arc::new(Mutex::new(TankLevelSwitchSignals::new(true, true, false)));
2590
2591 let join_handle_mock_schedule_check = thread::Builder::new()
2593 .name("mock_schedule_check".to_string())
2594 .spawn(move || {
2595 mock_schedule_check(
2596 &mut channels.schedule_check.tx_schedule_check_to_heating,
2597 &mut channels.schedule_check.rx_schedule_check_from_heating,
2598 None,
2599 true,
2600 );
2601 })
2602 .unwrap();
2603
2604 let mutex_device_scheduler_heating = Arc::new(Mutex::new(0));
2606
2607 let join_handle_mock_relay_manager = thread::Builder::new()
2609 .name("mock_relay_manager".to_string())
2610 .spawn(move || {
2611 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
2612 &mut channels.relay_manager.tx_relay_manager_to_heating,
2613 &mut channels.relay_manager.rx_relay_manager_from_heating,
2614 );
2615 assert_eq!(actuation_events.len(), 3);
2616 let actuation_event_3 = actuation_events.pop().unwrap();
2617 let actuation_event_2 = actuation_events.pop().unwrap();
2618 let actuation_event_1 = actuation_events.pop().unwrap();
2619 assert_eq!(
2620 actuation_event_1.command,
2621 InternalCommand::SwitchOff(AquariumDevice::Heater)
2622 );
2623 assert_eq!(
2624 actuation_event_2.command,
2625 InternalCommand::SwitchOn(AquariumDevice::Heater)
2626 );
2627 assert_eq!(
2628 actuation_event_3.command,
2629 InternalCommand::SwitchOff(AquariumDevice::Heater)
2630 );
2631 mock_actuator_states.check_terminal_condition_heating();
2632 })
2633 .unwrap();
2634
2635 let join_handle_test_environment = thread::Builder::new()
2637 .name("test_environment".to_string())
2638 .spawn(move || {
2639 let sleep_duration_twelve_seconds = Duration::from_secs(12);
2640 let spin_sleeper = SpinSleeper::default();
2641 spin_sleeper.sleep(sleep_duration_twelve_seconds);
2642 let _ = channels
2643 .signal_handler
2644 .send_to_heating(InternalCommand::Quit);
2645 channels.signal_handler.receive_from_heating().unwrap();
2646 let _ = channels
2647 .signal_handler
2648 .send_to_heating(InternalCommand::Terminate);
2649 })
2650 .unwrap();
2651
2652 spin_sleeper.sleep(sleep_duration_100_millis);
2654
2655 let join_handle_test_object = thread::Builder::new()
2657 .name("test_object".to_string())
2658 .spawn(move || {
2659 let mut tx_heating_to_schedule_check_for_test_case_finish =
2661 channels.heating.tx_heating_to_schedule_check.clone();
2662 let mut tx_heating_to_relay_manager_for_test_case_finish =
2663 channels.heating.tx_heating_to_relay_manager.clone();
2664
2665 let mut heating_stats_transfer =
2666 HeatingStatsMockDataTransfer::new(&mut sql_interface.get_connection().unwrap());
2667
2668 let mut heating_set_value_updater = MockSqlInterfaceHeatingSetVals::new(
2670 true,
2671 Some(50.0),
2672 Some(40.0),
2673 Some(Duration::from_secs(10)),
2674 );
2675
2676 let heating_mutexes = HeatingMutexes {
2677 mutex_sensor_manager_signals: Arc::new(Mutex::new(SensorManagerSignals::new(
2678 &config.sensor_manager,
2679 ))),
2680 mutex_tank_level_switch_signals,
2681 mutex_heating_status: Arc::new(Mutex::new(false)),
2682 };
2683
2684 heating.execute(
2685 mutex_device_scheduler_heating.clone(),
2686 &mut channels.heating,
2687 &mut heating_stats_transfer,
2688 &mut heating_set_value_updater,
2689 heating_mutexes,
2690 sql_interface_heating_stats,
2691 );
2692
2693 let _ =
2695 tx_heating_to_schedule_check_for_test_case_finish.send(InternalCommand::Quit);
2696 let _ =
2697 tx_heating_to_relay_manager_for_test_case_finish.send(InternalCommand::Quit);
2698
2699 println!("* [Heating] checking reaction to high temperature succeeded.");
2700 })
2701 .unwrap();
2702
2703 join_handle_mock_schedule_check
2704 .join()
2705 .expect("Mock schedule check did not finish.");
2706 join_handle_mock_relay_manager
2707 .join()
2708 .expect("Mock relay manager thread did not finish.");
2709 join_handle_test_environment
2710 .join()
2711 .expect("Test environment thread did not finish.");
2712 join_handle_test_object
2713 .join()
2714 .expect("Test object thread did not finish.");
2715 }
2716
2717 #[test]
2718 pub fn test_heating_with_measured_temperature_low_with_mutex_blocked() {
2724 let sleep_duration_100_millis = Duration::from_millis(100);
2725 let spin_sleeper = SpinSleeper::default();
2726
2727 let (mut config, mut heating, sql_interface, sql_interface_heating_stats) =
2728 prepare_heating_tests_mock_midnight_calculator(3600, 12, None);
2729
2730 config.sensor_manager.replacement_value_water_temperature = 20.0;
2732 config.sensor_manager.replacement_value_ambient_temperature = 24.0;
2733
2734 let mut channels = Channels::new_for_test();
2735
2736 let mutex_tank_level_switch_signals =
2738 Arc::new(Mutex::new(TankLevelSwitchSignals::new(true, true, false)));
2739
2740 let join_handle_mock_schedule_check = thread::Builder::new()
2742 .name("mock_schedule_check".to_string())
2743 .spawn(move || {
2744 mock_schedule_check(
2745 &mut channels.schedule_check.tx_schedule_check_to_heating,
2746 &mut channels.schedule_check.rx_schedule_check_from_heating,
2747 None,
2748 true,
2749 );
2750 })
2751 .unwrap();
2752
2753 let mutex_device_scheduler_heating = Arc::new(Mutex::new(0));
2754 let mutex_heating_status = Arc::new(Mutex::new(false));
2755 let mutex_heating_status_clone_for_test_environment = mutex_heating_status.clone();
2756
2757 let join_handle_mock_relay_manager = thread::Builder::new()
2759 .name("mock_relay_manager".to_string())
2760 .spawn(move || {
2761 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
2762 &mut channels.relay_manager.tx_relay_manager_to_heating,
2763 &mut channels.relay_manager.rx_relay_manager_from_heating,
2764 );
2765 assert_eq!(actuation_events.len(), 2);
2766 let last_actuation_event = actuation_events.pop().unwrap();
2767 assert_eq!(
2768 last_actuation_event.command,
2769 InternalCommand::SwitchOff(AquariumDevice::Heater)
2770 );
2771 let first_actuation_event = actuation_events.pop().unwrap();
2772 assert_eq!(
2773 first_actuation_event.command,
2774 InternalCommand::SwitchOn(AquariumDevice::Heater)
2775 );
2776 mock_actuator_states.check_terminal_condition_heating();
2777 })
2778 .unwrap();
2779
2780 let join_handle_test_environment = thread::Builder::new()
2782 .name("test_environment".to_string())
2783 .spawn(move || {
2784 let sleep_duration_one_second = Duration::from_secs(1);
2785 let spin_sleeper = SpinSleeper::default();
2786 for i in 0..10 {
2787 if i % 2 == 0 {
2788 spin_sleeper.sleep(sleep_duration_one_second);
2789 } else {
2790 {
2792 match mutex_heating_status_clone_for_test_environment.lock() {
2793 Ok(_) => {
2794 spin_sleeper.sleep(sleep_duration_one_second);
2795 }
2796 Err(_) => {
2797 }
2799 }
2800 }
2801 }
2802 }
2803 let _ = channels
2804 .signal_handler
2805 .send_to_heating(InternalCommand::Quit);
2806 channels.signal_handler.receive_from_heating().unwrap();
2807 let _ = channels
2808 .signal_handler
2809 .send_to_heating(InternalCommand::Terminate);
2810 })
2811 .unwrap();
2812
2813 spin_sleeper.sleep(sleep_duration_100_millis);
2815
2816 let join_handle_test_object = thread::Builder::new()
2818 .name("test_object".to_string())
2819 .spawn(move || {
2820 let mut tx_heating_to_schedule_check_for_test_case_finish =
2822 channels.heating.tx_heating_to_schedule_check.clone();
2823 let mut tx_heating_to_relay_manager_for_test_case_finish =
2824 channels.heating.tx_heating_to_relay_manager.clone();
2825
2826 let mut heating_stats_transfer =
2827 HeatingStatsMockDataTransfer::new(&mut sql_interface.get_connection().unwrap());
2828 let mut heating_set_value_updater =
2829 MockSqlInterfaceHeatingSetVals::new(false, None, None, None);
2830
2831 let heating_mutexes = HeatingMutexes {
2832 mutex_sensor_manager_signals: Arc::new(Mutex::new(SensorManagerSignals::new(
2833 &config.sensor_manager,
2834 ))),
2835 mutex_tank_level_switch_signals,
2836 mutex_heating_status
2837 };
2838
2839 heating.execute(
2840 mutex_device_scheduler_heating.clone(),
2841 &mut channels.heating,
2842 &mut heating_stats_transfer,
2843 &mut heating_set_value_updater,
2844 heating_mutexes,
2845 sql_interface_heating_stats
2846 );
2847
2848 let _ =
2850 tx_heating_to_schedule_check_for_test_case_finish.send(InternalCommand::Quit);
2851 let _ =
2852 tx_heating_to_relay_manager_for_test_case_finish.send(InternalCommand::Quit);
2853
2854 assert_eq!(heating.mutex_access_duration_exceeded, true);
2855
2856 println!("* [Heating] checking reaction to low temperature with mutex blocked succeeded.");
2857 })
2858 .unwrap();
2859
2860 join_handle_mock_schedule_check
2861 .join()
2862 .expect("Mock schedule check did not finish.");
2863 join_handle_mock_relay_manager
2864 .join()
2865 .expect("Mock relay manager thread did not finish.");
2866 join_handle_test_environment
2867 .join()
2868 .expect("Test environment thread did not finish.");
2869 join_handle_test_object
2870 .join()
2871 .expect("Test object thread did not finish.");
2872 }
2873}