aquarium_control/utilities/
proc_ext_req.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
10use log::{error, warn};
11
12#[cfg(not(test))]
13use log::info;
14
15use crate::food::{feed::Feed, food_injection::FoodInjection};
16use crate::recorder::data_logger::DataLogger;
17use crate::relays::relay_manager::RelayManager;
18use crate::utilities::channel_content::InternalCommand;
19
20#[cfg(target_os = "linux")]
21use crate::dispatch::messaging::Messaging;
22use crate::launch::channels::{AquaChannelError, AquaReceiver};
23use crate::mineral::{balling::Balling, mineral_injection::MineralInjection};
24use crate::permission::schedule_check::ScheduleCheck;
25use crate::sensors::dht::Dht;
26use crate::sensors::i2c_interface::I2cInterface;
27use crate::simulator::tcp_communication::TcpCommunication;
28use crate::watchmen::memory::Memory;
29use crate::watchmen::watchdog::Watchdog;
30use crate::water::{refill::Refill, water_injection::WaterInjection};
31
32/// This trait defines a common interface for processing external requests, such as
33/// commands received from a signal handler or a messaging system. It's designed
34/// to be implemented by various control modules in the system.
35///
36/// The default implementation provided here handles basic "Quit," "Start," and "Stop"
37/// commands, logging warnings for other inapplicable commands and errors for channel
38/// disconnection. This default is used by modules like heating control, ventilation control,
39/// and monitors.
40///
41/// More specialized modules, including Refill control, Data logger, Balling, Food injection,
42/// Water injection, Mineral injection, Schedule check, Controllino, Gpio handler, Feed,
43/// Messaging, and TCP communication, provide their own specific implementations of this trait.
44pub trait ProcessExternalRequestTrait {
45    /// Checks for and processes new commands received from the signal handler and an optional messaging channel.
46    ///
47    /// This function attempts to receive `InternalCommand` messages without blocking from two potential sources:
48    /// a required channel from the signal handler and an optional channel from a messaging system.
49    /// It specifically looks for `Quit` commands from the signal handler and `Start`/`Stop` commands
50    /// from the messaging channel, ignoring other commands and logging warnings for them.
51    /// Channel disconnection errors are also logged.
52    ///
53    /// # Arguments
54    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
55    ///   for commands originating from the signal handler.
56    /// * `rx_from_messaging_opt` - An `Option` containing a reference to the receiver end
57    ///   of the channel for commands from the messaging system. This channel is optional.
58    ///
59    /// # Returns
60    /// A tuple `(bool, bool, bool)` indicating the status of specific commands received:
61    /// - The first `bool` is `true` if a `Quit` command was received from the signal handler; otherwise `false`.
62    /// - The second `bool` is `true` if a `Start` command was received from messaging; otherwise `false`.
63    /// - The third `bool` is `true` if a `Stop` command was received from messaging; otherwise `false`.
64    fn process_external_request(
65        &mut self,
66        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
67        rx_from_messaging_opt: Option<&mut AquaReceiver<InternalCommand>>,
68    ) -> (bool, bool, bool) {
69        let quit_command_received = match rx_from_signal_handler.try_recv() {
70            Ok(c) => match c {
71                InternalCommand::Quit => true,
72                _ => {
73                    warn!(
74                        target: module_path!(),
75                        "ProcessExternalRequestTrait: ignoring inapplicable command from signal handler."
76                    );
77                    false
78                }
79            },
80            Err(e) => {
81                match e {
82                    #[cfg(feature = "debug_channels")]
83                    AquaChannelError::Full => { /* Not applicable. Do nothing */ }
84                    AquaChannelError::Empty => { /* empty buffer - no action required */ }
85                    AquaChannelError::Disconnected => {
86                        #[cfg(all(not(test), not(feature = "debug_signal_handler")))]
87                        error!(
88                            target: module_path!(),
89                            "receiving command from signal handler failed ({e:?})"
90                        );
91                    }
92                }
93                false
94            }
95        };
96
97        let (start_command_received, stop_command_received) = match rx_from_messaging_opt {
98            Some(rx_from_messaging) => {
99                match rx_from_messaging.try_recv() {
100                    Ok(c) => match c {
101                        InternalCommand::Start => (true, false),
102                        InternalCommand::Stop => (false, true),
103                        _ => {
104                            warn!(
105                                target: module_path!(),
106                                "ProcessExternalRequestTrait: ignoring inapplicable command from messaging."
107                            );
108                            (false, false)
109                        }
110                    },
111                    Err(e) => {
112                        match e {
113                            #[cfg(feature = "debug_channels")]
114                            AquaChannelError::Full => { /* Not applicable. Do nothing */ }
115                            AquaChannelError::Empty => { /* empty buffer - no action required */ }
116                            AquaChannelError::Disconnected => {
117                                #[cfg(not(test))]
118                                error!(
119                                    target: module_path!(),
120                                    "(standard) receiving command from messaging failed ({e:?})"
121                                );
122                            }
123                        }
124                        (false, false)
125                    }
126                }
127            }
128            None => (false, false),
129        };
130
131        (
132            quit_command_received,
133            start_command_received,
134            stop_command_received,
135        )
136    }
137}
138
139impl ProcessExternalRequestTrait for Refill {
140    /// Checks for and processes new commands relevant to the Refill control module from external channels.
141    ///
142    /// This is the specialized implementation of `ProcessExternalRequestTrait` for the `Refill` module.
143    /// In addition to handling standard "Quit", "Start", and "Stop" commands, it specifically
144    /// intercepts the `InternalCommand::ResetAllErrors` command from the messaging channel
145    /// to reset the refill control's internal error states.
146    ///
147    /// # Arguments
148    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
149    ///   for commands originating from the signal handler.
150    /// * `rx_from_messaging_opt` - An `Option` containing a reference to the receiver end
151    ///   of the channel for commands from the messaging system. This channel is optional.
152    ///
153    /// # Returns
154    /// A tuple `(bool, bool, bool)` indicating the status of specific commands received:
155    /// - The first `bool` is `true` if a `Quit` command was received from the signal handler; otherwise `false`.
156    /// - The second `bool` is `true` if a `Start` command was received from messaging; otherwise `false`.
157    /// - The third `bool` is `true` if a `Stop` command was received from messaging; otherwise `false`.
158    fn process_external_request(
159        &mut self,
160        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
161        rx_from_messaging_opt: Option<&mut AquaReceiver<InternalCommand>>,
162    ) -> (bool, bool, bool) {
163        let quit_command_received = match rx_from_signal_handler.try_recv() {
164            Ok(c) => match c {
165                InternalCommand::Quit => true,
166                _ => {
167                    warn!(
168                        target: module_path!(),
169                        "ignoring inapplicable command from signal handler."
170                    );
171                    false
172                }
173            },
174            Err(e) => {
175                match e {
176                    #[cfg(feature = "debug_channels")]
177                    AquaChannelError::Full => { /* Not applicable. Do nothing */ }
178                    AquaChannelError::Empty => { /* empty buffer - no action required */ }
179                    AquaChannelError::Disconnected => {
180                        #[cfg(not(test))]
181                        error!(
182                            target: module_path!(),
183                            "receiving command from signal handler failed ({e:?})"
184                        );
185                    }
186                }
187                false
188            }
189        };
190
191        let (start_command_received, stop_command_received) = match rx_from_messaging_opt {
192            Some(rx_from_messaging) => {
193                match rx_from_messaging.try_recv() {
194                    Ok(c) => match c {
195                        InternalCommand::Start => (true, false),
196                        InternalCommand::Stop => (false, true),
197                        InternalCommand::ResetAllErrors => {
198                            #[cfg(not(test))]
199                            info!(
200                                target: module_path!(),
201                                "Received reset command for errors of refill control."
202                            );
203                            self.refill_errors.reset_all_errors();
204                            (false, false)
205                        }
206                        _ => {
207                            warn!(
208                                target: module_path!(),
209                                "ignoring inapplicable command from messaging."
210                            );
211                            (false, false)
212                        }
213                    },
214                    Err(e) => {
215                        match e {
216                            #[cfg(feature = "debug_channels")]
217                            AquaChannelError::Full => { /* Not applicable. Do nothing */ }
218                            AquaChannelError::Empty => { /* empty buffer - no action required */ }
219                            AquaChannelError::Disconnected => {
220                                #[cfg(not(test))]
221                                error!(
222                                    target: module_path!(),
223                                    "(refill) receiving command from messaging failed ({e:?})"
224                                );
225                            }
226                        }
227                        (false, false)
228                    }
229                }
230            }
231            None => (false, false),
232        };
233
234        (
235            quit_command_received,
236            start_command_received,
237            stop_command_received,
238        )
239    }
240}
241
242/// Checks for new commands from the signal handler, specifically for modules that don't use messaging.
243///
244/// This associated function provides a streamlined way to process external requests
245/// for components that only listen to the signal handler channel. It attempts to
246/// receive without blocking `InternalCommand` messages and specifically looks for
247/// a `Quit` command.
248///
249/// # Arguments
250/// * `rx_from_signal_handler` - A reference to the receiver end of the channel
251///   for commands originating from the signal handler.
252///
253/// # Returns
254/// A tuple `(bool, bool, bool)` indicating the status of commands received:
255/// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
256/// - The second `bool` is always `false` as "Start" commands are not processed by this function.
257/// - The third `bool` is always `false` as "Stop" commands are not processed by this function.
258fn process_external_request_without_messaging(
259    rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
260    sender: String,
261) -> (bool, bool, bool) {
262    let quit_command_received = match rx_from_signal_handler.try_recv() {
263        Ok(c) => match c {
264            InternalCommand::Quit => true,
265            _ => {
266                warn!(
267                    target: module_path!(),
268                    "ignoring inapplicable command from signal handler."
269                );
270                false
271            }
272        },
273        Err(e) => {
274            match e {
275                #[cfg(feature = "debug_channels")]
276                AquaChannelError::Full => { /* Not applicable. Do nothing */ }
277                AquaChannelError::Empty => { /* empty buffer - no action required */ }
278                AquaChannelError::Disconnected => {
279                    error!(
280                        target: module_path!(),
281                        "({sender}) receiving command from signal handler failed ({e:?})"
282                    );
283                }
284            }
285            false
286        }
287    };
288
289    (quit_command_received, false, false)
290}
291
292impl ProcessExternalRequestTrait for DataLogger {
293    /// Checks for and processes new commands relevant to the Data Logger module from external channels.
294    ///
295    /// This is the specialized implementation of `ProcessExternalRequestTrait` for the `DataLogger`.
296    /// It delegates directly to `process_external_request_without_messaging`, indicating
297    /// that the `DataLogger` only processes commands from the signal handler and
298    /// ignores any input from a messaging channel.
299    ///
300    /// # Arguments
301    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
302    ///   for commands originating from the signal handler.
303    /// * `_` - This parameter is ignored, as the `DataLogger` does not process
304    ///   messages from a messaging channel.
305    ///
306    /// # Returns
307    /// A tuple `(bool, bool, bool)` indicating the status of commands received:
308    /// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
309    /// - The second `bool` is always `false` (no "Start" commands processed).
310    /// - The third `bool` is always `false` (no "Stop" commands processed).
311    fn process_external_request(
312        &mut self,
313        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
314        _: Option<&mut AquaReceiver<InternalCommand>>,
315    ) -> (bool, bool, bool) {
316        process_external_request_without_messaging(rx_from_signal_handler, "DataLogger".to_string())
317    }
318}
319
320impl ProcessExternalRequestTrait for Dht {
321    /// Checks for and processes new commands relevant to the Dht module from external channels.
322    ///
323    /// This is the specialized implementation of `ProcessExternalRequestTrait` for the `DataLogger`.
324    /// It delegates directly to `process_external_request_without_messaging`, indicating
325    /// that the `DataLogger` only processes commands from the signal handler and
326    /// ignores any input from a messaging channel.
327    ///
328    /// # Arguments
329    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
330    ///   for commands originating from the signal handler.
331    /// * `_` - This parameter is ignored, as the `DataLogger` does not process
332    ///   messages from a messaging channel.
333    ///
334    /// # Returns
335    /// A tuple `(bool, bool, bool)` indicating the status of commands received:
336    /// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
337    /// - The second `bool` is always `false` (no "Start" commands processed).
338    /// - The third `bool` is always `false` (no "Stop" commands processed).
339    fn process_external_request(
340        &mut self,
341        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
342        _: Option<&mut AquaReceiver<InternalCommand>>,
343    ) -> (bool, bool, bool) {
344        process_external_request_without_messaging(rx_from_signal_handler, "Dht".to_string())
345    }
346}
347
348impl ProcessExternalRequestTrait for FoodInjection {
349    /// Checks for and processes new commands relevant to the Food Injection module from external channels.
350    ///
351    /// This is the specialized implementation of `ProcessExternalRequestTrait` for `FoodInjection`.
352    /// It delegates directly to `process_external_request_without_messaging`, indicating
353    /// that the `FoodInjection` module only processes commands from the signal handler
354    /// and ignores any input from a messaging channel.
355    ///
356    /// # Arguments
357    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
358    ///   for commands originating from the signal handler.
359    /// * `_` - This parameter is ignored, as the `FoodInjection` module does not process
360    ///   messages from a messaging channel.
361    ///
362    /// # Returns
363    /// A tuple `(bool, bool, bool)` indicating the status of commands received:
364    /// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
365    /// - The second `bool` is always `false` (no "Start" commands processed).
366    /// - The third `bool` is always `false` (no "Stop" commands processed).
367    fn process_external_request(
368        &mut self,
369        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
370        _: Option<&mut AquaReceiver<InternalCommand>>,
371    ) -> (bool, bool, bool) {
372        process_external_request_without_messaging(
373            rx_from_signal_handler,
374            "FoodInjection".to_string(),
375        )
376    }
377}
378
379impl ProcessExternalRequestTrait for MineralInjection {
380    /// Checks for and processes new commands relevant to the Mineral Injection module from external channels.
381    ///
382    /// This is the specialized implementation of `ProcessExternalRequestTrait` for `MineralInjection`.
383    /// It delegates directly to `process_external_request_without_messaging`, indicating
384    /// that the `MineralInjection` module only processes commands from the signal handler
385    /// and ignores any input from a messaging channel.
386    ///
387    /// # Arguments
388    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
389    ///   for commands originating from the signal handler.
390    /// * `_` - This parameter is ignored, as the `MineralInjection` module does not process
391    ///   messages from a messaging channel.
392    ///
393    /// # Returns
394    /// A tuple `(bool, bool, bool)` indicating the status of commands received:
395    /// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
396    /// - The second `bool` is always `false` (no "Start" commands processed).
397    /// - The third `bool` is always `false` (no "Stop" commands processed).
398    fn process_external_request(
399        &mut self,
400        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
401        _: Option<&mut AquaReceiver<InternalCommand>>,
402    ) -> (bool, bool, bool) {
403        process_external_request_without_messaging(
404            rx_from_signal_handler,
405            "MineralInjection".to_string(),
406        )
407    }
408}
409
410impl ProcessExternalRequestTrait for WaterInjection {
411    /// Checks for and processes new commands relevant to the Water Injection module from external channels.
412    ///
413    /// This is the specialized implementation of `ProcessExternalRequestTrait` for `WaterInjection`.
414    /// It delegates directly to `process_external_request_without_messaging`, indicating
415    /// that the `WaterInjection` module only processes commands from the signal handler
416    /// and ignores any input from a messaging channel.
417    ///
418    /// # Arguments
419    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
420    ///   for commands originating from the signal handler.
421    /// * `_` - This parameter is ignored, as the `WaterInjection` module does not process
422    ///   messages from a messaging channel.
423    ///
424    /// # Returns
425    /// A tuple `(bool, bool, bool)` indicating the status of commands received:
426    /// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
427    /// - The second `bool` is always `false` (no "Start" commands processed).
428    /// - The third `bool` is always `false` (no "Stop" commands processed).
429    fn process_external_request(
430        &mut self,
431        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
432        _: Option<&mut AquaReceiver<InternalCommand>>,
433    ) -> (bool, bool, bool) {
434        process_external_request_without_messaging(
435            rx_from_signal_handler,
436            "WaterInjection".to_string(),
437        )
438    }
439}
440
441impl ProcessExternalRequestTrait for I2cInterface {
442    /// Checks for and processes new commands relevant to the I2cInterface module from external channels.
443    ///
444    /// This is the specialized implementation of `ProcessExternalRequestTrait` for `I2cInterface`.
445    /// It delegates directly to `process_external_request_without_messaging`, indicating
446    /// that the `I2cInterface` module only processes commands from the signal handler
447    /// and ignores any input from a messaging channel.
448    ///
449    /// # Arguments
450    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
451    ///   for commands originating from the signal handler.
452    /// * `_` - This parameter is ignored, as the `I2cInterface` module does not process
453    ///   messages from a messaging channel.
454    ///
455    /// # Returns
456    /// A tuple `(bool, bool, bool)` indicating the status of commands received:
457    /// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
458    /// - The second `bool` is always `false` (no "Start" commands processed).
459    /// - The third `bool` is always `false` (no "Stop" commands processed).
460    fn process_external_request(
461        &mut self,
462        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
463        _: Option<&mut AquaReceiver<InternalCommand>>,
464    ) -> (bool, bool, bool) {
465        process_external_request_without_messaging(
466            rx_from_signal_handler,
467            "I2cInterface".to_string(),
468        )
469    }
470}
471
472impl ProcessExternalRequestTrait for ScheduleCheck {
473    /// Checks for and processes new commands relevant to the Schedule Check module from external channels.
474    ///
475    /// This is the specialized implementation of `ProcessExternalRequestTrait` for `ScheduleCheck`.
476    /// It delegates directly to `process_external_request_without_messaging`, indicating
477    /// that the `ScheduleCheck` module only processes commands from the signal handler
478    /// and ignores any input from a messaging channel.
479    ///
480    /// # Arguments
481    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
482    ///   for commands originating from the signal handler.
483    /// * `_` - This parameter is ignored, as the `ScheduleCheck` module does not process
484    ///   messages from a messaging channel.
485    ///
486    /// # Returns
487    /// A tuple `(bool, bool, bool)` indicating the status of commands received:
488    /// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
489    /// - The second `bool` is always `false` (no "Start" commands processed).
490    /// - The third `bool` is always `false` (no "Stop" commands processed).
491    fn process_external_request(
492        &mut self,
493        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
494        _: Option<&mut AquaReceiver<InternalCommand>>,
495    ) -> (bool, bool, bool) {
496        process_external_request_without_messaging(
497            rx_from_signal_handler,
498            "ScheduleCheck".to_string(),
499        )
500    }
501}
502
503impl ProcessExternalRequestTrait for RelayManager {
504    /// Checks for and processes new commands relevant to the Relay Manager module from external channels.
505    ///
506    /// This is the specialized implementation of `ProcessExternalRequestTrait` for `RelayManager`.
507    /// It delegates directly to `process_external_request_without_messaging`, indicating
508    /// that the `RelayManager` (which often interacts with hardware like Controllino)
509    /// only processes commands from the signal handler and ignores any input from a messaging channel.
510    ///
511    /// # Arguments
512    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
513    ///   for commands originating from the signal handler.
514    /// * `_` - This parameter is ignored, as the `RelayManager` does not process
515    ///   messages from a messaging channel.
516    ///
517    /// # Returns
518    /// A tuple `(bool, bool, bool)` indicating the status of commands received:
519    /// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
520    /// - The second `bool` is always `false` (no "Start" commands processed).
521    /// - The third `bool` is always `false` (no "Stop" commands processed).
522    fn process_external_request(
523        &mut self,
524        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
525        _: Option<&mut AquaReceiver<InternalCommand>>,
526    ) -> (bool, bool, bool) {
527        process_external_request_without_messaging(
528            rx_from_signal_handler,
529            "RelayManager".to_string(),
530        )
531    }
532}
533
534#[cfg(target_os = "linux")]
535impl ProcessExternalRequestTrait for Messaging {
536    /// Checks for and processes new commands relevant to the Messaging module from external channels.
537    ///
538    /// This is the specialized implementation of `ProcessExternalRequestTrait` for `Messaging`.
539    /// It delegates directly to `process_external_request_without_messaging`, indicating
540    /// that the `Messaging` module only processes commands from the signal handler
541    /// and ignores any input from a separate messaging channel.
542    ///
543    /// # Arguments
544    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
545    ///   for commands originating from the signal handler.
546    /// * `_` - This parameter is ignored, as the `Messaging` module does not process
547    ///   messages from a secondary messaging channel.
548    ///
549    /// # Returns
550    /// A tuple `(bool, bool, bool)` indicating the status of commands received:
551    /// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
552    /// - The second `bool` is always `false` (no "Start" commands processed).
553    /// - The third `bool` is always `false` (no "Stop" commands processed).
554    ///
555    /// This code is specific for a Linux operating system because the messaging system
556    /// is only implemented for that platform.
557    fn process_external_request(
558        &mut self,
559        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
560        _: Option<&mut AquaReceiver<InternalCommand>>,
561    ) -> (bool, bool, bool) {
562        process_external_request_without_messaging(rx_from_signal_handler, "Messaging".to_string())
563    }
564}
565
566impl ProcessExternalRequestTrait for TcpCommunication {
567    /// Checks for and processes new commands relevant to the TCP Communication module from external channels.
568    ///
569    /// This is the specialized implementation of `ProcessExternalRequestTrait` for `TcpCommunication`.
570    /// It delegates directly to `process_external_request_without_messaging`, indicating
571    /// that the `TcpCommunication` module primarily processes commands from the signal handler
572    /// and does not use a separate messaging channel for external requests.
573    ///
574    /// # Arguments
575    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
576    ///   for commands originating from the signal handler.
577    /// * `_` - This parameter is ignored, as the `TcpCommunication` module does not process
578    ///   messages from a messaging channel in this context.
579    ///
580    /// # Returns
581    /// A tuple `(bool, bool, bool)` indicating the status of commands received:
582    /// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
583    /// - The second `bool` is always `false` (no "Start" commands processed).
584    /// - The third `bool` is always `false` (no "Stop" commands processed).
585    fn process_external_request(
586        &mut self,
587        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
588        _: Option<&mut AquaReceiver<InternalCommand>>,
589    ) -> (bool, bool, bool) {
590        process_external_request_without_messaging(
591            rx_from_signal_handler,
592            "TcpCommunication".to_string(),
593        )
594    }
595}
596
597/// Checks for specific standard commands (Terminate or Quit) from the signal handler channel.
598///
599/// This associated function is a streamlined helper for implementations that
600/// primarily need to react to global termination signals. It attempts
601/// to receive without blocking `InternalCommand` messages and returns `true` if either
602/// a `Terminate` or `Quit` command is found. Other commands are logged as warnings.
603///
604/// # Arguments
605/// * `rx_from_signal_handler` - A reference to the receiver end of the channel
606///   for commands originating from the signal handler.
607///
608/// # Returns
609/// `true` if either a `Terminate` or `Quit` command was received; otherwise `false`.
610fn process_standard_request(rx_from_signal_handler: &mut AquaReceiver<InternalCommand>) -> bool {
611    match rx_from_signal_handler.try_recv() {
612        Ok(c) => match c {
613            InternalCommand::Terminate => true,
614            InternalCommand::Quit => true,
615            _ => {
616                warn!(
617                    target: module_path!(),
618                    "ignoring inapplicable command {c} from signal handler."
619                );
620                false
621            }
622        },
623        Err(e) => {
624            match e {
625                #[cfg(feature = "debug_channels")]
626                AquaChannelError::Full => { /* Not applicable. Do nothing */ }
627                AquaChannelError::Empty => { /* empty buffer - no action required */ }
628                AquaChannelError::Disconnected => {
629                    #[cfg(not(test))]
630                    error!(
631                        target: module_path!(),
632                        "receiving command from signal handler failed ({e:?})"
633                    );
634                }
635            }
636            false
637        }
638    }
639}
640
641impl ProcessExternalRequestTrait for Feed {
642    /// Checks for and processes new commands relevant to the Feed control module from external channels.
643    ///
644    /// This is the specialized implementation of `ProcessExternalRequestTrait` for the `Feed` module.
645    /// It handles standard "Terminate" or "Quit" commands from the signal handler.
646    /// From the messaging channel, it specifically processes "Start," "Stop," "ResetAllErrors,"
647    /// and "Execute" commands, updating internal state variables based on these commands.
648    /// Inapplicable commands are ignored with warnings.
649    ///
650    /// # Arguments
651    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
652    ///   for commands originating from the signal handler.
653    /// * `rx_from_messaging_opt` - An `Option` containing a reference to the receiver end
654    ///   of the channel for commands from the messaging system. This channel is optional.
655    ///
656    /// # Returns
657    /// A tuple `(bool, bool, bool)` indicating the status of specific commands received:
658    /// - The first `bool` is `true` if a `Quit` or `Terminate` command was received from the signal handler; otherwise `false`.
659    /// - The second `bool` is `true` if a `Start` command was received from messaging; otherwise `false`.
660    /// - The third `bool` is `true` if a `Stop` command was received from messaging; otherwise `false`.
661    fn process_external_request(
662        &mut self,
663        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
664        rx_from_messaging_opt: Option<&mut AquaReceiver<InternalCommand>>,
665    ) -> (bool, bool, bool) {
666        let quit_command_received = process_standard_request(rx_from_signal_handler);
667
668        self.execute_command_received = false;
669        self.profile_id_requested = -1;
670        let (start_command_received, stop_command_received, execute_command_received, profile_id) =
671            match rx_from_messaging_opt {
672                Some(rx_from_messaging) => {
673                    match rx_from_messaging.try_recv() {
674                        Ok(c) => match c {
675                            InternalCommand::Start => (true, false, false, 0),
676                            InternalCommand::Stop => (false, true, false, 0),
677                            InternalCommand::ResetAllErrors => {
678                                #[cfg(not(test))]
679                                info!(
680                                    target: module_path!(),
681                                    "Received reset command for errors of feed control."
682                                );
683                                self.execution_blocked = false;
684                                (false, false, false, 0)
685                            }
686                            InternalCommand::Execute(c) => (false, false, true, c),
687                            _ => {
688                                warn!(
689                                    target: module_path!(),
690                                    "ignoring inapplicable command {c} from messaging."
691                                );
692                                (false, false, false, 0)
693                            }
694                        },
695                        Err(e) => {
696                            match e {
697                                #[cfg(feature = "debug_channels")]
698                                AquaChannelError::Full => { /* Not applicable. Do nothing */ }
699                                AquaChannelError::Empty => { /* empty buffer - no action required */
700                                }
701                                AquaChannelError::Disconnected => {
702                                    #[cfg(not(test))]
703                                    error!(
704                                        target: module_path!(),
705                                        "(feed) - receiving command from messaging failed ({e:?})"
706                                    );
707                                }
708                            }
709                            (false, false, false, 0)
710                        }
711                    }
712                }
713                None => (false, false, false, 0),
714            };
715
716        self.execute_command_received = execute_command_received;
717        self.profile_id_requested = profile_id;
718
719        (
720            quit_command_received,
721            start_command_received,
722            stop_command_received,
723        )
724    }
725}
726
727impl ProcessExternalRequestTrait for Balling {
728    /// Checks for and processes new commands relevant to the Balling control module from external channels.
729    ///
730    /// This is the specialized implementation of `ProcessExternalRequestTrait` for the `Balling` module.
731    /// It handles standard "Terminate" or "Quit" commands from the signal handler.
732    /// From the messaging channel, it specifically processes "Start," "Stop," and "Execute" commands,
733    /// updating internal state variables (`execute_command_received`, `pump_id_requested`) based on these commands.
734    /// Inapplicable commands are ignored with warnings.
735    ///
736    /// # Arguments
737    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
738    ///   for commands originating from the signal handler.
739    /// * `rx_from_messaging_opt` - An `Option` containing a reference to the receiver end
740    ///   of the channel for commands from the messaging system. This channel is optional.
741    ///
742    /// # Returns
743    /// A tuple `(bool, bool, bool)` indicating the status of specific commands received:
744    /// - The first `bool` is `true` if a `Quit` or `Terminate` command was received from the signal handler; otherwise `false`.
745    /// - The second `bool` is `true` if a `Start` command was received from messaging; otherwise `false`.
746    /// - The third `bool` is `true` if a `Stop` command was received from messaging; otherwise `false`.
747    fn process_external_request(
748        &mut self,
749        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
750        rx_from_messaging_opt: Option<&mut AquaReceiver<InternalCommand>>,
751    ) -> (bool, bool, bool) {
752        let quit_command_received = process_standard_request(rx_from_signal_handler);
753
754        self.execute_command_received = false;
755        self.pump_id_requested = -1;
756        let (start_command_received, stop_command_received, execute_command_received, pump_id) =
757            match rx_from_messaging_opt {
758                Some(rx_from_messaging) => {
759                    match rx_from_messaging.try_recv() {
760                        Ok(c) => match c {
761                            InternalCommand::Start => (true, false, false, 0),
762                            InternalCommand::Stop => (false, true, false, 0),
763                            InternalCommand::Execute(c) => (false, false, true, c),
764                            _ => {
765                                warn!(
766                                    target: module_path!(),
767                                    "ignoring inapplicable command {c} from messaging."
768                                );
769                                (false, false, false, 0)
770                            }
771                        },
772                        Err(e) => {
773                            match e {
774                                #[cfg(feature = "debug_channels")]
775                                AquaChannelError::Full => { /* Not applicable. Do nothing */ }
776                                AquaChannelError::Empty => { /* empty buffer - no action required */
777                                }
778                                AquaChannelError::Disconnected => {
779                                    #[cfg(not(test))]
780                                    error!(
781                                        target: module_path!(),
782                                        "(balling) receiving command from messaging failed ({e:?})"
783                                    );
784                                }
785                            }
786                            (false, false, false, 0)
787                        }
788                    }
789                }
790                None => (false, false, false, 0),
791            };
792
793        self.execute_command_received = execute_command_received;
794        self.pump_id_requested = pump_id;
795
796        (
797            quit_command_received,
798            start_command_received,
799            stop_command_received,
800        )
801    }
802}
803
804impl ProcessExternalRequestTrait for Watchdog {
805    /// Checks for and processes new commands relevant to the Watchdog module from external channels.
806    ///
807    /// This is the specialized implementation of `ProcessExternalRequestTrait` for the `Watchdog`.
808    /// It handles standard "Terminate" or "Quit" commands from the signal handler.
809    /// From the messaging channel, it specifically processes "Start" and "Stop" commands,
810    /// ignoring other inapplicable commands with warnings.
811    ///
812    /// # Arguments
813    /// * `rx_from_signal_handler` - A reference to the receiver end of the channel
814    ///   for commands originating from the signal handler.
815    /// * `rx_from_messaging_opt` - An `Option` containing a reference to the receiver end
816    ///   of the channel for commands from the messaging system. This channel is optional.
817    ///
818    /// # Returns
819    /// A tuple `(bool, bool, bool)` indicating the status of specific commands received:
820    /// - The first `bool` is `true` if a `Quit` or `Terminate` command was received from the signal handler; otherwise `false`.
821    /// - The second `bool` is `true` if a `Start` command was received from messaging; otherwise `false`.
822    /// - The third `bool` is `true` if a `Stop` command was received from messaging; otherwise `false`.
823    fn process_external_request(
824        &mut self,
825        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
826        rx_from_messaging_opt: Option<&mut AquaReceiver<InternalCommand>>,
827    ) -> (bool, bool, bool) {
828        let quit_command_received = process_standard_request(rx_from_signal_handler);
829
830        let (start_command_received, stop_command_received) = match rx_from_messaging_opt {
831            Some(rx_from_messaging) => {
832                match rx_from_messaging.try_recv() {
833                    Ok(c) => match c {
834                        InternalCommand::Start => (true, false),
835                        InternalCommand::Stop => (false, true),
836                        _ => {
837                            warn!(
838                                target: module_path!(),
839                                "ignoring inapplicable command {c} from messaging."
840                            );
841                            (false, false)
842                        }
843                    },
844                    Err(e) => {
845                        match e {
846                            #[cfg(feature = "debug_channels")]
847                            AquaChannelError::Full => { /* Not applicable. Do nothing */ }
848                            AquaChannelError::Empty => { /* empty buffer - no action required */ }
849                            AquaChannelError::Disconnected => {
850                                #[cfg(not(test))]
851                                error!(
852                                    target: module_path!(),
853                                    "(watchdog) receiving command from messaging failed ({e:?})"
854                                );
855                            }
856                        }
857                        (false, false)
858                    }
859                }
860            }
861            None => (false, false),
862        };
863
864        (
865            quit_command_received,
866            start_command_received,
867            stop_command_received,
868        )
869    }
870}
871
872impl ProcessExternalRequestTrait for Memory {
873    /// Checks for and processes new commands relevant to the Memory module from external channels.
874    ///
875    /// This is the specialized implementation of `ProcessExternalRequestTrait` for `TcpCommunication`.
876    /// It delegates directly to `process_external_request_without_messaging`, indicating
877    /// that the `Memory` module primarily processes commands from the signal handler
878    /// and does not use a separate messaging channel for external requests.
879    ///
880    /// # Arguments
881    /// * `rx_from_memory` - A reference to the receiver end of the channel
882    ///   for commands originating from the signal handler.
883    /// * `_` - This parameter is ignored, as the `Memory` module does not process
884    ///   messages from a messaging channel in this context.
885    ///
886    /// # Returns
887    /// A tuple `(bool, bool, bool)` indicating the status of commands received:
888    /// - The first `bool` is `true` if a `Quit` command was received; otherwise `false`.
889    /// - The second `bool` is always `false` (no "Start" commands processed).
890    /// - The third `bool` is always `false` (no "Stop" commands processed).
891    fn process_external_request(
892        &mut self,
893        rx_from_signal_handler: &mut AquaReceiver<InternalCommand>,
894        _: Option<&mut AquaReceiver<InternalCommand>>,
895    ) -> (bool, bool, bool) {
896        process_external_request_without_messaging(rx_from_signal_handler, "Memory".to_string())
897    }
898}