viva_genapi/nodes.rs
1//! Node type definitions for the GenApi node system.
2
3use std::cell::RefCell;
4use std::collections::HashMap;
5
6use viva_genapi_xml::{
7 AccessMode, Addressing, BitField, ByteOrder, EnumEntryDecl, FloatEncoding, Sign,
8};
9pub use viva_genapi_xml::{NodeMeta, PredicateRefs, Representation, SkOutput, Visibility};
10
11use crate::swissknife::{AstNode, Value};
12
13/// Node kinds supported by the Tier-1 subset.
14#[derive(Debug)]
15#[non_exhaustive]
16pub enum Node {
17 /// Signed integer feature stored in a fixed-width register block.
18 Integer(IntegerNode),
19 /// Floating point feature with optional scale/offset conversion.
20 Float(FloatNode),
21 /// Enumeration feature mapping integers to symbolic names.
22 Enum(EnumNode),
23 /// Boolean feature represented as an integer register.
24 Boolean(BooleanNode),
25 /// Command feature triggering a device-side action when written.
26 Command(CommandNode),
27 /// Category organising related features.
28 Category(CategoryNode),
29 /// SwissKnife expression producing a computed value.
30 SwissKnife(SkNode),
31 /// Converter transforming raw values to/from float values via formulas.
32 Converter(ConverterNode),
33 /// IntConverter transforming raw values to/from integer values via formulas.
34 IntConverter(IntConverterNode),
35 /// StringReg for string-typed register access.
36 String(StringNode),
37 /// Raw byte-array register access.
38 Register(RegisterNode),
39}
40
41impl Node {
42 /// Return the GenICam node type name (e.g. "Integer", "Float", "Enumeration").
43 pub fn kind_name(&self) -> &'static str {
44 match self {
45 Node::Integer(_) => "Integer",
46 Node::Float(_) => "Float",
47 Node::Enum(_) => "Enumeration",
48 Node::Boolean(_) => "Boolean",
49 Node::Command(_) => "Command",
50 Node::Category(_) => "Category",
51 Node::SwissKnife(_) => "SwissKnife",
52 Node::Converter(_) => "Converter",
53 Node::IntConverter(_) => "IntConverter",
54 Node::String(_) => "StringReg",
55 Node::Register(_) => "Register",
56 }
57 }
58
59 /// Return the access mode of the node, if applicable.
60 pub fn access_mode(&self) -> Option<viva_genapi_xml::AccessMode> {
61 match self {
62 Node::Integer(n) => Some(n.access),
63 Node::Float(n) => Some(n.access),
64 Node::Enum(n) => Some(n.access),
65 Node::Boolean(n) => Some(n.access),
66 Node::Command(_) => Some(viva_genapi_xml::AccessMode::WO),
67 Node::Category(_) => None,
68 Node::SwissKnife(_) => Some(viva_genapi_xml::AccessMode::RO),
69 Node::Converter(_) => Some(viva_genapi_xml::AccessMode::RO),
70 Node::IntConverter(_) => Some(viva_genapi_xml::AccessMode::RO),
71 Node::String(n) => Some(n.access),
72 Node::Register(n) => Some(n.access),
73 }
74 }
75
76 /// Return the node name.
77 pub fn name(&self) -> &str {
78 match self {
79 Node::Integer(n) => &n.name,
80 Node::Float(n) => &n.name,
81 Node::Enum(n) => &n.name,
82 Node::Boolean(n) => &n.name,
83 Node::Command(n) => &n.name,
84 Node::Category(n) => &n.name,
85 Node::SwissKnife(n) => &n.name,
86 Node::Converter(n) => &n.name,
87 Node::IntConverter(n) => &n.name,
88 Node::String(n) => &n.name,
89 Node::Register(n) => &n.name,
90 }
91 }
92
93 /// Return the shared metadata for this node.
94 pub fn meta(&self) -> &NodeMeta {
95 match self {
96 Node::Integer(n) => &n.meta,
97 Node::Float(n) => &n.meta,
98 Node::Enum(n) => &n.meta,
99 Node::Boolean(n) => &n.meta,
100 Node::Command(n) => &n.meta,
101 Node::Category(n) => &n.meta,
102 Node::SwissKnife(n) => &n.meta,
103 Node::Converter(n) => &n.meta,
104 Node::IntConverter(n) => &n.meta,
105 Node::String(n) => &n.meta,
106 Node::Register(n) => &n.meta,
107 }
108 }
109
110 /// Return the visibility level of this node.
111 pub fn visibility(&self) -> Visibility {
112 self.meta().visibility
113 }
114
115 /// Return the description of this node, if any.
116 pub fn description(&self) -> Option<&str> {
117 self.meta().description.as_deref()
118 }
119
120 /// Return the tooltip of this node, if any.
121 pub fn tooltip(&self) -> Option<&str> {
122 self.meta().tooltip.as_deref()
123 }
124
125 /// Return the display name of this node, if any.
126 pub fn display_name(&self) -> Option<&str> {
127 self.meta().display_name.as_deref()
128 }
129
130 /// Return the recommended representation for this node, if any.
131 pub fn representation(&self) -> Option<Representation> {
132 self.meta().representation
133 }
134
135 /// Return the predicate references (`pIsImplemented`, `pIsAvailable`,
136 /// `pIsLocked`) declared on this node.
137 pub fn predicates(&self) -> &PredicateRefs {
138 match self {
139 Node::Integer(n) => &n.predicates,
140 Node::Float(n) => &n.predicates,
141 Node::Enum(n) => &n.predicates,
142 Node::Boolean(n) => &n.predicates,
143 Node::Command(n) => &n.predicates,
144 Node::Category(n) => &n.predicates,
145 Node::SwissKnife(n) => &n.predicates,
146 Node::Converter(n) => &n.predicates,
147 Node::IntConverter(n) => &n.predicates,
148 Node::String(n) => &n.predicates,
149 Node::Register(n) => &n.predicates,
150 }
151 }
152
153 pub(crate) fn invalidate_cache(&self) {
154 match self {
155 Node::Integer(node) => {
156 node.cache.replace(None);
157 node.raw_cache.replace(None);
158 }
159 Node::Float(node) => {
160 node.cache.replace(None);
161 }
162 Node::Enum(node) => node.invalidate(),
163 Node::Boolean(node) => {
164 node.cache.replace(None);
165 node.raw_cache.replace(None);
166 }
167 Node::SwissKnife(node) => {
168 node.cache.replace(None);
169 }
170 Node::Converter(node) => {
171 node.cache.replace(None);
172 }
173 Node::IntConverter(node) => {
174 node.cache.replace(None);
175 }
176 Node::String(node) => {
177 node.cache.replace(None);
178 }
179 Node::Register(node) => {
180 node.cache.replace(None);
181 }
182 Node::Command(_) | Node::Category(_) => {}
183 }
184 }
185}
186
187/// Integer feature metadata extracted from the XML description.
188#[derive(Debug)]
189pub struct IntegerNode {
190 /// Unique feature name.
191 pub name: String,
192 /// Shared metadata (visibility, description, tooltip, etc.).
193 pub meta: NodeMeta,
194 /// Register addressing metadata (absent when delegated via `pvalue`).
195 pub addressing: Option<Addressing>,
196 /// Nominal register length in bytes.
197 pub len: u32,
198 /// Declared access rights.
199 pub access: AccessMode,
200 /// Minimum permitted user value.
201 pub min: i64,
202 /// Maximum permitted user value.
203 pub max: i64,
204 /// Optional increment step the value must respect.
205 pub inc: Option<i64>,
206 /// Optional engineering unit such as "us".
207 pub unit: Option<String>,
208 /// Optional bitfield metadata restricting active bits.
209 pub bitfield: Option<BitField>,
210 /// Whether the register payload is signed. GenICam defaults to unsigned.
211 pub sign: Sign,
212 /// Selector nodes controlling the visibility of this node.
213 pub selectors: Vec<String>,
214 /// Selector gating rules in the form `(selector, allowed values)`.
215 pub selected_if: Vec<(String, Vec<String>)>,
216 /// Node providing the value (delegates read/write).
217 pub pvalue: Option<String>,
218 /// Node providing the dynamic maximum.
219 pub p_max: Option<String>,
220 /// Node providing the dynamic minimum.
221 pub p_min: Option<String>,
222 /// Static value for constant nodes.
223 pub value: Option<i64>,
224 /// Predicate refs gating implementation / availability / lock state.
225 pub predicates: PredicateRefs,
226 pub(crate) cache: RefCell<Option<i64>>,
227 pub(crate) raw_cache: RefCell<Option<Vec<u8>>>,
228}
229
230/// Floating point feature metadata.
231#[derive(Debug)]
232pub struct FloatNode {
233 pub name: String,
234 /// Shared metadata (visibility, description, tooltip, etc.).
235 pub meta: NodeMeta,
236 /// Register addressing metadata (absent when delegated via `pvalue`).
237 pub addressing: Option<Addressing>,
238 pub access: AccessMode,
239 pub min: f64,
240 pub max: f64,
241 pub unit: Option<String>,
242 /// Optional rational scale `(numerator, denominator)` applied to the raw value.
243 pub scale: Option<(i64, i64)>,
244 /// Optional offset added after scaling.
245 pub offset: Option<f64>,
246 pub selectors: Vec<String>,
247 pub selected_if: Vec<(String, Vec<String>)>,
248 /// Node providing the value (delegates read/write).
249 pub pvalue: Option<String>,
250 /// How the register payload is encoded (IEEE 754 or scaled integer).
251 pub encoding: FloatEncoding,
252 /// Byte order of the register payload.
253 pub byte_order: ByteOrder,
254 /// Predicate refs gating implementation / availability / lock state.
255 pub predicates: PredicateRefs,
256 pub(crate) cache: RefCell<Option<f64>>,
257}
258
259/// Enumeration feature metadata and mapping tables.
260#[derive(Debug)]
261pub struct EnumNode {
262 pub name: String,
263 /// Shared metadata (visibility, description, tooltip, etc.).
264 pub meta: NodeMeta,
265 /// Register addressing metadata (absent when delegated via `pvalue`).
266 pub addressing: Option<Addressing>,
267 pub access: AccessMode,
268 /// Node providing the integer value (delegates register read/write).
269 pub pvalue: Option<String>,
270 pub entries: Vec<EnumEntryDecl>,
271 pub default: Option<String>,
272 pub selectors: Vec<String>,
273 pub selected_if: Vec<(String, Vec<String>)>,
274 pub providers: Vec<String>,
275 /// Predicate refs gating implementation / availability / lock state.
276 pub predicates: PredicateRefs,
277 pub(crate) value_cache: RefCell<Option<String>>,
278 pub(crate) mapping_cache: RefCell<Option<EnumMapping>>,
279}
280
281#[derive(Debug, Clone)]
282pub(crate) struct EnumMapping {
283 pub by_value: HashMap<i64, String>,
284 pub by_name: HashMap<String, i64>,
285}
286
287impl EnumNode {
288 pub(crate) fn invalidate(&self) {
289 self.value_cache.replace(None);
290 self.mapping_cache.replace(None);
291 }
292}
293
294/// Boolean feature metadata.
295#[derive(Debug)]
296pub struct BooleanNode {
297 pub name: String,
298 /// Shared metadata (visibility, description, tooltip, etc.).
299 pub meta: NodeMeta,
300 /// Register addressing metadata (absent when delegated via `pvalue`).
301 pub addressing: Option<Addressing>,
302 pub len: u32,
303 pub access: AccessMode,
304 /// Optional bitfield (absent for pValue-backed booleans).
305 pub bitfield: Option<BitField>,
306 pub selectors: Vec<String>,
307 pub selected_if: Vec<(String, Vec<String>)>,
308 /// Node providing the value (delegates read/write).
309 pub pvalue: Option<String>,
310 /// On value for pValue-backed booleans.
311 pub on_value: Option<i64>,
312 /// Off value for pValue-backed booleans.
313 pub off_value: Option<i64>,
314 /// Predicate refs gating implementation / availability / lock state.
315 pub predicates: PredicateRefs,
316 pub(crate) cache: RefCell<Option<bool>>,
317 pub(crate) raw_cache: RefCell<Option<Vec<u8>>>,
318}
319
320/// SwissKnife node evaluating an arithmetic expression referencing other nodes.
321///
322/// `<IntSwissKnife>` (`output` = [`SkOutput::Integer`]) evaluates in integer
323/// arithmetic, where `/` truncates and 64-bit values stay exact;
324/// `<SwissKnife>` evaluates in floating point.
325#[derive(Debug)]
326pub struct SkNode {
327 /// Unique feature name.
328 pub name: String,
329 /// Shared metadata (visibility, description, tooltip, etc.).
330 pub meta: NodeMeta,
331 /// Desired output type as declared in the XML.
332 pub output: SkOutput,
333 /// Parsed expression AST.
334 pub ast: AstNode,
335 /// Mapping of variable identifiers to provider node names.
336 pub vars: Vec<(String, String)>,
337 /// Predicate refs gating implementation / availability.
338 pub predicates: PredicateRefs,
339 /// Cached value alongside the generation it was computed in.
340 pub cache: RefCell<Option<(Value, u64)>>,
341}
342
343/// Command feature metadata.
344#[derive(Debug)]
345pub struct CommandNode {
346 pub name: String,
347 /// Shared metadata (visibility, description, tooltip, etc.).
348 pub meta: NodeMeta,
349 /// Fixed register address (absent when delegated via `pvalue`).
350 pub address: Option<u64>,
351 pub len: u32,
352 /// Node providing the command register.
353 pub pvalue: Option<String>,
354 /// Value to write when executing the command.
355 pub command_value: Option<i64>,
356 /// Predicate refs gating implementation / availability / lock state.
357 pub predicates: PredicateRefs,
358}
359
360/// Category node describing child feature names.
361#[derive(Debug)]
362pub struct CategoryNode {
363 pub name: String,
364 /// Shared metadata (visibility, description, tooltip, etc.).
365 pub meta: NodeMeta,
366 pub children: Vec<String>,
367 /// Predicate refs gating implementation / availability.
368 pub predicates: PredicateRefs,
369}
370
371/// Converter node transforming a raw register value to/from a float feature
372/// value via two formulas.
373///
374/// The GenICam direction names read backwards at first glance, and getting
375/// them the wrong way round is silent — the identity converter behaves the
376/// same either way. They are named after the *target* of the conversion:
377///
378/// - `<FormulaFrom>` converts **from the raw value to the feature value** and
379/// is what a **read** evaluates. Its `TO` variable is bound to `p_value`.
380/// - `<FormulaTo>` converts **from the feature value to the raw value** and is
381/// what a **write** evaluates. Its `FROM` variable is bound to the incoming
382/// value and `OLD`, where declared, to the current raw value.
383#[derive(Debug)]
384pub struct ConverterNode {
385 /// Unique feature name.
386 pub name: String,
387 /// Shared metadata (visibility, description, tooltip, etc.).
388 pub meta: NodeMeta,
389 /// Name of the node providing the raw register value.
390 pub p_value: String,
391 /// Parsed `<FormulaTo>`: feature value → raw value, evaluated on write.
392 pub ast_to: AstNode,
393 /// Parsed `<FormulaFrom>`: raw value → feature value, evaluated on read.
394 pub ast_from: AstNode,
395 /// Variable mappings for `<FormulaTo>` (the write direction).
396 pub vars_to: Vec<(String, String)>,
397 /// Variable mappings for `<FormulaFrom>` (the read direction).
398 pub vars_from: Vec<(String, String)>,
399 /// Optional engineering unit.
400 pub unit: Option<String>,
401 /// Desired output type.
402 pub output: SkOutput,
403 /// Predicate refs gating implementation / availability / lock state.
404 pub predicates: PredicateRefs,
405 /// Cached user-facing value alongside the generation it was computed in.
406 pub cache: RefCell<Option<(Value, u64)>>,
407}
408
409/// IntConverter node transforming a raw register value to/from an integer
410/// feature value via two formulas.
411///
412/// Same direction convention as [`ConverterNode`], evaluated in integer
413/// arithmetic.
414#[derive(Debug)]
415pub struct IntConverterNode {
416 /// Unique feature name.
417 pub name: String,
418 /// Shared metadata (visibility, description, tooltip, etc.).
419 pub meta: NodeMeta,
420 /// Name of the node providing the raw register value.
421 pub p_value: String,
422 /// Parsed `<FormulaTo>`: feature value → raw value, evaluated on write.
423 pub ast_to: AstNode,
424 /// Parsed `<FormulaFrom>`: raw value → feature value, evaluated on read.
425 pub ast_from: AstNode,
426 /// Variable mappings for `<FormulaTo>` (the write direction).
427 pub vars_to: Vec<(String, String)>,
428 /// Variable mappings for `<FormulaFrom>` (the read direction).
429 pub vars_from: Vec<(String, String)>,
430 /// Optional engineering unit.
431 pub unit: Option<String>,
432 /// Predicate refs gating implementation / availability / lock state.
433 pub predicates: PredicateRefs,
434 /// Cached user-facing value alongside the generation it was computed in.
435 pub cache: RefCell<Option<(i64, u64)>>,
436}
437
438/// StringReg node for string-typed register access.
439#[derive(Debug)]
440pub struct StringNode {
441 /// Unique feature name.
442 pub name: String,
443 /// Shared metadata (visibility, description, tooltip, etc.).
444 pub meta: NodeMeta,
445 /// Register addressing metadata.
446 pub addressing: Addressing,
447 /// Declared access rights.
448 pub access: AccessMode,
449 /// Predicate refs gating implementation / availability / lock state.
450 pub predicates: PredicateRefs,
451 /// Cached string value alongside the generation it was computed in.
452 pub cache: RefCell<Option<(String, u64)>>,
453}
454
455/// `<Register>` node: raw byte-array access to a register block.
456///
457/// The base register type — an address, a byte count, and no interpretation of
458/// the bytes. `StringNode` is this plus UTF-8/NUL decoding.
459#[derive(Debug)]
460pub struct RegisterNode {
461 /// Unique feature name.
462 pub name: String,
463 /// Shared metadata (visibility, description, tooltip, etc.).
464 pub meta: NodeMeta,
465 /// Register addressing metadata, including the block length.
466 pub addressing: Addressing,
467 /// Declared access rights.
468 pub access: AccessMode,
469 /// `<pPort>` target; `None` or `"Device"` means the device port.
470 ///
471 /// Any other port is parsed and listed but cannot be read — see GA-12.
472 pub port: Option<String>,
473 /// Predicate refs gating implementation / availability / lock state.
474 pub predicates: PredicateRefs,
475 /// Cached payload alongside the generation it was read in.
476 pub cache: RefCell<Option<(Vec<u8>, u64)>>,
477}
478
479impl RegisterNode {
480 /// Declared length of the register block in bytes.
481 ///
482 /// `None` for a selector-mapped block, whose length depends on the current
483 /// selector value and therefore needs a transport to resolve — use
484 /// `NodeMap::register_address` for that.
485 ///
486 /// This exists so a consumer can report a register's size without naming
487 /// `Addressing`, which is not re-exported (backlog `API-02`).
488 pub fn declared_len(&self) -> Option<u32> {
489 match &self.addressing {
490 Addressing::Sum { len, .. } => Some(*len),
491 Addressing::BySelector { .. } => None,
492 }
493 }
494}