Arrays and waveforms
✅ Verified ·
waveform.rs·demo_waveform.py· checkdocs_verify·The badge reports the whole
docs-verifysuite, not this chapter alone.
What you'll build
A 1024-point spectrum, updated ten times a second. Same shape as a detector trace, a scope capture, or an image row.
Rust
Serve it:
#![allow(unused)] fn main() { let server = PvaServer::builder() .waveform("SIM:SPECTRUM", ScalarArrayValue::F64(vec![0.0; 1024])) .build(); }
Update it:
#![allow(unused)] fn main() { tokio::spawn(async move { const N: usize = 1024; let mut tick = 0u64; loop { let phase = (tick as f64) * 0.03; let samples = (0..N) .map(|i| { let x = i as f64; (phase + x * 0.02).sin() + 0.25 * (phase * 0.5 + x * 0.05).cos() }) .collect::<Vec<_>>(); store .set_array_value("SIM:SPECTRUM", ScalarArrayValue::F64(samples)) .await; tick += 1; tokio::time::sleep(std::time::Duration::from_millis(100)).await; } }); }
store.set_array_value(name, ScalarArrayValue::F64(v)) replaces the whole
array. ScalarArrayValue has a variant per element type — F64, I32,
U8, Bool, Str, and the rest — and the variant is fixed when the
record is created.
Python
spectrum = spvirit.waveform("SIM:SPECTRUM", [0.0] * 1024)
server = spvirit.Server(pvs=[spectrum])
server.start()
tick = 0
while True:
phase = tick * 0.03
spectrum.set([
math.sin(phase + i * 0.02) + 0.25 * math.cos(phase * 0.5 + i * 0.05)
for i in range(1024)
])
tick += 1
time.sleep(0.1)
Note server.start() rather than server.run(). run() blocks forever;
start() returns so the loop below it can drive the PV.
The three array record types
| Builder | Record | Clients may write |
|---|---|---|
.waveform(name, data) | waveform | yes |
.aai(name, data) | aai | no |
.aao(name, data) | aao | yes |
Pick aai for anything a client should only read — a detector spectrum, a
computed histogram. Pick aao or waveform for a lookup table or a
scan trajectory a client is meant to load.
.sub_array(name, data, indx, nelm) serves a window into a larger array,
which is the EPICS subArray record.
What to notice
Python array setters take a list. A numpy array is not a list —
call .tolist() first:
spectrum.set(my_numpy_array.tolist())
The one exception is U8 arrays, which also accept bytes. type=
selects the element type explicitly when the Python values are ambiguous:
spvirit.waveform("IMG", data, type="ushort").
on_put and scan are not available on array PVs in Python. Both
raise TypeError. Drive an array from your own loop with pv.set(...),
as the example above does.
Arrays carry their own timestamp handling. Unlike NtScalar — where a
None timestamp makes the encoder stamp at encode time — array payloads
encode the timestamp they hold verbatim. The server stamps on every
set_array_value, so this only matters if you build payloads by hand at
the raw-NT level.
Every update sends the whole array. There is no delta encoding. A
1024-point f64 waveform at 10 Hz is about 80 kB/s per subscriber. MDEL
does not help here — the deadband gate only applies to numeric scalars,
so an array posts on every change.
Run it
# Terminal 1
cargo run -p spvirit-server --example waveform
# or: python spvirit-py/examples/demo_waveform.py
# Terminal 2
spget SIM:SPECTRUM
spmonitor SIM:SPECTRUM
Brace yourself: spget prints all 1024 elements on one line. There is
no truncation anywhere in the tool chain.
$ spget SIM:SPECTRUM
SIM:SPECTRUM 2026-08-06 09:14:04.728 [0.279967, 0.299451, 0.318292, 0.336483,
0.354022, 0.370907, ... 1.163854, 1.153073]
(Elided here for the page — your terminal shows every value.) Pipe it
somewhere if you want to keep it: spget SIM:SPECTRUM > spectrum.txt.
spinfo is the readable view. Note the type ID and the [] on value:
$ spinfo SIM:SPECTRUM
SIM:SPECTRUM:
struct epics:nt/NTScalarArray:1.0
value: double[]
alarm: structure
severity: int
status: int
message: string
timeStamp: structure
secondsPastEpoch: long
nanoseconds: int
userTag: int
display: structure
limitLow: double
limitHigh: double
description: string
units: string
precision: int
control: structure
limitLow: double
limitHigh: double
minStep: double
There is no valueAlarm — array records have no limit checking to do. And
spmonitor SIM:SPECTRUM re-sends the whole 1024-element array on every
update; PVAccess has no partial-array delta.