1use log::error;
11#[cfg(not(test))]
12use log::warn;
13
14#[cfg(not(test))]
15use log::info;
16
17use crate::database::thermal_set_value_updater_trait::ThermalSetValueUpdaterTrait;
18use crate::sensors::sensor_manager::SensorManagerSignals;
19use crate::utilities::channel_content::{ActuatorState, AquariumDevice, InternalCommand};
20use crate::utilities::common::check_if_mutex_is_blocked;
21use crate::utilities::proc_ext_req::ProcessExternalRequestTrait;
22use crate::utilities::sawtooth_profile::SawToothProfile;
23use spin_sleep::SpinSleeper;
24use std::sync::{Arc, Mutex};
25use std::time::{Duration, Instant};
26
27use crate::thermal::ventilation_channels::VentilationChannels;
28use crate::thermal::ventilation_config::VentilationConfig;
29use crate::utilities::acknowledge_signal_handler::AcknowledgeSignalHandlerTrait;
30use crate::utilities::check_mutex_access_duration::CheckMutexAccessDurationTrait;
31use crate::utilities::logger::log_error_chain;
32use crate::utilities::wait_for_termination::WaitForTerminationTrait;
33use crate::{manage_cycle_time_thermal, perform_schedule_check, update_thermal_set_values};
34#[cfg(all(not(test), target_os = "linux"))]
35use nix::unistd::gettid;
36
37const CYCLE_TIME_VENTILATION_MILLIS: u64 = 500;
38
39const TIME_INCREMENT_VENTILATION_SECS: f32 = CYCLE_TIME_VENTILATION_MILLIS as f32 / 1000.0;
40
41const MAX_MUTEX_ACCESS_DURATION_MILLIS: u64 = 10;
43
44fn actuate_ventilation(
56 command: InternalCommand,
57 mutex_device_scheduler_ventilation: &Arc<Mutex<i32>>,
58 mutex_blocked_during_actuation: &mut bool,
59 ventilation_channels: &mut VentilationChannels,
60) {
61 *mutex_blocked_during_actuation |=
63 check_if_mutex_is_blocked(mutex_device_scheduler_ventilation);
64
65 let mut mutex_data = mutex_device_scheduler_ventilation.lock().unwrap();
67
68 if let Err(e) = ventilation_channels.send_to_relay_manager(command.clone()) {
70 let error_message = format!("Channel communication to relay manager for {command} failed.");
71 log_error_chain(module_path!(), &error_message, e);
72 } else {
73 if let Err(e) = ventilation_channels.receive_from_relay_manager() {
75 let error_message =
76 format!("Receiving answer from relay manager for {command} failed.");
77 log_error_chain(module_path!(), &error_message, e);
78 }
79 };
80 *mutex_data += 1;
81}
82
83fn switch_on_ventilation(
94 mutex_device_scheduler_ventilation: &Arc<Mutex<i32>>,
95 mutex_blocked_during_actuation: &mut bool,
96 ventilation_channels: &mut VentilationChannels,
97) {
98 actuate_ventilation(
99 InternalCommand::SwitchOn(AquariumDevice::Ventilation),
100 mutex_device_scheduler_ventilation,
101 mutex_blocked_during_actuation,
102 ventilation_channels,
103 );
104}
105
106fn switch_off_ventilation(
117 mutex_device_scheduler_ventilation: &Arc<Mutex<i32>>,
118 mutex_blocked_during_actuation: &mut bool,
119 ventilation_channels: &mut VentilationChannels,
120) {
121 actuate_ventilation(
122 InternalCommand::SwitchOff(AquariumDevice::Ventilation),
123 mutex_device_scheduler_ventilation,
124 mutex_blocked_during_actuation,
125 ventilation_channels,
126 );
127}
128
129#[cfg_attr(doc, aquamarine::aquamarine)]
130pub struct Ventilation {
146 config: VentilationConfig,
147
148 lock_error_channel_send_schedule_check: bool,
150
151 lock_error_channel_receive_schedule_check: bool,
153
154 pub lock_warn_max_mutex_access_duration: bool,
156
157 #[cfg(test)]
159 pub mutex_access_duration_exceeded: bool,
160
161 lock_error_ventilation_set_value_read_failure: bool,
163
164 pub lock_warn_inapplicable_command_signal_handler: bool,
166
167 pub lock_error_channel_receive_termination: bool,
169
170 pub max_mutex_access_duration: Duration,
172}
173
174impl ProcessExternalRequestTrait for Ventilation {}
175
176impl Ventilation {
177 pub fn new(config: VentilationConfig) -> Ventilation {
192 Self {
193 config,
194 lock_error_channel_send_schedule_check: false,
195 lock_error_channel_receive_schedule_check: false,
196 lock_warn_max_mutex_access_duration: false,
197 lock_error_ventilation_set_value_read_failure: false,
198
199 lock_warn_inapplicable_command_signal_handler: false,
200 #[cfg(test)]
201 mutex_access_duration_exceeded: false,
202 lock_error_channel_receive_termination: false,
203 max_mutex_access_duration: Duration::from_millis(MAX_MUTEX_ACCESS_DURATION_MILLIS),
204 }
205 }
206
207 fn calc_normalized_control_deviation(&self, measured_value: f32) -> f32 {
223 let target_value_delta: f32 =
224 self.config.switch_on_temperature - self.config.switch_off_temperature;
225 let measured_value_delta: f32 = measured_value - self.config.switch_off_temperature;
226 if target_value_delta > 0.0 {
227 measured_value_delta / target_value_delta
228 } else {
229 0.0 }
231 }
232
233 fn conditional_ventilation_switch(
249 condition: bool,
250 ventilation_state: &mut ActuatorState,
251 mutex_device_scheduler_ventilation: &Arc<Mutex<i32>>,
252 ventilation_channels: &mut VentilationChannels,
253 mutex_was_blocked_during_actuation: &mut bool,
254 ) {
255 match condition {
256 true => {
257 if *ventilation_state != ActuatorState::On {
259 switch_on_ventilation(
260 mutex_device_scheduler_ventilation,
261 mutex_was_blocked_during_actuation,
262 ventilation_channels,
263 );
264 *ventilation_state = ActuatorState::On;
265 }
266 }
267 false => {
268 if *ventilation_state != ActuatorState::Off {
270 switch_off_ventilation(
271 mutex_device_scheduler_ventilation,
272 mutex_was_blocked_during_actuation,
273 ventilation_channels,
274 );
275 *ventilation_state = ActuatorState::Off;
276 }
277 }
278 }
279 }
280
281 pub fn execute(
307 &mut self,
308 mutex_device_scheduler_ventilation: Arc<Mutex<i32>>,
309 ventilation_channels: &mut VentilationChannels,
310 ventilation_set_val_updater: &mut impl ThermalSetValueUpdaterTrait,
311 mutex_sensor_manager_signals: Arc<Mutex<SensorManagerSignals>>,
312 mutex_ventilation_status: Arc<Mutex<bool>>,
313 ) {
314 #[cfg(all(target_os = "linux", not(test)))]
315 info!(target: module_path!(), "Thread started with TID: {}", gettid());
316
317 let cycle_time_duration = Duration::from_millis(CYCLE_TIME_VENTILATION_MILLIS);
318 let sleep_duration_hundred_millis = Duration::from_millis(100);
319 let spin_sleeper = SpinSleeper::default();
320 let mut saw_tooth_profile = SawToothProfile::new(&self.config.saw_tooth_profile_config);
321 let mut measured_water_temperature = 0.0;
322 let mut measurement_error_water_temperature: bool;
323 let mut ventilation_state: ActuatorState = ActuatorState::Undefined;
324 let mut schedule_check_result: bool;
325 let mut ventilation_inhibited = false; let mut lock_warn_cycle_time_exceeded = false;
327 let mut start_time = Instant::now();
328 let mut actuation_mutex_was_blocked_during_actuation: bool = false;
329
330 loop {
331 let (quit_command_received, start_command_received, stop_command_received) = self
332 .process_external_request(
333 &mut ventilation_channels.rx_ventilation_from_signal_handler,
334 ventilation_channels
335 .rx_ventilation_from_messaging_opt
336 .as_mut(),
337 );
338 if quit_command_received {
339 break;
340 }
341 if stop_command_received {
342 #[cfg(not(test))]
343 info!(
344 target: module_path!(),
345 "received Stop command. Inhibiting ventilation."
346 );
347 ventilation_inhibited = true;
348 }
349 if start_command_received {
350 #[cfg(not(test))]
351 info!(
352 target: module_path!(),
353 "received Start command. Restarting ventilation."
354 );
355 ventilation_inhibited = false;
356 }
357
358 if self.config.active {
359 {
361 match mutex_sensor_manager_signals.lock() {
362 Ok(c) => {
363 measurement_error_water_temperature = false;
364 measured_water_temperature = c.water_temperature;
365 }
366 Err(_) => {
367 measurement_error_water_temperature = true;
368 }
369 };
370 }
371
372 update_thermal_set_values!(
374 ventilation_set_val_updater,
375 self.config.switch_off_temperature,
376 self.config.switch_on_temperature,
377 self.lock_error_ventilation_set_value_read_failure,
378 module_path!()
379 );
380
381 perform_schedule_check!(
383 ventilation_channels,
384 schedule_check_result,
385 self.lock_error_channel_send_schedule_check,
386 self.lock_error_channel_receive_schedule_check,
387 module_path!()
388 );
389
390 if schedule_check_result && !ventilation_inhibited {
391 if (measured_water_temperature >= self.config.switch_off_temperature)
393 && (measured_water_temperature <= self.config.switch_on_temperature)
394 {
395 if (self.calc_normalized_control_deviation(measured_water_temperature)
397 > saw_tooth_profile.level_normalized)
398 && !measurement_error_water_temperature
399 {
400 if ventilation_state != ActuatorState::On {
402 switch_on_ventilation(
403 &mutex_device_scheduler_ventilation,
404 &mut actuation_mutex_was_blocked_during_actuation,
405 ventilation_channels,
406 );
407 ventilation_state = ActuatorState::On;
408 }
409 } else {
410 if ventilation_state != ActuatorState::Off {
412 switch_off_ventilation(
413 &mutex_device_scheduler_ventilation,
414 &mut actuation_mutex_was_blocked_during_actuation,
415 ventilation_channels,
416 );
417 ventilation_state = ActuatorState::Off;
418 }
419 }
420 } else {
421 if measured_water_temperature > self.config.switch_on_temperature {
423 if ventilation_state != ActuatorState::On {
425 switch_on_ventilation(
426 &mutex_device_scheduler_ventilation,
427 &mut actuation_mutex_was_blocked_during_actuation,
428 ventilation_channels,
429 );
430 ventilation_state = ActuatorState::On;
431 }
432 }
433 if measured_water_temperature < self.config.switch_off_temperature {
434 if ventilation_state != ActuatorState::Off {
436 switch_off_ventilation(
437 &mutex_device_scheduler_ventilation,
438 &mut actuation_mutex_was_blocked_during_actuation,
439 ventilation_channels,
440 );
441 ventilation_state = ActuatorState::Off;
442 }
443 }
444 }
445 } else if ventilation_inhibited {
446 Self::conditional_ventilation_switch(
448 self.config.switch_on_when_external_stop,
449 &mut ventilation_state,
450 &mutex_device_scheduler_ventilation,
451 ventilation_channels,
452 &mut actuation_mutex_was_blocked_during_actuation,
453 );
454 } else {
455 Self::conditional_ventilation_switch(
457 self.config.switch_on_when_out_of_schedule,
458 &mut ventilation_state,
459 &mutex_device_scheduler_ventilation,
460 ventilation_channels,
461 &mut actuation_mutex_was_blocked_during_actuation,
462 );
463 }
464 }
465
466 let instant_before_locking_mutex = Instant::now();
467 let mut instant_after_locking_mutex = Instant::now(); {
471 match mutex_ventilation_status.lock() {
472 Ok(mut c) => {
473 instant_after_locking_mutex = Instant::now();
474 *c = match ventilation_state {
475 ActuatorState::On => true,
476 ActuatorState::Off => false,
477 _ => false,
478 }
479 }
480 Err(_) => {
481 }
483 }
484 }
485
486 self.check_mutex_access_duration(
488 None,
489 instant_after_locking_mutex,
490 instant_before_locking_mutex,
491 );
492
493 saw_tooth_profile.execute_with(TIME_INCREMENT_VENTILATION_SECS);
494
495 manage_cycle_time_thermal!(
497 start_time,
498 cycle_time_duration,
499 CYCLE_TIME_VENTILATION_MILLIS,
500 spin_sleeper,
501 lock_warn_cycle_time_exceeded,
502 actuation_mutex_was_blocked_during_actuation,
503 module_path!()
504 );
505 }
506
507 if self.config.switch_on_when_terminating {
508 if ventilation_state != ActuatorState::On {
510 switch_on_ventilation(
511 &mutex_device_scheduler_ventilation,
512 &mut actuation_mutex_was_blocked_during_actuation,
513 ventilation_channels,
514 );
515 }
516 } else {
517 if ventilation_state != ActuatorState::Off {
519 switch_off_ventilation(
520 &mutex_device_scheduler_ventilation,
521 &mut actuation_mutex_was_blocked_during_actuation,
522 ventilation_channels,
523 );
524 }
525 }
526
527 ventilation_channels.acknowledge_signal_handler();
528
529 self.wait_for_termination(
533 &mut ventilation_channels.rx_ventilation_from_signal_handler,
534 sleep_duration_hundred_millis,
535 module_path!(),
536 );
537 }
538}
539
540#[cfg(test)]
541pub mod tests {
542 use crate::launch::channels::{AquaReceiver, AquaSender, Channels};
543 use crate::mocks::mock_relay_manager::tests::mock_relay_manager;
544 use crate::mocks::mock_schedule_check::tests::mock_schedule_check;
545 use crate::mocks::mock_ventilation::tests::MockSqlInterfaceVentilationSetVals;
546 use crate::sensors::sensor_manager::SensorManagerSignals;
547 use crate::thermal::ventilation::Ventilation;
548 use crate::utilities::channel_content::{AquariumDevice, InternalCommand};
549 use crate::utilities::common::tests::update_min_max_actuation_duration;
550 use crate::utilities::config::{read_config_file, ConfigData};
551 use crate::utilities::signal_handler::SignalHandlerChannels;
552 use all_asserts::{assert_ge, assert_le};
553 use spin_sleep::SpinSleeper;
554 use std::sync::{Arc, Mutex};
555 use std::thread;
556 use std::time::Duration;
557
558 fn prepare_ventilation_tests() -> (ConfigData, Ventilation) {
567 let config_for_ventilation: ConfigData =
569 read_config_file("/config/aquarium_control_test_generic.toml".to_string()).unwrap();
570 let config: ConfigData =
571 read_config_file("/config/aquarium_control_test_generic.toml".to_string()).unwrap();
572 (config, Ventilation::new(config_for_ventilation.ventilation))
573 }
574
575 fn create_test_environment(
599 duration_seconds: u64,
600 mut signal_handler_channels: SignalHandlerChannels,
601 mutex_ventilation_status_opt: Option<Arc<Mutex<bool>>>,
602 ) -> thread::JoinHandle<()> {
603 thread::Builder::new()
604 .name("test_environment".to_string())
605 .spawn(move || {
606 if mutex_ventilation_status_opt.is_some() {
607 let mutex_ventilation_status = mutex_ventilation_status_opt.unwrap();
609 let sleep_duration_one_second = Duration::from_secs(1);
610 let spin_sleeper = SpinSleeper::default();
611 for i in 0..10 {
612 if i % 2 == 0 {
613 spin_sleeper.sleep(sleep_duration_one_second);
614 } else {
615 {
617 match mutex_ventilation_status.lock() {
618 Ok(_) => {
619 spin_sleeper.sleep(sleep_duration_one_second);
620 }
621 Err(_) => {
622 }
624 }
625 }
626 }
627 }
628 } else {
629 let sleep_duration_ten_seconds = Duration::from_secs(duration_seconds);
630 let spin_sleeper = SpinSleeper::default();
631 spin_sleeper.sleep(sleep_duration_ten_seconds);
632 }
633 let _ = signal_handler_channels.send_to_ventilation(InternalCommand::Quit);
634 signal_handler_channels.receive_from_ventilation().unwrap();
635 let _ = signal_handler_channels.send_to_ventilation(InternalCommand::Terminate);
636 })
637 .unwrap()
638 }
639
640 #[test]
643 pub fn test_ventilation_with_measured_temperature_high_without_blocking_mutex() {
644 let sleep_duration_100_millis = Duration::from_millis(100);
645 let spin_sleeper = SpinSleeper::default();
646
647 let (mut config, mut ventilation) = prepare_ventilation_tests();
648
649 config.sensor_manager.replacement_value_water_temperature = 30.0;
651
652 let mut channels = Channels::new_for_test();
653
654 let join_handle_mock_schedule_check = thread::Builder::new()
656 .name("mock_schedule_check".to_string())
657 .spawn(move || {
658 mock_schedule_check(
659 &mut channels.schedule_check.tx_schedule_check_to_ventilation,
660 &mut channels.schedule_check.rx_schedule_check_from_ventilation,
661 None,
662 true,
663 );
664 })
665 .unwrap();
666
667 let mutex_sensor_manager_signals = Arc::new(Mutex::new(SensorManagerSignals::new(
669 &config.sensor_manager,
670 )));
671
672 let mutex_device_scheduler_ventilation = Arc::new(Mutex::new(0));
673
674 let join_handle_mock_relay_manager = create_mock_relay_manager_for_ventilation_on(
676 channels.relay_manager.tx_relay_manager_to_ventilation,
677 channels.relay_manager.rx_relay_manager_from_ventilation,
678 );
679
680 let join_handle_test_environment = create_test_environment(
682 10,
683 channels.signal_handler,
684 None, );
686
687 spin_sleeper.sleep(sleep_duration_100_millis);
688
689 let join_handle_test_object = thread::Builder::new()
691 .name("test_object".to_string())
692 .spawn(move || {
693 let mut tx_ventilation_to_schedule_check_for_test_case_finish = channels
695 .ventilation
696 .tx_ventilation_to_schedule_check
697 .clone();
698 let mut tx_ventilation_to_relay_manager_for_test_case_finish =
699 channels.ventilation.tx_ventilation_to_relay_manager.clone();
700
701 let mut ventilation_set_value_updater =
702 MockSqlInterfaceVentilationSetVals::new(false, None, None, None);
703
704 ventilation.execute(
705 mutex_device_scheduler_ventilation.clone(),
706 &mut channels.ventilation,
707 &mut ventilation_set_value_updater,
708 mutex_sensor_manager_signals,
709 Arc::new(Mutex::new(false)),
710 );
711
712 assert_eq!(ventilation.mutex_access_duration_exceeded, false);
713
714 let _ = tx_ventilation_to_schedule_check_for_test_case_finish
716 .send(InternalCommand::Quit);
717 let _ = tx_ventilation_to_relay_manager_for_test_case_finish
718 .send(InternalCommand::Quit);
719 println!("* [Ventilation] checking reaction to high temperature succeeded.");
720 })
721 .unwrap();
722
723 join_handle_mock_schedule_check
724 .join()
725 .expect("Mock schedule check did not finish.");
726 join_handle_mock_relay_manager
727 .join()
728 .expect("Mock relay manager thread did not finish.");
729 join_handle_test_environment
730 .join()
731 .expect("Test environment thread did not finish.");
732 join_handle_test_object
733 .join()
734 .expect("Test object thread did not finish.");
735 }
736
737 fn create_mock_relay_manager(
760 mut tx_relay_manager_to_ventilation: AquaSender<bool>,
761 mut rx_relay_manager_from_ventilation: AquaReceiver<InternalCommand>,
762 ) -> thread::JoinHandle<()> {
763 thread::Builder::new()
764 .name("mock_relay_manager".to_string())
765 .spawn(move || {
766 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
767 &mut tx_relay_manager_to_ventilation,
768 &mut rx_relay_manager_from_ventilation,
769 );
770 assert_eq!(actuation_events.len(), 2);
771 let actuation_event = actuation_events.pop().unwrap();
772 assert_eq!(
773 actuation_event.command,
774 InternalCommand::SwitchOn(AquariumDevice::Ventilation)
775 );
776 let actuation_event = actuation_events.pop().unwrap();
777 assert_eq!(
778 actuation_event.command,
779 InternalCommand::SwitchOff(AquariumDevice::Ventilation)
780 );
781 mock_actuator_states.check_terminal_condition_ventilation();
782 })
783 .unwrap()
784 }
785
786 #[test]
789 pub fn test_ventilation_with_measured_temperature_low() {
790 let sleep_duration_100_millis = Duration::from_millis(100);
791 let spin_sleeper = SpinSleeper::default();
792
793 let (mut config, mut ventilation) = prepare_ventilation_tests();
794
795 config.sensor_manager.replacement_value_water_temperature = 20.0;
797
798 let mut channels = Channels::new_for_test();
799
800 let join_handle_mock_schedule_check = thread::Builder::new()
802 .name("mock_schedule_check".to_string())
803 .spawn(move || {
804 mock_schedule_check(
805 &mut channels.schedule_check.tx_schedule_check_to_ventilation,
806 &mut channels.schedule_check.rx_schedule_check_from_ventilation,
807 None,
808 true,
809 );
810 })
811 .unwrap();
812
813 let mutex_sensor_manager_signals = Arc::new(Mutex::new(SensorManagerSignals::new(
815 &config.sensor_manager,
816 )));
817
818 let mutex_device_scheduler_ventilation = Arc::new(Mutex::new(0));
819
820 let join_handle_mock_relay_manager = create_mock_relay_manager(
822 channels.relay_manager.tx_relay_manager_to_ventilation,
823 channels.relay_manager.rx_relay_manager_from_ventilation,
824 );
825
826 let join_handle_test_environment = create_test_environment(
828 58,
829 channels.signal_handler,
830 None, );
832
833 spin_sleeper.sleep(sleep_duration_100_millis);
834
835 let join_handle_test_object = thread::Builder::new()
837 .name("test_object".to_string())
838 .spawn(move || {
839 let mut tx_ventilation_to_schedule_check_for_test_case_finish = channels
841 .ventilation
842 .tx_ventilation_to_schedule_check
843 .clone();
844 let mut tx_ventilation_to_relay_manager_for_test_case_finish =
845 channels.ventilation.tx_ventilation_to_relay_manager.clone();
846
847 let mut ventilation_set_value_updater =
848 MockSqlInterfaceVentilationSetVals::new(false, None, None, None);
849
850 ventilation.execute(
851 mutex_device_scheduler_ventilation.clone(),
852 &mut channels.ventilation,
853 &mut ventilation_set_value_updater,
854 mutex_sensor_manager_signals,
855 Arc::new(Mutex::new(false)),
856 );
857
858 let _ = tx_ventilation_to_schedule_check_for_test_case_finish
860 .send(InternalCommand::Quit);
861 let _ = tx_ventilation_to_relay_manager_for_test_case_finish
862 .send(InternalCommand::Quit);
863 println!("* [Ventilation] checking reaction to low temperature.");
864 })
865 .unwrap();
866
867 join_handle_mock_schedule_check
868 .join()
869 .expect("Mock schedule check did not finish.");
870 join_handle_mock_relay_manager
871 .join()
872 .expect("Mock relay manager thread did not finish.");
873 join_handle_test_environment
874 .join()
875 .expect("Test environment thread did not finish.");
876 join_handle_test_object
877 .join()
878 .expect("Test object thread did not finish.");
879 }
880
881 #[test]
885 pub fn test_ventilation_with_measured_temperature_50_percent() {
886 let sleep_duration_100_millis = Duration::from_millis(100);
887 let spin_sleeper = SpinSleeper::default();
888
889 let (mut config, mut ventilation) = prepare_ventilation_tests();
890
891 config.sensor_manager.replacement_value_water_temperature = 25.0;
893
894 let mut channels = Channels::new_for_test();
895
896 let join_handle_mock_schedule_check = thread::Builder::new()
898 .name("mock_schedule_check".to_string())
899 .spawn(move || {
900 mock_schedule_check(
901 &mut channels.schedule_check.tx_schedule_check_to_ventilation,
902 &mut channels.schedule_check.rx_schedule_check_from_ventilation,
903 None,
904 true,
905 );
906 })
907 .unwrap();
908
909 let mutex_sensor_manager_signals = Arc::new(Mutex::new(SensorManagerSignals::new(
911 &config.sensor_manager,
912 )));
913
914 let mutex_device_scheduler_ventilation = Arc::new(Mutex::new(0));
915
916 let join_handle_mock_relay_manager = thread::Builder::new()
918 .name("mock_relay_manager".to_string())
919 .spawn(move || {
920 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
921 &mut channels.relay_manager.tx_relay_manager_to_ventilation,
922 &mut channels.relay_manager.rx_relay_manager_from_ventilation,
923 );
924 println!(
925 "Mock relay manager received {} commands:",
926 actuation_events.len()
927 );
928 for actuation_event in actuation_events.clone() {
929 println!("{}", actuation_event);
930 }
931 let actuation_event = actuation_events.pop().unwrap();
932 assert_eq!(
933 actuation_event.command,
934 InternalCommand::SwitchOn(AquariumDevice::Ventilation)
935 );
936 for actuation_event in actuation_events.clone() {
937 println!("{}", actuation_event);
938 }
939 let _last_actuation_event = actuation_events.pop().unwrap();
941 let second_last_actuation_event = actuation_events.pop().unwrap();
942 let third_last_actuation_event = actuation_events.pop().unwrap();
943 let fourth_last_actuation_event = actuation_events.pop().unwrap();
944 let fifth_last_actuation_event = actuation_events.pop().unwrap();
945 let sixth_last_actuation_event = actuation_events.pop().unwrap();
946 let seventh_last_actuation_event = actuation_events.pop().unwrap();
947
948 let mut min_actuation_duration: u128 = 100000;
949 let mut max_actuation_duration: u128 = 0;
950
951 let second_last_actuation_duration = second_last_actuation_event
952 .time
953 .duration_since(third_last_actuation_event.time)
954 .as_millis();
955 (min_actuation_duration, max_actuation_duration) =
956 update_min_max_actuation_duration(
957 min_actuation_duration,
958 max_actuation_duration,
959 second_last_actuation_duration,
960 );
961 println!(
962 "second_last_actuation_duration={}",
963 second_last_actuation_duration
964 );
965
966 let third_last_actuation_duration = third_last_actuation_event
967 .time
968 .duration_since(fourth_last_actuation_event.time)
969 .as_millis();
970 (min_actuation_duration, max_actuation_duration) =
971 update_min_max_actuation_duration(
972 min_actuation_duration,
973 max_actuation_duration,
974 second_last_actuation_duration,
975 );
976 println!(
977 "third_last_actuation_duration={}",
978 third_last_actuation_duration
979 );
980
981 let fourth_last_actuation_duration = fourth_last_actuation_event
982 .time
983 .duration_since(fifth_last_actuation_event.time)
984 .as_millis();
985 (min_actuation_duration, max_actuation_duration) =
986 update_min_max_actuation_duration(
987 min_actuation_duration,
988 max_actuation_duration,
989 second_last_actuation_duration,
990 );
991 println!(
992 "fourth_last_actuation_duration={}",
993 fourth_last_actuation_duration
994 );
995
996 let fifth_last_actuation_duration = fifth_last_actuation_event
997 .time
998 .duration_since(sixth_last_actuation_event.time)
999 .as_millis();
1000 (min_actuation_duration, max_actuation_duration) =
1001 update_min_max_actuation_duration(
1002 min_actuation_duration,
1003 max_actuation_duration,
1004 second_last_actuation_duration,
1005 );
1006 println!(
1007 "fifth_last_actuation_duration={}",
1008 fifth_last_actuation_duration
1009 );
1010
1011 let sixth_last_actuation_duration = sixth_last_actuation_event
1012 .time
1013 .duration_since(seventh_last_actuation_event.time)
1014 .as_millis();
1015 (min_actuation_duration, max_actuation_duration) =
1016 update_min_max_actuation_duration(
1017 min_actuation_duration,
1018 max_actuation_duration,
1019 second_last_actuation_duration,
1020 );
1021 println!(
1022 "sixth_last_actuation_duration={}",
1023 sixth_last_actuation_duration
1024 );
1025
1026 let delta_min_max_duration = max_actuation_duration - min_actuation_duration;
1027
1028 cfg_if::cfg_if! {
1031 if #[cfg(target_os = "linux")] {
1032 assert_le!(second_last_actuation_duration, 7600);
1033 assert_ge!(second_last_actuation_duration, 5400);
1034 assert_le!(third_last_actuation_duration, 7600);
1035 assert_ge!(third_last_actuation_duration, 5400);
1036 assert_le!(fourth_last_actuation_duration, 7600);
1037 assert_ge!(fourth_last_actuation_duration, 5400);
1038 assert_le!(fifth_last_actuation_duration, 7600);
1039 assert_ge!(fifth_last_actuation_duration, 5400);
1040 assert_le!(sixth_last_actuation_duration, 7600);
1041 assert_ge!(sixth_last_actuation_duration, 5400);
1042 }
1043 else {
1044 assert_le!(second_last_actuation_duration, 7600);
1045 assert_ge!(second_last_actuation_duration, 5000);
1046 assert_le!(third_last_actuation_duration, 7600);
1047 assert_ge!(third_last_actuation_duration, 5000);
1048 assert_le!(fourth_last_actuation_duration, 7600);
1049 assert_ge!(fourth_last_actuation_duration, 5000);
1050 assert_le!(fifth_last_actuation_duration, 7600);
1051 assert_ge!(fifth_last_actuation_duration, 5000);
1052 assert_le!(sixth_last_actuation_duration, 7600);
1053 assert_ge!(sixth_last_actuation_duration, 5000);
1054 }
1055 }
1056 assert_le!(delta_min_max_duration, 50);
1057 mock_actuator_states.check_terminal_condition_ventilation();
1058 })
1059 .unwrap();
1060
1061 let join_handle_test_environment = create_test_environment(
1063 58,
1064 channels.signal_handler,
1065 None, );
1067
1068 spin_sleeper.sleep(sleep_duration_100_millis);
1069
1070 let join_handle_test_object = thread::Builder::new()
1072 .name("test_object".to_string())
1073 .spawn(move || {
1074 let mut tx_ventilation_to_schedule_check_for_test_case_finish = channels
1076 .ventilation
1077 .tx_ventilation_to_schedule_check
1078 .clone();
1079 let mut tx_ventilation_to_relay_manager_for_test_case_finish =
1080 channels.ventilation.tx_ventilation_to_relay_manager.clone();
1081
1082 let mut ventilation_set_value_updater =
1083 MockSqlInterfaceVentilationSetVals::new(false, None, None, None);
1084
1085 ventilation.execute(
1086 mutex_device_scheduler_ventilation.clone(),
1087 &mut channels.ventilation,
1088 &mut ventilation_set_value_updater,
1089 mutex_sensor_manager_signals,
1090 Arc::new(Mutex::new(false)),
1091 );
1092
1093 let _ = tx_ventilation_to_schedule_check_for_test_case_finish
1095 .send(InternalCommand::Quit);
1096 let _ = tx_ventilation_to_relay_manager_for_test_case_finish
1097 .send(InternalCommand::Quit);
1098 println!("* [Heating] checking reaction to medium temperature succeeded.");
1099 })
1100 .unwrap();
1101
1102 join_handle_mock_schedule_check
1103 .join()
1104 .expect("Mock schedule check did not finish.");
1105 join_handle_mock_relay_manager
1106 .join()
1107 .expect("Mock relay manager thread did not finish.");
1108 join_handle_test_environment
1109 .join()
1110 .expect("Test environment thread did not finish.");
1111 join_handle_test_object
1112 .join()
1113 .expect("Test object thread did not finish.");
1114 }
1115
1116 #[test]
1118 pub fn test_ventilation_block_by_schedule() {
1119 let sleep_duration_100_millis = Duration::from_millis(100);
1120 let spin_sleeper = SpinSleeper::default();
1121
1122 let (mut config, mut ventilation) = prepare_ventilation_tests();
1123
1124 config.sensor_manager.replacement_value_water_temperature = 30.0;
1126
1127 let mut channels = Channels::new_for_test();
1128
1129 let join_handle_mock_schedule_check = thread::Builder::new()
1131 .name("mock_schedule_check".to_string())
1132 .spawn(move || {
1133 mock_schedule_check(
1134 &mut channels.schedule_check.tx_schedule_check_to_ventilation,
1135 &mut channels.schedule_check.rx_schedule_check_from_ventilation,
1136 None,
1137 false,
1138 );
1139 })
1140 .unwrap();
1141
1142 let mutex_sensor_manager_signals = Arc::new(Mutex::new(SensorManagerSignals::new(
1144 &config.sensor_manager,
1145 )));
1146
1147 let mutex_device_scheduler_ventilation = Arc::new(Mutex::new(0));
1148
1149 let join_handle_mock_relay_manager = create_mock_relay_manager(
1151 channels.relay_manager.tx_relay_manager_to_ventilation,
1152 channels.relay_manager.rx_relay_manager_from_ventilation,
1153 );
1154
1155 let join_handle_test_environment = create_test_environment(
1157 10,
1158 channels.signal_handler,
1159 None, );
1161
1162 spin_sleeper.sleep(sleep_duration_100_millis);
1163
1164 let join_handle_test_object = thread::Builder::new()
1166 .name("test_object".to_string())
1167 .spawn(move || {
1168 let mut tx_ventilation_to_schedule_check_for_test_case_finish = channels
1170 .ventilation
1171 .tx_ventilation_to_schedule_check
1172 .clone();
1173 let mut tx_ventilation_to_relay_manager_for_test_case_finish =
1174 channels.ventilation.tx_ventilation_to_relay_manager.clone();
1175
1176 let mut ventilation_set_value_updater =
1177 MockSqlInterfaceVentilationSetVals::new(false, None, None, None);
1178
1179 ventilation.execute(
1180 mutex_device_scheduler_ventilation.clone(),
1181 &mut channels.ventilation,
1182 &mut ventilation_set_value_updater,
1183 mutex_sensor_manager_signals,
1184 Arc::new(Mutex::new(false)),
1185 );
1186
1187 let _ = tx_ventilation_to_schedule_check_for_test_case_finish
1189 .send(InternalCommand::Quit);
1190 let _ = tx_ventilation_to_relay_manager_for_test_case_finish
1191 .send(InternalCommand::Quit);
1192
1193 let actuation_count = *mutex_device_scheduler_ventilation.lock().unwrap();
1194
1195 assert_eq!(actuation_count, 2);
1197
1198 println!("* [Ventilation] checking if schedule checker can block actuation.");
1199 })
1200 .unwrap();
1201
1202 join_handle_mock_schedule_check
1203 .join()
1204 .expect("Mock schedule check did not finish.");
1205 join_handle_mock_relay_manager
1206 .join()
1207 .expect("Mock relay manager thread did not finish.");
1208 join_handle_test_environment
1209 .join()
1210 .expect("Test environment thread did not finish.");
1211 join_handle_test_object
1212 .join()
1213 .expect("Test object thread did not finish.");
1214 }
1215
1216 #[test]
1221 pub fn test_messaging_stops_starts_ventilation() {
1222 let mut config: ConfigData =
1223 read_config_file("/config/aquarium_control_test_generic.toml".to_string()).unwrap();
1224
1225 config.sensor_manager.replacement_value_water_temperature = 30.0;
1227
1228 let mutex_sensor_manager_signals = Arc::new(Mutex::new(SensorManagerSignals::new(
1230 &config.sensor_manager,
1231 )));
1232
1233 let mut ventilation = Ventilation::new(config.ventilation);
1234
1235 let mut channels = Channels::new_for_test();
1236
1237 let mutex_device_scheduler_ventilation = Arc::new(Mutex::new(0));
1238 let mutex_device_scheduler_test_environment = mutex_device_scheduler_ventilation.clone();
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_ventilation,
1246 &mut channels.schedule_check.rx_schedule_check_from_ventilation,
1247 None,
1248 true,
1249 );
1250 })
1251 .unwrap();
1252
1253 let join_handle_mock_relay_manager = thread::Builder::new()
1255 .name("mock_relay_manager".to_string())
1256 .spawn(move || {
1257 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
1258 &mut channels.relay_manager.tx_relay_manager_to_ventilation,
1259 &mut channels.relay_manager.rx_relay_manager_from_ventilation,
1260 );
1261 println!("actuation_events:");
1262 for actuation_event in &actuation_events {
1263 println!("{}", actuation_event);
1264 }
1265 assert_eq!(actuation_events.len(), 5);
1266 let actuation_event = actuation_events.pop().unwrap();
1267 assert_eq!(
1268 actuation_event.command,
1269 InternalCommand::SwitchOn(AquariumDevice::Ventilation)
1270 );
1271 let actuation_event = actuation_events.pop().unwrap();
1272 assert_eq!(
1273 actuation_event.command,
1274 InternalCommand::SwitchOff(AquariumDevice::Ventilation)
1275 );
1276 let actuation_event = actuation_events.pop().unwrap();
1277 assert_eq!(
1278 actuation_event.command,
1279 InternalCommand::SwitchOn(AquariumDevice::Ventilation)
1280 );
1281 let actuation_event = actuation_events.pop().unwrap();
1282 assert_eq!(
1283 actuation_event.command,
1284 InternalCommand::SwitchOff(AquariumDevice::Ventilation)
1285 );
1286 let actuation_event = actuation_events.pop().unwrap();
1287 assert_eq!(
1288 actuation_event.command,
1289 InternalCommand::SwitchOn(AquariumDevice::Ventilation)
1290 );
1291 mock_actuator_states.check_terminal_condition_ventilation();
1292 })
1293 .unwrap();
1294
1295 let join_handle_test_environment = thread::Builder::new()
1297 .name("test_environment".to_string())
1298 .spawn(move || {
1299 let sleep_duration_100_millis = Duration::from_millis(100);
1300 let spin_sleeper = SpinSleeper::default();
1301
1302 for _ in 0..10 {
1304 spin_sleeper.sleep(sleep_duration_100_millis);
1305 }
1306 assert_eq!(*mutex_device_scheduler_test_environment.lock().unwrap(), 1);
1308
1309 match channels
1311 .messaging
1312 .tx_messaging_to_ventilation
1313 .send(InternalCommand::Stop)
1314 {
1315 Ok(()) => { }
1316 Err(e) => {
1317 panic!(
1318 "{}: error when sending stop command to test object ({e:?})",
1319 module_path!()
1320 );
1321 }
1322 }
1323
1324 for _ in 0..10 {
1326 spin_sleeper.sleep(sleep_duration_100_millis);
1327 }
1328 assert_eq!(*mutex_device_scheduler_test_environment.lock().unwrap(), 2);
1330
1331 match channels
1333 .messaging
1334 .tx_messaging_to_ventilation
1335 .send(InternalCommand::Start)
1336 {
1337 Ok(()) => { }
1338 Err(e) => {
1339 panic!(
1340 "{}: error when sending start command to test object ({e:?})",
1341 module_path!()
1342 );
1343 }
1344 }
1345
1346 for _ in 0..10 {
1348 spin_sleeper.sleep(sleep_duration_100_millis);
1349 }
1350 assert_eq!(*mutex_device_scheduler_test_environment.lock().unwrap(), 3);
1352
1353 match channels
1355 .messaging
1356 .tx_messaging_to_ventilation
1357 .send(InternalCommand::Stop)
1358 {
1359 Ok(()) => { }
1360 Err(e) => {
1361 panic!(
1362 "{}: error when sending stop command to test object ({e:?})",
1363 module_path!()
1364 );
1365 }
1366 }
1367
1368 for _ in 0..10 {
1370 spin_sleeper.sleep(sleep_duration_100_millis);
1371 }
1372 assert_eq!(*mutex_device_scheduler_test_environment.lock().unwrap(), 4);
1374
1375 let _ = channels
1377 .signal_handler
1378 .send_to_ventilation(InternalCommand::Quit);
1379 channels.signal_handler.receive_from_ventilation().unwrap();
1380
1381 assert_eq!(*mutex_device_scheduler_test_environment.lock().unwrap(), 5);
1383 let _ = channels
1384 .signal_handler
1385 .send_to_ventilation(InternalCommand::Terminate);
1386 })
1387 .unwrap();
1388
1389 let join_handle_test_object = thread::Builder::new()
1391 .name("test_object".to_string())
1392 .spawn(move || {
1393 let mut tx_ventilation_to_schedule_check_for_test_case_finish = channels
1395 .ventilation
1396 .tx_ventilation_to_schedule_check
1397 .clone();
1398 let mut tx_ventilation_to_relay_manager_for_test_case_finish =
1399 channels.ventilation.tx_ventilation_to_relay_manager.clone();
1400
1401 let mut ventilation_set_value_updater =
1402 MockSqlInterfaceVentilationSetVals::new(false, None, None, None);
1403
1404 ventilation.execute(
1405 mutex_device_scheduler_ventilation.clone(),
1406 &mut channels.ventilation,
1407 &mut ventilation_set_value_updater,
1408 mutex_sensor_manager_signals,
1409 Arc::new(Mutex::new(false)),
1410 );
1411
1412 let _ = tx_ventilation_to_schedule_check_for_test_case_finish
1414 .send(InternalCommand::Quit);
1415 let _ = tx_ventilation_to_relay_manager_for_test_case_finish
1416 .send(InternalCommand::Quit);
1417
1418 println!("* [Ventilation] checking if messaging can block and restart actuation.");
1419 })
1420 .unwrap();
1421
1422 join_handle_mock_schedule_check
1423 .join()
1424 .expect("Mock schedule check did not finish.");
1425 join_handle_mock_relay_manager
1426 .join()
1427 .expect("Mock relay manager thread did not finish.");
1428 join_handle_test_environment
1429 .join()
1430 .expect("Test environment thread did not finish.");
1431 join_handle_test_object
1432 .join()
1433 .expect("Test object thread did not finish.");
1434 }
1435
1436 #[test]
1441 pub fn test_ventilation_with_decreased_set_values() {
1442 let sleep_duration_100_millis = Duration::from_millis(100);
1443 let spin_sleeper = SpinSleeper::default();
1444
1445 let (mut config, mut ventilation) = prepare_ventilation_tests();
1446
1447 config.sensor_manager.replacement_value_water_temperature = 30.0;
1449
1450 let mut channels = Channels::new_for_test();
1451
1452 let join_handle_mock_schedule_check = thread::Builder::new()
1454 .name("mock_schedule_check".to_string())
1455 .spawn(move || {
1456 mock_schedule_check(
1457 &mut channels.schedule_check.tx_schedule_check_to_ventilation,
1458 &mut channels.schedule_check.rx_schedule_check_from_ventilation,
1459 None,
1460 true,
1461 );
1462 })
1463 .unwrap();
1464
1465 let mutex_sensor_manager_signals = Arc::new(Mutex::new(SensorManagerSignals::new(
1467 &config.sensor_manager,
1468 )));
1469
1470 let mutex_device_scheduler_ventilation = Arc::new(Mutex::new(0));
1471
1472 let join_handle_mock_relay_manager = thread::Builder::new()
1474 .name("mock_relay_manager".to_string())
1475 .spawn(move || {
1476 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
1477 &mut channels.relay_manager.tx_relay_manager_to_ventilation,
1478 &mut channels.relay_manager.rx_relay_manager_from_ventilation,
1479 );
1480 assert_eq!(actuation_events.len(), 3);
1481 let actuation_event_3 = actuation_events.pop().unwrap();
1482 let actuation_event_2 = actuation_events.pop().unwrap();
1483 let actuation_event_1 = actuation_events.pop().unwrap();
1484 assert_eq!(
1485 actuation_event_1.command,
1486 InternalCommand::SwitchOn(AquariumDevice::Ventilation)
1487 );
1488 assert_eq!(
1489 actuation_event_2.command,
1490 InternalCommand::SwitchOff(AquariumDevice::Ventilation)
1491 );
1492 assert_eq!(
1493 actuation_event_3.command,
1494 InternalCommand::SwitchOn(AquariumDevice::Ventilation)
1495 );
1496 mock_actuator_states.check_terminal_condition_ventilation();
1497 })
1498 .unwrap();
1499
1500 let join_handle_test_environment = create_test_environment(
1502 12,
1503 channels.signal_handler,
1504 None, );
1506
1507 spin_sleeper.sleep(sleep_duration_100_millis);
1508
1509 let join_handle_test_object = thread::Builder::new()
1511 .name("test_object".to_string())
1512 .spawn(move || {
1513 let mut tx_ventilation_to_schedule_check_for_test_case_finish = channels
1515 .ventilation
1516 .tx_ventilation_to_schedule_check
1517 .clone();
1518 let mut tx_ventilation_to_relay_manager_for_test_case_finish =
1519 channels.ventilation.tx_ventilation_to_relay_manager.clone();
1520
1521 let mut ventilation_set_value_updater = MockSqlInterfaceVentilationSetVals::new(
1523 true,
1524 Some(30.0),
1525 Some(40.0),
1526 Some(Duration::from_secs(10)),
1527 );
1528
1529 ventilation.execute(
1530 mutex_device_scheduler_ventilation.clone(),
1531 &mut channels.ventilation,
1532 &mut ventilation_set_value_updater,
1533 mutex_sensor_manager_signals,
1534 Arc::new(Mutex::new(false)),
1535 );
1536
1537 let _ = tx_ventilation_to_schedule_check_for_test_case_finish
1539 .send(InternalCommand::Quit);
1540 let _ = tx_ventilation_to_relay_manager_for_test_case_finish
1541 .send(InternalCommand::Quit);
1542 println!("* [Ventilation] checking reaction to high temperature succeeded.");
1543 })
1544 .unwrap();
1545
1546 join_handle_mock_schedule_check
1547 .join()
1548 .expect("Mock schedule check did not finish.");
1549 join_handle_mock_relay_manager
1550 .join()
1551 .expect("Mock relay manager thread did not finish.");
1552 join_handle_test_environment
1553 .join()
1554 .expect("Test environment thread did not finish.");
1555 join_handle_test_object
1556 .join()
1557 .expect("Test object thread did not finish.");
1558 }
1559
1560 fn create_mock_relay_manager_for_ventilation_on(
1562 mut tx_relay_manager_to_ventilation: AquaSender<bool>,
1563 mut rx_relay_manager_from_ventilation: AquaReceiver<InternalCommand>,
1564 ) -> thread::JoinHandle<()> {
1565 thread::Builder::new()
1566 .name("mock_relay_manager".to_string())
1567 .spawn(move || {
1568 let (mut actuation_events, mock_actuator_states) = mock_relay_manager(
1569 &mut tx_relay_manager_to_ventilation,
1570 &mut rx_relay_manager_from_ventilation,
1571 );
1572 assert_eq!(actuation_events.len(), 1);
1573 let actuation_event = actuation_events.pop().unwrap();
1574 assert_eq!(
1575 actuation_event.command,
1576 InternalCommand::SwitchOn(AquariumDevice::Ventilation)
1577 );
1578 mock_actuator_states.check_terminal_condition_ventilation();
1579 })
1580 .unwrap()
1581 }
1582
1583 #[test]
1586 pub fn test_ventilation_with_measured_temperature_high_with_blocking_mutex() {
1587 let sleep_duration_100_millis = Duration::from_millis(100);
1588 let spin_sleeper = SpinSleeper::default();
1589
1590 let (mut config, mut ventilation) = prepare_ventilation_tests();
1591
1592 config.sensor_manager.replacement_value_water_temperature = 30.0;
1594
1595 let mut channels = Channels::new_for_test();
1596
1597 let mutex_ventilation_status = Arc::new(Mutex::new(false));
1598 let mutex_ventilation_status_clone_for_test_environment = mutex_ventilation_status.clone();
1599
1600 let join_handle_mock_schedule_check = thread::Builder::new()
1602 .name("mock_schedule_check".to_string())
1603 .spawn(move || {
1604 mock_schedule_check(
1605 &mut channels.schedule_check.tx_schedule_check_to_ventilation,
1606 &mut channels.schedule_check.rx_schedule_check_from_ventilation,
1607 None,
1608 true,
1609 );
1610 })
1611 .unwrap();
1612
1613 let mutex_sensor_manager_signals = Arc::new(Mutex::new(SensorManagerSignals::new(
1615 &config.sensor_manager,
1616 )));
1617
1618 let mutex_device_scheduler_ventilation = Arc::new(Mutex::new(0));
1619
1620 let join_handle_mock_relay_manager = create_mock_relay_manager_for_ventilation_on(
1622 channels.relay_manager.tx_relay_manager_to_ventilation,
1623 channels.relay_manager.rx_relay_manager_from_ventilation,
1624 );
1625
1626 let join_handle_test_environment = create_test_environment(
1628 10,
1629 channels.signal_handler,
1630 Some(mutex_ventilation_status_clone_for_test_environment), );
1632
1633 spin_sleeper.sleep(sleep_duration_100_millis);
1634
1635 let join_handle_test_object = thread::Builder::new()
1637 .name("test_object".to_string())
1638 .spawn(move || {
1639 let mut tx_ventilation_to_schedule_check_for_test_case_finish = channels
1641 .ventilation
1642 .tx_ventilation_to_schedule_check
1643 .clone();
1644 let mut tx_ventilation_to_relay_manager_for_test_case_finish =
1645 channels.ventilation.tx_ventilation_to_relay_manager.clone();
1646
1647 let mut ventilation_set_value_updater =
1648 MockSqlInterfaceVentilationSetVals::new(false, None, None, None);
1649
1650 ventilation.execute(
1651 mutex_device_scheduler_ventilation.clone(),
1652 &mut channels.ventilation,
1653 &mut ventilation_set_value_updater,
1654 mutex_sensor_manager_signals,
1655 mutex_ventilation_status,
1656 );
1657
1658 assert_eq!(ventilation.mutex_access_duration_exceeded, true);
1659
1660 let _ = tx_ventilation_to_schedule_check_for_test_case_finish
1662 .send(InternalCommand::Quit);
1663 let _ = tx_ventilation_to_relay_manager_for_test_case_finish
1664 .send(InternalCommand::Quit);
1665 println!("* [Ventilation] checking reaction to high temperature succeeded.");
1666 })
1667 .unwrap();
1668
1669 join_handle_mock_schedule_check
1670 .join()
1671 .expect("Mock schedule check did not finish.");
1672 join_handle_mock_relay_manager
1673 .join()
1674 .expect("Mock relay manager thread did not finish.");
1675 join_handle_test_environment
1676 .join()
1677 .expect("Test environment thread did not finish.");
1678 join_handle_test_object
1679 .join()
1680 .expect("Test object thread did not finish.");
1681 }
1682}