Registers & features
Goal of this tutorial:
- Read and write GenApi features such as
ExposureTimeorGain. - Understand how features map to the underlying registers.
- Use selectors (e.g.
GainSelector) and understand what they change. - Do all of it from both the
viva-camctlCLI and Rust.
Work through Discovery first, so you know your camera’s IP and which host interface you are using.
Concepts: features vs registers
GenICam describes camera configuration as features in the GenApi XML:
- A feature has a name (
ExposureTime,Gain,PixelFormat, …) and a type (Integer, Float, Boolean, Enumeration, Command, String, …). - Under the hood, a feature usually corresponds to one or more registers. A simple one reads a single 32-bit register; others are derived through SwissKnife expressions, or depend on selectors.
The layering:
viva-genapi-xmlparses the XML into anXmlModel.viva-genapibuilds aNodeMapfrom it and evaluates nodes on demand.viva-genicamandviva-camctlsit on top and hide the addressing.
Step 1 – Inspect features with viva-camctl
You need the camera IP from the discovery tutorial, and the host interface IP if you have several NICs.
1.1. Read a feature by name
cargo run -p viva-camctl -- get --ip 192.168.0.10 --name ExposureTime
For machine-readable output, note that --json is a top-level flag and goes
before the subcommand:
cargo run -p viva-camctl -- --json get --ip 192.168.0.10 --name ExposureTime
1.2. Write a feature by name
cargo run -p viva-camctl -- set --ip 192.168.0.10 --name ExposureTime --value 5000
cargo run -p viva-camctl -- get --ip 192.168.0.10 --name ExposureTime
If the value does not change, the usual causes are:
- The feature is locked right now. GenApi expresses this with
pIsLocked, which the library evaluates before every write — so a locked feature is refused locally with a clear error, rather than being sent to the camera and failing there. - The value violates a constraint (range, increment, alignment).
- Another feature is overriding manual control —
ExposureAutois the usual culprit forExposureTime,GainAutoforGain.
1.3. Which features does this camera have?
The report bundle lists node and feature counts along with the XML itself:
cargo run -p viva-camctl -- report --ip 192.168.0.10 --out viva-report.txt
Step 2 – Work with selectors
Many cameras multiplex several logical settings onto the same registers:
GainSelector=All,Red,Green,Blue, …Gain= the value for the currently selected channel.
Changing the selector changes which “row” you are editing. The NodeMap
re-resolves the addressing and invalidates the cached values that depended on
it, so a read after a selector write returns the new channel’s value rather than
a stale one.
# What can the selector be set to?
cargo run -p viva-camctl -- --json get --ip 192.168.0.10 --name GainSelector
# Different gain per channel
cargo run -p viva-camctl -- set --ip 192.168.0.10 --name GainSelector --value Red
cargo run -p viva-camctl -- set --ip 192.168.0.10 --name Gain --value 5.0
cargo run -p viva-camctl -- set --ip 192.168.0.10 --name GainSelector --value Blue
cargo run -p viva-camctl -- set --ip 192.168.0.10 --name Gain --value 3.0
The selectors_demo example shows the same pattern in Rust:
cargo run -p viva-genicam --example selectors_demo
Step 3 – Do the same from Rust
cargo run -p viva-genicam --example get_set_feature
cargo run -p viva-genicam --example get_set_feature -- --name Gain --value 3.0
The whole of it:
// `connect_gige` fetches the GenApi XML and builds the NodeMap, so every
// feature the camera declares is addressable by name from here on.
let mut camera = connect_gige(&device).await?;
// Feature access is synchronous even inside an async program: the register
// I/O behind it blocks, and `GigeRegisterIo` steps off the async worker on
// its own rather than making every caller do it.
println!("{name} = {}", camera.get(&name)?);
if let Some(value) = value.as_deref() {
camera.set(&name, value)?;
println!("{name} = {} (after write)", camera.get(&name)?);
}
Two things are worth pointing out.
Values are strings at this boundary. Camera::get returns String and
Camera::set takes &str; the node’s own type decides how that text is parsed
and encoded. set_exposure_time_us and set_gain_db are typed conveniences
over the two most common cases.
Feature access is synchronous, including inside #[tokio::main]. The
register I/O behind it blocks, and GigeRegisterIo steps off the async worker
by itself — you do not need to wrap calls in spawn_blocking.
If you have no camera to hand, the same calls work against the fake camera:
cargo run -p viva-genicam --example demo_fake_camera
// `get` and `set` are synchronous. They block on register I/O, and
// `GigeRegisterIo` steps off the async worker itself, so no `spawn_blocking`
// wrapper is needed even here inside `#[tokio::main]`.
println!("Reading camera features:");
for feature in [
"Width",
"Height",
"PixelFormat",
"ExposureTime",
"Gain",
"GevTimestampTickFrequency",
] {
match camera.get(feature) {
Ok(value) => println!(" {feature} = {value}"),
Err(err) => println!(" {feature} = <error: {err}>"),
}
}
println!();
// ── 5. Write a feature ──────────────────────────────────────────────────
println!("Setting Width = 320, ExposureTime = 10000 ...");
camera.set("Width", "320")?;
camera.set_exposure_time_us(10_000.0)?;
println!(" Width readback = {}\n", camera.get("Width")?);
Step 4 – When you might need raw register access
Prefer features by name. You get the node’s type, you respect the vendor’s declared constraints, and your code stays portable across cameras.
Raw registers are still occasionally the right tool:
- Debugging unusual vendor behaviour or firmware bugs.
- Reaching something genuinely absent from the XML.
- Bringing up a device whose GenApi description is incomplete.
viva-gige and viva-gencp expose the primitives — see the
viva-gige and viva-gencp
chapters. Be careful: writing arbitrary registers can leave a device unusable
until it is power-cycled.
Recap
You should now be able to:
- Read and write features by name, from the CLI and from Rust.
- Recognise why a write was refused — a lock, a constraint, or an auto feature.
- Use selectors to address per-channel settings.
- Know that raw register access exists and is a last resort.
Next: GenApi XML — where the feature list comes from in the first place.