Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Arrays and waveforms

Verified · waveform.rs · demo_waveform.py · check docs_verify · docs-verify

The badge reports the whole docs-verify suite, 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

BuilderRecordClients may write
.waveform(name, data)waveformyes
.aai(name, data)aaino
.aao(name, data)aaoyes

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.

Next

Enums and binary records.