1use std::net::SocketAddr;
4use std::ops::ControlFlow;
5use std::time::Duration;
6
7use pyo3::prelude::*;
8use pyo3::types::{PyDict, PyList};
9
10use spvirit_client::pva_client::PvaClient;
11use spvirit_client::search::{build_auto_broadcast_targets, discover_servers};
12
13
14use crate::convert::{decoded_to_py, py_to_json};
15use crate::errors::to_py_err;
16use crate::runtime::RUNTIME;
17
18#[pyclass(name = "GetResult")]
21pub struct PyGetResult {
22 #[pyo3(get)]
23 pub pv_name: String,
24 value: PyObject,
25 #[pyo3(get)]
26 pub raw_pva: Vec<u8>,
27 #[pyo3(get)]
28 pub raw_pvd: Vec<u8>,
29}
30
31impl PyGetResult {
32 pub(crate) fn new(pv_name: String, value: PyObject, raw_pva: Vec<u8>, raw_pvd: Vec<u8>) -> Self {
33 Self {
34 pv_name,
35 value,
36 raw_pva,
37 raw_pvd,
38 }
39 }
40}
41
42#[pymethods]
43impl PyGetResult {
44 #[getter]
45 fn value(&self, py: Python<'_>) -> PyObject {
46 self.value.clone_ref(py)
47 }
48
49 fn __repr__(&self) -> String {
50 format!("GetResult(pv_name={:?})", self.pv_name)
51 }
52}
53
54#[pyclass(name = "MonitorEvent")]
57pub struct PyMonitorEvent {
58 #[pyo3(get)]
59 pub pv_name: String,
60 value: PyObject,
61}
62
63#[pymethods]
64impl PyMonitorEvent {
65 #[getter]
66 fn value(&self, py: Python<'_>) -> PyObject {
67 self.value.clone_ref(py)
68 }
69
70 fn __repr__(&self) -> String {
71 format!("MonitorEvent(pv_name={:?})", self.pv_name)
72 }
73}
74
75#[pyclass(name = "DiscoveredServer")]
78#[derive(Clone)]
79pub struct PyDiscoveredServer {
80 #[pyo3(get)]
81 pub guid: Vec<u8>,
82 #[pyo3(get)]
83 pub tcp_addr: String,
84}
85
86#[pymethods]
87impl PyDiscoveredServer {
88 fn __repr__(&self) -> String {
89 format!("DiscoveredServer(tcp_addr={:?})", self.tcp_addr)
90 }
91}
92
93#[pyclass(name = "ClientBuilder")]
96pub struct PyClientBuilder {
97 udp_port: u16,
98 tcp_port: u16,
99 timeout_secs: f64,
100 no_broadcast: bool,
101 name_servers: Vec<String>,
102 authnz_user: Option<String>,
103 authnz_host: Option<String>,
104 server_addr: Option<String>,
105 search_addr: Option<String>,
106 bind_addr: Option<String>,
107 debug: bool,
108}
109
110#[pymethods]
111impl PyClientBuilder {
112 #[new]
113 fn new() -> Self {
114 Self {
115 udp_port: 5076,
116 tcp_port: 5075,
117 timeout_secs: 5.0,
118 no_broadcast: false,
119 name_servers: Vec::new(),
120 authnz_user: None,
121 authnz_host: None,
122 server_addr: None,
123 search_addr: None,
124 bind_addr: None,
125 debug: false,
126 }
127 }
128
129 fn port(mut slf: PyRefMut<'_, Self>, port: u16) -> PyRefMut<'_, Self> {
130 slf.tcp_port = port;
131 slf
132 }
133
134 fn udp_port(mut slf: PyRefMut<'_, Self>, port: u16) -> PyRefMut<'_, Self> {
135 slf.udp_port = port;
136 slf
137 }
138
139 fn timeout(mut slf: PyRefMut<'_, Self>, secs: f64) -> PyRefMut<'_, Self> {
140 slf.timeout_secs = secs;
141 slf
142 }
143
144 fn no_broadcast(mut slf: PyRefMut<'_, Self>, enabled: bool) -> PyRefMut<'_, Self> {
145 slf.no_broadcast = enabled;
146 slf
147 }
148
149 fn name_server(mut slf: PyRefMut<'_, Self>, addr: String) -> PyResult<PyRefMut<'_, Self>> {
150 let _: SocketAddr = addr
151 .parse()
152 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid address: {e}")))?;
153 slf.name_servers.push(addr);
154 Ok(slf)
155 }
156
157 fn authnz_user(mut slf: PyRefMut<'_, Self>, user: String) -> PyRefMut<'_, Self> {
158 slf.authnz_user = Some(user);
159 slf
160 }
161
162 fn authnz_host(mut slf: PyRefMut<'_, Self>, host: String) -> PyRefMut<'_, Self> {
163 slf.authnz_host = Some(host);
164 slf
165 }
166
167 fn server_addr(mut slf: PyRefMut<'_, Self>, addr: String) -> PyResult<PyRefMut<'_, Self>> {
168 let _: SocketAddr = addr
169 .parse()
170 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid address: {e}")))?;
171 slf.server_addr = Some(addr);
172 Ok(slf)
173 }
174
175 fn search_addr(mut slf: PyRefMut<'_, Self>, addr: String) -> PyRefMut<'_, Self> {
176 slf.search_addr = Some(addr);
177 slf
178 }
179
180 fn bind_addr(mut slf: PyRefMut<'_, Self>, addr: String) -> PyRefMut<'_, Self> {
181 slf.bind_addr = Some(addr);
182 slf
183 }
184
185 fn debug(mut slf: PyRefMut<'_, Self>, enabled: bool) -> PyRefMut<'_, Self> {
186 slf.debug = enabled;
187 slf
188 }
189
190 fn build(&self) -> PyResult<PyClient> {
191 let mut b = PvaClient::builder()
192 .port(self.tcp_port)
193 .udp_port(self.udp_port)
194 .timeout(Duration::from_secs_f64(self.timeout_secs));
195 if self.no_broadcast {
196 b = b.no_broadcast();
197 }
198 for ns in &self.name_servers {
199 let addr: SocketAddr = ns.parse().map_err(|e| {
200 pyo3::exceptions::PyValueError::new_err(format!("invalid address: {e}"))
201 })?;
202 b = b.name_server(addr);
203 }
204 if let Some(ref user) = self.authnz_user {
205 b = b.authnz_user(user);
206 }
207 if let Some(ref host) = self.authnz_host {
208 b = b.authnz_host(host);
209 }
210 if let Some(ref addr) = self.server_addr {
211 let sa: SocketAddr = addr.parse().map_err(|e| {
212 pyo3::exceptions::PyValueError::new_err(format!("invalid address: {e}"))
213 })?;
214 b = b.server_addr(sa);
215 }
216 if let Some(ref addr) = self.search_addr {
217 let ip: std::net::IpAddr = addr.parse().map_err(|e| {
218 pyo3::exceptions::PyValueError::new_err(format!("invalid IP: {e}"))
219 })?;
220 b = b.search_addr(ip);
221 }
222 if let Some(ref addr) = self.bind_addr {
223 let ip: std::net::IpAddr = addr.parse().map_err(|e| {
224 pyo3::exceptions::PyValueError::new_err(format!("invalid IP: {e}"))
225 })?;
226 b = b.bind_addr(ip);
227 }
228 if self.debug {
229 b = b.debug();
230 }
231 Ok(PyClient {
232 inner: b.build(),
233 })
234 }
235}
236
237#[pyclass(name = "Client")]
240pub struct PyClient {
241 inner: PvaClient,
242}
243
244#[pymethods]
245impl PyClient {
246 #[new]
247 fn new() -> Self {
248 Self {
249 inner: PvaClient::builder().build(),
250 }
251 }
252
253 #[staticmethod]
255 fn builder() -> PyClientBuilder {
256 PyClientBuilder::new()
257 }
258
259 #[pyo3(signature = (pv_name, fields=None))]
264 fn get(
265 &self,
266 py: Python<'_>,
267 pv_name: String,
268 fields: Option<Vec<String>>,
269 ) -> PyResult<PyGetResult> {
270 let client = self.inner.clone();
271 let result = py
272 .allow_threads(|| {
273 RUNTIME.block_on(async {
274 match fields {
275 None => client.pvget(&pv_name).await,
276 Some(ref f) => {
277 let refs: Vec<&str> = f.iter().map(String::as_str).collect();
278 client.pvget_fields(&pv_name, &refs).await
279 }
280 }
281 })
282 })
283 .map_err(to_py_err)?;
284 let value = decoded_to_py(py, &result.value);
285 Ok(PyGetResult {
286 pv_name: result.pv_name,
287 value,
288 raw_pva: result.raw_pva,
289 raw_pvd: result.raw_pvd,
290 })
291 }
292
293 #[pyo3(signature = (pv_name, value, fields=None))]
298 fn put(
299 &self,
300 py: Python<'_>,
301 pv_name: String,
302 value: PyObject,
303 fields: Option<Vec<String>>,
304 ) -> PyResult<()> {
305 let json_val = py_to_json(value.bind(py))?;
306 let client = self.inner.clone();
307 py.allow_threads(|| {
308 RUNTIME.block_on(async {
309 match fields {
310 None => client.pvput(&pv_name, json_val).await,
311 Some(ref f) => {
312 let refs: Vec<&str> = f.iter().map(String::as_str).collect();
313 client.pvput_fields(&pv_name, json_val, &refs).await
314 }
315 }
316 })
317 })
318 .map_err(to_py_err)
319 }
320
321 #[pyo3(signature = (pv_name, callback, fields=None))]
326 fn monitor(
327 &self,
328 _py: Python<'_>,
329 pv_name: String,
330 callback: PyObject,
331 fields: Option<Vec<String>>,
332 ) -> PyResult<()> {
333 let client = self.inner.clone();
334 let fields = fields.unwrap_or_default();
335 let result = RUNTIME.block_on(async {
339 let refs: Vec<&str> = fields.iter().map(String::as_str).collect();
340 client
341 .pvmonitor_fields(&pv_name, &refs, |decoded| {
342 let keep_going = Python::with_gil(|py| {
343 let py_val = decoded_to_py(py, decoded);
344 match callback.call1(py, (py_val,)) {
345 Ok(ret) => {
346 ret.extract::<bool>(py).unwrap_or(true)
348 }
349 Err(_) => false,
350 }
351 });
352 if keep_going {
353 ControlFlow::Continue(())
354 } else {
355 ControlFlow::Break(())
356 }
357 })
358 .await
359 });
360 result.map_err(to_py_err)
362 }
363
364 fn info(&self, py: Python<'_>, pv_name: String) -> PyResult<PyObject> {
366 let client = self.inner.clone();
367 let desc = py
368 .allow_threads(|| RUNTIME.block_on(client.pvinfo(&pv_name)))
369 .map_err(to_py_err)?;
370 let dict = PyDict::new(py);
372 dict.set_item("struct_id", &desc.struct_id)?;
373 let fields: Vec<PyObject> = desc
374 .fields
375 .iter()
376 .map(|f| {
377 let fd = PyDict::new(py);
378 fd.set_item("name", &f.name).expect("set");
379 fd.set_item("field_type", format!("{:?}", f.field_type))
380 .expect("set");
381 fd.into_any().unbind()
382 })
383 .collect();
384 dict.set_item("fields", PyList::new(py, &fields)?)?;
385 Ok(dict.into_any().unbind())
386 }
387
388 fn pvlist(&self, py: Python<'_>, server_addr: String) -> PyResult<Vec<String>> {
390 let addr: SocketAddr = server_addr
391 .parse()
392 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid address: {e}")))?;
393 let client = self.inner.clone();
394 py.allow_threads(|| RUNTIME.block_on(client.pvlist(addr)))
395 .map_err(to_py_err)
396 }
397}
398
399#[pyfunction]
403#[pyo3(signature = (udp_port=5076, timeout=2.0, debug=false))]
404pub fn py_discover_servers(
405 py: Python<'_>,
406 udp_port: u16,
407 timeout: f64,
408 debug: bool,
409) -> PyResult<Vec<PyDiscoveredServer>> {
410 let targets = build_auto_broadcast_targets();
411 let dur = Duration::from_secs_f64(timeout);
412 let servers = py
413 .allow_threads(|| RUNTIME.block_on(discover_servers(udp_port, dur, &targets, debug)))
414 .map_err(to_py_err)?;
415 Ok(servers
416 .into_iter()
417 .map(|s| PyDiscoveredServer {
418 guid: s.guid.to_vec(),
419 tcp_addr: s.tcp_addr.to_string(),
420 })
421 .collect())
422}