1use crate::io::{
20 duration_from_str, duration_to_str, maybe_duration_from_str, maybe_duration_to_str,
21};
22pub use crate::State;
23use der::{Decode, Encode, Enumerated, Reader};
24use hifitime::{Duration, Unit};
25use serde::Deserialize;
26use serde::Serialize;
27use std::fmt::Debug;
28use typed_builder::TypedBuilder;
29
30#[cfg(feature = "python")]
31use pyo3::{exceptions::PyValueError, prelude::*, types::PyBytes, types::PyType};
32
33#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize, Default, Enumerated)]
35#[repr(u8)]
36#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
37pub enum Handoff {
38 #[default]
40 Eager = 0,
41 Greedy = 1,
43 Overlap = 2,
45}
46
47#[cfg(feature = "python")]
48#[cfg_attr(feature = "python", pymethods)]
49impl Handoff {
50 fn __repr__(&self) -> String {
51 format!("{self:?}")
52 }
53
54 fn __str__(&self) -> String {
55 format!("{self:?}")
56 }
57}
58
59#[derive(Copy, Clone, Debug, Default, Deserialize, PartialEq, Serialize, TypedBuilder)]
61#[cfg_attr(feature = "python", pyclass)]
62#[builder(doc)]
63pub struct Scheduler {
64 #[builder(default)]
66 pub handoff: Handoff,
67 #[builder(default)]
69 pub cadence: Cadence,
70 #[builder(default = 10)]
72 pub min_samples: u32,
73 #[builder(default = Some(Unit::Second * 1.0), setter(strip_option))]
76 #[serde(
77 serialize_with = "maybe_duration_to_str",
78 deserialize_with = "maybe_duration_from_str"
79 )]
80 pub sample_alignment: Option<Duration>,
81}
82
83#[derive(Copy, Clone, Deserialize, PartialEq, Serialize, Default)]
85pub enum Cadence {
86 #[default]
87 Continuous,
88 Intermittent {
90 #[serde(
91 serialize_with = "duration_to_str",
92 deserialize_with = "duration_from_str"
93 )]
94 on: Duration,
95 #[serde(
96 serialize_with = "duration_to_str",
97 deserialize_with = "duration_from_str"
98 )]
99 off: Duration,
100 },
101}
102
103#[cfg(feature = "python")]
104#[pyclass(name = "Cadence")]
105#[derive(Clone, Debug)]
106pub struct PyCadence {
107 pub inner: Cadence,
108}
109
110impl<'a> Decode<'a> for Cadence {
111 fn decode<R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
112 let tag = decoder.decode::<u8>()?;
113 match tag {
114 0 => Ok(Self::Continuous),
115 1 => {
116 let on_ns = decoder.decode::<i128>()?;
117 let off_ns = decoder.decode::<i128>()?;
118 Ok(Self::Intermittent {
119 on: Duration::from_total_nanoseconds(on_ns),
120 off: Duration::from_total_nanoseconds(off_ns),
121 })
122 }
123 _ => Err(der::ErrorKind::Value {
124 tag: der::Tag::Integer,
125 }
126 .into()),
127 }
128 }
129}
130
131impl Encode for Cadence {
132 fn encoded_len(&self) -> der::Result<der::Length> {
133 match self {
134 Self::Continuous => 0u8.encoded_len(),
135 Self::Intermittent { on, off } => {
136 1u8.encoded_len()?
137 + on.total_nanoseconds().encoded_len()?
138 + off.total_nanoseconds().encoded_len()?
139 }
140 }
141 }
142
143 fn encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
144 match self {
145 Self::Continuous => 0u8.encode(encoder),
146 Self::Intermittent { on, off } => {
147 1u8.encode(encoder)?;
148 on.total_nanoseconds().encode(encoder)?;
149 off.total_nanoseconds().encode(encoder)
150 }
151 }
152 }
153}
154
155#[cfg(feature = "python")]
156#[pymethods]
157impl PyCadence {
158 #[staticmethod]
159 fn continuous() -> Self {
160 Self {
161 inner: Cadence::Continuous,
162 }
163 }
164
165 #[staticmethod]
166 fn intermittent(on: Duration, off: Duration) -> Self {
167 Self {
168 inner: Cadence::Intermittent { on, off },
169 }
170 }
171
172 fn __repr__(&self) -> String {
173 format!("{:?}", self.inner)
174 }
175
176 fn __str__(&self) -> String {
177 format!("{:?}", self.inner)
178 }
179
180 #[classmethod]
185 pub fn from_asn1(_cls: &Bound<'_, PyType>, data: &[u8]) -> PyResult<Self> {
186 match Cadence::from_der(data) {
187 Ok(obj) => Ok(Self { inner: obj }),
188 Err(e) => Err(PyValueError::new_err(format!("ASN.1 decoding error: {e}"))),
189 }
190 }
191
192 pub fn to_asn1<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
196 let mut buf = Vec::new();
197 match self.inner.encode_to_vec(&mut buf) {
198 Ok(_) => Ok(PyBytes::new(py, &buf)),
199 Err(e) => Err(PyValueError::new_err(format!("ASN.1 encoding error: {e}"))),
200 }
201 }
202}
203
204impl Debug for Cadence {
205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206 match self {
207 Self::Continuous => write!(f, "Continuous"),
208 Self::Intermittent { on, off } => f
209 .debug_struct("Intermittent")
210 .field("on", &format!("{on}"))
211 .field("off", &format!("{off}"))
212 .finish(),
213 }
214 }
215}
216
217impl<'a> Decode<'a> for Scheduler {
218 fn decode<R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
219 let handoff = decoder.decode()?;
220 let cadence = decoder.decode()?;
221 let min_samples = decoder.decode()?;
222 let sample_alignment_ns = if decoder.decode::<bool>()? {
223 Some(decoder.decode::<i128>()?)
224 } else {
225 None
226 };
227
228 Ok(Self {
229 handoff,
230 cadence,
231 min_samples,
232 sample_alignment: sample_alignment_ns.map(Duration::from_total_nanoseconds),
233 })
234 }
235}
236
237impl Encode for Scheduler {
238 fn encoded_len(&self) -> der::Result<der::Length> {
239 let mut len = (self.handoff.encoded_len()?
240 + self.cadence.encoded_len()?
241 + self.min_samples.encoded_len()?
242 + self.sample_alignment.is_some().encoded_len()?)?;
243
244 if let Some(sa) = self.sample_alignment {
245 len = (len + sa.total_nanoseconds().encoded_len()?)?;
246 }
247 Ok(len)
248 }
249
250 fn encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
251 self.handoff.encode(encoder)?;
252 self.cadence.encode(encoder)?;
253 self.min_samples.encode(encoder)?;
254 if let Some(sa) = self.sample_alignment {
255 true.encode(encoder)?;
256 sa.total_nanoseconds().encode(encoder)?;
257 } else {
258 false.encode(encoder)?;
259 }
260 Ok(())
261 }
262}
263
264#[cfg(feature = "python")]
265#[cfg_attr(feature = "python", pymethods)]
266impl Scheduler {
267 #[new]
268 #[pyo3(signature = (handoff=Handoff::Eager, cadence=None, min_samples=10, sample_alignment=None))]
269 fn py_new(
270 handoff: Handoff,
271 cadence: Option<PyCadence>,
272 min_samples: u32,
273 sample_alignment: Option<Duration>,
274 ) -> Self {
275 Self {
276 handoff,
277 cadence: cadence.map(|c| c.inner).unwrap_or_default(),
278 min_samples,
279 sample_alignment,
280 }
281 }
282
283 #[getter]
284 fn get_handoff(&self) -> Handoff {
285 self.handoff
286 }
287
288 #[setter]
289 fn set_handoff(&mut self, handoff: Handoff) {
290 self.handoff = handoff;
291 }
292
293 #[getter]
294 fn get_cadence(&self) -> PyCadence {
295 PyCadence {
296 inner: self.cadence,
297 }
298 }
299
300 #[setter]
301 fn set_cadence(&mut self, cadence: PyCadence) {
302 self.cadence = cadence.inner;
303 }
304
305 #[getter]
306 fn get_min_samples(&self) -> u32 {
307 self.min_samples
308 }
309
310 #[setter]
311 fn set_min_samples(&mut self, min_samples: u32) {
312 self.min_samples = min_samples;
313 }
314
315 #[getter]
316 fn get_sample_alignment(&self) -> Option<Duration> {
317 self.sample_alignment
318 }
319
320 #[setter]
321 fn set_sample_alignment(&mut self, sample_alignment: Option<Duration>) {
322 self.sample_alignment = sample_alignment;
323 }
324
325 fn __repr__(&self) -> String {
326 format!("{self:?}")
327 }
328
329 fn __str__(&self) -> String {
330 format!("{self:?}")
331 }
332
333 #[classmethod]
338 pub fn from_asn1(_cls: &Bound<'_, PyType>, data: &[u8]) -> PyResult<Self> {
339 match Self::from_der(data) {
340 Ok(obj) => Ok(obj),
341 Err(e) => Err(PyValueError::new_err(format!("ASN.1 decoding error: {e}"))),
342 }
343 }
344
345 pub fn to_asn1<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
349 let mut buf = Vec::new();
350 match self.encode_to_vec(&mut buf) {
351 Ok(_) => Ok(PyBytes::new(py, &buf)),
352 Err(e) => Err(PyValueError::new_err(format!("ASN.1 encoding error: {e}"))),
353 }
354 }
355}
356
357#[cfg(test)]
358mod scheduler_ut {
359 use process::simulator::scheduler::Handoff;
360
361 use crate::od::prelude::*;
362
363 use super::Scheduler;
364
365 #[test]
366 fn serde_cadence() {
367 use hifitime::TimeUnits;
368 use serde_yml;
369
370 let cont: Cadence = serde_yml::from_str("!Continuous").unwrap();
371 assert_eq!(cont, Cadence::Continuous);
372
373 let int: Cadence =
374 serde_yml::from_str("!Intermittent {on: 1 h 35 min, off: 15 h 02 min 3 s}").unwrap();
375 assert_eq!(
376 int,
377 Cadence::Intermittent {
378 on: 1.hours() + 35.0.minutes(),
379 off: 15.hours() + 2.minutes() + 3.seconds()
380 }
381 );
382 assert_eq!(
383 format!("{int:?}"),
384 r#"Intermittent { on: "1 h 35 min", off: "15 h 2 min 3 s" }"#
385 );
386
387 let serialized = serde_yml::to_string(&int).unwrap();
388 let deserd: Cadence = serde_yml::from_str(&serialized).unwrap();
389 assert_eq!(deserd, int);
390 }
391
392 #[test]
393 fn api_and_serde_scheduler() {
394 use hifitime::TimeUnits;
395 use serde_yml;
396
397 let scheduler = Scheduler::default();
398 let serialized = serde_yml::to_string(&scheduler).unwrap();
399 assert_eq!(
400 serialized,
401 "handoff: Eager\ncadence: Continuous\nmin_samples: 0\nsample_alignment: null\n"
402 );
403 let deserd: Scheduler = serde_yml::from_str(&serialized).unwrap();
404 assert_eq!(deserd, scheduler);
405
406 let scheduler = Scheduler::builder()
407 .handoff(Handoff::Eager)
408 .cadence(Cadence::Intermittent {
409 on: 0.2.hours(),
410 off: 17.hours() + 5.minutes(),
411 })
412 .build();
413
414 let serialized = serde_yml::to_string(&scheduler).unwrap();
415 assert_eq!(
416 serialized,
417 "handoff: Eager\ncadence: !Intermittent\n 'on': '12 min'\n 'off': '17 h 5 min'\nmin_samples: 10\nsample_alignment: '1 s'\n"
418 );
419 let deserd: Scheduler = serde_yml::from_str(&serialized).unwrap();
420 assert_eq!(deserd, scheduler);
421 }
422
423 #[test]
424 fn defaults() {
425 let sched = Scheduler::default();
426
427 assert_eq!(sched.cadence, Cadence::Continuous);
428
429 assert_eq!(sched.handoff, Handoff::Eager);
430 }
431}