1use std::time::Instant;
2
3use epics_base_rs::error::{CaError, CaResult};
4use epics_base_rs::server::record::{
5 FieldDesc, ProcessAction, ProcessOutcome, Record, RecordProcessResult,
6};
7use epics_base_rs::types::{DbFieldType, EpicsValue};
8
9pub struct ThrottleRecord {
19 pub val: f64,
21 pub oval: f64,
23 pub sent: f64,
25 pub osent: f64,
27 pub wait: i16,
29 pub hopr: f64,
31 pub lopr: f64,
33 pub drvlh: f64,
35 pub drvll: f64,
37 pub drvls: i16,
39 pub drvlc: i16,
41 pub ver: String,
43 pub sts: i16,
45 pub prec: i16,
47 pub dprec: i16,
49 pub dly: f64,
51 pub out: String,
53 pub ov: i16,
55 pub sinp: String,
57 pub siv: i16,
59 pub sync: i16,
61
62 limit_flag: bool,
65 delay_active: bool,
67 last_send_time: Option<Instant>,
69 pending_value: Option<f64>,
71}
72
73impl Default for ThrottleRecord {
74 fn default() -> Self {
75 Self {
76 val: 0.0,
77 oval: 0.0,
78 sent: 0.0,
79 osent: 0.0,
80 wait: 0,
81 hopr: 0.0,
82 lopr: 0.0,
83 drvlh: 0.0,
84 drvll: 0.0,
85 drvls: 0, drvlc: 0, ver: "1.0.0".to_string(),
88 sts: 0, prec: 0,
90 dprec: 0,
91 dly: 0.0,
92 out: String::new(),
93 ov: 3, sinp: String::new(),
95 siv: 3, sync: 0, limit_flag: false,
98 delay_active: false,
99 last_send_time: None,
100 pending_value: None,
101 }
102 }
103}
104
105impl ThrottleRecord {
106 fn check_limits(&mut self, val: f64) -> Result<f64, ()> {
109 if !self.limit_flag {
110 self.drvls = 0; return Ok(val);
112 }
113
114 if val > self.drvlh {
115 self.drvls = 2; if self.drvlc != 0 {
117 return Ok(self.drvlh);
118 }
119 return Err(());
120 }
121
122 if val < self.drvll {
123 self.drvls = 1; if self.drvlc != 0 {
125 return Ok(self.drvll);
126 }
127 return Err(());
128 }
129
130 self.drvls = 0; Ok(val)
132 }
133
134 fn send_value(&mut self, value: f64) {
136 self.osent = self.sent;
137 self.sent = value;
138 self.last_send_time = Some(Instant::now());
139 self.sts = 2; }
141
142 fn delay_elapsed(&self) -> bool {
144 if self.dly <= 0.0 {
145 return true;
146 }
147 match self.last_send_time {
148 Some(t) => t.elapsed().as_secs_f64() >= self.dly,
149 None => true, }
151 }
152}
153
154static FIELDS: &[FieldDesc] = &[
155 FieldDesc {
156 name: "VAL",
157 dbf_type: DbFieldType::Double,
158 read_only: false,
159 },
160 FieldDesc {
161 name: "OVAL",
162 dbf_type: DbFieldType::Double,
163 read_only: true,
164 },
165 FieldDesc {
166 name: "SENT",
167 dbf_type: DbFieldType::Double,
168 read_only: true,
169 },
170 FieldDesc {
171 name: "OSENT",
172 dbf_type: DbFieldType::Double,
173 read_only: true,
174 },
175 FieldDesc {
176 name: "WAIT",
177 dbf_type: DbFieldType::Short,
178 read_only: true,
179 },
180 FieldDesc {
181 name: "HOPR",
182 dbf_type: DbFieldType::Double,
183 read_only: false,
184 },
185 FieldDesc {
186 name: "LOPR",
187 dbf_type: DbFieldType::Double,
188 read_only: false,
189 },
190 FieldDesc {
191 name: "DRVLH",
192 dbf_type: DbFieldType::Double,
193 read_only: false,
194 },
195 FieldDesc {
196 name: "DRVLL",
197 dbf_type: DbFieldType::Double,
198 read_only: false,
199 },
200 FieldDesc {
201 name: "DRVLS",
202 dbf_type: DbFieldType::Short,
203 read_only: true,
204 },
205 FieldDesc {
206 name: "DRVLC",
207 dbf_type: DbFieldType::Short,
208 read_only: false,
209 },
210 FieldDesc {
211 name: "VER",
212 dbf_type: DbFieldType::String,
213 read_only: true,
214 },
215 FieldDesc {
216 name: "STS",
217 dbf_type: DbFieldType::Short,
218 read_only: true,
219 },
220 FieldDesc {
221 name: "PREC",
222 dbf_type: DbFieldType::Short,
223 read_only: false,
224 },
225 FieldDesc {
226 name: "DPREC",
227 dbf_type: DbFieldType::Short,
228 read_only: false,
229 },
230 FieldDesc {
231 name: "DLY",
232 dbf_type: DbFieldType::Double,
233 read_only: false,
234 },
235 FieldDesc {
236 name: "OUT",
237 dbf_type: DbFieldType::String,
238 read_only: false,
239 },
240 FieldDesc {
241 name: "OV",
242 dbf_type: DbFieldType::Short,
243 read_only: true,
244 },
245 FieldDesc {
246 name: "SINP",
247 dbf_type: DbFieldType::String,
248 read_only: false,
249 },
250 FieldDesc {
251 name: "SIV",
252 dbf_type: DbFieldType::Short,
253 read_only: true,
254 },
255 FieldDesc {
256 name: "SYNC",
257 dbf_type: DbFieldType::Short,
258 read_only: false,
259 },
260];
261
262impl Record for ThrottleRecord {
263 fn record_type(&self) -> &'static str {
264 "throttle"
265 }
266
267 fn pre_process_actions(&mut self) -> Vec<ProcessAction> {
268 if self.sync == 1 {
271 self.sync = 0;
272 return vec![ProcessAction::ReadDbLink {
273 link_field: "SINP",
274 target_field: "VAL",
275 }];
276 }
277 Vec::new()
278 }
279
280 fn process(&mut self) -> CaResult<ProcessOutcome> {
281 let mut actions = Vec::new();
282
283 if self.delay_active {
285 if self.delay_elapsed() {
286 self.delay_active = false;
288 self.wait = 0;
289 if let Some(pv) = self.pending_value.take() {
290 self.send_value(pv);
291 actions.push(ProcessAction::WriteDbLink {
292 link_field: "OUT",
293 value: EpicsValue::Double(self.sent),
294 });
295 if self.dly > 0.0 {
297 self.delay_active = true;
298 self.wait = 1;
299 let delay = std::time::Duration::from_secs_f64(self.dly);
300 actions.push(ProcessAction::ReprocessAfter(delay));
301 return Ok(ProcessOutcome {
302 result: RecordProcessResult::Complete,
303 actions,
304 device_did_compute: false,
305 });
306 }
307 }
308 return Ok(ProcessOutcome::complete_with(actions));
309 } else {
310 self.pending_value = Some(self.val);
312 let remaining = self.dly
313 - self
314 .last_send_time
315 .map(|t| t.elapsed().as_secs_f64())
316 .unwrap_or(0.0);
317 let delay = std::time::Duration::from_secs_f64(remaining.max(0.001));
318 actions.push(ProcessAction::ReprocessAfter(delay));
319 return Ok(ProcessOutcome {
320 result: RecordProcessResult::Complete,
321 actions,
322 device_did_compute: false,
323 });
324 }
325 }
326
327 match self.check_limits(self.val) {
329 Ok(clamped) => {
330 self.oval = self.val;
331 self.val = clamped;
332 }
333 Err(()) => {
334 self.val = self.oval;
335 self.sts = 1; return Ok(ProcessOutcome::complete_with(actions));
337 }
338 }
339
340 if self.delay_elapsed() {
342 self.send_value(self.val);
344 actions.push(ProcessAction::WriteDbLink {
345 link_field: "OUT",
346 value: EpicsValue::Double(self.sent),
347 });
348
349 if self.dly > 0.0 {
351 self.delay_active = true;
352 self.wait = 1;
353 let delay = std::time::Duration::from_secs_f64(self.dly);
356 actions.push(ProcessAction::ReprocessAfter(delay));
357 return Ok(ProcessOutcome {
358 result: RecordProcessResult::Complete,
359 actions,
360 device_did_compute: false,
361 });
362 }
363
364 self.wait = 0;
365 Ok(ProcessOutcome::complete_with(actions))
366 } else {
367 self.pending_value = Some(self.val);
369 self.wait = 1;
370 self.delay_active = true;
371 let remaining = self.dly
372 - self
373 .last_send_time
374 .map(|t| t.elapsed().as_secs_f64())
375 .unwrap_or(0.0);
376 let delay = std::time::Duration::from_secs_f64(remaining.max(0.001));
377 actions.push(ProcessAction::ReprocessAfter(delay));
378 Ok(ProcessOutcome {
379 result: RecordProcessResult::Complete,
380 actions,
381 device_did_compute: false,
382 })
383 }
384 }
385
386 fn can_device_write(&self) -> bool {
387 true
388 }
389
390 fn special(&mut self, field: &str, after: bool) -> CaResult<()> {
391 if !after {
392 return Ok(());
393 }
394 match field {
395 "DLY" => {
396 if self.dly < 0.0 {
397 self.dly = 0.0;
398 }
399 }
400 "DRVLH" | "DRVLL" => {
401 self.limit_flag = self.drvlh > self.drvll;
402 if !self.limit_flag {
403 self.drvls = 0; }
405 }
406 _ => {}
407 }
408 Ok(())
409 }
410
411 fn get_field(&self, name: &str) -> Option<EpicsValue> {
412 match name {
413 "VAL" => Some(EpicsValue::Double(self.val)),
414 "OVAL" => Some(EpicsValue::Double(self.oval)),
415 "SENT" => Some(EpicsValue::Double(self.sent)),
416 "OSENT" => Some(EpicsValue::Double(self.osent)),
417 "WAIT" => Some(EpicsValue::Short(self.wait)),
418 "HOPR" => Some(EpicsValue::Double(self.hopr)),
419 "LOPR" => Some(EpicsValue::Double(self.lopr)),
420 "DRVLH" => Some(EpicsValue::Double(self.drvlh)),
421 "DRVLL" => Some(EpicsValue::Double(self.drvll)),
422 "DRVLS" => Some(EpicsValue::Short(self.drvls)),
423 "DRVLC" => Some(EpicsValue::Short(self.drvlc)),
424 "VER" => Some(EpicsValue::String(self.ver.clone())),
425 "STS" => Some(EpicsValue::Short(self.sts)),
426 "PREC" => Some(EpicsValue::Short(self.prec)),
427 "DPREC" => Some(EpicsValue::Short(self.dprec)),
428 "DLY" => Some(EpicsValue::Double(self.dly)),
429 "OUT" => Some(EpicsValue::String(self.out.clone())),
430 "OV" => Some(EpicsValue::Short(self.ov)),
431 "SINP" => Some(EpicsValue::String(self.sinp.clone())),
432 "SIV" => Some(EpicsValue::Short(self.siv)),
433 "SYNC" => Some(EpicsValue::Short(self.sync)),
434 _ => None,
435 }
436 }
437
438 fn put_field(&mut self, name: &str, value: EpicsValue) -> CaResult<()> {
439 match name {
440 "VAL" => match value {
441 EpicsValue::Double(v) => {
442 self.val = v;
443 Ok(())
444 }
445 _ => Err(CaError::TypeMismatch(name.into())),
446 },
447 "HOPR" => match value {
448 EpicsValue::Double(v) => {
449 self.hopr = v;
450 Ok(())
451 }
452 _ => Err(CaError::TypeMismatch(name.into())),
453 },
454 "LOPR" => match value {
455 EpicsValue::Double(v) => {
456 self.lopr = v;
457 Ok(())
458 }
459 _ => Err(CaError::TypeMismatch(name.into())),
460 },
461 "DRVLH" => match value {
462 EpicsValue::Double(v) => {
463 self.drvlh = v;
464 Ok(())
465 }
466 _ => Err(CaError::TypeMismatch(name.into())),
467 },
468 "DRVLL" => match value {
469 EpicsValue::Double(v) => {
470 self.drvll = v;
471 Ok(())
472 }
473 _ => Err(CaError::TypeMismatch(name.into())),
474 },
475 "DRVLC" => match value {
476 EpicsValue::Short(v) => {
477 self.drvlc = v;
478 Ok(())
479 }
480 _ => Err(CaError::TypeMismatch(name.into())),
481 },
482 "PREC" => match value {
483 EpicsValue::Short(v) => {
484 self.prec = v;
485 Ok(())
486 }
487 _ => Err(CaError::TypeMismatch(name.into())),
488 },
489 "DPREC" => match value {
490 EpicsValue::Short(v) => {
491 self.dprec = v;
492 Ok(())
493 }
494 _ => Err(CaError::TypeMismatch(name.into())),
495 },
496 "DLY" => match value {
497 EpicsValue::Double(v) => {
498 self.dly = v;
499 Ok(())
500 }
501 _ => Err(CaError::TypeMismatch(name.into())),
502 },
503 "OUT" => match value {
504 EpicsValue::String(v) => {
505 self.out = v;
506 Ok(())
507 }
508 _ => Err(CaError::TypeMismatch(name.into())),
509 },
510 "SINP" => match value {
511 EpicsValue::String(v) => {
512 self.sinp = v;
513 Ok(())
514 }
515 _ => Err(CaError::TypeMismatch(name.into())),
516 },
517 "SYNC" => match value {
518 EpicsValue::Short(v) => {
519 self.sync = v;
520 Ok(())
521 }
522 _ => Err(CaError::TypeMismatch(name.into())),
523 },
524 "OVAL" | "SENT" | "OSENT" | "WAIT" | "DRVLS" | "VER" | "STS" | "OV" | "SIV" => {
526 Err(CaError::ReadOnlyField(name.into()))
527 }
528 _ => Err(CaError::FieldNotFound(name.into())),
529 }
530 }
531
532 fn field_list(&self) -> &'static [FieldDesc] {
533 FIELDS
534 }
535
536 fn init_record(&mut self, pass: u8) -> CaResult<()> {
537 if pass == 1 {
538 self.limit_flag = self.drvlh > self.drvll;
539 }
540 Ok(())
541 }
542}