esp32_wroom_rp/
tcp_client.rs

1//! Send/receive data to/from a TCP server and associated types.
2//!
3//! ## Usage
4//!
5//! ```no_run
6//! let hostname = "github.com";
7//! // let ip_address: IpAddress = [140, 82, 114, 3]; // github.com
8//!
9//! let port: Port = 80;
10//! let mode: TransportMode = TransportMode::Tcp;
11//! if let Err(e) = TcpClient::build(&mut wifi).connect(
12//!     hostname,
13//!     port,
14//!     mode,
15//!     &mut delay,
16//!     &mut |tcp_client| {
17//!         defmt::info!(
18//!             "TCP connection to {:?}:{:?} successful",
19//!             hostname,
20//!             port
21//!         );
22//!         defmt::info!("Hostname: {:?}", tcp_client.server_hostname());
23//!         defmt::info!("Sending HTTP Document: {:?}", http_document.as_str());
24//!         match tcp_client.send_data(&http_document) {
25//!             Ok(response) => {
26//!                 defmt::info!("Response: {:?}", response)
27//!             }
28//!             Err(e) => {
29//!                 defmt::error!("Response error: {:?}", e)
30//!             }
31//!         }
32//!     },
33//! ) {
34//!     defmt::error!(
35//!         "TCP connection to {:?}:{:?} failed: {:?}",
36//!         hostname,
37//!         port,
38//!         e
39//!     );
40//! }
41//! ```
42//!
43
44use embedded_hal::blocking::delay::DelayMs;
45use embedded_hal::blocking::spi::Transfer;
46
47use heapless::String;
48
49use super::gpio::EspControlInterface;
50use super::network::{
51    ConnectionState, Hostname, IpAddress, NetworkError, Port, Socket, TransportMode,
52};
53use super::protocol::{NinaProtocolHandler, ProtocolInterface};
54use super::wifi::Wifi;
55use super::{Error, ARRAY_LENGTH_PLACEHOLDER};
56
57const MAX_HOSTNAME_LENGTH: usize = 255;
58
59/// Allows for a [`TcpClient`] instance to connect to a remote server by providing
60/// either a [`Hostname`] or an [`IpAddress`]. This trait also makes it possible to
61/// implement and support IPv6 addresses.
62pub trait Connect<'a, S, B, C> {
63    /// Enable a client to connect to `server` on `port` using transport layer `mode`.
64    fn connect<F: FnMut(&mut TcpClient<'a, B, C>), D: DelayMs<u16>>(
65        &mut self,
66        server: S,
67        port: Port,
68        mode: TransportMode,
69        delay: &mut D,
70        f: &mut F,
71    ) -> Result<(), Error>;
72}
73
74/// A client type that connects to and performs send/receive operations with a remote
75/// server using the TCP protocol.
76pub struct TcpClient<'a, B, C> {
77    pub(crate) protocol_handler: &'a mut NinaProtocolHandler<B, C>,
78    pub(crate) socket: Option<Socket>,
79    pub(crate) server_ip_address: Option<IpAddress>,
80    pub(crate) port: Port,
81    pub(crate) mode: TransportMode,
82    pub(crate) server_hostname: Option<String<MAX_HOSTNAME_LENGTH>>,
83}
84
85impl<'a, B, C> Connect<'a, IpAddress, B, C> for TcpClient<'a, B, C>
86where
87    B: Transfer<u8>,
88    C: EspControlInterface,
89{
90    fn connect<F: FnMut(&mut TcpClient<'a, B, C>), D: DelayMs<u16>>(
91        &mut self,
92        ip: IpAddress,
93        port: Port,
94        mode: TransportMode,
95        delay: &mut D,
96        f: &mut F,
97    ) -> Result<(), Error> {
98        let socket = self.get_socket()?;
99        self.socket = Some(socket);
100        self.server_ip_address = Some(ip);
101        self.server_hostname = Some(String::new());
102        self.port = port;
103        self.mode = mode;
104
105        self.connect_common(delay, f)
106    }
107}
108
109impl<'a, B, C> Connect<'a, Hostname<'_>, B, C> for TcpClient<'a, B, C>
110where
111    B: Transfer<u8>,
112    C: EspControlInterface,
113{
114    fn connect<F: FnMut(&mut TcpClient<'a, B, C>), D: DelayMs<u16>>(
115        &mut self,
116        server_hostname: Hostname,
117        port: Port,
118        mode: TransportMode,
119        delay: &mut D,
120        f: &mut F,
121    ) -> Result<(), Error> {
122        let socket = self.get_socket()?;
123        self.socket = Some(socket);
124        self.server_hostname = Some(server_hostname.into()); // into() makes a copy of the &str slice
125        self.port = port;
126        self.mode = mode;
127
128        self.connect_common(delay, f)
129    }
130}
131
132impl<'a, B, C> TcpClient<'a, B, C>
133where
134    B: Transfer<u8>,
135    C: EspControlInterface,
136{
137    /// Build a new instance of a [`TcpClient`] provided a [`Wifi`] instance.
138    pub fn build(wifi: &'a mut Wifi<B, C>) -> Self {
139        Self {
140            protocol_handler: wifi.protocol_handler.get_mut(),
141            socket: None,
142            server_ip_address: None,
143            port: 0,
144            mode: TransportMode::Tcp,
145            server_hostname: Some(String::new()),
146        }
147    }
148
149    /// Get an [`IpAddress`] of the remote server to communicate with that is
150    /// set by calling [`TcpClient::connect`].
151    pub fn server_ip_address(&self) -> Option<IpAddress> {
152        self.server_ip_address
153    }
154
155    /// Get a [`Hostname`] of the remote server to communicate with that is
156    /// set by calling [`TcpClient::connect`].
157    pub fn server_hostname(&self) -> &str {
158        self.server_hostname.as_ref().unwrap().as_str()
159    }
160
161    /// Get a [`Port`] of the remote server to communicate with that is
162    /// set by calling [`TcpClient::connect`].
163    pub fn port(&self) -> Port {
164        self.port
165    }
166
167    /// Get a [`TransportMode`] used in communication with the remote server that is
168    /// set by calling [`TcpClient::connect`].
169    pub fn mode(&self) -> TransportMode {
170        self.mode
171    }
172
173    /// Request current `Socket` handle.
174    pub fn get_socket(&mut self) -> Result<Socket, Error> {
175        self.protocol_handler.get_socket()
176    }
177
178    /// Send a string slice of data to a connected server.
179    pub fn send_data(&mut self, data: &str) -> Result<[u8; ARRAY_LENGTH_PLACEHOLDER], Error> {
180        self.protocol_handler
181            .send_data(data, self.socket.unwrap_or_default())
182    }
183
184    // Provides the in-common connect() functionality used by the public interface's
185    // connect(ip_address) or connect(hostname) instances.
186    fn connect_common<F: FnMut(&mut TcpClient<'a, B, C>), D: DelayMs<u16>>(
187        &mut self,
188        delay: &mut D,
189        mut f: F,
190    ) -> Result<(), Error> {
191        let socket = self.socket.unwrap_or_default();
192        let mode = self.mode;
193        let mut ip = self.server_ip_address.unwrap_or_default();
194        let hostname = self.server_hostname.as_ref().unwrap();
195        let port = self.port;
196
197        if !hostname.is_empty() {
198            ip = self
199                .protocol_handler
200                .resolve(hostname.as_str())
201                .unwrap_or_default();
202        }
203
204        self.protocol_handler
205            .start_client_tcp(socket, ip, port, &mode)?;
206
207        // FIXME: without this delay, we'll frequently see timing issues and receive
208        // a CmdResponseErr. We may not be handling busy/ack flag handling properly
209        // and needs further investigation. I suspect that the ESP32 isn't ready to
210        // receive another command yet. (copied this from POC)
211        delay.delay_ms(250);
212
213        let mut retry_limit = 10_000;
214
215        while retry_limit > 0 {
216            match self.protocol_handler.get_client_state_tcp(socket) {
217                Ok(ConnectionState::Established) => {
218                    f(self);
219
220                    self.protocol_handler.stop_client_tcp(socket, &mode)?;
221
222                    return Ok(());
223                }
224                Ok(_status) => {
225                    delay.delay_ms(100);
226                    retry_limit -= 1;
227                }
228                Err(error) => {
229                    // At this point any error will likely be a protocol level error.
230                    // We do not currently consider any ConnectionState variants as errors.
231                    self.protocol_handler.stop_client_tcp(socket, &mode)?;
232
233                    return Err(error);
234                }
235            }
236        }
237
238        self.protocol_handler.stop_client_tcp(socket, &mode)?;
239
240        Err(NetworkError::ConnectionTimeout.into())
241    }
242}