aquarium_control/
main.rs

1/* Copyright 2024 Uwe Martin
2
3Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
5The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
7THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8*/
9
10#![deny(missing_docs)]
11//! Aquarium control is an open-source application that is part of a control system for salt-water aquariums.
12//! It contains the following functionality:
13//! - Data acquisition of sensors (water temperature, pH, conductivity, ambient air temperature, ambient humidity)
14//! - Fresh water refill
15//! - Heating control
16//! - Ventilation control
17//! - Automatic feeding
18//! - Balling mineral dosing
19
20/// This is the container module for the database communication.
21/// Communicating with the SQL database includes preparing SQL queries, executing the queries and processing of query results.
22mod database;
23
24/// Communication with the user via client application or webserver using POSIX message queues.
25mod dispatch;
26
27/// This container module features all the functionality to execute the automatic feeding.
28mod food;
29
30/// This container module features all the functionality to execute the Balling mineral dosing.
31mod mineral;
32
33/// The container module contains the schedule checker.
34mod permission;
35
36/// This container module contains all functionality to periodically store signals either in one table of the SQL database or in specific text files stored in the RAM disk
37mod recorder;
38
39/// This container module features all functionality to actuate the relays which control the actuators.
40mod relays;
41
42/// This container module features all functionality to read and postprocess sensor data.
43mod sensors;
44
45/// This container module features all functionality to communicate with the test server via TCP.
46mod simulator;
47
48#[cfg(test)]
49/// This container module features a set of mock functionality used by test cases.
50mod mocks;
51
52/// This container module features all functionality to either cool the water using ventilation heat the water using a heater.
53mod thermal;
54
55/// This container module features all functionality that could not be assigned to any other container module.
56mod utilities;
57
58/// This container module features all functionality to monitor the control system.
59mod watchmen;
60
61/// This container module features all functionality related to RGB LED.
62mod beacon;
63
64/// This container module features all functionality to replace the evaporated water with fresh water.
65mod water;
66
67/// This container module features all supporting functionality for the main module.
68mod 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
164/// Displays the valid command line parameter combinations
165fn 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
171/// Checks if a command-line argument is a recognized command and executes it.
172///
173/// This function handles special command-line flags that provide information
174/// to the user (e.g., help, version) without starting the main application.
175///
176/// # Arguments
177/// * `command` - A string slice representing the potential command (e.g., "-h", "--version").
178///
179/// # Returns
180/// A `Result` indicating the outcome:
181/// - `Ok(true)`: If the argument was a recognized command and was handled successfully.
182/// - `Ok(false)`: If the argument was not a recognized command.
183///
184/// # Errors
185/// Returns a `VersionInformationError` if the `-v` or `--version` command is used,
186/// but the application fails to retrieve its own version information (e.g., cannot
187/// calculate the executable's hash).
188fn 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            // try to calculate hash of executable
196            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), // All other strings do not match
205    }
206}
207
208/// Checks if the provided string is a valid path to a `.toml` configuration file.
209///
210/// This function validates a user-selected configuration file path by ensuring that:
211/// - The path exists on the file system.
212/// - It points to a regular file (not a directory).
213/// - The file has an extension.
214/// - The file's extension is `.toml`.
215///
216/// # Arguments
217/// * `user_selected_config_file_name` - A string slice representing the potential path to the configuration file.
218///
219/// # Returns
220/// A `Result` containing the validated and owned file path as a `String` on success.
221///
222/// # Errors
223/// Returns a `ConfigFileDefinitionError` if any validation check fails:
224/// - `ConfigFileDefinitionError::PathDoesNotExist`: If no file or directory exists at the given path.
225/// - `ConfigFileDefinitionError::IsNotAFile`: If the path points to a directory, not a regular file.
226/// - `ConfigFileDefinitionError::FileNameHasNoSuffix`: If the path does not have a file extension.
227/// - `ConfigFileDefinitionError::SuffixIsNotToml`: If the file's extension is not `.toml`.
228fn 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/// Checks if the current process is running with root (UID 0) privileges.
264///
265/// This function inspects the Effective User ID (EUID) of the process.
266/// On Unix-like systems, if the EUID is 0, the process has root privileges.
267///
268/// # Returns
269/// `true` if the process has root privileges, `false` otherwise.
270///
271/// # Panics
272/// This function may panic if the underlying system call `geteuid` fails,
273/// which is highly unlikely in normal operation.
274#[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)]
281/// The main entry point of the Aquarium Control application.
282///
283/// This function initializes and orchestrates all modules of the control system.
284/// It performs the following high-level steps:
285/// 1.  Sets up global logging and handles command-line arguments for configuration file selection.
286/// 2.  Checks if the user has provided specific command line parameters and acts accordingly.
287/// 3.  Checks if the user has root privileges.
288/// 4.  Loads the application configuration from the specified `.toml` file.
289/// 5.  Publishes the application's Process ID (PID) to a file and sets up a custom panic hook for cleanup.
290/// 6.  Performs version checks against the database to ensure compatibility.
291/// 7.  Initializes all SQL database interfaces and validates database readiness.
292/// 8.  Sets up all inter-thread communication channels (`mpsc`).
293/// 9.  Initializes all application modules (sensors, controls, managers) with their respective configurations and channels/mutexes.
294/// 10. Spawns each module in its own dedicated thread within a `std::thread::scope` for structured concurrency.
295/// 11. Manages the initial data acquisition phase, waiting for sensors to provide first readings before control loops become fully active.
296///
297/// This function is responsible for the overall lifecycle of the application, including graceful startup and panic handling.
298///
299/// # Returns
300/// An empty `Result` (`Ok(())`) on a graceful shutdown. This occurs if the user
301/// provides a command-line flag like `-h` or if the application is terminated
302/// normally by the signal handler.
303///
304/// # Errors
305/// Returns a `StartupError` if any part of the initialization or setup fails.
306/// This error enum covers a wide range of potential issues, including:
307/// - `StartupError::Config`: If the configuration file is invalid or cannot be parsed.
308/// - `StartupError::LoggingSetupFailure`: If the logger cannot be initialized (e.g., due to file permissions).
309/// - `StartupError::NotRoot`: If the application is run without the necessary root privileges.
310/// - `StartupError::PidCheckError`: If another instance is already running or the PID file cannot be written.
311/// - `StartupError::Database`: If any database connection, query, or setup fails.
312/// - `StartupError::GpioHandlerSetupFailure`: If the GPIO interface cannot be initialized on the target hardware.
313/// - And various other setup failures for specific modules.
314///
315/// Global thread communication is as follows:
316/// ```mermaid
317/// graph LR
318///     tank_level_switch -.-> refill[Refill]
319///     refill --> relay_manager[Controllino]
320///     relay_manager --> refill
321///     sensor_manager[SensorManager] -.-> data_logger[DataLogger]
322///     refill -.-> data_logger
323///     signal_handler[SignalHandler] --> refill
324///     refill --> signal_handler
325///     signal_handler --> tank_level_switch
326///     tank_level_switch --> signal_handler
327///     signal_handler --> tcp_communication[TcpCommunication]
328///     tcp_communication --> signal_handler
329///     signal_handler --> relay_manager
330///     relay_manager --> signal_handler
331///     balling[Balling] --> signal_handler
332///     signal_handler --> balling
333///     feed[Feed] --> signal_handler
334///     signal_handler --> feed
335///     heating[Heating] --> signal_handler
336///     signal_handler --> heating
337///     ventilation[Ventilation] --> signal_handler
338///     signal_handler --> ventilation
339///     relay_manager --> tcp_communication
340///     sensor_manager --> tcp_communication
341///     tcp_communication --> sensor_manager
342///     atlas_scientific --> tcp_communication
343///     tcp_communication --> atlas_scientific
344///     tank_level_switch --> tcp_communication
345///     tcp_communication --> tank_level_switch
346///     balling --> relay_manager
347///     relay_manager --> balling
348///     feed --> relay_manager
349///     relay_manager --> feed
350///     atlas_scientific -.-> data_logger
351///     heating -.-> data_logger
352///     atlas_scientific -.-> heating
353///     heating --> relay_manager
354///     relay_manager --> heating
355///     tank_level_switch -.-> heating
356///     sensor_manager -.-> heating
357///     tank_level_switch -.-> data_logger
358///     ventilation -.-> data_logger
359///     signal_handler --> data_logger
360///     data_logger --> signal_handler
361///     ventilation --> relay_manager
362///     atlas_scientific -.-> ventilation
363///     relay_manager --> ventilation
364///     monitors[Monitors] --> relay_manager
365///     relay_manager --> monitors
366///     monitors --> refill
367///     refill --> monitors
368///     monitors --> signal_handler
369///     signal_handler --> monitors
370///     ventilation --> schedule_check[ScheduleCheck]
371///     schedule_check --> ventilation
372///     heating --> schedule_check
373///     schedule_check --> heating
374///     refill --> schedule_check
375///     schedule_check --> refill
376///     balling --> schedule_check
377///     schedule_check --> balling
378///     signal_handler --> schedule_check
379///     schedule_check --> signal_handler
380///     signal_handler --> messaging[Messaging]
381///     signal_handler --> sensor_manager
382///     signal_handler --> watchdog
383///     sensor_manager --> signal_handler
384///     signal_handler --> atlas_scientific
385///     atlas_scientific --> signal_handler
386///     atlas_scientific --> i2c_interface[I2C interface]
387///     i2c_interface --> atlas_scientific
388///     dht -.-> sensor_manager
389///     messaging --> refill
390///     messaging --> ventilation
391///     messaging --> heating
392///     messaging --> balling
393///     messaging --> feed
394///     messaging --> monitors
395///     messaging --> watchdog
396/// ```
397fn 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    // get command line parameters from OS
404    let args: Vec<String> = env::args().collect();
405
406    // initialize with a default configuration file name
407    let mut config_file_name = "/etc/aquarium_control/config/aquarium_control.toml".to_string();
408
409    // Check command line parameters
410    if args.len() > 1 {
411        // If the first argument contains a path and file name, then let's use that.
412        config_file_name = match check_argument_for_valid_config_file_name(&args[1]) {
413            Ok(valid_file_name) => valid_file_name, // The file is valid. Start operation.
414            Err(config_file_definition_error) => {
415                let mut user_arg_identified = false;
416                // Iterate through the command line parameters and check if they are valid.
417                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                    // If the user provided an invalid filename and no valid command, output error message and abort.
429                    Err(StartupError::Config(config_file_definition_error))
430                } else {
431                    // At least one valid user command was provided. Graceful exit.
432                    Ok(())
433                };
434            }
435        }
436    }
437    // read the configuration file for operating the control system
438    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 logging to write to the log output file and stdout
449    setup_logger(config.logger).map_err(|e| StartupError::LoggingSetupFailure { source: e })?;
450
451    // calculate hash of executable
452    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    // check if the user has root privileges - disabled when developing on the Windows platform
458    #[cfg(any(target_os = "linux", target_os = "macos"))]
459    if !is_running_as_root() {
460        return Err(StartupError::NotRoot);
461    }
462
463    // publish the PID to a file so that the client application can communicate with this application
464    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        // Custom panic handler: resets the PID to zero before entering in panic
471        // This allows later runs of the application without needing to manually
472        // clear the PID file.
473        // Print the panic message to stderr.
474        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        // Additionally, log an error if the PID could not be reset
485        if let Err(error_reset_pid) = result_reset_pid {
486            error!("Could not reset PID file: {error_reset_pid}");
487        }
488    }));
489
490    // check if the control parameters of heating and ventilation are valid
491    if !distinct_thermal_control_interval_check(&config.heating, &config.ventilation) {
492        return Err(StartupError::InvalidThermalConfig);
493    }
494
495    // instantiate the duration for the database ping
496    let database_ping_duration = Duration::from_secs(config.sql_interface.db_ping_interval);
497
498    // get values from config before moving the config
499    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    // instantiate driver for RGB LED
510    #[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    // connect to SQL database, subsequently set up component-specific interfaces
529    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    // check if any of the components requires use of the simulator
685    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    // The following structs have a high amount of platform-specific code:
708    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            // clone information from config before giving config to GpioHandler
718            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                // use the simulator-version for TankLevelSwitch
726                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                // Actual DHT sensor reading is not required in this case. Use a stub version.
733                dht = Dht::new(config.dht, None, gpio_dht22_io, gpio_dht22_vcc);
734            }
735            else if gpio_handler_opt.is_none() {
736                // defensive programming - this case should not occur
737                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    //**************************************** Mutexes *********************************************
772    let mutex_device_scheduler = Arc::new(Mutex::new(0));
773
774    // Mutexes for DS18B20 signals
775    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    // Mutexes for Atlas Scientific signals
787    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    // Mutexes for TankLevelSwitch
803    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    // Mutex for heating
811    let mutex_heating_status = Arc::new(Mutex::new(false));
812    let mutex_heating_status_clone_for_data_logger = mutex_heating_status.clone();
813
814    // Mutex for ventilation
815    let mutex_ventilation_status = Arc::new(Mutex::new(false));
816    let mutex_ventilation_status_clone_for_data_logger = mutex_ventilation_status.clone();
817
818    // Mutex for refill
819    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    // Mutex for Dht
827    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    // Mutex for Sensor manager
831    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    //**********************************************************************************************
838
839    #[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        // open tcp connection only if configuration requires use of simulator instead of HW
915        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        // give sensor reading components some time so that they can acquire a first data set
1048        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            // Refill needs actuation of relay
1097            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            // Heating needs actuation of relay
1117            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            // Ventilation needs actuation of relay
1145            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            // Monitors needs communication with the Controllino device
1164            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            // Feed needs actuation of relay
1177            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            // Balling dosing needs actuation of relays
1195            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        // IPC via messages
1212        scope.spawn(move || {
1213            // read/send messages
1214            #[cfg(target_os = "linux")]
1215            messaging.execute(&mut channels.messaging);
1216
1217            // remove PID from the file
1218            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            // Watchdog communication
1234            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            // Memory utilization monitoring
1245            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            // Ds18b20 communication
1256            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
1273/// The main function is a simple error handler.
1274/// The main entry point for the application is within the run() function.
1275fn main() {
1276    // setup preliminary logger
1277    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}