aquarium_control/launch/
startup_error.rs

1/* Copyright 2025 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//! Defines the single, top-level error type for all application startup failures.
11//!
12//! This module contains the `StartupError` enum, which aggregates every possible error
13//! that can occur during the application's initialization phase. By consolidating all
14//! potential failures into a single type, it provides a unified and robust error
15//! handling mechanism for the `main` function.
16//!
17//! ## Design and Purpose
18//!
19//! The `StartupError` enum is the root of the error handling tree for the entire
20//! application launch sequence. It is designed using the `thiserror` crate to provide
21//! several key benefits:
22//!
23//! - **Unified Error Type**: Functions involved in the startup process can return a
24//!   `Result<_, StartupError>`, simplifying the function signatures and error
25//!   propagation logic.
26//!
27//! - **Automatic Conversion**: Through `#[from]` and `#[error(transparent)]`, many
28//!   lower-level errors (like `ConfigError`) are automatically converted into a
29//!   `StartupError` variant, reducing boilerplate code.
30//!
31//! - **Rich Context and Source Chaining**: Most variants use `#[source]` to wrap the
32//!   original error. This preserves the full error chain, allowing logs to display not
33//!   just *that* a subsystem failed to initialize, but exactly *why* it failed,
34//!   including the original error from the underlying library.
35//!
36//! ## Error Categories
37//!
38//! The startup errors can be grouped into several logical categories:
39//!
40//! 1.  **Configuration and Environment Errors**: Failures related to loading or parsing
41//!     the configuration file, invalid command-line arguments, or environment checks
42//!     (e.g., `NotRoot`, `PidCheckError`).
43//!
44//! 2.  **Subsystem Initialization Failures**: The largest category, covering failures
45//!     when setting up core components like the logger, database connection, or IPC
46//!     messaging (`LoggingSetupFailure`, `Database`, `Messaging`).
47//!
48//! 3.  **Hardware Interface Failures**: Errors that occur when initializing communication
49//!     with physical hardware, such as Controllino, GPIO pins, I2C devices, or specific
50//!     sensors (`ControllinoSetupFailure`, `I2cSetupFailure`, `Ds18b20SetupFailure`).
51//!     These are often conditionally compiled based on the target hardware.
52
53#[cfg(all(feature = "target_hw", target_os = "linux"))]
54use crate::beacon::ws2812b::Ws2812BError;
55use crate::database::sql_interface_error::SqlInterfaceError;
56#[cfg(target_os = "linux")]
57use crate::dispatch::messaging_error::MessagingError;
58use crate::mineral::balling_error::BallingError;
59use crate::recorder::data_logger::DataLoggerError;
60use crate::relays::actuate_controllino::ActuateControllinoError;
61#[cfg(all(feature = "target_hw", target_os = "linux"))]
62use crate::relays::relay_error::RelayError;
63use crate::sensors::ds18b20_error::Ds18b20Error;
64#[cfg(all(feature = "target_hw", target_os = "linux"))]
65use crate::sensors::gpio_handler::GpioHandlerError;
66#[cfg(all(feature = "target_hw", target_os = "linux"))]
67use crate::sensors::i2c_error::I2cError;
68use crate::sensors::sensor_manager::SensorManagerError;
69use crate::sensors::tank_level_switch::TankLevelSwitchError;
70use crate::simulator::tcp_communication_error::TcpCommunicationError;
71use crate::utilities::config_error::ConfigError;
72use crate::utilities::config_file_definition_error::ConfigFileDefinitionError;
73use crate::utilities::logger::LoggerSetupError;
74use crate::utilities::publish_pid::PublishPidError;
75use crate::utilities::version_information::VersionInformationError;
76use crate::watchmen::memory_config_check::MemoryConfigError;
77use crate::watchmen::watchdog::WatchdogError;
78use thiserror::Error;
79
80/// Contains top-level error definitions for errors which may happen at startup.
81/// No additional parameter to identify the module is required here.
82#[derive(Error, Debug)]
83pub enum StartupError {
84    /// Invalid command-line arguments
85    #[error("Invalid command-line arguments: {0}")]
86    Config(#[from] ConfigFileDefinitionError),
87
88    /// The error message will come directly from ConfigError.
89    #[error(transparent)]
90    LoadConfig(#[from] ConfigError),
91
92    /// This application must be run as root.
93    #[error("This application must be run as root.")]
94    NotRoot,
95
96    /// Configuration of temperature ranges for heating and ventilation control is not valid.
97    #[error(
98        "Configuration error: temperature ranges for heating and ventilation control are conflicting each other."
99    )]
100    InvalidThermalConfig,
101
102    /// A database error occurred.
103    #[error("A database error occurred.")]
104    Database {
105        #[source]
106        source: Box<SqlInterfaceError>,
107    },
108
109    /// The Balling dosing configuration is invalid.
110    #[error("Configuration error: Balling dosing configuration is invalid.")]
111    InvalidBallingConfiguration { source: BallingError },
112
113    /// Could not open TCP connection to test server.
114    #[error("Could not open TCP connection to test server.")]
115    TcpConnection {
116        #[source]
117        source: TcpCommunicationError,
118    },
119
120    /// Could not open POSIX message queue.
121    #[cfg(target_os = "linux")]
122    #[error("Could not establish connection to messaging system.")]
123    Messaging {
124        #[source]
125        source: MessagingError,
126    },
127
128    /// Configuration error: GPIO handler not initialized.
129    #[allow(unused)]
130    #[error("Configuration error: GPIO handler not initialized.")]
131    GpioHandlerMissing,
132
133    /// Configuration error: Memory configuration of the operating system is invalid.
134    #[error("Configuration error: Memory configuration of operating system is invalid.")]
135    InvalidMemoryConfig {
136        #[source]
137        source: MemoryConfigError,
138    },
139
140    /// Could not retrieve version information.
141    #[error("Could not retrieve version information.")]
142    VersionInformationRetrievalFailure {
143        #[source]
144        source: VersionInformationError,
145    },
146
147    /// Could not initialize the logging system.
148    #[error("Could not setup logger.")]
149    LoggingSetupFailure {
150        #[source]
151        source: LoggerSetupError,
152    },
153
154    /// Could not initialize the data recording system.
155    #[error("Could not setup data logger.")]
156    DataLoggerSetupFailure {
157        #[source]
158        source: DataLoggerError,
159    },
160
161    /// Could not initialize communication with Controllino.
162    #[error("Could not setup communication with Controllino.")]
163    ControllinoSetupFailure {
164        #[source]
165        source: ActuateControllinoError,
166    },
167
168    /// Could not initialize the interface to Gpio.
169    #[cfg(all(feature = "target_hw", target_os = "linux"))]
170    #[error("Could not setup interface to Gpio.")]
171    ActuateGpioSetupFailure {
172        #[source]
173        source: RelayError,
174    },
175
176    /// Could not initialize the interface to the GPIO.
177    #[cfg(all(feature = "target_hw", target_os = "linux"))]
178    #[error("Could not setup the interface to the GPIO.")]
179    GpioHandlerSetupFailure {
180        #[source]
181        source: GpioHandlerError,
182    },
183
184    /// Could not initialize the interface to the I2c.
185    #[cfg(all(feature = "target_hw", target_os = "linux"))]
186    #[error("Could not setup the interface to the I2C.")]
187    I2cSetupFailure {
188        #[source]
189        source: I2cError,
190    },
191
192    /// Could not initialize the Ws2812B LED driver
193    #[cfg(all(feature = "target_hw", target_os = "linux"))]
194    #[error("Could not setup the Ws2812B LED driver.")]
195    Ws2812BSetupFailure {
196        #[source]
197        source: Ws2812BError,
198    },
199
200    /// Could not initialize the tank level switch measurement and calculation.
201    #[error("Could not initialize the tank level switch measurement and calculation.")]
202    TankLevelSwitchSetupFailure {
203        #[source]
204        source: TankLevelSwitchError,
205    },
206
207    /// Could not initialize the sensor manager.
208    #[error("Could not initialize the sensor manager.")]
209    SensorManagerSetupFailure {
210        #[source]
211        source: SensorManagerError,
212    },
213
214    /// Could not initialize the Ds18b20 sensor communication.
215    #[error("Could not initialize Ds18b20 sensor communication.")]
216    Ds18b20SetupFailure {
217        #[source]
218        source: Ds18b20Error,
219    },
220
221    /// Could not check for an already running version of the application.
222    #[error("Could not check for already running version of the application.")]
223    PidCheckError {
224        #[source]
225        source: PublishPidError,
226    },
227
228    /// Could not set up watchdog.
229    #[error("Could not setup watchdog.")]
230    WatchdogSetupFailure {
231        #[source]
232        source: WatchdogError,
233    },
234}