aquarium_control/utilities/
channel_content.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#[cfg(target_os = "linux")]
11use crate::dispatch::messaging::Message;
12use std::fmt;
13
14/// Enum is used for internal thread messaging from control circuits to Controllino.
15/// These values are nested into enum InternalCommand. They represent all actuation devices.
16#[derive(Clone, Debug, PartialEq)]
17pub enum AquariumDevice {
18    // protein skimmer
19    Skimmer,
20
21    // main pump #1
22    MainPump1,
23
24    // main pump #2
25    MainPump2,
26
27    // auxiliary pump #1
28    AuxPump1,
29
30    // auxiliary pump #2
31    AuxPump2,
32
33    // heater
34    Heater,
35
36    // ventilation
37    Ventilation,
38
39    // refill pump
40    RefillPump,
41
42    // feeder motor
43    Feeder,
44
45    // Balling dosing pump #1
46    PeristalticPump1,
47
48    // Balling dosing pump #2
49    PeristalticPump2,
50
51    // Balling dosing pump #3
52    PeristalticPump3,
53
54    // Balling dosing pump #4
55    PeristalticPump4,
56}
57
58impl fmt::Display for AquariumDevice {
59    /// Formats the `AquariumDevice` enum into its human-readable string representation.
60    ///
61    /// This implementation enables `AquariumDevice` variants to be printed directly
62    /// using macros like `println!` or `format!`, returning the name of the device.
63    ///
64    /// # Arguments
65    /// * `f` - A mutable reference to the formatter, as required by the `fmt::Display` trait.
66    ///
67    /// # Returns
68    /// A `fmt::Result` indicating whether the formatting operation was successful.
69    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70        match self {
71            AquariumDevice::Skimmer => write!(f, "Skimmer"),
72            AquariumDevice::MainPump1 => write!(f, "MainPump1"),
73            AquariumDevice::MainPump2 => write!(f, "MainPump2"),
74            AquariumDevice::AuxPump1 => write!(f, "AuxPump1"),
75            AquariumDevice::AuxPump2 => write!(f, "AuxPump2"),
76            AquariumDevice::Heater => write!(f, "Heater"),
77            AquariumDevice::Ventilation => write!(f, "Ventilation"),
78            AquariumDevice::RefillPump => write!(f, "RefillPump"),
79            AquariumDevice::Feeder => write!(f, "Feeder"),
80            AquariumDevice::PeristalticPump1 => write!(f, "PeristalticPump1"),
81            AquariumDevice::PeristalticPump2 => write!(f, "PeristalticPump2"),
82            AquariumDevice::PeristalticPump3 => write!(f, "PeristalticPump3"),
83            AquariumDevice::PeristalticPump4 => write!(f, "PeristalticPump4"),
84        }
85    }
86}
87
88/// Enum is used for internal thread messaging between various threads
89/// The values indicate which information the thread shall return.
90#[derive(Clone, Debug, PartialEq)]
91pub enum AquariumSignal {
92    /// water temperature in °C
93    WaterTemperature,
94
95    #[allow(non_camel_case_types)]
96    /// pH Value
97    pH,
98
99    /// conductivity im uS/cm
100    Conductivity,
101
102    /// ambient (air) temperature in °C
103    AmbientTemperature,
104
105    /// ambient humidity in %
106    AmbientHumidity,
107
108    /// position of tank level switch
109    TankLevelSwitchPosition,
110
111    #[allow(unused)]
112    /// status bit of tank level switch signal
113    TankLevelSwitchInvalid,
114
115    #[allow(unused)]
116    /// stabilized position of tank level switch
117    TankLevelSwitchPositionStabilized,
118
119    #[allow(unused)]
120    /// heating control status
121    HeatingControlStatus,
122
123    #[allow(unused)]
124    /// ventilation control status
125    VentilationControlStatus,
126
127    #[allow(unused)]
128    /// refill pump status
129    RefillPumpStatus,
130
131    #[cfg(test)]
132    MockInvalidSignal,
133}
134
135impl AquariumSignal {
136    /// Formats a given floating-point signal value into a `String` based on the specific `AquariumSignal` type.
137    ///
138    /// This function applies specific formatting rules (e.g., number of decimal places)
139    /// for common aquarium sensor signals to ensure consistent output strings,
140    /// suitable for display or logging.
141    ///
142    /// # Arguments
143    /// * `signal` - The `f32` floating-point value of the signal to be formatted.
144    ///
145    /// # Returns
146    /// A `String` representation of the signal, formatted according to its `AquariumSignal` type.
147    /// For example, `WaterTemperature` is formatted to one decimal place, `pH` to two, etc.
148    pub fn output_file_string(&self, signal: f32) -> String {
149        match self {
150            AquariumSignal::WaterTemperature => format!("{signal:.1}"),
151            AquariumSignal::Conductivity => format!("{signal:.0}"),
152            AquariumSignal::pH => format!("{signal:.2}"),
153            AquariumSignal::AmbientTemperature => format!("{signal:.1}"),
154            AquariumSignal::AmbientHumidity => format!("{signal:.1}"),
155            #[cfg(all(target_os = "linux", feature = "target_hw", test))]
156            AquariumSignal::MockInvalidSignal => "MockInvalid".to_string(),
157            _ => signal.to_string(),
158        }
159    }
160
161    /// Defines the sequence of measurement used by AtlasScientific
162    #[allow(unused)] // used in conditionally compiled code
163    pub fn get_next_atlas_scientific_signal(&self) -> AquariumSignal {
164        match self {
165            AquariumSignal::WaterTemperature => AquariumSignal::pH,
166            AquariumSignal::pH => AquariumSignal::Conductivity,
167            AquariumSignal::Conductivity => AquariumSignal::WaterTemperature,
168            _ => AquariumSignal::WaterTemperature, // default value
169        }
170    }
171}
172
173impl fmt::Display for AquariumSignal {
174    /// Formats the `AquariumSignal` enum into its human-readable string representation.
175    ///
176    /// This implementation enables `AquariumSignal` variants to be printed directly
177    /// using macros like `println!` or `format!`, returning the descriptive name of the signal.
178    ///
179    /// # Arguments
180    /// * `f` - A mutable reference to the formatter, as required by the `fmt::Display` trait.
181    ///
182    /// # Returns
183    /// A `fmt::Result` indicating whether the formatting operation was successful.
184    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
185        match self {
186            AquariumSignal::WaterTemperature => write!(f, "WaterTemperature"),
187            AquariumSignal::pH => write!(f, "pH"),
188            AquariumSignal::Conductivity => write!(f, "Conductivity"),
189            AquariumSignal::AmbientTemperature => write!(f, "AmbientTemperature"),
190            AquariumSignal::AmbientHumidity => write!(f, "AmbientHumidity"),
191            AquariumSignal::TankLevelSwitchPosition => write!(f, "TankLevelSwitchPosition"),
192            AquariumSignal::TankLevelSwitchInvalid => write!(f, "TankLevelSwitchInvalid"),
193            AquariumSignal::TankLevelSwitchPositionStabilized => {
194                write!(f, "TankLevelSwitchPositionStabilized")
195            }
196            AquariumSignal::HeatingControlStatus => write!(f, "HeatingControlStatus"),
197            AquariumSignal::VentilationControlStatus => write!(f, "VentilationControlStatus"),
198            AquariumSignal::RefillPumpStatus => write!(f, "RefillPumpStatus"),
199
200            #[cfg(test)]
201            AquariumSignal::MockInvalidSignal => write!(f, "MockInvalidSignal"),
202        }
203    }
204}
205
206/// Enum is used for internal thread messaging.
207/// Note: Relay state and device state do not necessarily correlate.
208#[derive(Clone, Debug, PartialEq)]
209pub enum InternalCommand {
210    /// Switch on a device
211    SwitchOn(AquariumDevice),
212
213    /// Switch off a device
214    SwitchOff(AquariumDevice),
215
216    /// Pulse a device (switch on, pause, switch off)
217    #[allow(dead_code)]
218    Pulse(AquariumDevice, u16),
219
220    /// Set a relay
221    SetRelay(u16),
222
223    /// Unset a relay
224    UnsetRelay(u16),
225
226    /// Pulse a relay (switch on, pause, switch off)
227    #[allow(dead_code)]
228    PulseRelay(u16, u16),
229
230    /// Request a signal
231    RequestSignal(AquariumSignal),
232
233    #[allow(dead_code)]
234    /// Reset existing errors (used for refill control)
235    ResetAllErrors,
236
237    /// Quit the application - first step to terminate the application
238    Quit,
239
240    /// Terminate - second step to terminating the application
241    Terminate,
242
243    /// ScheduleCheck - request permission to run based on time of day
244    ScheduleCheck,
245
246    #[allow(dead_code)]
247    /// (Re-)start controller (Balling, Ventilation, Refill, Heating)
248    Start,
249
250    #[allow(dead_code)]
251    /// (Temporarily) stop controller (Balling, Ventilation, Refill, Heating)
252    Stop,
253
254    #[allow(dead_code)]
255    /// Execute a (feed) profile with a given ID
256    Execute(i32),
257
258    #[allow(dead_code)]
259    /// Default value used when unknown command is received via IPC-message
260    Unknown,
261}
262
263impl fmt::Display for InternalCommand {
264    /// Formats the `InternalCommand` enum into its human-readable string representation.
265    ///
266    /// This implementation allows `InternalCommand` variants to be displayed directly
267    /// using macros like `println!` or `format!`, returning the name of the command.
268    /// For variants that hold data (e.g., `SwitchOn(_)`), only the command name is displayed,
269    /// not the associated data.
270    ///
271    /// # Arguments
272    /// * `f` - A mutable reference to the formatter, as required by the `fmt::Display` trait.
273    ///
274    /// # Returns
275    /// A `fmt::Result` indicating whether the formatting operation was successful.
276    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
277        match self {
278            InternalCommand::SwitchOn(_) => write!(f, "SwitchOn"),
279            InternalCommand::SwitchOff(_) => write!(f, "SwitchOff"),
280            InternalCommand::Pulse(_, _) => write!(f, "Pulse"),
281            InternalCommand::SetRelay(_) => write!(f, "SetRelay"),
282            InternalCommand::UnsetRelay(_) => write!(f, "UnsetRelay"),
283            InternalCommand::PulseRelay(_, _) => write!(f, "PulseRelay"),
284            InternalCommand::RequestSignal(_) => write!(f, "RequestSignal"),
285            InternalCommand::ResetAllErrors => write!(f, "ResetAllErrors"),
286            InternalCommand::Quit => write!(f, "Quit"),
287            InternalCommand::Terminate => write!(f, "Terminate"),
288            InternalCommand::ScheduleCheck => write!(f, "ScheduleCheck"),
289            InternalCommand::Start => write!(f, "Start"),
290            InternalCommand::Stop => write!(f, "Stop"),
291            InternalCommand::Execute(_) => write!(f, "Execute"),
292            InternalCommand::Unknown => write!(f, "Unknown"),
293        }
294    }
295}
296
297#[cfg(target_os = "linux")]
298impl InternalCommand {
299    /// Converts contents of a `Message` into an `InternalCommand`.
300    pub fn from_msg(msg: &Message) -> Self {
301        match msg.command {
302            // Only convert variants that are implemented.
303            1 => InternalCommand::ResetAllErrors,
304            3 => InternalCommand::Stop,
305            4 => InternalCommand::Start,
306            10 => InternalCommand::Execute(msg.command_param1),
307            _ => InternalCommand::Unknown,
308        }
309    }
310
311    #[cfg(test)]
312    pub fn to_numeric(&self) -> i32 {
313        match self {
314            InternalCommand::ResetAllErrors => 1,
315            InternalCommand::Stop => 3,
316            InternalCommand::Start => 4,
317            InternalCommand::Execute(_id) => 10,
318            _ => -1,
319        }
320    }
321}
322
323/// Enum is used for representing the relay states.
324#[derive(PartialEq, Debug)]
325pub enum ActuatorState {
326    /// The actuator is on.
327    On,
328
329    /// The actuator is off.
330    Off,
331
332    /// The application has not yet sent a command to the actuator.
333    Undefined,
334}