Skip to content

SY-4299: Strongly-Typed NI Task Types (Part 3 — Driver)#2485

Open
emilbon99 wants to merge 32 commits into
sy-4299-strongly-type-ni-task-types-part-1from
sy-4299-strongly-type-ni-task-types-part-3
Open

SY-4299: Strongly-Typed NI Task Types (Part 3 — Driver)#2485
emilbon99 wants to merge 32 commits into
sy-4299-strongly-type-ni-task-types-part-1from
sy-4299-strongly-type-ni-task-types-part-3

Conversation

@emilbon99

@emilbon99 emilbon99 commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Issue Pull Request

Linear Issue

SY-4299

Description

Makes the C++ driver consume the Oracle-generated NI types, completing the third leg of the NI strong-typing work (part 1: core/client types, part 2: console). The driver's hand-rolled JSON parsing and its ~50-class virtual-inheritance channel hierarchy are replaced entirely: the generated wire schemas are now the driver's channel representation, so the driver, console, and all clients parse NI task configs from a single source of truth.

Architecture

The old channels.h fused three concerns per channel class: JSON parsing, converted-field storage, and the DAQmx apply() call. After this PR there are exactly two kinds of code left:

  • Two generic wrappers (Input/Output) holding the parsed generated union (AIChannel/CIChannel/DIChannel, AOChannel/DOChannel) plus the runtime state the task binds (resolved Synnax channel, device location).
  • One free apply(const GeneratedChannel &, ApplyContext &, dmx, handle) function per channel type (47 total) that converts strings to DAQmx constants inline and makes the hardware call. Dispatch is std::visit over the generated unions, so adding a schema variant without a driver apply is a compile error instead of a runtime failure.

parse_input/parse_output dispatch on the task type through the generated union parsers, so each task accepts exactly its own channel family at parse time with proper field errors. Legacy wire keys (terminalA/B/Z, activeEdge, ai_frequency_voltage) are normalized into the schema fields at parse, so configs persisted by old consoles keep byte-identical behavior.

Testing

Adds a generated recording FakeAPI for the full 397-method DAQmx surface. Every channel test now parses a wire fixture and asserts the actual DAQmx call and its arguments (physical channel string, converted unit/enum constants, scalars) — strictly stronger than the old tests, which asserted intermediate converted members and never exercised the DAQmx boundary.

Plumbing and newly working

  • Adds //client/cpp/ni, //client/cpp/task/common, and //driver/ni/daqmx:fake Bazel targets; //driver/ni/channel is the first driver consumer of generated client types.
  • Eight channel types that existed but had no factory mapping (ai_voltage_rms, ai_current_rms, ai_voltage_with_excit, ai_accel_charge, ai_thermistor_iex/_vex, ai_rosette_strain_gage, ai_freq_voltage) are now configurable, and CreateAIThrmstrChanVex (declared but never implemented) now links.
  • Table scales now parse (the legacy parse read pre_scaled/scaled while consoles always wrote pre_scaled_vals/scaled_vals); polynomial scales use the schema's reverse_coeffs directly; rosette strain gauges pass the full measurement-type array to DAQmx instead of only the first entry.
  • Counter read tasks support per-channel devices (cross-device), covered by a new config test.

Basic Readiness

  • I have performed a self-review of my code.
  • I have added relevant, automated tests to cover the changes.
  • I have updated documentation to reflect the changes.

emilbon99 added 16 commits June 12, 2026 12:06
driver::common::BaseReadTaskConfig now inherits the oracle-generated
synnax::common::BaseReadConfig instead of re-parsing sample_rate/
stream_rate/auto_start/data_saving by hand, so the field set has a single
definition in the schema and the driver reads telem::Rate directly.
Consumers (config.sample_rate, etc.) are unchanged since the fields are
inherited; the driver keeps only the timing options and the rate
validation.

Adds nlohmann to_json/from_json to telem::Rate so field<Rate> compiles
(previously no generated config parsing a telem scalar was ever
instantiated). Missing sample_rate/stream_rate now fall back to the
schema defaults (10/5) rather than erroring; tests updated accordingly.
driver::common::BaseWriteTaskConfig now inherits the generated
synnax::common::BaseWriteConfig instead of re-parsing the device field
by hand, giving auto_start/data_saving/device a single definition in the
schema. The C++ member is renamed device_key -> device to match the
schema; the JSON wire key stays "device", so stored configs remain
compatible. Consumers (labjack/ni/opc write) updated to this->device.
…Config

- NI WriteTaskConfig inherits the generated ni::WriteConfig, reading
  state_rate from the single schema definition instead of by hand.
- Delete driver::common::BaseTaskConfig (a hand-written duplicate of the
  generated synnax::common::BaseConfig). arc and ethercat write configs
  now inherit the generated BaseConfig directly; common_test exercises
  BaseConfig::parse.

common + ni build and tests pass; arc/ethercat compile in CI (vendor
submodules absent locally).
…pes-part-1' into sy-4299-strongly-type-ni-task-types-part-3
…pes-part-1' into sy-4299-strongly-type-ni-task-types-part-3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant