1use pyo3::prelude::*;
2use pyo3::wrap_pyfunction;
3use pyo3::types::{PyDict, PyList, PyTuple};
4use pyo3::types::IntoPyDict;
5use tokio::runtime::Runtime;
6use std::collections::HashMap;
7use crate::{
8 EipClient, PlcValue, BatchOperation, BatchResult, BatchError,
9 SubscriptionOptions, TagSubscription, Result as EipResult
10};
11
12#[pymodule]
14fn rust_ethernet_ip(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
15 m.add_class::<PyEipClient>()?;
16 m.add_class::<PyPlcValue>()?;
17 m.add_class::<PySubscriptionOptions>()?;
18 Ok(())
19}
20
21#[pyclass]
23struct PyEipClient {
24 client: EipClient,
25 runtime: Runtime,
26}
27
28struct TagValueArg {
30 name: String,
31 value: PyPlcValue,
32}
33
34impl<'a> FromPyObject<'a> for TagValueArg {
35 fn extract(ob: &'a pyo3::PyAny) -> PyResult<Self> {
36 let tuple = ob.downcast::<PyTuple>()?;
37 if tuple.len() != 2 {
38 return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
39 "Expected tuple of length 2"
40 ));
41 }
42 let name = tuple.get_item(0)?.extract::<String>()?;
43 let value = tuple.get_item(1)?.extract::<PyPlcValue>()?;
44 Ok(TagValueArg { name, value })
45 }
46}
47
48struct TagSubOptArg {
50 name: String,
51 options: PySubscriptionOptions,
52}
53
54impl<'a> FromPyObject<'a> for TagSubOptArg {
55 fn extract(ob: &'a pyo3::PyAny) -> PyResult<Self> {
56 let tuple = ob.downcast::<PyTuple>()?;
57 if tuple.len() != 2 {
58 return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
59 "Expected tuple of length 2"
60 ));
61 }
62 let name = tuple.get_item(0)?.extract::<String>()?;
63 let options = tuple.get_item(1)?.extract::<PySubscriptionOptions>()?;
64 Ok(TagSubOptArg { name, options })
65 }
66}
67
68#[pymethods]
69impl PyEipClient {
70 #[new]
72 fn new(addr: &str) -> PyResult<Self> {
73 let runtime = Runtime::new().unwrap();
74 let client = runtime.block_on(async {
75 EipClient::connect(addr).await
76 }).map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
77
78 Ok(PyEipClient { client, runtime })
79 }
80
81 fn read_tag(&mut self, tag_name: &str) -> PyResult<PyPlcValue> {
83 let value = self.runtime.block_on(async {
84 self.client.read_tag(tag_name).await
85 }).map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
86
87 Ok(PyPlcValue { value })
88 }
89
90 fn write_tag(&mut self, tag_name: &str, value: &PyPlcValue) -> PyResult<bool> {
92 let result = self.runtime.block_on(async {
93 self.client.write_tag(tag_name, value.value.clone()).await
94 });
95 match result {
96 Ok(_) => Ok(true),
97 Err(e) => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string())),
98 }
99 }
100
101 fn read_tags_batch(&mut self, tag_names: Vec<String>) -> PyResult<Vec<(String, PyObject)>> {
103 Python::with_gil(|py| {
104 let runtime = tokio::runtime::Runtime::new().unwrap();
105 let results = runtime.block_on(async {
106 self.client.read_tags_batch(&tag_names.iter().map(|s| s.as_str()).collect::<Vec<_>>()).await
107 }).map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
108 Ok(results.into_iter().map(|(name, result)| {
109 let obj = match result {
110 Ok(v) => PyPlcValue { value: v }.into_py(py),
111 Err(e) => PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()).into_py(py),
112 };
113 (name, obj)
114 }).collect())
115 })
116 }
117
118 fn write_tags_batch(&mut self, tag_values: Vec<TagValueArg>) -> PyResult<Vec<(String, PyObject)>> {
120 Python::with_gil(|py| {
121 let runtime = tokio::runtime::Runtime::new().unwrap();
122 let results = runtime.block_on(async {
123 self.client.write_tags_batch(&tag_values.iter()
124 .map(|arg| (arg.name.as_str(), arg.value.value.clone()))
125 .collect::<Vec<_>>()).await
126 }).map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
127 Ok(results.into_iter().map(|(name, result)| {
128 let obj = match result {
129 Ok(()) => py.None(),
130 Err(e) => PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()).into_py(py),
131 };
132 (name, obj)
133 }).collect())
134 })
135 }
136
137 fn subscribe_to_tag(&self, tag_path: &str, options: &PySubscriptionOptions) -> PyResult<()> {
139 self.runtime.block_on(async {
140 self.client.subscribe_to_tag(tag_path, options.options.clone()).await
141 }).map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
142
143 Ok(())
144 }
145
146 fn subscribe_to_tags(&self, tags: Vec<TagSubOptArg>) -> PyResult<()> {
148 self.runtime.block_on(async {
149 self.client.subscribe_to_tags(&tags.iter()
150 .map(|arg| (arg.name.as_str(), arg.options.options.clone()))
151 .collect::<Vec<_>>()).await
152 }).map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
153 Ok(())
154 }
155
156 fn unregister_session(&mut self) -> PyResult<()> {
158 self.runtime.block_on(async {
159 self.client.unregister_session().await
160 }).map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))?;
161
162 Ok(())
163 }
164}
165
166#[pyclass]
168struct PyPlcValue {
169 value: PlcValue,
170}
171
172impl FromPyObject<'_> for PyPlcValue {
173 fn extract(ob: &PyAny) -> PyResult<Self> {
174 if let Ok(bool_val) = ob.extract::<bool>() {
175 Ok(PyPlcValue { value: PlcValue::Bool(bool_val) })
176 } else if let Ok(int_val) = ob.extract::<i32>() {
177 Ok(PyPlcValue { value: PlcValue::Dint(int_val) })
178 } else if let Ok(float_val) = ob.extract::<f64>() {
179 Ok(PyPlcValue { value: PlcValue::Lreal(float_val) })
180 } else if let Ok(string_val) = ob.extract::<String>() {
181 Ok(PyPlcValue { value: PlcValue::String(string_val) })
182 } else if let Ok(dict) = ob.downcast::<PyDict>() {
183 let mut map = HashMap::new();
184 for (key, value) in dict.iter() {
185 let key = key.extract::<String>()?;
186 let value = value.extract::<PyPlcValue>()?.value;
187 map.insert(key, value);
188 }
189 Ok(PyPlcValue { value: PlcValue::Udt(map) })
190 } else {
191 Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
192 "Unsupported value type"
193 ))
194 }
195 }
196}
197
198#[pymethods]
199impl PyPlcValue {
200 #[new]
201 fn new(value: PyObject) -> PyResult<Self> {
202 Python::with_gil(|py| {
203 if let Ok(val) = value.extract::<bool>(py) {
204 Ok(PyPlcValue { value: PlcValue::Bool(val) })
205 } else if let Ok(val) = value.extract::<i32>(py) {
206 Ok(PyPlcValue { value: PlcValue::Dint(val) })
207 } else if let Ok(val) = value.extract::<f32>(py) {
208 Ok(PyPlcValue { value: PlcValue::Real(val) })
209 } else if let Ok(val) = value.extract::<f64>(py) {
210 Ok(PyPlcValue { value: PlcValue::Real(val as f32) })
211 } else if let Ok(val) = value.extract::<String>(py) {
212 Ok(PyPlcValue { value: PlcValue::String(val) })
213 } else {
214 Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>("Unsupported value type"))
215 }
216 })
217 }
218
219 #[staticmethod]
220 fn real(val: f32) -> Self {
221 PyPlcValue { value: PlcValue::Real(val) }
222 }
223 #[staticmethod]
224 fn lreal(val: f64) -> Self {
225 PyPlcValue { value: PlcValue::Lreal(val) }
226 }
227 #[staticmethod]
228 fn dint(val: i32) -> Self {
229 PyPlcValue { value: PlcValue::Dint(val) }
230 }
231 #[staticmethod]
232 fn lint(val: i64) -> Self {
233 PyPlcValue { value: PlcValue::Lint(val) }
234 }
235 #[staticmethod]
236 fn string(val: String) -> Self {
237 PyPlcValue { value: PlcValue::String(val) }
238 }
239
240 #[getter]
241 fn value(&self, py: Python) -> PyResult<PyObject> {
242 match &self.value {
243 PlcValue::Bool(b) => Ok(b.into_py(py)),
244 PlcValue::Sint(i) => Ok(i.into_py(py)),
245 PlcValue::Int(i) => Ok(i.into_py(py)),
246 PlcValue::Dint(i) => Ok(i.into_py(py)),
247 PlcValue::Lint(i) => Ok(i.into_py(py)),
248 PlcValue::Usint(u) => Ok(u.into_py(py)),
249 PlcValue::Uint(u) => Ok(u.into_py(py)),
250 PlcValue::Udint(u) => Ok(u.into_py(py)),
251 PlcValue::Ulint(u) => Ok(u.into_py(py)),
252 PlcValue::Real(f) => Ok(f.into_py(py)),
253 PlcValue::Lreal(f) => Ok(f.into_py(py)),
254 PlcValue::String(s) => Ok(s.into_py(py)),
255 PlcValue::Udt(map) => {
256 let dict = PyDict::new(py);
257 for (k, v) in map.iter() {
258 let v_py = PyPlcValue { value: v.clone() }.value(py)?;
259 dict.set_item(k, v_py)?;
260 }
261 Ok(dict.into_py(py))
262 }
263 }
264 }
265
266 fn __str__(&self) -> String {
267 format!("{:?}", self.value)
268 }
269
270 fn __repr__(&self) -> String {
271 format!("PyPlcValue({:?})", self.value)
272 }
273}
274
275#[pyclass]
277struct PySubscriptionOptions {
278 options: SubscriptionOptions,
279}
280
281impl FromPyObject<'_> for PySubscriptionOptions {
282 fn extract(ob: &PyAny) -> PyResult<Self> {
283 let update_rate = ob.getattr("update_rate")?.extract::<u32>()?;
284 let change_threshold = ob.getattr("change_threshold")?.extract::<f32>()?;
285 let timeout = ob.getattr("timeout")?.extract::<u32>()?;
286
287 Ok(PySubscriptionOptions {
288 options: SubscriptionOptions {
289 update_rate,
290 change_threshold,
291 timeout,
292 }
293 })
294 }
295}
296
297#[pymethods]
298impl PySubscriptionOptions {
299 #[new]
300 fn new(update_rate: u32, change_threshold: f32, timeout: u32) -> PyResult<Self> {
301 let options = SubscriptionOptions {
302 update_rate,
303 change_threshold,
304 timeout,
305 };
306
307 Ok(PySubscriptionOptions { options })
308 }
309
310 #[getter]
312 fn update_rate(&self) -> u32 {
313 self.options.update_rate
314 }
315
316 #[getter]
318 fn change_threshold(&self) -> f32 {
319 self.options.change_threshold
320 }
321
322 #[getter]
324 fn timeout(&self) -> u32 {
325 self.options.timeout
326 }
327}