aquarium_control/dispatch/
messaging_domain.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 domains (subsystems) that can be controlled via Inter-Process Communication (IPC).
11//!
12//! This module serves as a dictionary for the application's controllable parts, such as
13//! `Refill`, `Heating`, and `Feed`. It provides both the raw integer constants used in the
14//! low-level POSIX message queue protocol and a type-safe Rust `enum` for use in the
15//! application's internal logic.
16//!
17//! This entire module is conditionally compiled and is only available on `target_os = "linux"`.
18//!
19//! ## Key Components
20//!
21//! - **`messaging_domains` Module**: A collection of `i32` constants that represent the
22//!   unique identifier for each domain in the raw byte message. These are the "on-the-wire"
23//!   values that form the external contract with command-line tools.
24//!
25//! - **`MessagingDomain` Enum**: A type-safe Rust representation of the domains. The main
26//!   `Messaging` thread translates the raw integer from an incoming message into one of
27//!   these variants before dispatching a command. This prevents logic errors related
28//!   to "magic numbers."
29//!
30//! ## Design and Purpose
31//!
32//! The separation between the raw constants and the type-safe enum is a deliberate design
33//! choice that enhances robustness.
34//!
35//! - **Protocol Stability**: The `messaging_domains` constants define a stable, language-agnostic
36//!   protocol. External C or Python scripts can use these same integer values
37//!   to communicate with the application.
38//!
39//! - **Type Safety**: By converting the raw integer to a `MessagingDomain` enum at the
40//!   earliest opportunity, the rest of the application can use pattern matching and
41//!   benefit from the compiler's checks, eliminating a whole class of potential bugs.
42//!
43//! - **Runtime Validation**: The `is_domain_thread_executed` method provides a way to
44//!   check if a target domain's thread is actually running before attempting to send
45//!   a message, preventing unnecessary channel errors for disabled features.
46//!
47//! ### Example Flow
48//!
49//! 1.  An external tool sends a message with the domain field set to `1`.
50//! 2.  The `Messaging` thread receives the message and reads the integer `1`.
51//! 3.  It matches `1` to `messaging_domains::REFILL`.
52//! 4.  It converts this into the `MessagingDomain::Refill` enum variant.
53//! 5.  It then uses this enum variant to route the command to the correct channel.
54
55#[cfg(target_os = "linux")]
56use crate::launch::execution_config::ExecutionConfig;
57#[cfg(target_os = "linux")]
58use std::fmt;
59
60#[derive(PartialEq, Debug, Clone)]
61/// Identifies the addressee of the message
62#[cfg(target_os = "linux")]
63#[repr(i32)]
64pub enum MessagingDomain {
65    /// refill control
66    Refill = 1,
67
68    /// ventilation control
69    Ventilation = 3,
70
71    /// feed control
72    Feed = 8,
73
74    /// heating control
75    Heating = 9,
76
77    /// Balling dosing control
78    Balling = 11,
79
80    /// monitors
81    Monitors = 12,
82
83    /// watchdog communication
84    Watchdog = 13,
85
86    /// in case the numeric value in the message could not be assigned to one of the above
87    Unknown = -1,
88}
89
90#[cfg(target_os = "linux")]
91impl From<i32> for MessagingDomain {
92    fn from(value: i32) -> Self {
93        match value {
94            x if x == MessagingDomain::Refill as i32 => MessagingDomain::Refill,
95            x if x == MessagingDomain::Ventilation as i32 => MessagingDomain::Ventilation,
96            x if x == MessagingDomain::Feed as i32 => MessagingDomain::Feed,
97            x if x == MessagingDomain::Heating as i32 => MessagingDomain::Heating,
98            x if x == MessagingDomain::Balling as i32 => MessagingDomain::Balling,
99            x if x == MessagingDomain::Monitors as i32 => MessagingDomain::Monitors,
100            x if x == MessagingDomain::Watchdog as i32 => MessagingDomain::Watchdog,
101            _ => MessagingDomain::Unknown,
102        }
103    }
104}
105
106#[cfg(all(target_os = "linux", test))]
107impl From<MessagingDomain> for i32 {
108    fn from(domain: MessagingDomain) -> Self {
109        match domain {
110            MessagingDomain::Refill => 1,
111            MessagingDomain::Ventilation => 3,
112            MessagingDomain::Feed => 8,
113            MessagingDomain::Heating => 9,
114            MessagingDomain::Balling => 11,
115            MessagingDomain::Monitors => 12,
116            MessagingDomain::Watchdog => 13,
117            MessagingDomain::Unknown => -1,
118        }
119    }
120}
121
122#[cfg(target_os = "linux")]
123impl MessagingDomain {
124    /// Checks if the thread corresponding to a specific messaging domain is active.
125    ///
126    /// This method consults the provided `ExecutionConfig` to determine if the thread
127    /// responsible for handling a given `MessagingDomain` was started at application launch.
128    /// This is useful for preventing attempts to send messages to threads that are not
129    /// running, which would otherwise result in channel errors.
130    ///
131    /// # Arguments
132    /// * `execution_config` - A reference to the application's runtime execution
133    ///   configuration, which holds the active status of each thread.
134    ///
135    /// # Returns
136    /// Returns `true` if the thread for the domain is configured as active, and `false`
137    /// if the thread is inactive or if the domain is `MessagingDomain::Unknown`.
138    pub fn is_domain_thread_executed(&self, execution_config: &ExecutionConfig) -> bool {
139        match self {
140            MessagingDomain::Refill => execution_config.refill,
141            MessagingDomain::Ventilation => execution_config.ventilation,
142            MessagingDomain::Feed => execution_config.feed,
143            MessagingDomain::Heating => execution_config.heating,
144            MessagingDomain::Balling => execution_config.balling,
145            MessagingDomain::Monitors => execution_config.monitors,
146            MessagingDomain::Watchdog => execution_config.watchdog,
147            MessagingDomain::Unknown => false,
148        }
149    }
150}
151
152#[cfg(target_os = "linux")]
153impl fmt::Display for MessagingDomain {
154    /// Formats the `MessagingDomain` enum into its human-readable string representation.
155    ///
156    /// This implementation allows `MessagingDomain` variants to be displayed directly
157    /// using macros like `println!` or `format!`, returning the name of the domain.
158    ///
159    /// # Arguments
160    /// * `f` - A mutable reference to the formatter, as required by the `fmt::Display` trait.
161    ///
162    /// # Returns
163    /// A `fmt::Result` indicating whether the formatting operation was successful.
164    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165        match self {
166            MessagingDomain::Refill => write!(f, "Refill"),
167            MessagingDomain::Ventilation => write!(f, "Ventilation"),
168            MessagingDomain::Feed => write!(f, "Feed"),
169            MessagingDomain::Heating => write!(f, "Heating"),
170            MessagingDomain::Balling => write!(f, "Balling"),
171            MessagingDomain::Monitors => write!(f, "Monitors"),
172            MessagingDomain::Watchdog => write!(f, "Watchdog"),
173            MessagingDomain::Unknown => write!(f, "Unknown"),
174        }
175    }
176}