rust_ethernet_ip/
plc_manager.rs1use std::collections::HashMap;
2use std::time::{Duration, Instant};
3use std::net::SocketAddr;
4use crate::error::{EtherNetIpError, Result};
5use crate::EipClient;
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 PlcManager {
100 pub fn new() -> Self {
102 Self {
103 configs: HashMap::new(),
104 connections: HashMap::new(),
105 health_check_interval: Duration::from_secs(30),
106 }
107 }
108
109 pub fn add_plc(&mut self, config: PlcConfig) {
111 self.configs.insert(config.address, config);
112 }
113
114 pub async fn get_connection(&mut self, address: SocketAddr) -> Result<&mut EipClient> {
116 let config = self.configs.get(&address)
117 .ok_or_else(|| EtherNetIpError::Connection("PLC not configured".to_string()))?;
118
119 if !self.connections.contains_key(&address) {
121 let mut client = EipClient::new(&address.to_string()).await?;
123 client.set_max_packet_size(config.max_packet_size as u32);
124 let mut new_conn = PlcConnection::new(client);
125 new_conn.last_used = Instant::now();
126 self.connections.insert(address, vec![new_conn]);
127 return Ok(&mut self.connections.get_mut(&address).unwrap()[0].client);
128 }
129
130 let connections = self.connections.get_mut(&address).unwrap();
132
133 for i in 0..connections.len() {
135 if !connections[i].health.is_active {
136 let mut client = EipClient::new(&address.to_string()).await?;
137 client.set_max_packet_size(config.max_packet_size as u32);
138 connections[i].client = client;
139 connections[i].health.is_active = true;
140 connections[i].health.last_success = Instant::now();
141 connections[i].health.failed_attempts = 0;
142 connections[i].health.latency = Duration::from_millis(0);
143 connections[i].last_used = Instant::now();
144 return Ok(&mut connections[i].client);
145 }
146 }
147
148 if connections.len() < config.max_connections as usize {
150 let mut client = EipClient::new(&address.to_string()).await?;
151 client.set_max_packet_size(config.max_packet_size as u32);
152 let mut new_conn = PlcConnection::new(client);
153 new_conn.last_used = Instant::now();
154 connections.push(new_conn);
155 return Ok(&mut connections.last_mut().unwrap().client);
156 }
157
158 let lru_index = connections.iter()
160 .enumerate()
161 .min_by_key(|(_, conn)| conn.last_used)
162 .map(|(i, _)| i)
163 .unwrap();
164
165 let mut client = EipClient::new(&address.to_string()).await?;
167 client.set_max_packet_size(config.max_packet_size as u32);
168 connections[lru_index].client = client;
169 connections[lru_index].health.is_active = true;
170 connections[lru_index].health.last_success = Instant::now();
171 connections[lru_index].health.failed_attempts = 0;
172 connections[lru_index].health.latency = Duration::from_millis(0);
173 connections[lru_index].last_used = Instant::now();
174 Ok(&mut connections[lru_index].client)
175 }
176
177 pub async fn check_health(&mut self) {
179 for (address, connections) in &mut self.connections {
180 let _config = self.configs.get(address).unwrap();
181
182 for conn in connections.iter_mut() {
183 if !conn.health.is_active {
184 if let Ok(new_client) = EipClient::new(&address.to_string()).await {
185 conn.client = new_client;
186 conn.health.is_active = true;
187 conn.health.last_success = Instant::now();
188 conn.health.failed_attempts = 0;
189 conn.health.latency = Duration::from_millis(0);
190 conn.last_used = Instant::now();
191 }
192 }
193 }
194 }
195 }
196
197 pub fn cleanup_connections(&mut self) {
199 for connections in self.connections.values_mut() {
200 connections.retain(|conn| conn.health.is_active);
201 }
202 }
203
204 pub async fn get_client(&mut self, address: &str) -> Result<&mut EipClient> {
205 let addr = address.parse::<SocketAddr>()
206 .map_err(|_| EtherNetIpError::Connection("Invalid address format".to_string()))?;
207 self.get_connection(addr).await
208 }
209
210 async fn _get_or_create_connection(&mut self, address: &SocketAddr) -> Result<&mut EipClient> {
211 let config = self.configs.get(address).cloned().unwrap();
212 let connections = self.connections.entry(*address).or_insert_with(Vec::new);
213
214 for i in 0..connections.len() {
216 if !connections[i].health.is_active {
217 let mut client = EipClient::new(&address.to_string()).await?;
218 client.set_max_packet_size(config.max_packet_size as u32);
219 connections[i].client = client;
220 connections[i].health.is_active = true;
221 connections[i].health.last_success = Instant::now();
222 connections[i].health.failed_attempts = 0;
223 connections[i].health.latency = Duration::from_millis(0);
224 connections[i].last_used = Instant::now();
225 return Ok(&mut connections[i].client);
226 }
227 }
228
229 if connections.len() < config.max_connections as usize {
231 let mut client = EipClient::new(&address.to_string()).await?;
232 client.set_max_packet_size(config.max_packet_size as u32);
233 let mut new_conn = PlcConnection::new(client);
234 new_conn.last_used = Instant::now();
235 connections.push(new_conn);
236 return Ok(&mut connections.last_mut().unwrap().client);
237 }
238
239 let lru_index = connections.iter()
241 .enumerate()
242 .min_by_key(|(_, conn)| conn.last_used)
243 .map(|(i, _)| i)
244 .unwrap();
245
246 let mut client = EipClient::new(&address.to_string()).await?;
248 client.set_max_packet_size(config.max_packet_size as u32);
249 connections[lru_index].client = client;
250 connections[lru_index].health.is_active = true;
251 connections[lru_index].health.last_success = Instant::now();
252 connections[lru_index].health.failed_attempts = 0;
253 connections[lru_index].health.latency = Duration::from_millis(0);
254 connections[lru_index].last_used = Instant::now();
255 Ok(&mut connections[lru_index].client)
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn test_plc_config_default() {
265 let config = PlcConfig::default();
266 assert_eq!(config.max_connections, 5);
267 assert_eq!(config.max_packet_size, 4000);
268 }
269
270 #[tokio::test]
271 async fn test_plc_manager_connection_pool() {
272 let mut manager = PlcManager::new();
273 let config = PlcConfig {
274 address: "127.0.0.1:44818".parse().unwrap(),
275 max_connections: 2,
276 ..Default::default()
277 };
278 manager.add_plc(config.clone());
279
280 let result = manager.get_connection(config.address).await;
283 assert!(result.is_err());
284 }
285}