1use crate::PlcValue;
2use crate::error::{EtherNetIpError, Result};
3use std::collections::HashMap;
4use std::sync::Arc;
5use std::sync::atomic::AtomicBool;
6use std::sync::{LazyLock, Mutex as StdMutex};
7use tokio::sync::{Mutex, mpsc};
8
9use futures::{Stream, stream};
10
11#[derive(Debug, Clone)]
13pub struct SubscriptionOptions {
14 pub update_rate: u32,
16 pub change_threshold: f32,
21 pub timeout: u32,
23}
24
25impl Default for SubscriptionOptions {
26 fn default() -> Self {
27 Self {
28 update_rate: 100, change_threshold: 0.001, timeout: 5000, }
32 }
33}
34
35#[derive(Debug, Clone)]
37pub struct TagSubscription {
38 pub tag_path: String,
40 pub options: SubscriptionOptions,
42 pub last_value: Arc<Mutex<Option<PlcValue>>>,
44 pub sender: Arc<Mutex<mpsc::Sender<PlcValue>>>,
46 pub receiver: Arc<Mutex<mpsc::Receiver<PlcValue>>>,
48 pub is_active: Arc<AtomicBool>,
50}
51
52#[derive(Debug, Clone, PartialEq)]
54#[non_exhaustive]
55pub enum TagSubscriptionEvent {
56 Value(PlcValue),
58 Error {
60 message: String,
62 terminal: bool,
64 },
65}
66
67#[derive(Clone)]
68struct TagSubscriptionEventChannels {
69 sender: Arc<Mutex<mpsc::Sender<TagSubscriptionEvent>>>,
70 receiver: Arc<Mutex<mpsc::Receiver<TagSubscriptionEvent>>>,
71}
72
73static TAG_SUBSCRIPTION_EVENTS: LazyLock<StdMutex<HashMap<usize, TagSubscriptionEventChannels>>> =
74 LazyLock::new(|| StdMutex::new(HashMap::new()));
75
76impl TagSubscription {
77 pub fn new(tag_name: String, options: SubscriptionOptions) -> Self {
79 let (sender, receiver) = mpsc::channel(100); let subscription = Self {
81 tag_path: tag_name,
82 options,
83 last_value: Arc::new(Mutex::new(None)),
84 sender: Arc::new(Mutex::new(sender)),
85 receiver: Arc::new(Mutex::new(receiver)),
86 is_active: Arc::new(AtomicBool::new(true)),
87 };
88 subscription.event_channels();
89 subscription
90 }
91
92 pub fn is_active(&self) -> bool {
94 self.is_active.load(std::sync::atomic::Ordering::Relaxed)
95 }
96
97 pub fn stop(&self) {
99 self.is_active
100 .store(false, std::sync::atomic::Ordering::Relaxed);
101 }
102
103 pub async fn update_value(&self, value: &PlcValue) -> Result<()> {
109 let mut last_value = self.last_value.lock().await;
110
111 if let Some(old) = last_value.as_ref()
113 && !Self::value_changed(old, value, self.options.change_threshold)
114 {
115 return Ok(());
116 }
117
118 *last_value = Some(value.clone());
120 drop(last_value);
121 try_send_drop_oldest(&self.sender, &self.receiver, value.clone())
122 .await
123 .map_err(|e| EtherNetIpError::Subscription(format!("Failed to send update: {e}")))?;
124 try_send_drop_oldest(
125 &self.event_channels().sender,
126 &self.event_channels().receiver,
127 TagSubscriptionEvent::Value(value.clone()),
128 )
129 .await
130 .map_err(|e| EtherNetIpError::Subscription(format!("Failed to send event: {e}")))?;
131
132 Ok(())
133 }
134
135 pub async fn publish_error(&self, error: &EtherNetIpError, terminal: bool) -> Result<()> {
140 if terminal {
141 self.stop();
142 }
143
144 try_send_drop_oldest(
145 &self.event_channels().sender,
146 &self.event_channels().receiver,
147 TagSubscriptionEvent::Error {
148 message: error.to_string(),
149 terminal,
150 },
151 )
152 .await
153 .map_err(|e| EtherNetIpError::Subscription(format!("Failed to send event: {e}")))
154 }
155
156 fn value_changed(old: &PlcValue, new: &PlcValue, threshold: f32) -> bool {
160 match (old, new) {
161 (PlcValue::Real(o), PlcValue::Real(n)) => (*n - *o).abs() >= threshold,
162 (PlcValue::Lreal(o), PlcValue::Lreal(n)) => (*n - *o).abs() >= threshold as f64,
163 (PlcValue::Bool(o), PlcValue::Bool(n)) => o != n,
164 (PlcValue::Sint(o), PlcValue::Sint(n)) => o != n,
165 (PlcValue::Int(o), PlcValue::Int(n)) => o != n,
166 (PlcValue::Dint(o), PlcValue::Dint(n)) => o != n,
167 (PlcValue::Lint(o), PlcValue::Lint(n)) => o != n,
168 (PlcValue::Usint(o), PlcValue::Usint(n)) => o != n,
169 (PlcValue::Uint(o), PlcValue::Uint(n)) => o != n,
170 (PlcValue::Udint(o), PlcValue::Udint(n)) => o != n,
171 (PlcValue::Ulint(o), PlcValue::Ulint(n)) => o != n,
172 (PlcValue::String(o), PlcValue::String(n)) => o != n,
173 _ => true,
175 }
176 }
177
178 pub async fn wait_for_update(&self) -> Result<PlcValue> {
180 let mut receiver = self.receiver.lock().await;
181 let next_value = receiver.recv().await;
182 drop(receiver);
183 next_value.ok_or_else(|| EtherNetIpError::Subscription("Channel closed".to_string()))
184 }
185
186 pub async fn wait_for_event(&self) -> Result<TagSubscriptionEvent> {
188 let channels = self.event_channels();
189 let mut receiver = channels.receiver.lock().await;
190 let next_event = receiver.recv().await;
191 drop(receiver);
192 next_event.ok_or_else(|| EtherNetIpError::Subscription("Channel closed".to_string()))
193 }
194
195 pub async fn get_last_value(&self) -> Option<PlcValue> {
197 self.last_value.lock().await.clone()
198 }
199
200 async fn recv_next_value(&self) -> Option<PlcValue> {
201 let mut receiver = self.receiver.lock().await;
202 let next_value = receiver.recv().await;
203 drop(receiver);
204 next_value
205 }
206
207 async fn recv_next_event(&self) -> Option<TagSubscriptionEvent> {
208 let channels = self.event_channels();
209 let mut receiver = channels.receiver.lock().await;
210 let next_event = receiver.recv().await;
211 drop(receiver);
212 next_event
213 }
214
215 pub fn into_stream(self: Arc<Self>) -> impl Stream<Item = PlcValue> + Send {
232 stream::unfold(self, |subscription| async move {
233 let next_value = subscription.recv_next_value().await;
234 next_value.map(|plc_value| (plc_value, subscription))
235 })
236 }
237
238 pub fn into_event_stream(self: Arc<Self>) -> impl Stream<Item = TagSubscriptionEvent> + Send {
240 stream::unfold(self, |subscription| async move {
241 let next_event = subscription.recv_next_event().await;
242 next_event.map(|event| (event, subscription))
243 })
244 }
245
246 fn event_channels(&self) -> TagSubscriptionEventChannels {
247 let key = self.event_key();
248 let mut channels = TAG_SUBSCRIPTION_EVENTS
249 .lock()
250 .unwrap_or_else(std::sync::PoisonError::into_inner);
251 channels
252 .entry(key)
253 .or_insert_with(|| {
254 let (sender, receiver) = mpsc::channel(100);
255 TagSubscriptionEventChannels {
256 sender: Arc::new(Mutex::new(sender)),
257 receiver: Arc::new(Mutex::new(receiver)),
258 }
259 })
260 .clone()
261 }
262
263 fn event_key(&self) -> usize {
264 Arc::as_ptr(&self.is_active) as usize
265 }
266}
267
268impl Drop for TagSubscription {
269 fn drop(&mut self) {
270 if Arc::strong_count(&self.is_active) == 1
271 && let Ok(mut channels) = TAG_SUBSCRIPTION_EVENTS.lock()
272 {
273 channels.remove(&self.event_key());
274 }
275 }
276}
277
278pub(crate) async fn try_send_drop_oldest<T>(
279 sender: &Arc<Mutex<mpsc::Sender<T>>>,
280 receiver: &Arc<Mutex<mpsc::Receiver<T>>>,
281 value: T,
282) -> std::result::Result<(), String> {
283 let sender = {
284 let sender = sender.lock().await;
285 sender.clone()
286 };
287
288 match sender.try_send(value) {
289 Ok(()) => Ok(()),
290 Err(mpsc::error::TrySendError::Closed(_)) => Err("channel closed".to_string()),
291 Err(mpsc::error::TrySendError::Full(value)) => {
292 if let Ok(mut receiver) = receiver.try_lock() {
293 let _ = receiver.try_recv();
294 match sender.try_send(value) {
295 Ok(()) => Ok(()),
296 Err(mpsc::error::TrySendError::Closed(_)) => Err("channel closed".to_string()),
297 Err(mpsc::error::TrySendError::Full(_)) => Ok(()),
298 }
299 } else {
300 Ok(())
301 }
302 }
303 }
304}
305
306#[derive(Debug, Clone)]
308#[deprecated(
309 since = "1.2.0",
310 note = "SubscriptionManager is not used by EipClient; use EipClient subscription methods or Client tag groups instead. The type will be removed in 2.0."
311)]
312pub struct SubscriptionManager {
313 subscriptions: Arc<Mutex<Vec<TagSubscription>>>,
314}
315
316#[expect(
317 deprecated,
318 reason = "CODEX-AQ keeps SubscriptionManager compatibility until 2.0 removal"
319)]
320impl Default for SubscriptionManager {
321 fn default() -> Self {
322 Self::new()
323 }
324}
325
326#[expect(
327 deprecated,
328 reason = "CODEX-AQ keeps SubscriptionManager compatibility until 2.0 removal"
329)]
330impl SubscriptionManager {
331 pub fn new() -> Self {
333 Self {
334 subscriptions: Arc::new(Mutex::new(Vec::new())),
335 }
336 }
337
338 pub async fn add_subscription(&self, subscription: TagSubscription) {
340 let mut subscriptions = self.subscriptions.lock().await;
341 subscriptions.push(subscription);
342 }
343
344 pub async fn remove_subscription(&self, tag_name: &str) {
346 let mut subscriptions = self.subscriptions.lock().await;
347 subscriptions.retain(|sub| sub.tag_path != tag_name);
348 }
349
350 pub async fn update_value(&self, tag_name: &str, value: &PlcValue) -> Result<()> {
352 let subscriptions = {
353 let subscriptions = self.subscriptions.lock().await;
354 subscriptions.clone()
355 };
356 for subscription in &subscriptions {
357 if subscription.tag_path == tag_name && subscription.is_active() {
358 subscription.update_value(value).await?;
359 }
360 }
361 Ok(())
362 }
363
364 pub async fn get_subscriptions(&self) -> Vec<TagSubscription> {
366 let subscriptions = self.subscriptions.lock().await;
367 subscriptions.clone()
368 }
369
370 pub async fn get_subscription(&self, tag_name: &str) -> Option<TagSubscription> {
372 let subscriptions = self.subscriptions.lock().await;
373 subscriptions
374 .iter()
375 .find(|sub| sub.tag_path == tag_name)
376 .cloned()
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use super::*;
383
384 #[test]
385 fn real_deadband_is_absolute_not_relative() {
386 let threshold = 0.001_f32;
389 assert!(!TagSubscription::value_changed(
391 &PlcValue::Real(1000.0),
392 &PlcValue::Real(1000.0005),
393 threshold
394 ));
395 assert!(TagSubscription::value_changed(
397 &PlcValue::Real(1000.0),
398 &PlcValue::Real(1000.002),
399 threshold
400 ));
401 assert!(TagSubscription::value_changed(
404 &PlcValue::Real(0.0),
405 &PlcValue::Real(0.002),
406 threshold
407 ));
408 }
409
410 #[test]
411 fn non_float_types_notify_on_any_change() {
412 assert!(TagSubscription::value_changed(
413 &PlcValue::Dint(1),
414 &PlcValue::Dint(2),
415 0.001
416 ));
417 assert!(!TagSubscription::value_changed(
418 &PlcValue::Dint(5),
419 &PlcValue::Dint(5),
420 0.001
421 ));
422 }
423}