1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use epics_macros_rs::EpicsRecord;
use crate::server::record::MENU_YES_NO;
use crate::types::PvString;
/// `int64inRecord.c:225` `get_control_double` lists one field the shared
/// VAL-class set does not: `SVAL`, which takes the record's own `HOPR`/`LOPR`
/// like `VAL` does. Without this it falls to the `default:` arm and reports
/// the DBF_INT64 range of ±9223372036854775807.
///
/// The third and last member of the SVAL family — `aiRecord.c:280` and
/// `longinRecord.c:220` list it too, and this record type was the one left
/// out. `int64outRecord.c:294-312` does NOT list SVAL (an output record has
/// no simulation buffer to serve), so the family ends here.
fn int64in_metadata_override(
rec: &Int64inRecord,
field: &str,
) -> Option<crate::server::record::FieldMetadataOverride> {
field
.eq_ignore_ascii_case("SVAL")
.then(|| crate::server::record::FieldMetadataOverride {
ctrl_limits: Some((rec.hopr as f64, rec.lopr as f64)),
..Default::default()
})
}
// int64in: 64-bit integer input.
// CA limitation: served as DBR_DOUBLE over Channel Access (f64, precision loss for |val|>2^53).
// Native i64 storage is lossless; precision is only lost at the CA wire boundary.
//
// Alarm threshold fields (HIHI/HIGH/LOW/LOLO/HHSV/HSV/LSV/LLSV) are intentionally absent
// from the field list so they route to RecordInstance::common.analog_alarm via
// put_common_field, matching the path used by longin/ao/ai.
#[derive(EpicsRecord)]
#[record(type = "int64in", metadata_override = int64in_metadata_override)]
pub struct Int64inRecord {
#[field(type = "Int64")]
pub val: i64,
#[field(type = "PvStr")]
pub egu: PvString,
// HOPR/LOPR/HYST/ADEL/MDEL are DBF_INT64 (int64inRecord.dbd.pod:140-268),
// not DBF_DOUBLE. Modeling them as i64 keys the string→native put parse on
// the integer row (`epicsParseInt64`), so a fractional or out-of-i64-range
// caput is REFUSED, matching C; served over CA as DBR_DOUBLE via
// `EpicsValue::Int64`'s wire mapping, the same as VAL.
#[field(type = "Int64")]
pub hopr: i64,
#[field(type = "Int64")]
pub lopr: i64,
#[field(type = "Int64")]
pub hyst: i64,
// LALM is DBF_INT64 too, but `special(SPC_NOMOD)` (dbd:233-236): read-only,
// so no client put reaches the parse. Kept f64 — it is the internal
// last-alarmed bookkeeping the alarm filter reads as a double.
#[field(type = "Double")]
pub lalm: f64,
#[field(type = "Int64")]
pub adel: i64,
#[field(type = "Int64")]
pub mdel: i64,
// Alarm-range time-constant filter (int64inRecord.c::checkAlarms:303-349).
// AFTC > 0 low-pass-filters the integer alarmRange so transient
// excursions don't immediately alarm; AFVL is the accumulator.
#[field(type = "Double")]
pub aftc: f64,
#[field(type = "Double")]
pub afvl: f64,
#[field(type = "Double")]
pub alst: f64,
#[field(type = "Double")]
pub mlst: f64,
// SIMM is `DBF_MENU menu(menuYesNo)` (int64inRecord.dbd.pod:279-283):
// the two-choice NO/YES simulation menu, served as DBR_ENUM.
#[field(type = "Short", menu_choices = MENU_YES_NO)]
pub simm: i16,
#[field(type = "String")]
pub siml: String,
#[field(type = "String")]
pub siol: String,
// SVAL is `DBF_INT64` (int64inRecord.dbd.pod:270-272) — the BUFFER C's
// `readValue` reads SIOL into (`dbGetLink(&prec->siol, DBR_INT64,
// &prec->sval)`, int64inRecord.c:409) before publishing `val = sval`.
#[field(type = "Int64")]
pub sval: i64,
#[field(type = "Short")]
pub sims: i16,
// SDLY — "Sim. Mode Async Delay" (`DBF_DOUBLE`, `initial("-1.0")`,
// int64inRecord.dbd.pod:301-307). A non-negative SDLY makes the simulated SIOL read asynchronous:
// C's `readValue` arms `callbackRequestProcessCallbackDelayed(..., sdly)`
// and holds PACT across the delay (int64inRecord.c:398-405). The framework reads the delay
// via `get_field("SDLY")`, so the field must exist for a `.db` to set it.
#[field(type = "Double")]
pub sdly: f64,
}
impl Default for Int64inRecord {
fn default() -> Self {
Self {
val: 0,
egu: PvString::new(),
hopr: 0,
lopr: 0,
hyst: 0,
lalm: 0.0,
adel: 0,
mdel: 0,
aftc: 0.0,
afvl: 0.0,
alst: 0.0,
mlst: 0.0,
simm: 0,
siml: String::new(),
siol: String::new(),
sval: 0,
sims: 0,
// C `field(SDLY,DBF_DOUBLE) { initial("-1.0") }` — negative means
// "synchronous simulation".
sdly: -1.0,
}
}
}
impl Int64inRecord {
pub fn new(val: i64) -> Self {
Self {
val,
..Default::default()
}
}
}