rust_ethernet_ip/
plc_manager.rs1use crate::error::{EtherNetIpError, Result};
2use crate::EipClient;
3use std::collections::HashMap;
4use std::net::SocketAddr;
5use std::time::{Duration, Instant};
6
7#[derive(Debug, Clone)]
9pub struct PlcConfig {
10 pub address: SocketAddr,
12 pub max_connections: u32,
14 pub connection_timeout: Duration,
16 pub health_check_interval: Duration,
18 pub max_packet_size: usize,
20}
21
22impl Default for PlcConfig {
23 fn default() -> Self {
24 Self {
25 address: "127.0.0.1:44818".parse().unwrap(),
26 max_connections: 5,
27 connection_timeout: Duration::from_secs(5),
28 health_check_interval: Duration::from_secs(30),
29 max_packet_size: 4000,
30 }
31 }
32}
33
34#[derive(Debug, Clone)]
36pub struct ConnectionHealth {
37 pub is_active: bool,
39 pub last_success: Instant,
41 pub failed_attempts: u32,
43 pub latency: Duration,
45}
46
47#[derive(Debug)]
49pub struct PlcConnection {
50 client: EipClient,
52 health: ConnectionHealth,
54 last_used: Instant,
56}
57
58impl PlcConnection {
59 pub fn new(client: EipClient) -> Self {
61 Self {
62 client,
63 health: ConnectionHealth {
64 is_active: true,
65 last_success: Instant::now(),
66 failed_attempts: 0,
67 latency: Duration::from_millis(0),
68 },
69 last_used: Instant::now(),
70 }
71 }
72
73 #[allow(dead_code)]
75 pub fn update_health(&mut self, is_active: bool, latency: Duration) {
76 self.health.is_active = is_active;
77 if is_active {
78 self.health.last_success = Instant::now();
79 self.health.failed_attempts = 0;
80 self.health.latency = latency;
81 } else {
82 self.health.failed_attempts += 1;
83 }
84 }
85}
86
87#[derive(Debug)]
89pub struct PlcManager {
90 configs: HashMap<SocketAddr, PlcConfig>,
92 connections: HashMap<SocketAddr, Vec<PlcConnection>>,
94 #[allow(dead_code)]
96 health_check_interval: Duration,
97}
98
99impl Default for PlcManager {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105impl PlcManager {
106 pub fn new() -> Self {
108 Self {
109 configs: HashMap::new(),
110 connections: HashMap::new(),
111 health_check_interval: Duration::from_secs(30),
112 }
113 }
114
115 pub fn add_plc(&mut self, config: PlcConfig) {
117 self.configs.insert(config.address, config);
118 }
119
120 pub async fn get_connection(&mut self, address: SocketAddr) -> Result<&mut EipClient> {
122 let config = self
123 .configs
124 .get(&address)
125 .ok_or_else(|| EtherNetIpError::Connection("PLC not configured".to_string()))?;
126
127 if !self.connections.contains_key(&address) {
129 let mut client = EipClient::new(&address.to_string()).await?;
131 client.set_max_packet_size(config.max_packet_size as u32);
132 let mut new_conn = PlcConnection::new(client);
133 new_conn.last_used = Instant::now();
134 self.connections.insert(address, vec![new_conn]);
135 return Ok(&mut self.connections.get_mut(&address).unwrap()[0].client);
136 }
137
138 let connections = self.connections.get_mut(&address).unwrap();
140
141 for i in 0..connections.len() {
143 if !connections[i].health.is_active {
144 let mut client = EipClient::new(&address.to_string()).await?;
145 client.set_max_packet_size(config.max_packet_size as u32);
146 connections[i].client = client;
147 connections[i].health.is_active = true;
148 connections[i].health.last_success = Instant::now();
149 connections[i].health.failed_attempts = 0;
150 connections[i].health.latency = Duration::from_millis(0);
151 connections[i].last_used = Instant::now();
152 return Ok(&mut connections[i].client);
153 }
154 }
155
156 if connections.len() < config.max_connections as usize {
158 let mut client = EipClient::new(&address.to_string()).await?;
159 client.set_max_packet_size(config.max_packet_size as u32);
160 let mut new_conn = PlcConnection::new(client);
161 new_conn.last_used = Instant::now();
162 connections.push(new_conn);
163 return Ok(&mut connections.last_mut().unwrap().client);
164 }
165
166 let lru_index = connections
168 .iter()
169 .enumerate()
170 .min_by_key(|(_, conn)| conn.last_used)
171 .map(|(i, _)| i)
172 .unwrap();
173
174 let mut client = EipClient::new(&address.to_string()).await?;
176 client.set_max_packet_size(config.max_packet_size as u32);
177 connections[lru_index].client = client;
178 connections[lru_index].health.is_active = true;
179 connections[lru_index].health.last_success = Instant::now();
180 connections[lru_index].health.failed_attempts = 0;
181 connections[lru_index].health.latency = Duration::from_millis(0);
182 connections[lru_index].last_used = Instant::now();
183 Ok(&mut connections[lru_index].client)
184 }
185
186 pub async fn check_health(&mut self) {
188 for (address, connections) in &mut self.connections {
189 let _config = self.configs.get(address).unwrap();
190
191 for conn in connections.iter_mut() {
192 if !conn.health.is_active {
193 if let Ok(new_client) = EipClient::new(&address.to_string()).await {
194 conn.client = new_client;
195 conn.health.is_active = true;
196 conn.health.last_success = Instant::now();
197 conn.health.failed_attempts = 0;
198 conn.health.latency = Duration::from_millis(0);
199 conn.last_used = Instant::now();
200 }
201 }
202 }
203 }
204 }
205
206 pub fn cleanup_connections(&mut self) {
208 for connections in self.connections.values_mut() {
209 connections.retain(|conn| conn.health.is_active);
210 }
211 }
212
213 pub async fn get_client(&mut self, address: &str) -> Result<&mut EipClient> {
214 let addr = address
215 .parse::<SocketAddr>()
216 .map_err(|_| EtherNetIpError::Connection("Invalid address format".to_string()))?;
217 self.get_connection(addr).await
218 }
219
220 async fn _get_or_create_connection(&mut self, address: &SocketAddr) -> Result<&mut EipClient> {
221 let config = self.configs.get(address).cloned().unwrap();
222 let connections = self.connections.entry(*address).or_default();
223
224 for i in 0..connections.len() {
226 if !connections[i].health.is_active {
227 let mut client = EipClient::new(&address.to_string()).await?;
228 client.set_max_packet_size(config.max_packet_size as u32);
229 connections[i].client = client;
230 connections[i].health.is_active = true;
231 connections[i].health.last_success = Instant::now();
232 connections[i].health.failed_attempts = 0;
233 connections[i].health.latency = Duration::from_millis(0);
234 connections[i].last_used = Instant::now();
235 return Ok(&mut connections[i].client);
236 }
237 }
238
239 if connections.len() < config.max_connections as usize {
241 let mut client = EipClient::new(&address.to_string()).await?;
242 client.set_max_packet_size(config.max_packet_size as u32);
243 let mut new_conn = PlcConnection::new(client);
244 new_conn.last_used = Instant::now();
245 connections.push(new_conn);
246 return Ok(&mut connections.last_mut().unwrap().client);
247 }
248
249 let lru_index = connections
251 .iter()
252 .enumerate()
253 .min_by_key(|(_, conn)| conn.last_used)
254 .map(|(i, _)| i)
255 .unwrap();
256
257 let mut client = EipClient::new(&address.to_string()).await?;
259 client.set_max_packet_size(config.max_packet_size as u32);
260 connections[lru_index].client = client;
261 connections[lru_index].health.is_active = true;
262 connections[lru_index].health.last_success = Instant::now();
263 connections[lru_index].health.failed_attempts = 0;
264 connections[lru_index].health.latency = Duration::from_millis(0);
265 connections[lru_index].last_used = Instant::now();
266 Ok(&mut connections[lru_index].client)
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn test_plc_config_default() {
276 let config = PlcConfig::default();
277 assert_eq!(config.max_connections, 5);
278 assert_eq!(config.max_packet_size, 4000);
279 }
280
281 #[tokio::test]
282 async fn test_plc_manager_connection_pool() {
283 let mut manager = PlcManager::new();
284 let config = PlcConfig {
285 address: "127.0.0.1:44818".parse().unwrap(),
286 max_connections: 2,
287 ..Default::default()
288 };
289 manager.add_plc(config.clone());
290
291 let result = manager.get_connection(config.address).await;
294 assert!(result.is_err());
295 }
296}