1#![deny(missing_docs)]
11mod database;
23
24mod dispatch;
26
27mod food;
29
30mod mineral;
32
33mod permission;
35
36mod recorder;
38
39mod relays;
41
42mod sensors;
44
45mod simulator;
47
48#[cfg(test)]
49mod mocks;
51
52mod thermal;
54
55mod utilities;
57
58mod watchmen;
60
61mod beacon;
63
64mod water;
66
67mod launch;
69
70use log::{error, info, warn};
71
72#[cfg(any(feature = "debug_main", feature = "debug_channels"))]
73use log::debug;
74
75#[cfg(any(target_os = "linux", target_os = "macos"))]
76use nix::unistd;
77
78use crate::database::database_interface_feed_trait::DatabaseInterfaceFeedTrait;
79use crate::database::sql_interface::SqlInterface;
80use crate::database::sql_interface_balling::SqlInterfaceBalling;
81use crate::database::sql_interface_data::SqlInterfaceData;
82use crate::database::sql_interface_error::SqlInterfaceError;
83use crate::database::sql_interface_feed::SqlInterfaceFeed;
84use crate::database::sql_interface_heating_setvals::SqlInterfaceHeatingSetVals;
85use crate::database::sql_interface_heating_stats::SqlInterfaceHeatingStats;
86use crate::database::sql_interface_heating_stats_data_transfer::HeatingStatsDataTransfer;
87use crate::database::sql_interface_midnight::SqlInterfaceMidnightCalculator;
88use crate::database::sql_interface_refill::{DatabaseInterfaceRefillTrait, SqlInterfaceRefill};
89use crate::database::sql_interface_schedule::SqlInterfaceSchedule;
90use crate::database::sql_interface_ventilation_setvals::SqlInterfaceVentilationSetVals;
91#[cfg(target_os = "linux")]
92use crate::dispatch::messaging::Messaging;
93
94use crate::food::feed::Feed;
95use crate::food::food_injection::FoodInjection;
96use crate::launch::channels::Channels;
97use crate::launch::startup_error::StartupError;
98use crate::mineral::balling::Balling;
99use crate::mineral::mineral_injection::MineralInjection;
100use crate::permission::schedule_check::ScheduleCheck;
101use crate::recorder::data_injection::DataInjection;
102use crate::recorder::data_logger::{DataLogger, DataLoggerMutexes};
103use crate::relays::actuate_controllino::ActuateControllino;
104#[cfg(all(target_os = "linux", feature = "target_hw"))]
105use crate::relays::actuate_gpio::{ActuateGpio, ActuateGpioConfig};
106use crate::relays::actuate_simulator::ActuateSimulator;
107use crate::relays::relay_manager::RelayManager;
108
109#[cfg(all(feature = "target_hw", target_os = "linux"))]
110use crate::sensors::atlas_scientific::AtlasScientific;
111
112use crate::sensors::atlas_scientific::AtlasScientificResultData;
113
114#[cfg(all(feature = "target_hw", target_os = "linux"))]
115use crate::sensors::dht::Dht;
116
117use crate::launch::execution_config::ExecutionConfig;
118use crate::sensors::dht::DhtResult;
119use crate::sensors::ds18b20::{Ds18b20, Ds18b20ResultData};
120use crate::sensors::sensor_manager::{SensorManager, SensorManagerMutexes, SensorManagerSignals};
121use crate::sensors::tank_level_switch::{TankLevelSwitch, TankLevelSwitchSignals};
122use crate::simulator::tcp_communication::TcpCommunication;
123use crate::thermal::distinct_interval_check::distinct_thermal_control_interval_check;
124use crate::thermal::heating::{Heating, HeatingMutexes};
125use crate::thermal::ventilation::Ventilation;
126use crate::utilities::config::read_config_file;
127use crate::utilities::config_file_definition_error::ConfigFileDefinitionError;
128use crate::utilities::logger::{log_error_chain, setup_logger};
129use crate::utilities::logger_config::LoggerConfig;
130use crate::utilities::publish_pid::PublishPid;
131use crate::utilities::signal_handler::handle_signals;
132use crate::utilities::version_information::{VersionInformation, VersionInformationError};
133use crate::watchmen::monitors::Monitors;
134use crate::watchmen::petting::Petting;
135use crate::watchmen::watchdog::Watchdog;
136use crate::water::refill::{Refill, RefillStatus};
137use crate::water::water_injection::WaterInjection;
138use spin_sleep::SpinSleeper;
139pub use std::sync::{atomic::AtomicBool, Arc, Mutex};
140use std::{env, panic, path::Path, thread::scope, time::Duration};
141
142cfg_if::cfg_if! {
143 if #[cfg(all(target_os = "linux", feature = "target_hw"))] {
144 use crate::sensors::gpio_handler::GpioHandler;
145 use crate::beacon::ws2812b::RgbLedColor;
146 use crate::beacon::ws2812b::Ws2812B;
147 }
148}
149cfg_if::cfg_if! {
150 if #[cfg(all(feature = "target_hw", target_os = "linux"))] {
151 use crate::sensors::i2c_interface::I2cInterface;
152 }
153}
154use crate::watchmen::memory::Memory;
155
156cfg_if::cfg_if! {
157 if #[cfg(feature = "jemalloc")] {
158 use tikv_jemallocator::Jemalloc;
159 #[global_allocator]
160 static GLOBAL: Jemalloc = Jemalloc;
161 }
162}
163
164fn display_usage() {
166 println!("aquarium_control requires a configuration file to be specified to enter operation. Alternatively, the following options are available:");
167 println!("-h, --help: Output this page.");
168 println!("-v, --version: Output version information including hash of executable.");
169}
170
171fn check_is_command(command: &str) -> Result<bool, VersionInformationError> {
189 match command {
190 "-h" | "--help" => {
191 display_usage();
192 Ok(true)
193 }
194 "-v" | "--version" => {
195 match VersionInformation::new() {
197 Ok(version_information) => {
198 println!("{version_information}");
199 Ok(true)
200 }
201 Err(e) => Err(e),
202 }
203 }
204 _ => Ok(false), }
206}
207
208fn check_argument_for_valid_config_file_name(
229 user_selected_config_file_name: &str,
230) -> Result<String, ConfigFileDefinitionError> {
231 let path = Path::new(&user_selected_config_file_name);
232
233 let desired_config_file_name = user_selected_config_file_name.to_string();
234
235 if !path.exists() {
236 return Err(ConfigFileDefinitionError::PathDoesNotExist(
237 desired_config_file_name,
238 ));
239 }
240
241 if !path.is_file() {
242 return Err(ConfigFileDefinitionError::IsNotAFile(
243 desired_config_file_name,
244 ));
245 }
246
247 match path.extension() {
248 Some(extension) => {
249 if extension == "toml" {
250 Ok(String::from(user_selected_config_file_name))
251 } else {
252 Err(ConfigFileDefinitionError::SuffixIsNotToml(
253 desired_config_file_name,
254 ))
255 }
256 }
257 None => Err(ConfigFileDefinitionError::FileNameHasNoSuffix(
258 desired_config_file_name,
259 )),
260 }
261}
262
263#[cfg(any(target_os = "linux", target_os = "macos"))]
275fn is_running_as_root() -> bool {
276 let euid = unistd::geteuid();
277 euid.is_root()
278}
279
280#[cfg_attr(doc, aquamarine::aquamarine)]
281fn run() -> Result<(), StartupError> {
398 let spin_sleeper = SpinSleeper::default();
399
400 #[cfg(all(not(target_os = "linux"), feature = "target_hw"))]
401 compile_error!("Target hardware is only available with Linux operating system.");
402
403 let args: Vec<String> = env::args().collect();
405
406 let mut config_file_name = "/etc/aquarium_control/config/aquarium_control.toml".to_string();
408
409 if args.len() > 1 {
411 config_file_name = match check_argument_for_valid_config_file_name(&args[1]) {
413 Ok(valid_file_name) => valid_file_name, Err(config_file_definition_error) => {
415 let mut user_arg_identified = false;
416 for user_arg in args[1..].iter() {
418 user_arg_identified = match check_is_command(user_arg) {
419 Ok(alternative_execution_path) => alternative_execution_path,
420 Err(e) => {
421 return Err(StartupError::VersionInformationRetrievalFailure {
422 source: e,
423 });
424 }
425 };
426 }
427 return if !user_arg_identified {
428 Err(StartupError::Config(config_file_definition_error))
430 } else {
431 Ok(())
433 };
434 }
435 }
436 }
437 let config = read_config_file(config_file_name)?;
439
440 let execution_config = ExecutionConfig::new(&config);
441 let execution_config_clone_for_signal_handler = execution_config.clone();
442 let execution_config_clone_for_schedule_check = execution_config.clone();
443 let execution_config_clone_for_relay_manager = execution_config.clone();
444 let execution_config_clone_for_tcp_communication = execution_config.clone();
445 #[cfg(target_os = "linux")]
446 let execution_config_clone_for_messaging = execution_config.clone();
447
448 setup_logger(config.logger).map_err(|e| StartupError::LoggingSetupFailure { source: e })?;
450
451 let version_information = VersionInformation::new()
453 .map_err(|e| StartupError::VersionInformationRetrievalFailure { source: e })?;
454 #[cfg(not(test))]
455 info!(target: module_path!(), "{version_information}");
456
457 #[cfg(any(target_os = "linux", target_os = "macos"))]
459 if !is_running_as_root() {
460 return Err(StartupError::NotRoot);
461 }
462
463 let publish_pid = PublishPid::new(config.publish_pid)
465 .map_err(|e| StartupError::PidCheckError { source: e })?;
466
467 let publish_pid_clone = publish_pid.clone();
468
469 panic::set_hook(Box::new(move |panic_info| {
470 let result_reset_pid = publish_pid_clone.erase_pid();
475 error!("Panic occurred: {panic_info}");
476
477 if let Some(location) = panic_info.location() {
478 error!(
479 "Panic occurred in file '{}' at line {}",
480 location.file(),
481 location.line()
482 );
483 }
484 if let Err(error_reset_pid) = result_reset_pid {
486 error!("Could not reset PID file: {error_reset_pid}");
487 }
488 }));
489
490 if !distinct_thermal_control_interval_check(&config.heating, &config.ventilation) {
492 return Err(StartupError::InvalidThermalConfig);
493 }
494
495 let database_ping_duration = Duration::from_secs(config.sql_interface.db_ping_interval);
497
498 let max_rows_balling_set_values = config.sql_interface.max_rows_balling_set_values;
500 let max_rows_feed_pattern = config.sql_interface.max_rows_feed_pattern;
501 let max_rows_feed_schedule = config.sql_interface.max_rows_feed_schedule;
502 let max_rows_heating_stats = config.sql_interface.max_rows_heating_stats;
503 let max_rows_schedule = config.sql_interface.max_rows_schedule;
504 let max_rows_data = config.sql_interface.max_rows_data;
505 let max_rows_refill = config.sql_interface.max_rows_refill;
506 let max_rows_balling_dosing_log = config.sql_interface.max_rows_balling_dosing_log;
507 let max_rows_feed_log = config.sql_interface.max_rows_feed_log;
508
509 #[cfg(all(target_os = "linux", feature = "target_hw"))]
511 if config.ws2812b.active {
512 let led_duration = Duration::from_millis(300);
513 let mut ws2812b = Ws2812B::new(config.ws2812b)
514 .map_err(|e| StartupError::Ws2812BSetupFailure { source: e })?;
515 if let Err(e) = ws2812b.set_color(RgbLedColor::Red) {
516 warn!(target: module_path!(), "Failed to set RGB LED color: {e}");
517 }
518 spin_sleeper.sleep(led_duration);
519 if let Err(e) = ws2812b.set_color(RgbLedColor::Blue) {
520 warn!(target: module_path!(), "Failed to set RGB LED color: {e}");
521 }
522 spin_sleeper.sleep(led_duration);
523 if let Err(e) = ws2812b.set_color(RgbLedColor::Green) {
524 warn!(target: module_path!(), "Failed to set RGB LED color: {e}");
525 }
526 }
527
528 let mut sql_interface =
530 SqlInterface::new(config.sql_interface).map_err(|e| StartupError::Database {
531 source: Box::new(e),
532 })?;
533
534 let database_connection_schedule =
535 sql_interface
536 .get_connection()
537 .map_err(|e| StartupError::Database {
538 source: Box::new(e),
539 })?;
540 let sql_interface_schedule =
541 SqlInterfaceSchedule::new(database_connection_schedule, max_rows_schedule).map_err(
542 |e| StartupError::Database {
543 source: Box::new(e),
544 },
545 )?;
546
547 let database_connection_balling =
548 sql_interface
549 .get_connection()
550 .map_err(|e| StartupError::Database {
551 source: Box::new(e),
552 })?;
553 let mut sql_interface_balling = SqlInterfaceBalling::new(
554 database_connection_balling,
555 max_rows_balling_set_values,
556 max_rows_balling_dosing_log,
557 )
558 .map_err(|e| StartupError::Database {
559 source: Box::new(e),
560 })?;
561
562 let database_connection_refill =
563 sql_interface
564 .get_connection()
565 .map_err(|e| StartupError::Database {
566 source: Box::new(e),
567 })?;
568 let sql_interface_refill: Box<dyn DatabaseInterfaceRefillTrait + Sync + Send> = Box::new(
569 SqlInterfaceRefill::new(database_connection_refill, max_rows_refill).map_err(|e| {
570 StartupError::Database {
571 source: Box::new(e),
572 }
573 })?,
574 );
575
576 let database_connection_midnight_calculator =
577 sql_interface
578 .get_connection()
579 .map_err(|e| StartupError::Database {
580 source: Box::new(e),
581 })?;
582 let sql_interface_midnight_calculator =
583 SqlInterfaceMidnightCalculator::new(database_connection_midnight_calculator);
584
585 let database_connection_heating_stats =
586 sql_interface
587 .get_connection()
588 .map_err(|e| StartupError::Database {
589 source: Box::new(e),
590 })?;
591 let sql_interface_heating = SqlInterfaceHeatingStats::new(
592 database_connection_heating_stats,
593 max_rows_heating_stats,
594 Box::new(sql_interface_midnight_calculator),
595 )
596 .map_err(|e| StartupError::Database {
597 source: Box::new(e),
598 })?;
599
600 let database_connection_feed =
601 sql_interface
602 .get_connection()
603 .map_err(|e| StartupError::Database {
604 source: Box::new(e),
605 })?;
606 let sql_interface_feed: Box<dyn DatabaseInterfaceFeedTrait + Sync + Send> = Box::new(
607 SqlInterfaceFeed::new(
608 database_connection_feed,
609 max_rows_feed_pattern,
610 max_rows_feed_schedule,
611 max_rows_feed_log,
612 )
613 .map_err(|e| StartupError::Database {
614 source: Box::new(e),
615 })?,
616 );
617
618 let database_connection_data =
619 sql_interface
620 .get_connection()
621 .map_err(|e| StartupError::Database {
622 source: Box::new(e),
623 })?;
624 let sql_interface_data = SqlInterfaceData::new(database_connection_data, max_rows_data)
625 .map_err(|e| StartupError::Database {
626 source: Box::new(e),
627 })?;
628
629 let tables = sql_interface
630 .get_tables()
631 .map_err(|e| StartupError::Database {
632 source: Box::new(e),
633 })?;
634
635 match sql_interface.get_hash_from_database(&version_information) {
636 Ok(c) => {
637 info!(
638 target: module_path!(),
639 "version information including hash of executable found in database: {c}"
640 );
641 if c != version_information.hash {
642 warn!(
643 target: module_path!(),
644 "hash value for version in database does not match hash version of executable. Proceed with caution.");
645 }
646 }
647 Err(e) => {
648 if matches!(
649 e,
650 SqlInterfaceError::SingleVersionRequestMultipleResults(_, _, _, _, _)
651 ) {
652 warn!(
653 target: module_path!(),
654 "main: Found multiple hash values for version in database. Proceed with caution.");
655 } else {
656 return Err(StartupError::Database {
657 source: Box::new(e),
658 });
659 }
660 }
661 }
662
663 let database = sql_interface
664 .get_database()
665 .map_err(|e| StartupError::Database {
666 source: Box::new(e),
667 })?;
668 info!(target: module_path!(), "current database name is {database}");
669
670 sql_interface
671 .check_required_tables_existing()
672 .map_err(|e| StartupError::Database {
673 source: Box::new(e),
674 })?;
675 info!(target: module_path!(), "required tables are existing in database.");
676
677 let mut tables_string: String = "read the following tables from database:".to_string();
678 for table in tables {
679 tables_string += " ";
680 tables_string += &table;
681 }
682 info!(target: module_path!(), "{tables_string}");
683
684 let use_simulator = config.gpio_handler.use_simulator
686 | config.relay_manager.use_simulator
687 | config.sensor_manager.use_simulator
688 | config.tank_level_switch.use_simulator;
689
690 let mut channels = Channels::new(
691 config.relay_manager.use_simulator,
692 config.sensor_manager.use_simulator,
693 config.tank_level_switch.use_simulator,
694 );
695
696 #[cfg(target_os = "linux")]
697 let mut messaging = Messaging::new(config.messaging, execution_config_clone_for_messaging)
698 .map_err(|e| StartupError::Messaging { source: e })?;
699
700 let mut food_injection = FoodInjection::new(&config.feed);
701 let mut mineral_injection = MineralInjection::new();
702 let mut water_injection = WaterInjection::new(&config.refill);
703 let mut petting = Petting::new();
704 let mut actuate_controllino = ActuateControllino::new(config.controllino_relay.clone())
705 .map_err(|e| StartupError::ControllinoSetupFailure { source: e })?;
706
707 let mut tank_level_switch: TankLevelSwitch;
709
710 cfg_if::cfg_if! {
711 if #[cfg(all(target_os = "linux", feature = "target_hw"))] {
712 let mut dht;
713 let gpio_dht22_io = config.gpio_handler.dht22_io;
714 let gpio_dht22_vcc = config.gpio_handler.dht22_vcc;
715 let mut actuate_gpio_opt: Option<ActuateGpio> = None;
716
717 let gpio_tank_level_switch = config.gpio_handler.tank_level_switch;
719 let actuate_gpio_config = ActuateGpioConfig::new(&config.gpio_handler);
720 let use_simulator_for_gpio = config.gpio_handler.use_simulator;
721 let gpio_handler_opt = GpioHandler::new(config.gpio_handler).
722 map_err(|e| StartupError::GpioHandlerSetupFailure { source: e })?;
723
724 if use_simulator_for_gpio {
725 tank_level_switch = TankLevelSwitch::new(
727 config.tank_level_switch,
728 true,
729 None,
730 gpio_tank_level_switch
731 ).map_err(|e| StartupError::TankLevelSwitchSetupFailure { source: e })?;
732 dht = Dht::new(config.dht, None, gpio_dht22_io, gpio_dht22_vcc);
734 }
735 else if gpio_handler_opt.is_none() {
736 return Err(StartupError::GpioHandlerMissing);
738 }
739 else {
740 let gpio_handler = gpio_handler_opt.unwrap();
741 let gpio_lib_handle = gpio_handler.gpio_lib_handle_opt.unwrap();
742 let gpio_lib_handle_cloned_for_tank_level_switch = gpio_lib_handle.clone();
743 let gpio_lib_handle_cloned_for_relay_manager = gpio_lib_handle.clone();
744
745 tank_level_switch = TankLevelSwitch::new(
746 config.tank_level_switch,
747 true,
748 Some(gpio_lib_handle_cloned_for_tank_level_switch),
749 gpio_tank_level_switch
750 ).map_err(|e| StartupError::TankLevelSwitchSetupFailure { source: e })?;
751 dht = Dht::new(config.dht, Some(gpio_lib_handle.clone()), gpio_dht22_io, gpio_dht22_vcc);
752 if config.relay_manager.use_gpio {
753 let actuate_gpio = ActuateGpio::new(
754 gpio_lib_handle_cloned_for_relay_manager,
755 actuate_gpio_config)
756 .map_err(|e| StartupError::ActuateGpioSetupFailure { source: e })?;
757 actuate_gpio_opt = Some(actuate_gpio);
758 }
759 }
760 let mut i2c_interface = I2cInterface::new(config.i2c_interface)
761 .map_err(|e| StartupError::I2cSetupFailure { source: e })?;
762 }
763 else {
764 tank_level_switch = TankLevelSwitch::new(
765 config.tank_level_switch,
766 true,
767 ).map_err(|e| StartupError::TankLevelSwitchSetupFailure { source: e })?;
768 }
769 }
770
771 let mutex_device_scheduler = Arc::new(Mutex::new(0));
773
774 let mutex_ds18b20_water_temperature = Arc::new(Mutex::new(Ok(Ds18b20ResultData::new(
776 config.sensor_manager.replacement_value_water_temperature,
777 ))));
778 let mutex_ds18b20_ambient_temperature = Arc::new(Mutex::new(Ok(Ds18b20ResultData::new(
779 config.sensor_manager.replacement_value_ambient_temperature,
780 ))));
781 let mutex_ds18b20_water_temperature_clone_for_sensor_manager =
782 mutex_ds18b20_water_temperature.clone();
783 let mutex_ds18b20_ambient_temperature_clone_for_sensor_manager =
784 mutex_ds18b20_ambient_temperature.clone();
785
786 let mutex_atlas_scientific_temperature = Arc::new(Mutex::new(AtlasScientificResultData::new(
788 config.sensor_manager.replacement_value_water_temperature,
789 )));
790 let mutex_atlas_scientific_temperature_clone_for_sensor_manager =
791 mutex_atlas_scientific_temperature.clone();
792 let mutex_atlas_scientific_ph = Arc::new(Mutex::new(AtlasScientificResultData::new(
793 config.sensor_manager.replacement_value_ph,
794 )));
795 let mutex_atlas_scientific_ph_clone_for_sensor_manager = mutex_atlas_scientific_ph.clone();
796 let mutex_atlas_scientific_conductivity = Arc::new(Mutex::new(AtlasScientificResultData::new(
797 config.sensor_manager.replacement_value_conductivity,
798 )));
799 let mutex_atlas_scientific_conductivity_clone_for_sensor_manager =
800 mutex_atlas_scientific_conductivity.clone();
801
802 let mutex_tank_level_switch_signals =
804 Arc::new(Mutex::new(TankLevelSwitchSignals::new(true, true, false)));
805 let mutex_tank_level_switch_signals_clone_for_data_logger =
806 mutex_tank_level_switch_signals.clone();
807 let mutex_tank_level_switch_signals_clone_for_heating = mutex_tank_level_switch_signals.clone();
808 let mutex_tank_level_switch_signals_clone_for_refill = mutex_tank_level_switch_signals.clone();
809
810 let mutex_heating_status = Arc::new(Mutex::new(false));
812 let mutex_heating_status_clone_for_data_logger = mutex_heating_status.clone();
813
814 let mutex_ventilation_status = Arc::new(Mutex::new(false));
816 let mutex_ventilation_status_clone_for_data_logger = mutex_ventilation_status.clone();
817
818 let refill_status = RefillStatus {
820 refill_in_progress_live: false,
821 refill_in_progress_for_database: false,
822 };
823 let mutex_refill_status = Arc::new(Mutex::new(refill_status));
824 let mutex_refill_status_clone_for_data_logger = mutex_refill_status.clone();
825
826 let mutex_dht: Arc<Mutex<DhtResult>> = Arc::new(Mutex::new(Ok((0.0, 0.0))));
828 let mutex_dht_clone_for_sensor_manager = mutex_dht.clone();
829
830 let mutex_sensor_manager_signals: Arc<Mutex<SensorManagerSignals>> = Arc::new(Mutex::new(
832 SensorManagerSignals::new(&config.sensor_manager),
833 ));
834 let mutex_sensor_manager_signals_clone_for_data_logger = mutex_sensor_manager_signals.clone();
835 let mutex_sensor_manager_signals_clone_for_heating = mutex_sensor_manager_signals.clone();
836 let mutex_sensor_manager_signals_clone_for_ventilation = mutex_sensor_manager_signals.clone();
837 #[cfg(all(target_os = "linux", feature = "target_hw"))]
840 let mut atlas_scientific = AtlasScientific::new(config.atlas_scientific);
841
842 let mut sensor_manager = SensorManager::new(config.sensor_manager)
843 .map_err(|e| StartupError::SensorManagerSetupFailure { source: e })?;
844
845 let mut relay_manager = RelayManager::new(
846 config.relay_manager.clone(),
847 execution_config_clone_for_relay_manager,
848 );
849 let mut schedule_check = ScheduleCheck::new(config.schedule_check, database_ping_duration);
850
851 let mut data_injection = DataInjection;
852 let mut data_logger = DataLogger::new(config.data_logger, database_ping_duration)
853 .map_err(|e| StartupError::DataLoggerSetupFailure { source: e })?;
854
855 let mut refill = Refill::new(config.refill, database_ping_duration);
856
857 let database_connection_heating_setvals =
858 sql_interface
859 .get_connection()
860 .map_err(|e| StartupError::Database {
861 source: Box::new(e),
862 })?;
863 let mut heating_set_value_updater =
864 SqlInterfaceHeatingSetVals::new(database_connection_heating_setvals, &config.heating)
865 .map_err(|e| StartupError::Database {
866 source: Box::new(e),
867 })?;
868
869 let mut heating = Heating::new(config.heating, database_ping_duration);
870
871 let mut feed = Feed::new(config.feed, database_ping_duration);
872
873 let database_connection_ventilation =
874 sql_interface
875 .get_connection()
876 .map_err(|e| StartupError::Database {
877 source: Box::new(e),
878 })?;
879 let mut ventilation_set_value_updater =
880 SqlInterfaceVentilationSetVals::new(database_connection_ventilation, &config.ventilation)
881 .map_err(|e| StartupError::Database {
882 source: Box::new(e),
883 })?;
884
885 let mut ventilation = Ventilation::new(config.ventilation);
886 let mut balling = Balling::new(
887 config.balling,
888 database_ping_duration,
889 &mut sql_interface_balling,
890 )
891 .map_err(|e| StartupError::InvalidBallingConfiguration { source: e })?;
892 let mut monitors = Monitors::new(config.monitors);
893 let mut watchdog = Watchdog::new(config.watchdog)
894 .map_err(|e| StartupError::WatchdogSetupFailure { source: e })?;
895 let mut memory =
896 Memory::new(config.memory).map_err(|e| StartupError::InvalidMemoryConfig { source: e })?;
897
898 let mut ds18b20 = Ds18b20::new(config.ds18b20)
899 .map_err(|e| StartupError::Ds18b20SetupFailure { source: e })?;
900
901 let mut tcp_communication_opt = None;
902 if use_simulator {
903 let tcp_communication = TcpCommunication::new(
904 &config.tcp_communication,
905 execution_config_clone_for_tcp_communication,
906 )
907 .map_err(|e| StartupError::TcpConnection { source: e })?;
908 tcp_communication_opt = Some(tcp_communication);
909 }
910
911 let tcp_communication_thread_started = execution_config.tcp_communication;
912 let relay_manager_thread_started = execution_config.relay_manager;
913 scope(|scope| {
914 if use_simulator && tcp_communication_thread_started {
916 scope.spawn(move || {
917 if let Some(mut tcp_communication) = tcp_communication_opt {
918 tcp_communication.execute(&mut channels.tcp_communication);
919 }
920 #[cfg(feature = "debug_channels")]
921 debug!(target: module_path!(), "{0}", channels.tcp_communication);
922 });
923 #[cfg(feature = "debug_main")]
924 debug!(target: module_path!(), "TcpCommunication terminated.");
925 };
926
927 if config.relay_manager.use_simulator && relay_manager_thread_started {
928 scope.spawn(move || {
929 let mut actuate_simulator = ActuateSimulator::new(
930 &mut channels.actuate_simulator,
931 config.controllino_relay,
932 );
933 relay_manager.execute(&mut channels.relay_manager, &mut actuate_simulator);
934 #[cfg(feature = "debug_channels")]
935 debug!(target: module_path!(), "{0}", channels.actuate_simulator);
936 #[cfg(feature = "debug_channels")]
937 debug!(target: module_path!(), "{0}", channels.relay_manager);
938 #[cfg(feature = "debug_main")]
939 debug!(target: module_path!(), "Controllino (simulator) terminated.");
940 });
941 } else if relay_manager_thread_started {
942 scope.spawn(move || {
943 if config.relay_manager.use_gpio {
944 #[cfg(all(feature = "target_hw", target_os = "linux"))]
945 relay_manager.execute(
946 &mut channels.relay_manager,
947 &mut actuate_gpio_opt.expect("Internal error: Illegal configuration (unwrapping of GPIO actuator failed)")
948 );
949 } else {
950 relay_manager.execute(&mut channels.relay_manager, &mut actuate_controllino);
951 }
952 #[cfg(feature = "debug_channels")]
953 debug!(target: module_path!(), "{0}", channels.relay_manager);
954 #[cfg(feature = "debug_main")]
955 debug!(target: module_path!(), "Controllino (with hardware) terminated.");
956 });
957 }
958
959 scope.spawn(move || {
960 let term = Arc::new(AtomicBool::new(false));
961
962 handle_signals(
963 term,
964 channels.signal_handler,
965 execution_config_clone_for_signal_handler,
966 );
967 #[cfg(feature = "debug_main")]
968 debug!(target: module_path!(), "Signal handler terminated.");
969 });
970
971 if execution_config.schedule_check {
972 scope.spawn(move || {
973 schedule_check.execute(
974 &mut channels.schedule_check,
975 execution_config_clone_for_schedule_check,
976 sql_interface_schedule,
977 );
978 #[cfg(feature = "debug_channels")]
979 debug!(target: module_path!(), "{0}", channels.schedule_check);
980 #[cfg(feature = "debug_main")]
981 debug!(target: module_path!(), "Schedule checker terminated.");
982 });
983 }
984
985 #[cfg(all(target_os = "linux", feature = "target_hw"))]
986 if !use_simulator && execution_config.i2c_interface {
987 scope.spawn(move || {
988 i2c_interface.execute(&mut channels.i2c_interface);
989 #[cfg(all(target_os = "linux", feature = "target_hw", feature = "debug_channels"))]
990 debug!(target: module_path!(), "{0}", channels.i2c_interface);
991 #[cfg(feature = "debug_main")]
992 debug!(target: module_path!(), "i2c_interface terminated.");
993 });
994 }
995
996 #[cfg(all(target_os = "linux", feature = "target_hw"))]
997 if !use_simulator && execution_config.atlas_scientific {
998 scope.spawn(move || {
999 atlas_scientific.execute(
1000 &mut channels.atlas_scientific,
1001 mutex_atlas_scientific_temperature,
1002 mutex_atlas_scientific_ph,
1003 mutex_atlas_scientific_conductivity,
1004 );
1005 #[cfg(feature = "debug_channels")]
1006 debug!(target: module_path!(), "{0}", channels.atlas_scientific);
1007 #[cfg(feature = "debug_main")]
1008 debug!(target: module_path!(), "Atlas Scientific terminated.");
1009 });
1010 }
1011
1012 #[cfg(all(target_os = "linux", feature = "target_hw"))]
1013 if execution_config.dht {
1014 scope.spawn(move || {
1015 dht.execute(mutex_dht, &mut channels.dht);
1016 #[cfg(feature = "debug_channels")]
1017 debug!(target: module_path!(), "{0}", channels.dht);
1018 #[cfg(feature = "debug_main")]
1019 debug!(target: module_path!(), "Dht terminated.");
1020 });
1021 }
1022
1023 if execution_config.sensor_manager {
1024 scope.spawn(move || {
1025 let mutexes_sensor_manager = SensorManagerMutexes {
1026 mutex_ds18b20_ambient_temperature:
1027 mutex_ds18b20_ambient_temperature_clone_for_sensor_manager,
1028 mutex_ds18b20_water_temperature:
1029 mutex_ds18b20_water_temperature_clone_for_sensor_manager,
1030 mutex_dht: mutex_dht_clone_for_sensor_manager,
1031 mutex_atlas_scientific_temperature:
1032 mutex_atlas_scientific_temperature_clone_for_sensor_manager,
1033 mutex_atlas_scientific_ph: mutex_atlas_scientific_ph_clone_for_sensor_manager,
1034 mutex_atlas_scientific_conductivity:
1035 mutex_atlas_scientific_conductivity_clone_for_sensor_manager,
1036 mutex_sensor_manager_signals,
1037 };
1038
1039 sensor_manager.execute(&mut channels.sensor_manager, mutexes_sensor_manager);
1040 #[cfg(feature = "debug_channels")]
1041 debug!(target: module_path!(), "{0}", channels.sensor_manager);
1042 #[cfg(feature = "debug_main")]
1043 debug!(target: module_path!(), "SensorManager terminated.");
1044 });
1045 }
1046
1047 let duration_one_second = Duration::from_secs(1);
1049 println!("trying to acquire data before starting control in 3 seconds...");
1050 spin_sleeper.sleep(duration_one_second);
1051 println!("trying to acquire data before starting control in 2 seconds...");
1052 spin_sleeper.sleep(duration_one_second);
1053 println!("trying to acquire data before starting control in 1 second...");
1054 spin_sleeper.sleep(duration_one_second);
1055 println!("starting control...");
1056
1057 if execution_config.data_logger {
1058 scope.spawn(move || {
1059 let data_logger_mutexes = DataLoggerMutexes {
1060 mutex_sensor_manager_signals:
1061 mutex_sensor_manager_signals_clone_for_data_logger,
1062 mutex_tank_level_switch_signals:
1063 mutex_tank_level_switch_signals_clone_for_data_logger,
1064 mutex_heating_status: mutex_heating_status_clone_for_data_logger,
1065 mutex_ventilation_status: mutex_ventilation_status_clone_for_data_logger,
1066 mutex_refill_status: mutex_refill_status_clone_for_data_logger,
1067 };
1068
1069 data_logger.execute(
1070 &mut data_injection,
1071 sql_interface_data,
1072 &mut channels.data_logger,
1073 data_logger_mutexes,
1074 );
1075 #[cfg(feature = "debug_channels")]
1076 debug!(target: module_path!(), "{0}", channels.data_logger);
1077 #[cfg(feature = "debug_main")]
1078 debug!(target: module_path!(), "Data logger terminated.");
1079 });
1080 }
1081
1082 if execution_config.tank_level_switch {
1083 scope.spawn(move || {
1084 let _ = tank_level_switch.execute(
1085 &mut channels.tank_level_switch,
1086 mutex_tank_level_switch_signals,
1087 );
1088 #[cfg(feature = "debug_channels")]
1089 debug!(target: module_path!(), "{0}", channels.tank_level_switch);
1090 #[cfg(feature = "debug_main")]
1091 debug!(target: module_path!(), "Tank level switch calculation terminated.");
1092 });
1093 }
1094
1095 if execution_config.refill {
1096 let mutex_device_scheduler_refill = Arc::clone(&mutex_device_scheduler);
1098
1099 scope.spawn(move || {
1100 refill.execute(
1101 mutex_device_scheduler_refill,
1102 &mut channels.refill,
1103 &mut water_injection,
1104 mutex_tank_level_switch_signals_clone_for_refill,
1105 mutex_refill_status,
1106 sql_interface_refill,
1107 );
1108 #[cfg(feature = "debug_channels")]
1109 debug!(target: module_path!(), "{0}", channels.refill);
1110 #[cfg(feature = "debug_main")]
1111 debug!(target: module_path!(), "Refill terminated.");
1112 });
1113 }
1114
1115 if execution_config.heating {
1116 let mutex_device_scheduler_heating = Arc::clone(&mutex_device_scheduler);
1118
1119 let mut heating_stats_transfer = HeatingStatsDataTransfer;
1120
1121 let heating_mutexes = HeatingMutexes {
1122 mutex_sensor_manager_signals: mutex_sensor_manager_signals_clone_for_heating,
1123 mutex_tank_level_switch_signals: mutex_tank_level_switch_signals_clone_for_heating,
1124 mutex_heating_status,
1125 };
1126
1127 scope.spawn(move || {
1128 heating.execute(
1129 mutex_device_scheduler_heating,
1130 &mut channels.heating,
1131 &mut heating_stats_transfer,
1132 &mut heating_set_value_updater,
1133 heating_mutexes,
1134 sql_interface_heating,
1135 );
1136 #[cfg(feature = "debug_channels")]
1137 debug!(target: module_path!(), "{0}", channels.heating);
1138 #[cfg(feature = "debug_main")]
1139 debug!(target: module_path!(), "Heating terminated.");
1140 });
1141 }
1142
1143 if execution_config.ventilation {
1144 let mutex_device_scheduler_ventilation = Arc::clone(&mutex_device_scheduler);
1146
1147 scope.spawn(move || {
1148 ventilation.execute(
1149 mutex_device_scheduler_ventilation,
1150 &mut channels.ventilation,
1151 &mut ventilation_set_value_updater,
1152 mutex_sensor_manager_signals_clone_for_ventilation,
1153 mutex_ventilation_status,
1154 );
1155 #[cfg(feature = "debug_channels")]
1156 debug!(target: module_path!(), "{0}", channels.ventilation);
1157 #[cfg(feature = "debug_main")]
1158 debug!(target: module_path!(), "Ventilation terminated.");
1159 });
1160 }
1161
1162 if execution_config.monitors {
1163 let mutex_device_scheduler_monitors = Arc::clone(&mutex_device_scheduler);
1165
1166 scope.spawn(move || {
1167 monitors.execute(mutex_device_scheduler_monitors, &mut channels.monitors);
1168 #[cfg(feature = "debug_channels")]
1169 debug!(target: module_path!(), "{0}", channels.monitors);
1170 #[cfg(feature = "debug_main")]
1171 debug!(target: module_path!(), "Monitors terminated.");
1172 });
1173 }
1174
1175 if execution_config.feed {
1176 let mutex_device_scheduler_feed = Arc::clone(&mutex_device_scheduler);
1178
1179 scope.spawn(move || {
1180 feed.execute(
1181 mutex_device_scheduler_feed,
1182 &mut channels.feed,
1183 &mut food_injection,
1184 sql_interface_feed,
1185 );
1186 #[cfg(feature = "debug_channels")]
1187 debug!(target: module_path!(), "{0}", channels.feed);
1188 #[cfg(feature = "debug_main")]
1189 debug!(target: module_path!(), "Feed terminated.");
1190 });
1191 }
1192
1193 if execution_config.balling {
1194 let mutex_device_scheduler_balling = Arc::clone(&mutex_device_scheduler);
1196
1197 scope.spawn(move || {
1198 balling.execute(
1199 mutex_device_scheduler_balling,
1200 &mut channels.balling,
1201 &mut mineral_injection,
1202 sql_interface_balling,
1203 );
1204 #[cfg(feature = "debug_channels")]
1205 debug!(target: module_path!(), "{0}", channels.balling);
1206 #[cfg(feature = "debug_main")]
1207 debug!(target: module_path!(), "Balling terminated.");
1208 });
1209 }
1210
1211 scope.spawn(move || {
1213 #[cfg(target_os = "linux")]
1215 messaging.execute(&mut channels.messaging);
1216
1217 let publish_pid_result = publish_pid.erase_pid();
1219 if let Err(e) = publish_pid_result {
1220 log_error_chain(
1221 module_path!(),
1222 "Error occurred when trying to reset PID.",
1223 e,
1224 );
1225 }
1226 #[cfg(all(target_os = "linux", feature = "debug_channels"))]
1227 debug!(target: module_path!(), "{0}", channels.messaging);
1228 #[cfg(feature = "debug_main")]
1229 debug!(target: module_path!(), "Messaging terminated.");
1230 });
1231
1232 if execution_config.watchdog {
1233 scope.spawn(move || {
1235 watchdog.execute(&mut channels.watchdog, &mut petting);
1236 #[cfg(all(target_os = "linux", feature = "target_hw", feature = "debug_channels"))]
1237 debug!(target: module_path!(), "{0}", channels.watchdog);
1238 #[cfg(feature = "debug_main")]
1239 debug!(target: module_path!(), "Watchdog terminated.");
1240 });
1241 }
1242
1243 if execution_config.memory {
1244 scope.spawn(move || {
1246 memory.execute(&mut channels.memory);
1247 #[cfg(all(target_os = "linux", feature = "target_hw", feature = "debug_channels"))]
1248 debug!(target: module_path!(), "{0}", channels.memory);
1249 #[cfg(feature = "debug_main")]
1250 debug!(target: module_path!(), "Memory terminated.");
1251 });
1252 }
1253
1254 if execution_config.ds18b20 {
1255 scope.spawn(move || {
1257 ds18b20.execute(
1258 &mut channels.ds18b20,
1259 mutex_ds18b20_water_temperature,
1260 mutex_ds18b20_ambient_temperature,
1261 );
1262 #[cfg(all(target_os = "linux", feature = "target_hw", feature = "debug_channels"))]
1263 debug!(target: module_path!(), "{0}", channels.ds18b20);
1264 #[cfg(feature = "debug_main")]
1265 debug!(target: module_path!(), "Ds18b20 terminated");
1266 });
1267 }
1268 });
1269
1270 Ok(())
1271}
1272
1273fn main() {
1276 if let Err(e) = setup_logger(LoggerConfig::default()) {
1278 println!("Error setting up preliminary logger ({e:?})");
1279 return;
1280 }
1281
1282 if let Err(e) = run() {
1283 log_error_chain(module_path!(), "Application failed to start.", e);
1284 std::process::exit(1);
1285 }
1286}