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

Hardware Integration: NI DAQmx

NI DAQmx Overview

The autocore-ni module integrates National Instruments DAQmx data acquisition hardware with AutoCore. It runs as an external module process, connects to autocore-server via IPC, and provides:

  • Live streaming via shared memory (one segment per task, updated every callback)
  • Triggered capture via shared memory (one segment per DAQ config, written on trigger)
  • Scalar statistics via Global Memory variables (value, min, max, rate per channel)
  • Console commands for building and managing configurations interactively

The module supports any mix of NI DAQmx channel types: analog voltage, strain gage, accelerometer, force sensor, linear/angular encoder, frequency, and edge counting.

Initial Setup

Before using the NI module, register its executable in config.ini:

[modules]
ni = /opt/autocore/bin/modules/autocore-ni

Then start the module from the console (no project.json entry needed yet):

ni> system.load_module --name ni

The module is now running and addressable. You can configure it interactively, then save to project.json so it starts automatically on future server restarts.

Registering Hardware on Linux

On Windows, NI MAX automatically discovers and registers network cDAQ chassis. On Linux there is no NI MAX, so devices must be registered manually by importing a .ini configuration file via nidaqmxconfig. The NI module can generate this file for you.

Step 1: Discover chassis on the network

The chassis must be powered on and network-reachable. Run:

ni> discover_devices

This calls nilsdev --verbose and returns a JSON array with each chassis’s name, product type, serial number, IP address, MAC address, and hostname. Make note of the device name — you will need it in the next step.

Step 2: Add the device to your configuration

Use add_device with the chassis name from step 1 and a modules array describing which NI modules are installed in each slot:

ni> add_device --name cdaq-3814 --modules '[{"product_type":"NI 9218","slot_num":1},{"product_type":"NI 9401","slot_num":2}]'

Each module entry requires product_type (the NI model, e.g. “NI 9218”) and slot_num (1-based physical slot). You can also provide an optional name field — if omitted, modules are named <device_name>-<slot_num> (e.g. cdaq-3814-1).

By default, reserve is set to true, which gives autocore-ni exclusive access to the chassis. Set --reserve false if you need to share the device.

Step 3: Generate the .ini and import it

ni> generate_device_config --import true

This combines the device configuration from step 2 with the runtime information from nilsdev to produce a .ini file, then imports it via nidaqmxconfig --import --replace. The generated file is saved alongside your project.json as ni_devices.ini.

To generate without importing (for review):

ni> generate_device_config

Step 4: Save and verify

ni> save_config

Verify the device is registered:

nilsdev --verbose

You should see your chassis and modules listed. The device is now ready for DAQmx tasks.

Complete example: register a cDAQ-9181 with two modules

# Start the module
ni> system.load_module --name ni

# See what's on the network
ni> discover_devices

# Register the chassis with a force sensor module (slot 1) and digital I/O (slot 2)
ni> add_device --name cdaq-3814 --modules '[{"product_type":"NI 9218","slot_num":1},{"product_type":"NI 9401","slot_num":2,"name":"digital-io"}]'

# Generate and import
ni> generate_device_config --import true

# Now configure tasks and channels as usual
ni> new_project --task_name AnalogInput
ni> add_channel --task AnalogInput --name load --physical_channel cdaq-3814Mod1/ai0 --type voltage --min_val -5 --max_val 5
ni> save_config
ni> start

The devices array is persisted in project.json alongside tasks and DAQ configs:

{
  "modules": {
    "ni": {
      "config": {
        "devices": [
          {
            "name": "cdaq-3814",
            "reserve": true,
            "modules": [
              { "product_type": "NI 9218", "slot_num": 1 },
              { "product_type": "NI 9401", "slot_num": 2, "name": "digital-io" }
            ]
          }
        ],
        "tasks": [ ... ],
        "daq": [ ... ]
      }
    }
  }
}

Configuring a Project from the Console

The fastest way to set up a new NI configuration is through console commands. This example creates a project with one analog voltage channel:

ni> new_project --task_name AnalogInput
ni> add_channel --task AnalogInput --name load --physical_channel Dev1/ai0 --type voltage --min_val -5 --max_val 5
ni> save_config
ni> start

Step by step:

  1. new_project creates a minimal config with one empty task (1 kHz, 1000 sample buffer)
  2. add_channel adds a channel using the voltage preset. The --type flag selects sensible defaults; --min_val and --max_val override specific parameters.
  3. save_config writes the config into the currently loaded project.json. On future server restarts, the module loads this config automatically.
  4. start begins hardware acquisition.

To modify the task parameters (sample rate, buffer size, etc.), use add_task with explicit values:

ni> new_project
ni> remove_task --name Task
ni> add_task --name AnalogInput --sample_rate 100000 --samples_per_channel 10000 --samples_per_event 1000
ni> add_channel --task AnalogInput --name load --physical_channel cDAQ1Mod1/ai0 --type force_bridge --min_val -2224 --max_val 2224 --voltage_excit_val 3.3
ni> save_config
ni> restart

Channel Type Presets

The --type flag on add_channel maps to a DAQmx channel creation function with sensible defaults. Use ni.list_channel_types to see all available presets.

TypeDAQmx FunctionDescription
voltageCreateAIVoltageChanAnalog voltage input
strain_gageCreateAIStrainGageChanStrain gage (quarter/half/full bridge)
accelerometerCreateAIAccelChanIEPE accelerometer
force_bridgeCreateAIForceBridgeTwoPointLinChanForce sensor via bridge two-point linear calibration
force_iepeCreateAIForceIEPEChanIEPE force sensor (e.g. PCB impact hammer)
linear_encoderCreateCILinEncoderChanQuadrature linear encoder (one per task)
angular_encoderCreateCIAngEncoderChanQuadrature angular/rotary encoder (one per task)
count_edgesCreateCICountEdgesChanCounter edge counting
frequencyCreateCIFreqChanFrequency measurement

Any additional --key value flags are passed through as parameter overrides on top of the preset defaults. For example, --type voltage --min_val -10 --max_val 10 uses the voltage preset but changes the input range.

For advanced use, you can bypass presets entirely with --create_function and --create_args:

ni> add_channel --task AI --name ch0 --physical_channel Dev1/ai0 --create_function CreateAIVoltageChan --create_args '{"terminal_config":10106,"min_val":-5,"max_val":5}'

Multi-Task Configurations (Encoders)

DAQmx counter input channels (encoders, frequency, edge counting) each require their own task. To synchronize them with an analog input task, set clock_type to external and parent_task to the master task name:

ni> new_project --task_name AnalogInput
ni> remove_task --name AnalogInput
ni> add_task --name AnalogInput --sample_rate 100000 --samples_per_channel 10000 --samples_per_event 1000
ni> add_channel --task AnalogInput --name load --physical_channel cDAQ1Mod1/ai0 --type force_bridge --min_val -2224 --max_val 2224

ni> add_task --name Encoder1 --sample_rate 100000 --samples_per_channel 10000 --samples_per_event 1000 --clock_type external --parent_task AnalogInput
ni> add_channel --task Encoder1 --name pos_x --physical_channel cDAQ1Mod2/ctr0 --type linear_encoder --dist_per_pulse 0.000004

ni> add_task --name Encoder2 --sample_rate 100000 --samples_per_channel 10000 --samples_per_event 1000 --clock_type external --parent_task AnalogInput
ni> add_channel --task Encoder2 --name pos_y --physical_channel cDAQ1Mod2/ctr1 --type linear_encoder --dist_per_pulse 0.000004

ni> add_task --name Encoder3 --sample_rate 100000 --samples_per_channel 10000 --samples_per_event 1000 --clock_type external --parent_task AnalogInput
ni> add_channel --task Encoder3 --name angle --physical_channel cDAQ1Mod2/ctr2 --type angular_encoder --pulses_per_rev 2048

ni> save_config
ni> restart

When parent_task is set, the module automatically synchronizes the clocks: it copies the master’s timebase, sets the arm start trigger, and starts slave tasks before the master. All channels across all synchronized tasks are sample-aligned.

Adding Triggered Capture (DAQ)

A DAQ configuration captures a fixed window of data from one or more channels when a trigger condition is met. Channels can span multiple tasks (as long as they share a clock via parent_task).

ni> add_daq --name impact --capture_length 10000 --pre_trigger_samples 100 --channels '["load","pos_x"]' --trigger '{"type":"rising_edge","source_channels":["load"],"level":50.0,"hysteresis":2.0}'
ni> save_config
ni> restart

The source_channels field accepts an array of one or more channel names. When multiple channels are specified, their sample values are summed before the trigger condition is evaluated. This is useful for force plates and multi-sensor setups where the trigger should fire on the combined signal regardless of which sensor sees the event:

ni> add_daq --name impact --capture_length 10000 --pre_trigger_samples 100 \
    --channels '["s1","s2","s3","s4","pos_x"]' \
    --trigger '{"type":"rising_edge","source_channels":["s1","s2","s3","s4"],"level":50.0,"hysteresis":2.0}'

All source channels must be in the same task.

To arm the trigger at runtime:

ni> impact.arm

When the trigger fires, the module writes the capture data to shared memory and sets the impact_data_ready GM variable.

Available trigger types: rising_edge, falling_edge, rising_window, falling_window.

Full Example: Load Cell + Three Encoders

This example configures a complete impact test system with a force sensor and three position encoders, all synchronized at 100 kHz:

ni> new_project
ni> remove_task --name Task

# Master task: analog input with load cell
ni> add_task --name AnalogInput --sample_rate 100000 --samples_per_channel 10000 --samples_per_event 1000
ni> add_channel --task AnalogInput --name load --physical_channel cDAQ1Mod1/ai0 --type force_bridge --min_val -2224 --max_val 2224 --voltage_excit_val 3.3

# Three encoder tasks, each synced to AnalogInput
ni> add_task --name EncX --sample_rate 100000 --samples_per_channel 10000 --samples_per_event 1000 --clock_type external --parent_task AnalogInput
ni> add_channel --task EncX --name pos_x --physical_channel cDAQ1Mod2/ctr0 --type linear_encoder --dist_per_pulse 0.000004

ni> add_task --name EncY --sample_rate 100000 --samples_per_channel 10000 --samples_per_event 1000 --clock_type external --parent_task AnalogInput
ni> add_channel --task EncY --name pos_y --physical_channel cDAQ1Mod2/ctr1 --type linear_encoder --dist_per_pulse 0.000004

ni> add_task --name EncZ --sample_rate 100000 --samples_per_channel 10000 --samples_per_event 1000 --clock_type external --parent_task AnalogInput
ni> add_channel --task EncZ --name pos_z --physical_channel cDAQ1Mod2/ctr2 --type linear_encoder --dist_per_pulse 0.000004

# Triggered capture across tasks
ni> add_daq --name impact --capture_length 10000 --pre_trigger_samples 100 --channels '["load","pos_x","pos_y","pos_z"]' --trigger '{"type":"rising_edge","source_channels":["load"],"level":50.0,"hysteresis":2.0}'

# Save and start
ni> save_config
ni> restart

# Check status
ni> status

# Arm the trigger
ni> impact.arm

After save_config, the full configuration is persisted in project.json. On future server restarts, the module starts automatically and begins acquisition (set auto_start: true in the config for production).

Modifying Configuration In-Place

You don’t need to remove and re-add a task or channel to change a single field. Use set_task and set_channel to update fields directly, and reset_task_field / reset_channel_field to revert them to defaults.

Updating task fields

Any task field except name can be updated:

# Make Encoder1 a slave of AnalogInput
ni> set_task --name Encoder1 --parent_task AnalogInput --clock_type external

# Change the sample rate on the master
ni> set_task --name AnalogInput --sample_rate 50000 --samples_per_event 500

# Apply changes
ni> restart

Updating channel fields

Top-level channel fields (physical_channel, create_function, value_aggregation, compute_rate, create_args) are set by name. Any unrecognized key is treated as a create_args sub-field, so you can set hardware parameters directly:

# Change the input range on a voltage channel
ni> set_channel --task AnalogInput --name load --min_val -500 --max_val 500

# Change the encoder resolution
ni> set_channel --task Encoder1 --name pos_x --dist_per_pulse 0.000002

# Switch aggregation mode
ni> set_channel --task AnalogInput --name load --value_aggregation rms

Resetting fields to defaults

Use reset_task_field and reset_channel_field to revert a field. For tasks, resettable fields are timeout_ms, phase_offset_samples, clock_type, clock_source, and parent_task. For channels, resettable fields are value_aggregation, compute_rate, and create_args (or any create_args sub-field by name):

# Remove clock sync — make this an independent task again
ni> reset_task_field --name Encoder1 --field parent_task
ni> reset_task_field --name Encoder1 --field clock_type

# Remove a create_args override so the preset default applies
ni> reset_channel_field --task Encoder1 --name pos_x --field z_index_enable

# Reset aggregation back to auto-inferred default
ni> reset_channel_field --task AnalogInput --name load --field value_aggregation

Inspecting configuration

# Full config as JSON
ni> get_config

# List all tasks with channels and actual timing
ni> list_tasks

# Detailed view of one task (includes create_args for each channel)
ni> task --name AnalogInput

When the worker is running, list_tasks and task include actual_sample_rate and time_increment fields read back from the hardware. These values are also written to shared memory as <task>_actual_sample_rate (f64) and <task>_time_increment (f64) so control programs can read them.

NI Module Command Reference

All commands use the ni. topic prefix. Use ni.help for a summary or ni.help --command <name> for detailed argument information.

Acquisition Control

CommandArgumentsDescription
startStart DAQmx acquisition
stopStop DAQmx acquisition
restartStop and restart (applies config changes)
statusShow module status, channel values, and errors

Configuration Inspection

CommandArgumentsDescription
get_configShow current in-memory configuration as JSON
show_configAlias for get_config
describeHuman-readable summary of tasks, channels, DAQ, and timing
list_tasksList all tasks with channels and actual timing info (JSON)
task--name (required)Show full configuration for a specific task (JSON)

Project Lifecycle

CommandArgumentsDescription
new_project--task_name (optional, default: “Task”)Create a minimal starter configuration
save_config--path (optional), --generate_variables (optional, bool)Save config to project.json. --generate_variables true also writes variable declarations.
generate_variables--path (optional)Generate project.json variable declarations for all NI channels

Task Management

CommandArgumentsDescription
add_task--name, --sample_rate, --samples_per_channel, --samples_per_event (all required); --timeout_ms (default: 2500), --clock_type (default: “internal”), --clock_source, --parent_taskAdd a new DAQmx task
set_task--name (required), plus any field --key value pairsUpdate fields on an existing task
remove_task--name (required)Remove a task by name
reset_task_field--name (required), --field (required)Reset a task field to its default value
duplicate_task--name (required), --new_name (required)Clone a task with a new name (channels not copied)
rename_task--name (required), --new_name (required)Rename a task (updates parent_task references)
calc_timing--name (required), --callback_hz (default: 100), --buffer_seconds (default: 1.0), --apply (default: true)Calculate and set samples_per_event and samples_per_channel from target callback rate

Channel Management

CommandArgumentsDescription
add_channel--task, --name, --physical_channel (all required); --type or --create_function (one required). Extra --key value flags override preset defaults.Add a channel to a task
set_channel--task, --name (required), plus any --key value pairs. Unrecognized keys become create_args sub-fields.Update fields on an existing channel
remove_channel--task (required), --name (required)Remove a channel from a task
reset_channel_field--task, --name, --field (all required)Reset a channel field to default, or remove a create_args sub-field
rename_channel--task, --name, --new_name (all required)Rename a channel (updates DAQ channel lists and trigger references)
list_channel_typesList available channel type presets with default parameters

DAQ (Triggered Capture) Management

CommandArgumentsDescription
add_daq--name, --capture_length, --channels (JSON array), --trigger (JSON object) (all required); --pre_trigger_samples (default: 0)Add a triggered capture configuration
remove_daq--name (required)Remove a DAQ configuration

Device Management (Linux)

CommandArgumentsDescription
discover_devicesRun nilsdev --verbose and return discovered chassis info
add_device--name (required), --modules (JSON array), --reserve (default: true)Add a cDAQ chassis device configuration
remove_device--name (required)Remove a device configuration
set_device--name (required), plus --key value pairsUpdate device fields (reserve, modules)
generate_device_config--path (optional), --import (default: false)Generate .ini from config + nilsdev and optionally import

Runtime Monitoring

CommandArgumentsDescription
reset_minmax--channel (optional, omit for all)Reset min/max tracking
list_devicesList connected NI DAQmx devices
clear_errorsClear the error log
<channel>Read all fields for a channel (value, min, max, rate, data_received)
<channel>.valueRead only the current value (scalar f64)
<channel>.minRead only the running minimum
<channel>.maxRead only the running maximum
<channel>.rateRead only the rate
<daq>.armArm a DAQ trigger
<daq>.disarmDisarm a DAQ trigger

Individual channel sub-fields (ni.ai0.value, ni.ai0.rate, etc.) return scalar values and can be used as link targets in project.json variable declarations.

Help and Discovery

CommandArgumentsDescription
help--command (optional)Show command list, or detailed help for one command
get_catalog--detailed (optional, bool)List all available endpoints