1use std::net::IpAddr;
4use std::sync::Arc;
5use std::time::Duration;
6
7use pyo3::prelude::*;
8
9use spvirit_codec::spvd_decode::DecodedValue;
10use spvirit_server::SimplePvStore;
11use spvirit_server::pva_server::PvaServer;
12use spvirit_types::{ScalarArrayValue, ScalarValue};
13
14use crate::convert::{decoded_to_py, py_to_scalar, py_to_scalar_array, scalar_to_py};
15use crate::nt::{nt_payload_to_py, py_to_nt_payload};
16use crate::runtime::{RUNTIME, block_on_py};
17use crate::source::{PyNotifier, PySourceAdapter};
18
19#[pyclass(name = "ServerBuilder")]
25pub struct PyServerBuilder {
26 builder: Option<spvirit_server::PvaServerBuilder>,
27 python_sources: Vec<(String, i32, Arc<PySourceAdapter>)>,
29}
30
31fn take_builder(
33 slf: &mut PyRefMut<'_, PyServerBuilder>,
34) -> PyResult<spvirit_server::PvaServerBuilder> {
35 slf.builder.take().ok_or_else(|| {
36 pyo3::exceptions::PyRuntimeError::new_err(
37 "ServerBuilder already consumed by build(); create a new builder",
38 )
39 })
40}
41
42#[pymethods]
43impl PyServerBuilder {
44 #[new]
45 fn new() -> Self {
46 Self {
47 builder: Some(PvaServer::builder()),
48 python_sources: Vec::new(),
49 }
50 }
51
52 fn ai(mut slf: PyRefMut<'_, Self>, name: String, initial: f64) -> PyResult<PyRefMut<'_, Self>> {
54 let b = take_builder(&mut slf)?;
55 slf.builder = Some(b.ai(name, initial));
56 Ok(slf)
57 }
58
59 fn ao(mut slf: PyRefMut<'_, Self>, name: String, initial: f64) -> PyResult<PyRefMut<'_, Self>> {
61 let b = take_builder(&mut slf)?;
62 slf.builder = Some(b.ao(name, initial));
63 Ok(slf)
64 }
65
66 fn bi(
68 mut slf: PyRefMut<'_, Self>,
69 name: String,
70 initial: bool,
71 ) -> PyResult<PyRefMut<'_, Self>> {
72 let b = take_builder(&mut slf)?;
73 slf.builder = Some(b.bi(name, initial));
74 Ok(slf)
75 }
76
77 fn bo(
79 mut slf: PyRefMut<'_, Self>,
80 name: String,
81 initial: bool,
82 ) -> PyResult<PyRefMut<'_, Self>> {
83 let b = take_builder(&mut slf)?;
84 slf.builder = Some(b.bo(name, initial));
85 Ok(slf)
86 }
87
88 fn string_in(
90 mut slf: PyRefMut<'_, Self>,
91 name: String,
92 initial: String,
93 ) -> PyResult<PyRefMut<'_, Self>> {
94 let b = take_builder(&mut slf)?;
95 slf.builder = Some(b.string_in(name, initial));
96 Ok(slf)
97 }
98
99 fn string_out(
101 mut slf: PyRefMut<'_, Self>,
102 name: String,
103 initial: String,
104 ) -> PyResult<PyRefMut<'_, Self>> {
105 let b = take_builder(&mut slf)?;
106 slf.builder = Some(b.string_out(name, initial));
107 Ok(slf)
108 }
109
110 fn waveform<'py>(
112 mut slf: PyRefMut<'py, Self>,
113 name: String,
114 data: &Bound<'py, PyAny>,
115 ) -> PyResult<PyRefMut<'py, Self>> {
116 let arr = py_to_scalar_array(data)?;
117 let b = take_builder(&mut slf)?;
118 slf.builder = Some(b.waveform(name, arr));
119 Ok(slf)
120 }
121
122 fn aai<'py>(
124 mut slf: PyRefMut<'py, Self>,
125 name: String,
126 data: &Bound<'py, PyAny>,
127 ) -> PyResult<PyRefMut<'py, Self>> {
128 let arr = py_to_scalar_array(data)?;
129 let b = take_builder(&mut slf)?;
130 slf.builder = Some(b.aai(name, arr));
131 Ok(slf)
132 }
133
134 fn aao<'py>(
136 mut slf: PyRefMut<'py, Self>,
137 name: String,
138 data: &Bound<'py, PyAny>,
139 ) -> PyResult<PyRefMut<'py, Self>> {
140 let arr = py_to_scalar_array(data)?;
141 let b = take_builder(&mut slf)?;
142 slf.builder = Some(b.aao(name, arr));
143 Ok(slf)
144 }
145
146 #[pyo3(signature = (name, data, indx=0, nelm=None))]
149 fn sub_array<'py>(
150 mut slf: PyRefMut<'py, Self>,
151 name: String,
152 data: &Bound<'py, PyAny>,
153 indx: usize,
154 nelm: Option<usize>,
155 ) -> PyResult<PyRefMut<'py, Self>> {
156 let arr = py_to_scalar_array(data)?;
157 let n = nelm.unwrap_or(arr.len());
158 let b = take_builder(&mut slf)?;
159 slf.builder = Some(b.sub_array(name, arr, indx, n));
160 Ok(slf)
161 }
162
163 fn nt_table<'py>(
165 mut slf: PyRefMut<'py, Self>,
166 name: String,
167 columns: &Bound<'py, PyAny>,
168 ) -> PyResult<PyRefMut<'py, Self>> {
169 let dict = columns.downcast::<pyo3::types::PyDict>().map_err(|_| {
170 pyo3::exceptions::PyTypeError::new_err("columns must be a dict of {name: list}")
171 })?;
172 let mut cols: Vec<(String, ScalarArrayValue)> = Vec::new();
173 for (key, val) in dict.iter() {
174 let col_name: String = key.extract()?;
175 let col_data = py_to_scalar_array(&val)?;
176 cols.push((col_name, col_data));
177 }
178 let b = take_builder(&mut slf)?;
179 slf.builder = Some(b.nt_table(name, cols));
180 Ok(slf)
181 }
182
183 fn nt_ndarray<'py>(
186 mut slf: PyRefMut<'py, Self>,
187 name: String,
188 data: &Bound<'py, PyAny>,
189 dims: Vec<(i32, i32)>,
190 ) -> PyResult<PyRefMut<'py, Self>> {
191 let arr = py_to_scalar_array(data)?;
192 let b = take_builder(&mut slf)?;
193 slf.builder = Some(b.nt_ndarray(name, arr, dims));
194 Ok(slf)
195 }
196
197 fn mbbi(
200 mut slf: PyRefMut<'_, Self>,
201 name: String,
202 choices: Vec<String>,
203 initial: i32,
204 ) -> PyResult<PyRefMut<'_, Self>> {
205 let b = take_builder(&mut slf)?;
206 slf.builder = Some(b.mbbi(name, choices, initial));
207 Ok(slf)
208 }
209
210 fn mbbo(
213 mut slf: PyRefMut<'_, Self>,
214 name: String,
215 choices: Vec<String>,
216 initial: i32,
217 ) -> PyResult<PyRefMut<'_, Self>> {
218 let b = take_builder(&mut slf)?;
219 slf.builder = Some(b.mbbo(name, choices, initial));
220 Ok(slf)
221 }
222
223 fn generic<'py>(
226 mut slf: PyRefMut<'py, Self>,
227 name: String,
228 struct_id: String,
229 fields: &Bound<'py, pyo3::types::PyDict>,
230 ) -> PyResult<PyRefMut<'py, Self>> {
231 let mut field_vec: Vec<(String, spvirit_types::PvValue)> = Vec::new();
232 for (key, val) in fields.iter() {
233 let field_name: String = key.extract()?;
234 let pv_val = py_to_pv_value(&val)?;
235 field_vec.push((field_name, pv_val));
236 }
237 let b = take_builder(&mut slf)?;
238 slf.builder = Some(b.generic(name, struct_id, field_vec));
239 Ok(slf)
240 }
241
242 fn db_file(mut slf: PyRefMut<'_, Self>, path: String) -> PyResult<PyRefMut<'_, Self>> {
244 let b = take_builder(&mut slf)?;
245 slf.builder = Some(b.db_file(path));
246 Ok(slf)
247 }
248
249 fn db_string(mut slf: PyRefMut<'_, Self>, content: String) -> PyResult<PyRefMut<'_, Self>> {
251 let b = take_builder(&mut slf)?;
252 slf.builder = Some(b.db_string(&content));
253 Ok(slf)
254 }
255
256 fn on_put(
257 mut slf: PyRefMut<'_, Self>,
258 name: String,
259 callback: PyObject,
260 ) -> PyResult<PyRefMut<'_, Self>> {
261 let b = take_builder(&mut slf)?;
262 slf.builder = Some(
263 b.on_put(name, move |pv_name: &str, decoded: &DecodedValue| {
264 Python::with_gil(|py| {
265 let py_val = decoded_to_py(py, decoded);
266 if let Err(e) = callback.call1(py, (pv_name, py_val)) {
267 tracing::error!("on_put callback error: {e}");
268 }
269 });
270 }),
271 );
272 Ok(slf)
273 }
274
275 fn scan(
276 mut slf: PyRefMut<'_, Self>,
277 name: String,
278 period: f64,
279 callback: PyObject,
280 ) -> PyResult<PyRefMut<'_, Self>> {
281 let b = take_builder(&mut slf)?;
282 let dur = Duration::from_secs_f64(period);
283 slf.builder = Some(b.scan(name, dur, move |pv_name: &str| {
284 Python::with_gil(|py| match callback.call1(py, (pv_name,)) {
285 Ok(ret) => py_to_scalar(ret.bind(py)).unwrap_or(ScalarValue::F64(0.0)),
286 Err(e) => {
287 tracing::error!("scan callback error: {e}");
288 ScalarValue::F64(0.0)
289 }
290 })
291 }));
292 Ok(slf)
293 }
294
295 fn port(mut slf: PyRefMut<'_, Self>, port: u16) -> PyResult<PyRefMut<'_, Self>> {
297 let b = take_builder(&mut slf)?;
298 slf.builder = Some(b.port(port));
299 Ok(slf)
300 }
301
302 fn udp_port(mut slf: PyRefMut<'_, Self>, port: u16) -> PyResult<PyRefMut<'_, Self>> {
304 let b = take_builder(&mut slf)?;
305 slf.builder = Some(b.udp_port(port));
306 Ok(slf)
307 }
308
309 fn listen_ip(mut slf: PyRefMut<'_, Self>, ip: String) -> PyResult<PyRefMut<'_, Self>> {
312 let ip_addr: IpAddr = ip
313 .parse()
314 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid IP: {e}")))?;
315 let b = take_builder(&mut slf)?;
316 slf.builder = Some(b.listen_ip(ip_addr));
317 Ok(slf)
318 }
319
320 fn advertise_ip(mut slf: PyRefMut<'_, Self>, ip: String) -> PyResult<PyRefMut<'_, Self>> {
323 let ip_addr: IpAddr = ip
324 .parse()
325 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid IP: {e}")))?;
326 let b = take_builder(&mut slf)?;
327 slf.builder = Some(b.advertise_ip(ip_addr));
328 Ok(slf)
329 }
330
331 fn compute_alarms(mut slf: PyRefMut<'_, Self>, enabled: bool) -> PyResult<PyRefMut<'_, Self>> {
333 let b = take_builder(&mut slf)?;
334 slf.builder = Some(b.compute_alarms(enabled));
335 Ok(slf)
336 }
337
338 fn beacon_period(mut slf: PyRefMut<'_, Self>, secs: f64) -> PyResult<PyRefMut<'_, Self>> {
341 let b = take_builder(&mut slf)?;
342 slf.builder = Some(b.beacon_period(secs.round().max(1.0) as u64));
343 Ok(slf)
344 }
345
346 fn __repr__(&self) -> &'static str {
347 if self.builder.is_some() {
348 "<spvirit.ServerBuilder>"
349 } else {
350 "<spvirit.ServerBuilder (consumed)>"
351 }
352 }
353
354 fn add_source(
363 mut slf: PyRefMut<'_, Self>,
364 label: String,
365 order: i32,
366 source: PyObject,
367 ) -> PyResult<PyRefMut<'_, Self>> {
368 let adapter = Arc::new(PySourceAdapter::new(source));
369 slf.python_sources
370 .push((label.clone(), order, adapter.clone()));
371 let b = take_builder(&mut slf)?;
372 let as_dyn: Arc<dyn spvirit_server::pvstore::Source> = adapter;
374 slf.builder = Some(b.source(label, order, as_dyn));
375 Ok(slf)
376 }
377
378 fn build(&mut self) -> PyResult<PyServer> {
380 let b = self
381 .builder
382 .take()
383 .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("builder already consumed"))?;
384 let mut server = b.build();
385 let store = server.store().clone();
386 let registry = server.monitor_registry();
389 let notifier = PyNotifier::new(registry);
390 let sources = std::mem::take(&mut self.python_sources);
391 for (_, _, adapter) in &sources {
393 adapter.invoke_on_start(notifier.clone());
394 }
395 Ok(PyServer {
396 server: Some(server),
397 store: Some(store),
398 notifier: Some(notifier),
399 post_build_sources: sources,
400 })
401 }
402}
403
404#[pyclass(name = "Server")]
410pub struct PyServer {
411 server: Option<PvaServer>,
412 store: Option<Arc<SimplePvStore>>,
413 notifier: Option<PyNotifier>,
415 #[allow(dead_code)]
418 post_build_sources: Vec<(String, i32, Arc<PySourceAdapter>)>,
419}
420
421#[pymethods]
422impl PyServer {
423 #[new]
429 #[pyo3(signature = (*, pvs=None, db_file=None, db_string=None, sources=None,
430 port=None, udp_port=None, listen_ip=None, advertise_ip=None,
431 compute_alarms=None, beacon_period=None))]
432 #[allow(clippy::too_many_arguments)]
433 fn new(
434 py: Python<'_>,
435 pvs: Option<Vec<crate::pv::PyPv>>,
436 db_file: Option<String>,
437 db_string: Option<String>,
438 sources: Option<Vec<(String, i32, PyObject)>>,
439 port: Option<u16>,
440 udp_port: Option<u16>,
441 listen_ip: Option<String>,
442 advertise_ip: Option<String>,
443 compute_alarms: Option<bool>,
444 beacon_period: Option<f64>,
445 ) -> PyResult<Self> {
446 let handles: Vec<spvirit_server::pv::AnyPv> =
447 pvs.unwrap_or_default().iter().map(|p| p.any()).collect();
448 let mut sb = PvaServer::serve(handles);
449 if let Some(p) = db_file {
450 sb = sb.db_file(p);
451 }
452 if let Some(s) = db_string {
453 sb = sb.db_string(&s);
454 }
455 if let Some(p) = port {
456 sb = sb.port(p);
457 }
458 if let Some(p) = udp_port {
459 sb = sb.udp_port(p);
460 }
461 if let Some(ip) = listen_ip {
462 let addr: IpAddr = ip
463 .parse()
464 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid IP: {e}")))?;
465 sb = sb.listen_ip(addr);
466 }
467 if let Some(ip) = advertise_ip {
468 let addr: IpAddr = ip
469 .parse()
470 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid IP: {e}")))?;
471 sb = sb.advertise_ip(addr);
472 }
473 if let Some(c) = compute_alarms {
474 sb = sb.compute_alarms(c);
475 }
476 if let Some(secs) = beacon_period {
477 sb = sb.beacon_period(secs.round().max(1.0) as u64);
478 }
479 let mut python_sources: Vec<(String, i32, Arc<PySourceAdapter>)> = Vec::new();
480 for (label, order, obj) in sources.unwrap_or_default() {
481 let adapter = Arc::new(PySourceAdapter::new(obj));
482 python_sources.push((label.clone(), order, adapter.clone()));
483 let as_dyn: Arc<dyn spvirit_server::pvstore::Source> = adapter;
484 sb = sb.source(label, order, as_dyn);
485 }
486 let mut server = py.allow_threads(|| RUNTIME.block_on(sb.build()));
487 let store = server.store().clone();
488 let registry = server.monitor_registry();
489 let notifier = PyNotifier::new(registry);
490 for (_, _, adapter) in &python_sources {
491 adapter.invoke_on_start(notifier.clone());
492 }
493 Ok(PyServer {
494 server: Some(server),
495 store: Some(store),
496 notifier: Some(notifier),
497 post_build_sources: python_sources,
498 })
499 }
500
501 #[staticmethod]
503 fn builder() -> PyServerBuilder {
504 PyServerBuilder::new()
505 }
506
507 fn start(&mut self) -> PyResult<()> {
509 self.start_background().map(|_| ())
510 }
511
512 fn __repr__(&self) -> &'static str {
513 if self.server.is_some() {
514 "<spvirit.Server>"
515 } else {
516 "<spvirit.Server (running)>"
517 }
518 }
519
520 fn pv(&self, py: Python<'_>, name: String) -> PyResult<crate::pv::PyPv> {
522 use crate::pv::{PvKind, PyPv, pv_err};
523 use spvirit_types::{NtPayload, ScalarValue};
524 let server = self
525 .server
526 .as_ref()
527 .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("server already consumed"))?;
528 let store = server.store().clone();
529 let sniff = block_on_py(py, store.get_nt(&name));
530 let kind = match sniff {
531 None => {
532 return Err(pyo3::exceptions::PyKeyError::new_err(format!(
533 "PV '{name}' not found"
534 )));
535 }
536 Some(NtPayload::Scalar(nt)) => match nt.value {
537 ScalarValue::F64(_) | ScalarValue::F32(_) => {
538 let h = block_on_py(py, server.pv::<f64>(&name)).map_err(pv_err)?;
539 PvKind::F64(h)
540 }
541 ScalarValue::Bool(_) => {
542 let h = block_on_py(py, server.pv::<bool>(&name)).map_err(pv_err)?;
543 PvKind::Bool(h)
544 }
545 ScalarValue::I8(_) | ScalarValue::I16(_) | ScalarValue::I32(_) => {
546 let h = block_on_py(py, server.pv::<i32>(&name)).map_err(pv_err)?;
547 PvKind::I32(h)
548 }
549 ScalarValue::Str(_) => {
550 let h = block_on_py(py, server.pv::<String>(&name)).map_err(pv_err)?;
551 PvKind::Str(h)
552 }
553 other => {
554 return Err(pyo3::exceptions::PyKeyError::new_err(format!(
555 "PV '{name}' has unsupported value type {other:?} for typed handles"
556 )));
557 }
558 },
559 Some(NtPayload::Enum(_)) => {
560 let h = block_on_py(py, server.pv::<i32>(&name)).map_err(pv_err)?;
561 PvKind::I32(h)
562 }
563 Some(NtPayload::ScalarArray(_)) => {
564 let h = block_on_py(py, server.array_pv(&name)).map_err(pv_err)?;
565 PvKind::Array(h)
566 }
567 Some(other) => {
568 return Err(pyo3::exceptions::PyKeyError::new_err(format!(
569 "PV '{name}' has unsupported payload {other:?} for typed handles"
570 )));
571 }
572 };
573 Ok(PyPv { kind })
574 }
575
576 fn store(&self) -> PyResult<PyStore> {
578 let store = self
579 .store
580 .as_ref()
581 .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("server already consumed"))?
582 .clone();
583 Ok(PyStore { inner: store })
584 }
585
586 fn notifier(&self) -> PyResult<PyNotifier> {
588 self.notifier
589 .clone()
590 .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("server already consumed"))
591 }
592
593 fn add_source(&mut self, label: String, order: i32, source: PyObject) -> PyResult<()> {
596 let server = self
597 .server
598 .as_mut()
599 .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("server already consumed"))?;
600 let adapter = Arc::new(PySourceAdapter::new(source));
601 if let Some(notifier) = self.notifier.clone() {
602 adapter.invoke_on_start(notifier);
603 }
604 let as_dyn: Arc<dyn spvirit_server::pvstore::Source> = adapter.clone();
605 server.add_source(label.clone(), order, as_dyn);
606 self.post_build_sources.push((label, order, adapter));
607 Ok(())
608 }
609
610 fn run(&mut self, py: Python<'_>) -> PyResult<()> {
612 let server = self
613 .server
614 .take()
615 .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("server already consumed"))?;
616 py.allow_threads(|| {
617 RUNTIME
618 .block_on(server.run())
619 .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
620 })
621 }
622
623 fn start_background(&mut self) -> PyResult<PyStore> {
625 let server = self
626 .server
627 .take()
628 .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("server already consumed"))?;
629 let store = self
630 .store
631 .as_ref()
632 .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("server already consumed"))?
633 .clone();
634
635 std::thread::spawn(move || {
636 if let Err(e) = RUNTIME.block_on(server.run()) {
637 tracing::error!("background server error: {e}");
638 }
639 });
640
641 Ok(PyStore { inner: store })
642 }
643}
644
645#[pyclass(name = "Store")]
650pub struct PyStore {
651 inner: Arc<SimplePvStore>,
652}
653
654#[pymethods]
655impl PyStore {
656 fn get_value(&self, py: Python<'_>, name: String) -> PyResult<PyObject> {
658 let store = self.inner.clone();
659 let val = block_on_py(py, store.get_value(&name));
660 Ok(match val {
661 Some(v) => scalar_to_py(py, &v),
662 None => py.None(),
663 })
664 }
665
666 fn get_nt(&self, py: Python<'_>, name: String) -> PyResult<PyObject> {
668 let store = self.inner.clone();
669 let val = block_on_py(py, store.get_nt(&name));
670 Ok(match val {
671 Some(payload) => nt_payload_to_py(py, payload),
672 None => py.None(),
673 })
674 }
675
676 fn set_value(&self, py: Python<'_>, name: String, value: &Bound<'_, PyAny>) -> PyResult<bool> {
678 let sv = py_to_scalar(value)?;
679 let store = self.inner.clone();
680 Ok(block_on_py(py, store.set_value(&name, sv)))
681 }
682
683 fn set_array_value(
685 &self,
686 py: Python<'_>,
687 name: String,
688 value: &Bound<'_, PyAny>,
689 ) -> PyResult<bool> {
690 let arr = py_to_scalar_array(value)?;
691 let store = self.inner.clone();
692 Ok(block_on_py(py, store.set_array_value(&name, arr)))
693 }
694
695 fn put_nt(&self, py: Python<'_>, name: String, nt: &Bound<'_, PyAny>) -> PyResult<bool> {
698 let payload = py_to_nt_payload(nt)?;
699 let store = self.inner.clone();
700 Ok(block_on_py(py, store.put_nt(&name, payload)))
701 }
702
703 fn pv_names(&self, py: Python<'_>) -> PyResult<Vec<String>> {
705 let store = self.inner.clone();
706 Ok(block_on_py(py, store.pv_names()))
707 }
708
709 fn __repr__(&self, py: Python<'_>) -> String {
710 let store = self.inner.clone();
711 let n = block_on_py(py, store.pv_names()).len();
712 format!("<spvirit.Store ({n} PVs)>")
713 }
714}
715
716fn py_to_pv_value(obj: &Bound<'_, PyAny>) -> PyResult<spvirit_types::PvValue> {
723 if let Ok(list) = obj.downcast::<pyo3::types::PyList>() {
724 let arr = py_to_scalar_array(list.as_any())?;
725 Ok(spvirit_types::PvValue::ScalarArray(arr))
726 } else {
727 let sv = py_to_scalar(obj)?;
728 Ok(spvirit_types::PvValue::Scalar(sv))
729 }
730}