1use pyo3::prelude::*;
4use pyo3::types::{PyDict, PyList};
5use spvirit_types::{
6 NtAlarm, NtControl, NtDisplay, NtNdArray, NtPayload, NtScalar, NtScalarArray, NtTable,
7 NtTimeStamp, PvValue,
8};
9
10use crate::convert::{py_to_scalar, py_to_scalar_array, scalar_array_to_py, scalar_to_py};
11
12#[pyclass(name = "Alarm")]
17#[derive(Clone)]
18pub struct PyAlarm {
19 #[pyo3(get)]
21 pub severity: i32,
22 #[pyo3(get)]
24 pub status: i32,
25 #[pyo3(get)]
27 pub message: String,
28}
29
30impl From<&NtAlarm> for PyAlarm {
31 fn from(a: &NtAlarm) -> Self {
32 Self {
33 severity: a.severity,
34 status: a.status,
35 message: a.message.clone(),
36 }
37 }
38}
39
40#[pymethods]
41impl PyAlarm {
42 #[new]
43 #[pyo3(signature = (severity=0, status=0, message=String::new()))]
44 fn py_new(severity: i32, status: i32, message: String) -> Self {
45 Self {
46 severity,
47 status,
48 message,
49 }
50 }
51
52 fn __repr__(&self) -> String {
53 format!(
54 "Alarm(severity={}, status={}, message={:?})",
55 self.severity, self.status, self.message
56 )
57 }
58}
59
60#[pyclass(name = "TimeStamp")]
64#[derive(Clone)]
65pub struct PyTimeStamp {
66 #[pyo3(get)]
68 pub seconds_past_epoch: i64,
69 #[pyo3(get)]
71 pub nanoseconds: i32,
72 #[pyo3(get)]
74 pub user_tag: i32,
75}
76
77impl From<&NtTimeStamp> for PyTimeStamp {
78 fn from(ts: &NtTimeStamp) -> Self {
79 Self {
80 seconds_past_epoch: ts.seconds_past_epoch,
81 nanoseconds: ts.nanoseconds,
82 user_tag: ts.user_tag,
83 }
84 }
85}
86
87#[pymethods]
88impl PyTimeStamp {
89 #[new]
90 #[pyo3(signature = (seconds_past_epoch=0, nanoseconds=0, user_tag=0))]
91 fn py_new(seconds_past_epoch: i64, nanoseconds: i32, user_tag: i32) -> Self {
92 Self {
93 seconds_past_epoch,
94 nanoseconds,
95 user_tag,
96 }
97 }
98
99 fn __repr__(&self) -> String {
100 format!(
101 "TimeStamp(seconds={}, ns={})",
102 self.seconds_past_epoch, self.nanoseconds
103 )
104 }
105}
106
107#[pyclass(name = "Display")]
111#[derive(Clone)]
112pub struct PyDisplay {
113 #[pyo3(get)]
115 pub limit_low: f64,
116 #[pyo3(get)]
118 pub limit_high: f64,
119 #[pyo3(get)]
121 pub description: String,
122 #[pyo3(get)]
124 pub units: String,
125 #[pyo3(get)]
127 pub precision: i32,
128}
129
130impl From<&NtDisplay> for PyDisplay {
131 fn from(d: &NtDisplay) -> Self {
132 Self {
133 limit_low: d.limit_low,
134 limit_high: d.limit_high,
135 description: d.description.clone(),
136 units: d.units.clone(),
137 precision: d.precision,
138 }
139 }
140}
141
142#[pymethods]
143impl PyDisplay {
144 #[new]
145 #[pyo3(signature = (limit_low=0.0, limit_high=0.0, description=String::new(), units=String::new(), precision=0))]
146 fn py_new(
147 limit_low: f64,
148 limit_high: f64,
149 description: String,
150 units: String,
151 precision: i32,
152 ) -> Self {
153 Self {
154 limit_low,
155 limit_high,
156 description,
157 units,
158 precision,
159 }
160 }
161
162 fn __repr__(&self) -> String {
163 format!(
164 "Display(low={}, high={}, units={:?})",
165 self.limit_low, self.limit_high, self.units
166 )
167 }
168}
169
170#[pyclass(name = "Control")]
174#[derive(Clone)]
175pub struct PyControl {
176 #[pyo3(get)]
178 pub limit_low: f64,
179 #[pyo3(get)]
181 pub limit_high: f64,
182 #[pyo3(get)]
184 pub min_step: f64,
185}
186
187impl From<&NtControl> for PyControl {
188 fn from(c: &NtControl) -> Self {
189 Self {
190 limit_low: c.limit_low,
191 limit_high: c.limit_high,
192 min_step: c.min_step,
193 }
194 }
195}
196
197#[pymethods]
198impl PyControl {
199 #[new]
200 #[pyo3(signature = (limit_low=0.0, limit_high=0.0, min_step=0.0))]
201 fn py_new(limit_low: f64, limit_high: f64, min_step: f64) -> Self {
202 Self {
203 limit_low,
204 limit_high,
205 min_step,
206 }
207 }
208
209 fn __repr__(&self) -> String {
210 format!(
211 "Control(low={}, high={}, min_step={})",
212 self.limit_low, self.limit_high, self.min_step
213 )
214 }
215}
216
217#[pyclass(name = "NtScalar")]
223pub struct PyNtScalar {
224 inner: NtScalar,
225}
226
227impl PyNtScalar {
228 pub fn new(inner: NtScalar) -> Self {
229 Self { inner }
230 }
231}
232
233#[pymethods]
234impl PyNtScalar {
235 #[new]
237 #[pyo3(signature = (value, units=String::new(), display_low=0.0, display_high=0.0, display_description=String::new(), display_precision=0, control_low=0.0, control_high=0.0, control_min_step=0.0, alarm_severity=0, alarm_status=0, alarm_message=String::new()))]
238 fn py_new(
239 value: &Bound<'_, PyAny>,
240 units: String,
241 display_low: f64,
242 display_high: f64,
243 display_description: String,
244 display_precision: i32,
245 control_low: f64,
246 control_high: f64,
247 control_min_step: f64,
248 alarm_severity: i32,
249 alarm_status: i32,
250 alarm_message: String,
251 ) -> PyResult<Self> {
252 let sv = py_to_scalar(value)?;
253 let mut nt = NtScalar::from_value(sv);
254 nt.units = units;
255 nt.display_low = display_low;
256 nt.display_high = display_high;
257 nt.display_description = display_description;
258 nt.display_precision = display_precision;
259 nt.control_low = control_low;
260 nt.control_high = control_high;
261 nt.control_min_step = control_min_step;
262 nt.alarm_severity = alarm_severity;
263 nt.alarm_status = alarm_status;
264 nt.alarm_message = alarm_message;
265 Ok(Self { inner: nt })
266 }
267
268 #[getter]
270 fn value(&self, py: Python<'_>) -> PyObject {
271 scalar_to_py(py, &self.inner.value)
272 }
273
274 #[getter]
276 fn alarm_severity(&self) -> i32 {
277 self.inner.alarm_severity
278 }
279
280 #[getter]
282 fn alarm_status(&self) -> i32 {
283 self.inner.alarm_status
284 }
285
286 #[getter]
288 fn alarm_message(&self) -> &str {
289 &self.inner.alarm_message
290 }
291
292 #[getter]
294 fn units(&self) -> &str {
295 &self.inner.units
296 }
297
298 #[getter]
300 fn display_low(&self) -> f64 {
301 self.inner.display_low
302 }
303
304 #[getter]
306 fn display_high(&self) -> f64 {
307 self.inner.display_high
308 }
309
310 #[getter]
312 fn display_description(&self) -> &str {
313 &self.inner.display_description
314 }
315
316 #[getter]
318 fn display_precision(&self) -> i32 {
319 self.inner.display_precision
320 }
321
322 #[getter]
324 fn control_low(&self) -> f64 {
325 self.inner.control_low
326 }
327
328 #[getter]
330 fn control_high(&self) -> f64 {
331 self.inner.control_high
332 }
333
334 #[getter]
336 fn control_min_step(&self) -> f64 {
337 self.inner.control_min_step
338 }
339
340 fn __repr__(&self, py: Python<'_>) -> String {
341 let val = scalar_to_py(py, &self.inner.value);
342 format!("NtScalar(value={}, units={:?})", val, self.inner.units)
343 }
344}
345
346#[pyclass(name = "NtScalarArray")]
351pub struct PyNtScalarArray {
352 inner: NtScalarArray,
353}
354
355impl PyNtScalarArray {
356 pub fn new(inner: NtScalarArray) -> Self {
357 Self { inner }
358 }
359}
360
361#[pymethods]
362impl PyNtScalarArray {
363 #[new]
365 fn py_new(value: &Bound<'_, PyAny>) -> PyResult<Self> {
366 let arr = py_to_scalar_array(value)?;
367 Ok(Self {
368 inner: NtScalarArray::from_value(arr),
369 })
370 }
371
372 #[getter]
374 fn value(&self, py: Python<'_>) -> PyObject {
375 scalar_array_to_py(py, &self.inner.value)
376 }
377
378 #[getter]
380 fn alarm(&self) -> PyAlarm {
381 PyAlarm::from(&self.inner.alarm)
382 }
383
384 #[getter]
386 fn time_stamp(&self) -> PyTimeStamp {
387 PyTimeStamp::from(&self.inner.time_stamp)
388 }
389
390 #[getter]
392 fn display(&self) -> PyDisplay {
393 PyDisplay::from(&self.inner.display)
394 }
395
396 #[getter]
398 fn control(&self) -> PyControl {
399 PyControl::from(&self.inner.control)
400 }
401
402 fn __repr__(&self, py: Python<'_>) -> String {
403 let val = scalar_array_to_py(py, &self.inner.value);
404 format!("NtScalarArray(value={})", val)
405 }
406}
407
408#[pyclass(name = "NtTable")]
413pub struct PyNtTable {
414 inner: NtTable,
415}
416
417impl PyNtTable {
418 pub fn new(inner: NtTable) -> Self {
419 Self { inner }
420 }
421}
422
423#[pymethods]
424impl PyNtTable {
425 #[getter]
427 fn labels(&self, py: Python<'_>) -> PyResult<PyObject> {
428 let items: Vec<PyObject> = self
429 .inner
430 .labels
431 .iter()
432 .map(|s| pyo3::types::PyString::new(py, s).into_any().unbind())
433 .collect();
434 Ok(PyList::new(py, &items)?.into_any().unbind())
435 }
436
437 fn columns(&self, py: Python<'_>) -> PyResult<PyObject> {
439 let dict = pyo3::types::PyDict::new(py);
440 for col in &self.inner.columns {
441 dict.set_item(&col.name, scalar_array_to_py(py, &col.values))?;
442 }
443 Ok(dict.into_any().unbind())
444 }
445
446 #[getter]
448 fn descriptor(&self) -> Option<&str> {
449 self.inner.descriptor.as_deref()
450 }
451
452 #[getter]
454 fn alarm(&self) -> Option<PyAlarm> {
455 self.inner.alarm.as_ref().map(PyAlarm::from)
456 }
457
458 #[getter]
460 fn time_stamp(&self) -> Option<PyTimeStamp> {
461 self.inner.time_stamp.as_ref().map(PyTimeStamp::from)
462 }
463
464 fn __repr__(&self) -> String {
465 format!(
466 "NtTable(labels={:?}, columns={})",
467 self.inner.labels,
468 self.inner.columns.len()
469 )
470 }
471}
472
473#[pyclass(name = "NtNdArray")]
478pub struct PyNtNdArray {
479 inner: NtNdArray,
480}
481
482impl PyNtNdArray {
483 pub fn new(inner: NtNdArray) -> Self {
484 Self { inner }
485 }
486}
487
488#[pymethods]
489impl PyNtNdArray {
490 #[getter]
492 fn value(&self, py: Python<'_>) -> PyObject {
493 scalar_array_to_py(py, &self.inner.value)
494 }
495
496 #[getter]
498 fn unique_id(&self) -> i32 {
499 self.inner.unique_id
500 }
501
502 #[getter]
504 fn compressed_size(&self) -> i64 {
505 self.inner.compressed_size
506 }
507
508 #[getter]
510 fn uncompressed_size(&self) -> i64 {
511 self.inner.uncompressed_size
512 }
513
514 fn dimensions(&self, py: Python<'_>) -> PyResult<PyObject> {
517 let items: Vec<PyObject> = self
518 .inner
519 .dimension
520 .iter()
521 .map(|d| {
522 let dict = pyo3::types::PyDict::new(py);
523 dict.set_item("size", d.size).expect("set");
524 dict.set_item("offset", d.offset).expect("set");
525 dict.set_item("full_size", d.full_size).expect("set");
526 dict.set_item("binning", d.binning).expect("set");
527 dict.set_item("reverse", d.reverse).expect("set");
528 dict.into_any().unbind()
529 })
530 .collect();
531 Ok(PyList::new(py, &items)?.into_any().unbind())
532 }
533
534 #[getter]
536 fn data_time_stamp(&self) -> PyTimeStamp {
537 PyTimeStamp::from(&self.inner.data_time_stamp)
538 }
539
540 fn __repr__(&self) -> String {
541 let dims: Vec<i32> = self.inner.dimension.iter().map(|d| d.size).collect();
542 format!(
543 "NtNdArray(unique_id={}, dims={:?})",
544 self.inner.unique_id, dims
545 )
546 }
547}
548
549pub fn py_to_nt_payload(obj: &Bound<'_, PyAny>) -> PyResult<NtPayload> {
553 if let Ok(s) = obj.downcast::<PyNtScalar>() {
554 Ok(NtPayload::Scalar(s.borrow().inner.clone()))
555 } else if let Ok(a) = obj.downcast::<PyNtScalarArray>() {
556 Ok(NtPayload::ScalarArray(a.borrow().inner.clone()))
557 } else if let Ok(t) = obj.downcast::<PyNtTable>() {
558 Ok(NtPayload::Table(t.borrow().inner.clone()))
559 } else if let Ok(n) = obj.downcast::<PyNtNdArray>() {
560 Ok(NtPayload::NdArray(n.borrow().inner.clone()))
561 } else {
562 Err(pyo3::exceptions::PyTypeError::new_err(
563 "expected NtScalar, NtScalarArray, NtTable, or NtNdArray",
564 ))
565 }
566}
567
568pub fn nt_payload_to_py(py: Python<'_>, payload: NtPayload) -> PyObject {
569 match payload {
570 NtPayload::Scalar(s) => PyNtScalar::new(s)
571 .into_pyobject(py)
572 .expect("NtScalar")
573 .into_any()
574 .unbind(),
575 NtPayload::ScalarArray(a) => PyNtScalarArray::new(a)
576 .into_pyobject(py)
577 .expect("NtScalarArray")
578 .into_any()
579 .unbind(),
580 NtPayload::Table(t) => PyNtTable::new(t)
581 .into_pyobject(py)
582 .expect("NtTable")
583 .into_any()
584 .unbind(),
585 NtPayload::NdArray(n) => PyNtNdArray::new(n)
586 .into_pyobject(py)
587 .expect("NtNdArray")
588 .into_any()
589 .unbind(),
590 NtPayload::Enum(e) => {
591 let d = PyDict::new(py);
592 d.set_item("index", e.index).ok();
593 d.set_item("choices", &e.choices).ok();
594 d.set_item("selected", e.selected().unwrap_or("")).ok();
595 d.unbind().into_any()
596 }
597 NtPayload::Generic { struct_id, fields } => {
598 let d = PyDict::new(py);
599 d.set_item("struct_id", &struct_id).ok();
600 for (name, val) in &fields {
601 d.set_item(name, pvvalue_to_py(py, val)).ok();
602 }
603 d.unbind().into_any()
604 }
605 }
606}
607
608fn pvvalue_to_py(py: Python<'_>, val: &PvValue) -> PyObject {
609 match val {
610 PvValue::Scalar(s) => scalar_to_py(py, s),
611 PvValue::ScalarArray(a) => scalar_array_to_py(py, a),
612 PvValue::Structure { struct_id, fields } => {
613 let d = PyDict::new(py);
614 d.set_item("struct_id", struct_id).ok();
615 for (name, v) in fields {
616 d.set_item(name, pvvalue_to_py(py, v)).ok();
617 }
618 d.unbind().into_any()
619 }
620 }
621}