aquarium_control/sensors/
ds18b20.rs1#[cfg(all(not(test), target_os = "linux"))]
34use nix::unistd::gettid;
35use spin_sleep::SpinSleeper;
36use std::sync::{Arc, Mutex};
37use std::time::{Duration, Instant};
38
39use std::fs;
40use std::path::PathBuf;
41use std::thread::sleep;
42
43use crate::sensors::ds18b20_channels::Ds18b20Channels;
44use crate::sensors::ds18b20_config::Ds18b20Config;
45use crate::sensors::ds18b20_error::Ds18b20Error;
46use crate::utilities::check_mutex_access_duration::CheckMutexAccessDurationTrait;
47use crate::utilities::proc_ext_req::ProcessExternalRequestTrait;
48#[cfg(feature = "debug_ds18b20")]
49use log::debug;
50#[cfg(all(not(test), target_os = "linux"))]
51use log::info;
52
53mod ds18b20_constants {
54 pub const MIN_TEMPERATURE: f32 = 0.0;
56
57 pub const MAX_TEMPERATURE: f32 = 100.0;
59
60 pub const MAX_MUTEX_ACCESS_DURATION_MILLIS: u64 = 10;
62}
63
64#[derive(Clone)]
65pub struct Ds18b20ResultData {
66 pub value: f32,
67
68 #[allow(unused)]
69 invalid: bool,
70
71 #[allow(unused)]
72 measurement_instant: Instant,
73}
74
75impl Ds18b20ResultData {
76 pub fn new(value: f32) -> Self {
77 Ds18b20ResultData {
78 value,
79 invalid: false,
80 measurement_instant: Instant::now(),
81 }
82 }
83}
84pub type Ds18b20Result = Result<Ds18b20ResultData, Ds18b20Error>;
85
86pub struct Ds18b20 {
111 #[allow(unused)]
112 config: Ds18b20Config,
114
115 pub lock_warn_max_mutex_access_duration: bool,
117
118 #[cfg(test)]
119 pub mutex_access_duration_exceeded: bool,
121
122 pub max_mutex_access_duration: Duration,
124}
125
126impl ProcessExternalRequestTrait for Ds18b20 {}
127
128impl Ds18b20 {
129 pub fn new(config: Ds18b20Config) -> Result<Ds18b20, Ds18b20Error> {
158 if config.system_driver_base_path.is_empty() {
159 return Err(Ds18b20Error::SystemDriverBasePathEmpty(
160 module_path!().to_string(),
161 ));
162 }
163 if config.system_driver_sensor_path_prefix.is_empty() {
164 return Err(Ds18b20Error::SystemDriverSensorPathEmpty(
165 module_path!().to_string(),
166 ));
167 }
168 if config.system_driver_file_name.is_empty() {
169 return Err(Ds18b20Error::SystemDriverFileNameEmpty(
170 module_path!().to_string(),
171 ));
172 }
173 if config.active
174 && config.water_temperature_sensor_id.is_empty()
175 && config.ambient_temperature_sensor_id.is_empty()
176 {
177 return Err(Ds18b20Error::NoSensorIdProvided(module_path!().to_string()));
178 }
179 if config.active
180 && config.water_temperature_sensor_id == config.ambient_temperature_sensor_id
181 {
182 return Err(Ds18b20Error::SensorIdsIdentical(module_path!().to_string()));
183 }
184 Ok(Ds18b20 {
185 config,
186 lock_warn_max_mutex_access_duration: false,
187 #[cfg(test)]
188 mutex_access_duration_exceeded: false,
189 max_mutex_access_duration: Duration::from_millis(
190 ds18b20_constants::MAX_MUTEX_ACCESS_DURATION_MILLIS,
191 ),
192 })
193 }
194
195 #[allow(unused)]
196 pub fn read(&self, sensor_id: &str) -> Ds18b20Result {
224 #[cfg(test)]
225 println!("{}: reading from {}", module_path!(), sensor_id);
226
227 let base_dir = PathBuf::from(self.config.system_driver_base_path.clone());
228 let device_folder =
229 base_dir.join(self.config.system_driver_sensor_path_prefix.to_string() + sensor_id);
230
231 if !device_folder.exists() {
232 return Err(Ds18b20Error::SensorIdNotFound(
233 module_path!().to_string(),
234 sensor_id.to_string(),
235 ));
236 }
237
238 let device_file = device_folder.join(self.config.system_driver_file_name.clone());
239 let mut last_error = Ds18b20Error::UnidentifiedError(module_path!().to_string());
240
241 for _ in 0..=self.config.max_retries {
243 match fs::read_to_string(&device_file) {
244 Ok(contents) => {
245 match self.parse_contents(&contents) {
247 Ok(celsius) => return Ok(Ds18b20ResultData::new(celsius)),
248 Err(e) => last_error = e, }
250 }
251 Err(_) => {
252 last_error = Ds18b20Error::ReadToStringFailure(module_path!().to_string());
253 }
254 }
255 sleep(Duration::from_millis(
256 self.config.retry_pause_duration_millis,
257 ));
258 }
259
260 Err(last_error)
262 }
263
264 fn parse_contents(&self, contents: &str) -> Result<f32, Ds18b20Error> {
267 let lines: Vec<&str> = contents.lines().collect();
268
269 if lines.len() < 2 {
270 return Err(Ds18b20Error::InsufficientLinesInFile(
271 module_path!().to_string(),
272 ));
273 }
274
275 if !lines[0].ends_with(" YES") {
276 return Err(Ds18b20Error::Line0NotEndingWithYes(
277 module_path!().to_string(),
278 ));
279 }
280
281 let temp_line = lines[1];
282 if let Some(temp_index) = temp_line.find("t=") {
283 let temp_str = temp_line[temp_index + 2..].trim();
284 let temp_val = temp_str
286 .parse::<f32>()
287 .map_err(|_| Ds18b20Error::TemperatureParseError(module_path!().to_string()))?;
288
289 let celsius = temp_val / 1000.0;
290 if (ds18b20_constants::MIN_TEMPERATURE..=ds18b20_constants::MAX_TEMPERATURE)
291 .contains(&celsius)
292 {
293 Ok(celsius)
294 } else {
295 Err(Ds18b20Error::TemperatureOutOfRange(
296 module_path!().to_string(),
297 ))
298 }
299 } else {
300 Err(Ds18b20Error::TemperatureEntryNotFound(
301 module_path!().to_string(),
302 ))
303 }
304 }
305
306 pub fn execute(
321 &mut self,
322 ds18b20_channels: &mut Ds18b20Channels,
323 mutex_water_temperature: Arc<Mutex<Ds18b20Result>>,
324 mutex_ambient_temperature: Arc<Mutex<Ds18b20Result>>,
325 ) {
326 #[cfg(all(target_os = "linux", not(test)))]
327 info!(target: module_path!(), "Thread started with TID: {}", gettid());
328
329 let sleep_duration_main_cycle = Duration::from_millis(100);
330 let spin_sleeper = SpinSleeper::default();
331 let measurement_interval =
332 Duration::from_millis(self.config.measurement_pause_duration_millis);
333
334 let mut last_measurement_instant = Instant::now() - measurement_interval;
337
338 loop {
339 if self
341 .process_external_request(
342 &mut ds18b20_channels.rx_ds18b20_from_signal_handler,
343 None,
344 )
345 .0
346 {
347 break;
348 }
349
350 if self.config.active && (last_measurement_instant.elapsed() >= measurement_interval) {
351 let water_temp_result = if !self.config.water_temperature_sensor_id.is_empty() {
352 Some(self.read(self.config.water_temperature_sensor_id.as_str()))
353 } else {
354 None
355 };
356
357 let ambient_temp_result = if !self.config.ambient_temperature_sensor_id.is_empty() {
358 Some(self.read(self.config.ambient_temperature_sensor_id.as_str()))
359 } else {
360 None
361 };
362
363 let instant_before_locking_mutex = Instant::now();
365
366 if let Some(result) = water_temp_result {
367 *mutex_water_temperature.lock().unwrap() = result;
369 }
370
371 if let Some(result) = ambient_temp_result {
372 *mutex_ambient_temperature.lock().unwrap() = result;
373 }
374
375 let instant_after_locking_mutex = Instant::now();
376 last_measurement_instant = Instant::now(); self.check_mutex_access_duration(
380 None,
381 instant_after_locking_mutex,
382 instant_before_locking_mutex,
383 );
384 }
385
386 spin_sleeper.sleep(sleep_duration_main_cycle);
388 }
389 }
390}
391
392#[cfg(test)]
393pub mod tests {
394 use crate::launch::channels::{channel, AquaReceiver, AquaSender, Channels};
395 use crate::sensors::ds18b20::{Ds18b20, Ds18b20Config, Ds18b20Error};
396 use crate::sensors::ds18b20::{Ds18b20Result, Ds18b20ResultData};
397 use crate::sensors::ds18b20_channels::Ds18b20Channels;
398 use crate::utilities::channel_content::InternalCommand;
399 use crate::utilities::config::read_config_file;
400 use spin_sleep::SpinSleeper;
401 use std::sync::{Arc, Mutex};
402 use std::time::Duration;
403 use std::{env, thread};
404
405 #[test]
406 #[ignore]
407 #[cfg(all(target_os = "linux", feature = "target_hw"))]
408 fn test_read_ds18b20() {
412 let config = read_config_file("config/aquarium_control_test_generic.toml".to_string());
413
414 let water_temperature_sensor_id = config.ds18b20.water_temperature_sensor_id.clone();
415
416 let ds18b20 = Ds18b20::new(config.ds18b20);
417
418 match ds18b20.read(&water_temperature_sensor_id) {
419 Ok(temperature) => {
420 println!("Temperature: {:.2}°C ", temperature.value);
421 assert!(true);
422 }
423 Err(e) => {
424 println!("Error reading DS18B20: {}", e);
425 assert!(false);
426 }
427 }
428 }
429
430 #[test]
431 fn test_read_ds18b20_from_file() {
433 let mut config =
434 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
435
436 let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| {
437 panic!("Could not read environment variable CARGO_MANIFEST_DIR.");
438 });
439
440 config.ds18b20.system_driver_base_path = out_dir + "/tests/fixtures";
441
442 let water_temperature_sensor_id = config.ds18b20.water_temperature_sensor_id.clone();
443
444 let ds18b20 = Ds18b20::new(config.ds18b20).unwrap();
445 match ds18b20.read(&water_temperature_sensor_id) {
446 Ok(temperature) => {
447 println!(
448 "{}: Temperature read from file: {:.2}°C ({} milliseconds ago)",
449 module_path!(),
450 temperature.value,
451 temperature.measurement_instant.elapsed().as_millis()
452 );
453 assert!(true);
454 }
455 Err(e) => {
456 println!("Error reading DS18B20: {}", e);
457 assert!(false);
458 }
459 }
460 }
461
462 #[test]
463 fn test_dsb18b20_init_with_invalid_system_driver_base_path() {
465 let mut config =
466 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
467 config.ds18b20.system_driver_base_path = "".to_string();
468 let result = Ds18b20::new(config.ds18b20);
469 assert!(matches!(
470 result,
471 Err(Ds18b20Error::SystemDriverBasePathEmpty(_))
472 ));
473 }
474
475 #[test]
476 fn test_dsb18b20_init_with_invalid_system_driver_sensor_path_prefix() {
478 let mut config =
479 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
480 config.ds18b20.system_driver_sensor_path_prefix = "".to_string();
481 let result = Ds18b20::new(config.ds18b20);
482 assert!(matches!(
483 result,
484 Err(Ds18b20Error::SystemDriverSensorPathEmpty(_))
485 ));
486 }
487
488 #[test]
489 fn test_dsb18b20_init_with_invalid_system_driver_file_name() {
491 let mut config =
492 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
493 config.ds18b20.system_driver_file_name = "".to_string();
494 let result = Ds18b20::new(config.ds18b20);
495 assert!(matches!(
496 result,
497 Err(Ds18b20Error::SystemDriverFileNameEmpty(_))
498 ));
499 }
500 #[test]
501 fn test_new_fails_with_identical_sensor_ids() {
502 let identical_id = "28-00000abcdef12".to_string();
505 let config = Ds18b20Config {
506 active: true,
507 water_temperature_sensor_id: identical_id.clone(),
508 ambient_temperature_sensor_id: identical_id,
509 system_driver_base_path: "/sys/bus/w1/devices".to_string(),
511 system_driver_sensor_path_prefix: "28-".to_string(),
512 system_driver_file_name: "w1_slave".to_string(),
513 max_retries: 3,
514 retry_pause_duration_millis: 100,
515 measurement_pause_duration_millis: 1000,
516 execute: true,
517 };
518
519 let result = Ds18b20::new(config);
521
522 assert!(matches!(result, Err(Ds18b20Error::SensorIdsIdentical(_))));
525 }
526
527 #[test]
528 fn test_dsb18b20_init_with_invalid_sensor_ids() {
530 let mut config =
531 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
532 config.ds18b20.active = true;
533 config.ds18b20.water_temperature_sensor_id = "".to_string();
534 config.ds18b20.ambient_temperature_sensor_id = "".to_string();
535 let result = Ds18b20::new(config.ds18b20);
536 assert!(matches!(result, Err(Ds18b20Error::NoSensorIdProvided(_))));
537 }
538
539 #[test]
540 fn test_dsb18b20_read_from_invalid_sensor_id() {
542 let mut config =
543 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
544
545 let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| {
546 panic!("Could not read environment variable CARGO_MANIFEST_DIR.");
547 });
548
549 config.ds18b20.system_driver_base_path = out_dir + "/tests/fixtures";
550
551 let invalid_temperature_sensor_id = "33";
552
553 let ds18b20 = Ds18b20::new(config.ds18b20).unwrap();
554
555 assert_eq!(
556 matches!(
557 ds18b20.read(&invalid_temperature_sensor_id),
558 Err(Ds18b20Error::SensorIdNotFound(
559 _,
560 _invalid_temperature_sensor_id
561 ))
562 ),
563 true
564 );
565 }
566
567 #[test]
568 fn test_dsb18b20_read_from_file_failed() {
570 let mut config =
571 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
572
573 config.ds18b20.system_driver_file_name = "xxx".to_string(); let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| {
575 panic!("Could not read environment variable CARGO_MANIFEST_DIR.");
576 });
577 config.ds18b20.system_driver_base_path = out_dir + "/tests/fixtures";
578
579 let valid_temperature_sensor_id = config.ds18b20.water_temperature_sensor_id.clone();
580
581 let ds18b20 = Ds18b20::new(config.ds18b20).unwrap();
582
583 assert_eq!(
584 matches!(
585 ds18b20.read(&valid_temperature_sensor_id),
586 Err(Ds18b20Error::ReadToStringFailure(_))
587 ),
588 true
589 );
590 }
591
592 #[test]
593 fn test_dsb18b20_file_with_one_line_only() {
595 let mut config =
596 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
597
598 config.ds18b20.system_driver_file_name += "_one_line"; let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| {
600 panic!("Could not read environment variable CARGO_MANIFEST_DIR.");
601 });
602 config.ds18b20.system_driver_base_path = out_dir + "/tests/fixtures";
603
604 let valid_temperature_sensor_id = config.ds18b20.water_temperature_sensor_id.clone();
605
606 let ds18b20 = Ds18b20::new(config.ds18b20).unwrap();
607
608 assert_eq!(
609 matches!(
610 ds18b20.read(&valid_temperature_sensor_id),
611 Err(Ds18b20Error::InsufficientLinesInFile(_))
612 ),
613 true
614 );
615 }
616
617 #[test]
618 fn test_dsb18b20_file_with_line_not_ending_with_yes() {
620 let mut config =
621 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
622
623 config.ds18b20.system_driver_file_name += "_no_yes"; let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| {
625 panic!("Could not read environment variable CARGO_MANIFEST_DIR.");
626 });
627 config.ds18b20.system_driver_base_path = out_dir + "/tests/fixtures";
628
629 let valid_temperature_sensor_id = config.ds18b20.water_temperature_sensor_id.clone();
630
631 let ds18b20 = Ds18b20::new(config.ds18b20).unwrap();
632
633 assert_eq!(
634 matches!(
635 ds18b20.read(&valid_temperature_sensor_id),
636 Err(Ds18b20Error::Line0NotEndingWithYes(_))
637 ),
638 true
639 );
640 }
641
642 #[test]
643 fn test_dsb18b20_file_without_temperature() {
645 let mut config =
646 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
647
648 config.ds18b20.system_driver_file_name += "_no_temperature"; let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| {
650 panic!("Could not read environment variable CARGO_MANIFEST_DIR.");
651 });
652 config.ds18b20.system_driver_base_path = out_dir + "/tests/fixtures";
653
654 let valid_temperature_sensor_id = config.ds18b20.water_temperature_sensor_id.clone();
655
656 let ds18b20 = Ds18b20::new(config.ds18b20).unwrap();
657
658 assert_eq!(
659 matches!(
660 ds18b20.read(&valid_temperature_sensor_id),
661 Err(Ds18b20Error::TemperatureEntryNotFound(_))
662 ),
663 true
664 );
665 }
666
667 #[test]
668 fn test_dsb18b20_unparsable() {
670 let mut config =
671 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
672
673 config.ds18b20.system_driver_file_name += "_unparsable"; let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| {
675 panic!("Could not read environment variable CARGO_MANIFEST_DIR.");
676 });
677 config.ds18b20.system_driver_base_path = out_dir + "/tests/fixtures";
678
679 let valid_temperature_sensor_id = config.ds18b20.water_temperature_sensor_id.clone();
680
681 let ds18b20 = Ds18b20::new(config.ds18b20).unwrap();
682
683 assert_eq!(
684 matches!(
685 ds18b20.read(&valid_temperature_sensor_id),
686 Err(Ds18b20Error::TemperatureParseError(_))
687 ),
688 true
689 );
690 }
691
692 #[test]
693 fn test_dsb18b20_temperature_too_hot() {
695 let mut config =
696 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
697
698 config.ds18b20.system_driver_file_name += "_too_hot"; let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| {
700 panic!("Could not read environment variable CARGO_MANIFEST_DIR.");
701 });
702 config.ds18b20.system_driver_base_path = out_dir + "/tests/fixtures";
703
704 let valid_temperature_sensor_id = config.ds18b20.water_temperature_sensor_id.clone();
705
706 let ds18b20 = Ds18b20::new(config.ds18b20).unwrap();
707
708 assert_eq!(
709 matches!(
710 ds18b20.read(&valid_temperature_sensor_id),
711 Err(Ds18b20Error::TemperatureOutOfRange(_))
712 ),
713 true
714 );
715 }
716
717 #[test]
718 fn test_dsb18b20_temperature_too_cold() {
720 let mut config =
721 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
722
723 config.ds18b20.system_driver_file_name += "_too_cold"; let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| {
725 panic!("Could not read environment variable CARGO_MANIFEST_DIR.");
726 });
727 config.ds18b20.system_driver_base_path = out_dir + "/tests/fixtures";
728
729 let valid_temperature_sensor_id = config.ds18b20.water_temperature_sensor_id.clone();
730
731 let ds18b20 = Ds18b20::new(config.ds18b20).unwrap();
732
733 assert_eq!(
734 matches!(
735 ds18b20.read(&valid_temperature_sensor_id),
736 Err(Ds18b20Error::TemperatureOutOfRange(_))
737 ),
738 true
739 );
740 }
741
742 #[test]
743 fn test_dsb18b20_channel_mutexes_without_locking_mutexes() {
744 let mut config =
745 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
746
747 let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| {
748 panic!("Could not read environment variable CARGO_MANIFEST_DIR.");
749 });
750
751 config.ds18b20.system_driver_base_path = out_dir + "/tests/fixtures";
752 config.ds18b20.ambient_temperature_sensor_id = "0114540015ab".to_string();
753
754 let mutex_ds18b20_water_temperature: Arc<Mutex<Ds18b20Result>> = Arc::new(Mutex::new(Ok(
756 Ds18b20ResultData::new(config.sensor_manager.replacement_value_water_temperature),
757 )));
758 let mutex_ds18b20_ambient_temperature: Arc<Mutex<Ds18b20Result>> =
759 Arc::new(Mutex::new(Ok(Ds18b20ResultData::new(
760 config.sensor_manager.replacement_value_ambient_temperature,
761 ))));
762 let mutex_ds18b20_water_temperature_clone_for_asserts =
763 mutex_ds18b20_water_temperature.clone();
764 let mutex_ds18b20_ambient_temperature_clone_for_asserts =
765 mutex_ds18b20_ambient_temperature.clone();
766
767 let mut ds18b20 = Ds18b20::new(config.ds18b20).unwrap();
768
769 let mut channels = Channels::new_for_test();
770
771 let join_handle_test_environment = thread::Builder::new()
773 .name("test_environment".to_string())
774 .spawn(move || {
775 let sleep_time = Duration::from_millis(1000);
777 let spin_sleeper = SpinSleeper::default();
778 spin_sleeper.sleep(sleep_time);
779 channels
780 .signal_handler
781 .send_to_ds18b20(InternalCommand::Quit)
782 .unwrap();
783 })
784 .unwrap();
785
786 let join_handle_test_object = thread::Builder::new()
788 .name("test_object".to_string())
789 .spawn(move || {
790 ds18b20.execute(
791 &mut channels.ds18b20,
792 mutex_ds18b20_water_temperature,
793 mutex_ds18b20_ambient_temperature,
794 );
795 assert_eq!(ds18b20.mutex_access_duration_exceeded, false);
796 })
797 .unwrap();
798
799 join_handle_test_object
800 .join()
801 .expect("Test object did not finish.");
802 join_handle_test_environment
803 .join()
804 .expect("Test environment did not finish.");
805
806 match mutex_ds18b20_water_temperature_clone_for_asserts.lock() {
807 Ok(result) => {
808 let result_data = result.clone().unwrap();
809 assert_eq!(result_data.value, 24.437);
810 }
811 Err(e) => {
812 panic!("mutex_ds18b20_water_temperature lock poisoned: {:?}", e);
813 }
814 };
815 match mutex_ds18b20_ambient_temperature_clone_for_asserts.lock() {
816 Ok(result) => {
817 let result_data = result.clone().unwrap();
818 assert_eq!(result_data.value, 24.637);
819 }
820 Err(e) => {
821 panic!("mutex_ds18b20_ambient_temperature lock poisoned: {:?}", e);
822 }
823 };
824 }
825
826 #[test]
827 fn test_dsb18b20_channel_mutexes_with_locking_mutexes() {
828 let mut config =
829 read_config_file("config/aquarium_control_test_generic.toml".to_string()).unwrap();
830
831 let out_dir = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| {
832 panic!("Could not read environment variable CARGO_MANIFEST_DIR.");
833 });
834
835 config.ds18b20.system_driver_base_path = out_dir + "/tests/fixtures";
836 config.ds18b20.ambient_temperature_sensor_id = "0114540015ab".to_string();
837 config.ds18b20.measurement_pause_duration_millis = 500;
838
839 let mutex_ds18b20_water_temperature: Arc<Mutex<Ds18b20Result>> = Arc::new(Mutex::new(Ok(
841 Ds18b20ResultData::new(config.sensor_manager.replacement_value_water_temperature),
842 )));
843 let mutex_ds18b20_ambient_temperature: Arc<Mutex<Ds18b20Result>> =
844 Arc::new(Mutex::new(Ok(Ds18b20ResultData::new(
845 config.sensor_manager.replacement_value_ambient_temperature,
846 ))));
847 let mutex_ds18b20_water_temperature_clone_for_test_environment =
848 mutex_ds18b20_water_temperature.clone();
849 let mutex_ds18b20_water_temperature_clone_for_asserts =
850 mutex_ds18b20_water_temperature.clone();
851 let mutex_ds18b20_ambient_temperature_clone_for_asserts =
852 mutex_ds18b20_ambient_temperature.clone();
853
854 let mut ds18b20 = Ds18b20::new(config.ds18b20).unwrap();
855
856 let (mut tx_signal_handler_to_ds18b20, rx_ds18b20_from_signal_handler): (
858 AquaSender<InternalCommand>,
859 AquaReceiver<InternalCommand>,
860 ) = channel(1);
861 let mut ds18b20_channels = Ds18b20Channels {
862 rx_ds18b20_from_signal_handler,
863 #[cfg(feature = "debug_channels")]
864 cnt_rx_ds18b20_from_signal_handler: 0,
865 };
866
867 let join_handle_test_environment = thread::Builder::new()
869 .name("test_environment".to_string())
870 .spawn(move || {
871 let sleep_time = Duration::from_millis(2000);
873 let spin_sleeper = SpinSleeper::default();
874
875 {
876 match mutex_ds18b20_water_temperature_clone_for_test_environment.lock() {
878 Ok(_) => {
879 spin_sleeper.sleep(sleep_time);
880 }
881 Err(e) => {
882 panic!("mutex_ds18b20_water_temperature lock poisoned: {:?}", e);
883 }
884 };
885 }
886
887 tx_signal_handler_to_ds18b20
888 .send(InternalCommand::Quit)
889 .unwrap();
890 spin_sleeper.sleep(sleep_time);
891 })
892 .unwrap();
893
894 let join_handle_test_object = thread::Builder::new()
896 .name("test_object".to_string())
897 .spawn(move || {
898 ds18b20.execute(
899 &mut ds18b20_channels,
900 mutex_ds18b20_water_temperature,
901 mutex_ds18b20_ambient_temperature,
902 );
903 assert_eq!(ds18b20.mutex_access_duration_exceeded, true);
904 })
905 .unwrap();
906
907 join_handle_test_object
908 .join()
909 .expect("Test object did not finish.");
910 join_handle_test_environment
911 .join()
912 .expect("Test environment did not finish.");
913
914 match mutex_ds18b20_water_temperature_clone_for_asserts.lock() {
915 Ok(result) => {
916 let result_data = result.clone().unwrap();
917 assert_eq!(result_data.value, 24.437);
918 }
919 Err(e) => {
920 panic!("mutex_ds18b20_water_temperature lock poisoned: {:?}", e);
921 }
922 };
923 match mutex_ds18b20_ambient_temperature_clone_for_asserts.lock() {
924 Ok(result) => {
925 let result_data = result.clone().unwrap();
926 assert_eq!(result_data.value, 24.637);
927 }
928 Err(e) => {
929 panic!("mutex_ds18b20_ambient_temperature lock poisoned: {:?}", e);
930 }
931 };
932 }
933}