aquarium_control/relays/actuate_simulator.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::warn;
11
12use crate::relays::actuate_controllino::get_relay_command;
13use crate::relays::actuate_controllino_config::ActuateControllinoConfig;
14use crate::relays::actuate_simulator_channels::ActuateSimulatorChannels;
15use crate::relays::relay_error::RelayError;
16use crate::relays::relay_manager::RelayActuationTrait;
17use crate::utilities::channel_content::InternalCommand;
18
19/// Contains trait implementation for system testing purposes.
20pub struct ActuateSimulator<'a> {
21 config: ActuateControllinoConfig,
22 actuate_simulator_channels: &'a mut ActuateSimulatorChannels,
23}
24
25impl<'a> RelayActuationTrait for ActuateSimulator<'a> {
26 /// Simulates relay actuation by sending a command over a channel, typically for testing.
27 ///
28 /// This function translates a high-level `InternalCommand` (like `SwitchOn` or `SwitchOff`)
29 /// into a low-level relay command (e.g., `SetRelay`) using the same logic as the
30 /// physical Controllino actuator. The resulting command is then sent over an `mpsc`
31 /// channel, presumably to a test harness or a TCP listener that simulates the hardware.
32 ///
33 /// This implementation is intended **solely for development and system testing**
34 /// and does not interact with physical hardware.
35 ///
36 /// # Arguments
37 /// * `internal_command` - The `InternalCommand` specifying the `AquariumDevice` and the
38 /// desired state (`SwitchOn` or `SwitchOff`).
39 ///
40 /// # Returns
41 /// An empty `Result` (`Ok(())`) if the translated command was successfully sent
42 /// via the channel.
43 ///
44 /// # Errors
45 /// This function will return a `RelayError` if any part of the simulation fails:
46 /// - `RelayError::SimulatorNotInitialized`: If the simulator was created without a
47 /// valid sender channel (`tx_to_tcp_opt` was `None`).
48 /// - `RelayError::IrrelevantCommand`: If the provided `internal_command` is not one that
49 /// can be translated into a relay action (e.g., `RequestSignal`).
50 /// - `RelayError::PulseNotAllowedForDevice`: If a `Pulse` command is attempted for a
51 /// device that doesn't support it (propagated from `get_relay_command`).
52 /// - `RelayError::SimulatorSendFailed`: If sending the command over the `mpsc`
53 /// channel fails, which typically means the receiver has been dropped.
54 fn actuate(&mut self, internal_command: &InternalCommand) -> Result<(), RelayError> {
55 let tx_actuate_simulator_to_tcp = self
56 .actuate_simulator_channels
57 .tx_actuate_simulator_to_tcp_opt
58 .as_mut()
59 .unwrap();
60 match internal_command {
61 InternalCommand::SwitchOn(ref c) | InternalCommand::SwitchOff(ref c) => {
62 let relay_command = match get_relay_command(
63 internal_command,
64 c.clone(),
65 &self.config,
66 ) {
67 Ok(c) => c,
68 Err(e) => {
69 panic!("Controllino: Error in preparation of communication to simulator: {e:?}");
70 }
71 };
72 match tx_actuate_simulator_to_tcp.send(relay_command.clone()) {
73 Ok(()) => Ok(()),
74 Err(e) => {
75 panic!(
76 "Controllino: error when sending relay command {relay_command} to TCP thread: {e:?}"
77 );
78 }
79 }
80 }
81 _ => {
82 warn!(
83 target: module_path!(),
84 "ignoring irrelevant internal command {internal_command}."
85 );
86 Err(RelayError::IrrelevantCommand(
87 module_path!().to_string(),
88 internal_command.clone(),
89 ))
90 }
91 }
92 }
93
94 /// Returns the heartbeat interval, which is not applicable for the simulator.
95 ///
96 /// The simulator does not require a keep-alive signal, so this implementation
97 /// always returns `None`.
98 ///
99 /// # Returns
100 /// Always returns `None`.
101 fn get_heartbeat_interval_seconds(&self) -> Option<u64> {
102 None
103 }
104
105 /// Performs a heartbeat action, which is not applicable for the simulator.
106 ///
107 /// This is a no-op for the simulator implementation as there is no persistent
108 /// communication channel that needs to be kept alive.
109 ///
110 /// # Returns
111 /// Always returns `Ok(())`.
112 ///
113 /// # Errors
114 /// This function will never return an error.
115 fn heartbeat(&mut self) -> Result<(), RelayError> {
116 // do nothing
117 Ok(())
118 }
119
120 /// Flushes a communication buffer, which is not applicable for the simulator.
121 ///
122 /// This is a no-op for the simulator implementation as there is no serial
123 /// communication buffer to flush.
124 ///
125 /// # Returns
126 /// Always returns `Ok(())`.
127 ///
128 /// # Errors
129 /// This function will never return an error.
130 fn flush_buffer(&mut self) -> Result<(), RelayError> {
131 // do nothing
132 Ok(())
133 }
134}
135
136impl<'a> ActuateSimulator<'a> {
137 /// Creates a new `ActuateSimulator` instance.
138 ///
139 /// This constructor initializes the simulator with an optional sender channel
140 /// for TCP communication and the Controllino-specific configuration.
141 /// It's used to set up the mock actuation logic for testing purposes.
142 ///
143 /// # Arguments
144 /// * `actuate_simulator_channels` - A mutable reference to the struct containing the channel.
145 /// * `config` - The `ActuateControllinoConfig` struct, providing device-to-relay mappings
146 /// and other configuration relevant when simulating Controllino behavior.
147 ///
148 /// # Returns
149 /// A new `ActuateSimulator` struct.
150 pub fn new(
151 actuate_simulator_channels: &'a mut ActuateSimulatorChannels,
152 config: ActuateControllinoConfig,
153 ) -> Self {
154 Self {
155 actuate_simulator_channels,
156 config,
157 }
158 }
159}