nidaqlib¶
nidaqlib ¶
nidaqlib — Experiment-facing NI-DAQmx acquisition layer.
nidaqlib is not a replacement for NI's nidaqmx-python. It is a typed,
lifecycle-managed acquisition layer built on top of it, designed to fit the
same scientific-instrumentation ecosystem as alicatlib and sartoriuslib.
Core API is async (built on anyio); a sync facade is available at
:mod:nidaqlib.sync for scripts, notebooks, and REPL use.
See docs/design.md for the architectural design.
AcquisitionMode ¶
Bases: StrEnum
Sample-clock acquisition mode.
Mirrors a subset of nidaqmx.constants.AcquisitionType. Kept as a
library-side enum so :class:TaskSpec round-trips through JSON without
pulling NI's enum machinery into the serialisation layer.
ON_DEMAND
class-attribute
instance-attribute
¶
Software-timed; no hardware sample clock is configured.
AcquisitionSummary
dataclass
¶
AcquisitionSummary(
blocks_emitted=0,
blocks_dropped=0,
errors_observed=0,
started_at=(lambda: datetime.now(UTC))(),
finished_at=None,
)
Per-run counters, yielded alongside the block stream.
Mirrors sartoriuslib.AcquisitionSummary shape but is intentionally
mutable: counters are updated in place during the run so consumers
can poll progress (e.g. for a TUI bar) and read final counts after
exit. The recorder is the only writer; consumers MUST treat the
object as read-only.
Attributes:
| Name | Type | Description |
|---|---|---|
blocks_emitted |
int
|
Total :class: |
blocks_dropped |
int
|
Records dropped because of an
:class: |
errors_observed |
int
|
Wrapped NI errors seen during the run, regardless
of :class: |
started_at |
datetime
|
Wall-clock at recorder entry. |
finished_at |
datetime | None
|
Wall-clock at recorder exit. |
AnalogEdgeStartTrigger
dataclass
¶
Bases: TriggerSpec
Start the task when an analog channel crosses level.
Maps to task.triggers.start_trigger.cfg_anlg_edge_start_trig.
Attributes:
| Name | Type | Description |
|---|---|---|
level |
float
|
Threshold level, in the source channel's engineering units. |
slope |
AnalogTriggerSlope
|
Active slope (rising / falling). Rising by default. |
from_dict
classmethod
¶
Deserialise, restoring :class:AnalogTriggerSlope from its value.
Source code in src/nidaqlib/tasks/triggers.py
to_dict ¶
Serialise; encode :class:AnalogTriggerSlope to its string value.
AnalogInputBase
dataclass
¶
AnalogInputBase(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
adc_timing_mode=None,
adc_custom_timing_mode=None,
auto_zero_mode=None,
)
Bases: ChannelSpec
Shared base for analog-input channel specs.
Carries the per-channel knobs NI exposes as channel properties on the
object returned by add_ai_*_chan(...) — currently ADC timing mode
and auto-zero mode. Hardware support is module-specific: NI surfaces
unsupported attributes as a DaqError at set time, which the
backend re-raises as :class:~nidaqlib.errors.NIDaqBackendError.
Attributes:
| Name | Type | Description |
|---|---|---|
adc_timing_mode |
ADCTimingMode | None
|
One of :class: |
adc_custom_timing_mode |
int | None
|
Device-specific integer code, only
meaningful when |
auto_zero_mode |
AutoZeroType | None
|
One of :class: |
__post_init__ ¶
Validate the ADC-timing pairing on top of the base channel checks.
Source code in src/nidaqlib/channels/analog_input.py
to_dict ¶
Serialise via each enum's .value so the payload is JSON-encodable.
Source code in src/nidaqlib/channels/analog_input.py
AnalogInputVoltage
dataclass
¶
AnalogInputVoltage(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
adc_timing_mode=None,
adc_custom_timing_mode=None,
auto_zero_mode=None,
min_val=-10.0,
max_val=10.0,
terminal_config=None,
custom_scale_name=None,
)
Bases: AnalogInputBase
Voltage analog-input channel.
Maps to Task.ai_channels.add_ai_voltage_chan on the NI side.
Attributes:
| Name | Type | Description |
|---|---|---|
min_val |
float
|
Lower limit of the expected input range, in volts. |
max_val |
float
|
Upper limit of the expected input range, in volts. The NI driver uses the (min, max) range to select the most appropriate on-board gain. |
terminal_config |
TerminalConfiguration | None
|
Terminal configuration (RSE / NRSE / DIFF /
PSEUDO_DIFF). |
custom_scale_name |
str | None
|
Optional name of a pre-configured custom scale
registered in MAX. When set, |
Inherits :attr:adc_timing_mode and :attr:adc_custom_timing_mode
from :class:AnalogInputBase.
__post_init__ ¶
Validate the voltage range.
Source code in src/nidaqlib/channels/analog_input.py
from_dict
classmethod
¶
Reconstruct, restoring enum members from their serialised .value ints.
Source code in src/nidaqlib/channels/analog_input.py
to_dict ¶
Serialise, encoding terminal_config via its .value int.
Source code in src/nidaqlib/channels/analog_input.py
AnalogOutputVoltage
dataclass
¶
AnalogOutputVoltage(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
min_val=-10.0,
max_val=10.0,
safe_min=None,
safe_max=None,
requires_confirm=True,
terminal_config=None,
custom_scale_name=None,
)
Bases: ChannelSpec
Voltage analog-output channel.
Maps to Task.ao_channels.add_ao_voltage_chan on the NI side. Writes
are gated through :meth:DaqSession.write, which rejects out-of-range
values against safe_min / safe_max and requires
confirm=True whenever any target channel sets
requires_confirm (design doc §17.1).
Attributes:
| Name | Type | Description |
|---|---|---|
min_val |
float
|
Lower bound of the device output range, in volts. |
max_val |
float
|
Upper bound of the device output range, in volts. NI uses
|
safe_min |
float | None
|
Optional lower-end safety clamp for application writes.
|
safe_max |
float | None
|
Optional upper-end safety clamp. |
requires_confirm |
bool
|
When |
terminal_config |
TerminalConfiguration | None
|
Terminal configuration (RSE / DIFF / ...). |
custom_scale_name |
str | None
|
Optional name of a pre-configured custom scale
registered in MAX. When set, |
effective_safe_max
property
¶
Resolved upper clamp — falls back to :attr:max_val.
effective_safe_min
property
¶
Resolved lower clamp — falls back to :attr:min_val.
__post_init__ ¶
Validate the output and safety ranges.
Source code in src/nidaqlib/channels/analog_output.py
AnalogTriggerSlope ¶
Bases: StrEnum
Active slope for an analog edge trigger.
Mirrors nidaqmx.constants.Slope. Kept library-side so
:class:AnalogEdgeStartTrigger round-trips through JSON without
pulling NI's enum machinery into the serialisation layer.
ChannelSpec
dataclass
¶
Application-facing description of one DAQ channel.
Attributes:
| Name | Type | Description |
|---|---|---|
physical_channel |
str
|
NI physical channel identifier, e.g. |
name |
str | None
|
Optional friendly name; defaults to the physical channel. |
unit |
str | None
|
Optional engineering unit string ( |
metadata |
Mapping[str, str | int | float | bool]
|
Free-form scalar metadata propagated into emitted records. |
kind
class-attribute
¶
Discriminator used by :meth:from_dict. Concrete subclasses override.
__post_init__ ¶
Validate and freeze common channel metadata.
Source code in src/nidaqlib/channels/base.py
from_dict
classmethod
¶
Deserialise from a dict produced by :meth:to_dict.
On the base class, this dispatches to the registered subclass for the
kind discriminator. On a concrete subclass, this validates that
kind matches and constructs the dataclass directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Mapping[str, Any]
|
Mapping carrying the |
required |
Raises:
| Type | Description |
|---|---|
NIDaqValidationError
|
|
Source code in src/nidaqlib/channels/base.py
to_dict ¶
Serialise to a JSON/TOML-friendly dict, including kind.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict carrying |
dict[str, Any]
|
copied to plain |
Source code in src/nidaqlib/channels/base.py
CounterEdgeCountInput
dataclass
¶
CounterEdgeCountInput(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
edge=Edge.RISING,
initial_count=0,
count_up=True,
)
Bases: ChannelSpec
Edge-count counter-input channel.
Maps to Task.ci_channels.add_ci_count_edges_chan on the NI side.
Useful for encoders, totalisers, or anything that needs raw edge
accumulation.
Attributes:
| Name | Type | Description |
|---|---|---|
edge |
Edge
|
Edge that increments / decrements the counter. Rising by default. |
initial_count |
int
|
Starting value of the counter. Defaults to 0. |
count_up |
bool
|
When |
__post_init__ ¶
from_dict
classmethod
¶
Deserialise, restoring :class:Edge from its string value.
Source code in src/nidaqlib/channels/counter_input.py
to_dict ¶
Serialise; encode :class:Edge to its string value.
CounterFrequencyInput
dataclass
¶
CounterFrequencyInput(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
min_val,
max_val,
edge=Edge.RISING,
)
Bases: ChannelSpec
Frequency-measurement counter-input channel.
Maps to Task.ci_channels.add_ci_freq_chan on the NI side. NI uses
(min_val, max_val) to choose timebases that resolve frequencies in
the expected range.
Attributes:
| Name | Type | Description |
|---|---|---|
min_val |
float
|
Lower bound of the expected frequency, in Hz. |
max_val |
float
|
Upper bound of the expected frequency, in Hz. |
edge |
Edge
|
Edge of the input signal that increments the counter. Rising by default. |
__post_init__ ¶
Validate the expected frequency range.
Source code in src/nidaqlib/channels/counter_input.py
from_dict
classmethod
¶
Deserialise, restoring :class:Edge from its string value.
Source code in src/nidaqlib/channels/counter_input.py
to_dict ¶
CounterPeriodInput
dataclass
¶
CounterPeriodInput(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
min_val,
max_val,
edge=Edge.RISING,
)
Bases: ChannelSpec
Period-measurement counter-input channel.
Maps to Task.ci_channels.add_ci_period_chan on the NI side.
Attributes:
| Name | Type | Description |
|---|---|---|
min_val |
float
|
Lower bound of the expected period, in seconds. |
max_val |
float
|
Upper bound of the expected period, in seconds. |
edge |
Edge
|
Starting edge of the period measurement. Rising by default. |
__post_init__ ¶
Validate the expected period range.
Source code in src/nidaqlib/channels/counter_input.py
from_dict
classmethod
¶
Deserialise, restoring :class:Edge from its string value.
Source code in src/nidaqlib/channels/counter_input.py
to_dict ¶
Serialise; encode :class:Edge to its string value.
CounterPulseFrequency
dataclass
¶
CounterPulseFrequency(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
frequency,
duty_cycle=0.5,
initial_delay=0.0,
idle_high=False,
requires_confirm=True,
)
Bases: ChannelSpec
Pulse-train counter output specified by frequency + duty cycle.
Maps to Task.co_channels.add_co_pulse_chan_freq on the NI side.
Attributes:
| Name | Type | Description |
|---|---|---|
frequency |
float
|
Pulse-train frequency, in Hz. |
duty_cycle |
float
|
Fractional duty cycle in |
initial_delay |
float
|
Optional delay before the first pulse, in seconds. Defaults to 0. |
idle_high |
bool
|
When |
requires_confirm |
bool
|
When |
__post_init__ ¶
Validate pulse-train parameters.
Source code in src/nidaqlib/channels/counter_output.py
CounterPulseTicks
dataclass
¶
CounterPulseTicks(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
source_terminal,
high_ticks,
low_ticks,
initial_delay=0,
idle_high=False,
requires_confirm=True,
)
Bases: ChannelSpec
Pulse-train counter output specified by high / low tick counts.
Maps to Task.co_channels.add_co_pulse_chan_ticks on the NI side.
The tick reference is given by source_terminal.
Attributes:
| Name | Type | Description |
|---|---|---|
source_terminal |
str
|
NI terminal supplying the tick clock (e.g.
|
high_ticks |
int
|
Number of source ticks in the high state. |
low_ticks |
int
|
Number of source ticks in the low state. |
initial_delay |
int
|
Optional initial-delay tick count. |
idle_high |
bool
|
When |
requires_confirm |
bool
|
Defaults to |
__post_init__ ¶
Validate pulse tick parameters.
Source code in src/nidaqlib/channels/counter_output.py
CounterPulseTime
dataclass
¶
CounterPulseTime(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
high_time,
low_time,
initial_delay=0.0,
idle_high=False,
requires_confirm=True,
)
Bases: ChannelSpec
Pulse-train counter output specified by high / low durations in seconds.
Maps to Task.co_channels.add_co_pulse_chan_time on the NI side.
Attributes:
| Name | Type | Description |
|---|---|---|
high_time |
float
|
High-state duration, in seconds. |
low_time |
float
|
Low-state duration, in seconds. |
initial_delay |
float
|
Optional delay before the first pulse, in seconds. |
idle_high |
bool
|
When |
requires_confirm |
bool
|
Defaults to |
__post_init__ ¶
Validate pulse timing parameters.
Source code in src/nidaqlib/channels/counter_output.py
DaqBlock
dataclass
¶
DaqBlock(
*,
device,
task=None,
channels,
data,
block_index,
first_sample_index,
samples_per_channel,
block_period_ns,
t_mono_ns,
t_utc,
t_midpoint_mono_ns,
task_started_at,
t0,
read_started_at,
read_finished_at,
elapsed_s,
units,
error=None,
)
One rectangular block of hardware-clocked samples.
The data field is the natural shape for Parquet row groups, NumPy
slicing, and TDMS — do not scalarize unless the user opts in via
:func:nidaqlib.block_to_rows.
To recover the wall-clock or monotonic timestamp of sample k::
t_mono_k = block.t_mono_ns + k * block.block_period_ns
elapsed_k = (block.first_sample_index + k) / block.sample_rate_hz
t_wall_k = block.task_started_at + timedelta(seconds=elapsed_k)
Do not interpolate sample times off read_started_at —
that drifts block-to-block.
Attributes:
| Name | Type | Description |
|---|---|---|
device |
str
|
Manager-add name, or |
task |
str | None
|
Underlying |
channels |
tuple[str, ...]
|
Channel display names in the row order of |
data |
ndarray
|
NumPy array of shape |
block_index |
int
|
0-based, monotonic per task. Resets on a new task. |
first_sample_index |
int
|
Cumulative sample offset since |
samples_per_channel |
int
|
|
block_period_ns |
int | None
|
Integer nanoseconds between consecutive samples.
|
t_mono_ns |
int
|
|
t_utc |
datetime
|
Wall-clock at sample index 0 (UTC, tz-aware). |
t_midpoint_mono_ns |
int | None
|
Midpoint of the full block window in
|
task_started_at |
datetime
|
Wall-clock anchor for sample-time reconstruction. |
t0 |
datetime
|
Wall-clock at the first sample of this block; equals
|
read_started_at |
datetime
|
Wall-clock just before the read (provenance). |
read_finished_at |
datetime
|
Wall-clock just after the read (provenance). |
elapsed_s |
float
|
|
units |
Mapping[str, str | None]
|
Engineering units keyed by channel display name. |
error |
NIDaqError | None
|
Populated only under |
__post_init__ ¶
Validate the rectangular-shape invariant.
Raises:
| Type | Description |
|---|---|
NIDaqValidationError
|
|
Source code in src/nidaqlib/tasks/models.py
DaqManager ¶
Lifecycle, dispatch, and group operations across multiple NI tasks.
Construction does not touch the driver. Add tasks via :meth:add
(lazy — no NI calls), then call :meth:start to bring them up.
:meth:close always tears down in reverse-add order.
The manager is async-context-manager-aware: async with DaqManager()
closes every session on exit, even on raised errors.
Create a manager.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
error_policy
|
ErrorPolicy
|
Default policy for group operations
(:meth: |
RAISE
|
Source code in src/nidaqlib/manager.py
add
async
¶
Register a task with this manager. Idempotent on duplicate name.
Performs a best-effort preflight conflict check against tasks already managed (design doc §15.3). NI is the final authority — the preflight only catches obvious overlaps.
add does not allocate NI resources — it constructs a
:class:DaqSession and records it. The session's
:meth:DaqSession.configure (which creates the NI task and applies
channels / timing / logging / triggers) runs lazily on the first
:meth:start for the task. Any NI rejection of the spec (bad
physical channel, unsupported channel kind, sample rate above
device max, …) therefore surfaces at :meth:start time, not
:meth:add time. The preflight catches operator-side overlap
only; everything NI validates lives downstream.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Manager-side label for this task. Must be unique. |
required |
source
|
TaskSpec | DaqSession
|
Either a :class: |
required |
backend
|
DaqBackend | None
|
Optional :class: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
The |
DaqSession
|
class: |
DaqSession
|
the same |
|
DaqSession
|
bumps a refcount. |
Raises:
| Type | Description |
|---|---|
NIDaqTaskStateError
|
|
NIDaqResourceError
|
|
Source code in src/nidaqlib/manager.py
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 | |
close
async
¶
Tear down every managed session in LIFO order. Idempotent.
Failures are collected into an :class:ExceptionGroup; one slow /
broken close does not prevent others from running.
Source code in src/nidaqlib/manager.py
get ¶
Return the session registered under name.
Raises:
| Type | Description |
|---|---|
KeyError
|
|
poll
async
¶
Poll one or more tasks once each. Returns one :class:DaqReading per task.
Source code in src/nidaqlib/manager.py
read_block
async
¶
Read one block per task in parallel.
Source code in src/nidaqlib/manager.py
remove
async
¶
Decrement refcount; tear down on the last :meth:remove.
A no-op for unknown names — matches sibling parity.
Raises:
| Type | Description |
|---|---|
NIDaqError
|
Surfaced from session close (collected into a
group when called from :meth: |
Source code in src/nidaqlib/manager.py
start
async
¶
Start one or more managed tasks. Defaults to all in add-order.
Source code in src/nidaqlib/manager.py
start_synchronized
async
¶
Arm slaves first, then start master.
Multi-task synchronisation requires strict ordering: each slave is configured against a shared sample clock or trigger and must reach the armed-and-waiting state before the master is started — once the master arms its clock or fires its trigger, the slaves react immediately. If a slave is started after the master, samples before its first edge are lost.
Slaves are armed sequentially (not concurrently): NI's
start_task returns once the task is armed, so issuing the
starts in order guarantees every slave has reached the armed state
before the master starts. This is intentionally simpler than the
parallel fan-out used by :meth:start; the difference matters
when one slave fails to arm — the master must not start at all.
On failure during slave arming, every slave that had already armed is stopped (in reverse order) before the error is raised; the master is not started.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
master
|
str
|
Manager-add name of the master task. |
required |
slaves
|
Sequence[str]
|
Manager-add names of the slave tasks. Order is respected — slaves are armed left-to-right. |
required |
error_policy
|
ErrorPolicy | None
|
Optional override; defaults to the manager's policy. |
None
|
confirm
|
bool
|
Required when any task being started can actuate hardware immediately. |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
One |
Mapping[str, DeviceResult[None]]
|
class: |
Mapping[str, DeviceResult[None]]
|
entry of |
Raises:
| Type | Description |
|---|---|
KeyError
|
|
BaseExceptionGroup
|
One or more tasks failed under
:attr: |
Source code in src/nidaqlib/manager.py
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 | |
stop
async
¶
Stop one or more managed tasks. Defaults to all in reverse-add.
Source code in src/nidaqlib/manager.py
DaqReading
dataclass
¶
DaqReading(
*,
device,
task=None,
values,
units,
t_mono_ns,
t_utc,
t_midpoint_mono_ns=None,
requested_at,
received_at,
latency_s,
metadata=_empty_metadata(),
error=None,
)
One scalar (or low-rate) reading across the channels of a task.
Attributes:
| Name | Type | Description |
|---|---|---|
device |
str
|
Manager-add name, or |
task |
str | None
|
Underlying |
values |
Mapping[str, float | int | bool]
|
One entry per channel, keyed by channel display name. |
units |
Mapping[str, str | None]
|
Engineering units, keyed by channel display name. |
t_mono_ns |
int
|
|
t_utc |
datetime
|
Wall-clock at the midpoint of the request/receive window. |
t_midpoint_mono_ns |
int | None
|
Optional integration-window midpoint. |
requested_at |
datetime
|
Wall-clock immediately before the read (provenance). |
received_at |
datetime
|
Wall-clock immediately after the read returns (provenance). |
latency_s |
float
|
|
metadata |
Mapping[str, str | int | float | bool]
|
Free-form scalar metadata. |
error |
NIDaqError | None
|
Populated only under |
DaqSession ¶
Owns one underlying NI task plus its lifecycle state.
Construction does not touch the driver. Call :meth:start (or use
:func:open_device) to create the task, add channels, and configure
timing. read_block / poll are valid once started.
Create a session for spec against backend.
The constructor only stores its arguments; it never touches the
driver. That keeps __init__ exception-free and avoids a
partially-initialised task object on configuration errors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
TaskSpec
|
Declarative :class: |
required |
backend
|
DaqBackend
|
Backend that proxies operations into NI (or the fake). |
required |
timeout
|
float
|
Default per-operation timeout in seconds. Individual
|
10.0
|
Source code in src/nidaqlib/tasks/session.py
device_info
property
¶
Cached identity for the device backing this task; None before configure.
has_active_callback_bridge
property
¶
True while a §11.3.2 callback bridge is registered.
is_configured
property
¶
True after :meth:configure succeeds and before :meth:close.
A configured session has a backing NI task with channels, timing,
logging, and triggers applied — but task.start() has not yet
been called. Buffer-event callback registration (§11.3.2) is only
valid in this window.
raw_task
property
¶
The underlying backend task handle.
For :class:~nidaqlib.backend.nidaqmx_backend.NidaqmxBackend this is
an nidaqmx.Task; for the fake backend it is an opaque
_FakeTask. Use this for advanced NI features that aren't exposed
via the wrapper — the escape hatch from design doc §7.4.
The handle is available once :meth:configure has succeeded — that
is, in either the configured-not-started or started state. The
callback bridge (§11.3.2) needs the handle pre-start to register
the buffer event.
Raises:
| Type | Description |
|---|---|
NIDaqTaskStateError
|
The session has not been configured yet. |
recoverable_error_count
property
¶
Count of :class:NIDaqTransientError events swallowed under RETURN policy.
Reset to 0 on every :meth:configure (i.e. fresh task build).
task_started_at
property
¶
Wall-clock anchor for sample-time reconstruction.
Returns None until :meth:start has succeeded. Once set, this
value is the truth that :class:DaqBlock.task_started_at carries —
it is captured exactly once per session, immediately before
backend.start_task, so that the first sample's wall-clock can be
reconstructed deterministically from
task_started_at + first_sample_index / rate_hz (design doc §8.7).
__aenter__
async
¶
__aexit__
async
¶
acquire
async
¶
Run one finite acquisition and return its :class:DaqBlock.
Convenience wrapper for the §12.3 finite-mode pattern: configure
finite, start, read, stop. Requires a session whose
:class:Timing.mode is :attr:AcquisitionMode.FINITE. After the
read completes, the underlying NI task is stopped — call
:meth:start again before another acquisition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
samples_per_channel
|
int
|
Number of samples per channel to read. |
required |
timeout
|
float | None
|
Optional per-call timeout in seconds. Falls back to the session-wide default. |
None
|
Raises:
| Type | Description |
|---|---|
NIDaqTaskStateError
|
The session is not started, is closed, or
its timing mode is not :attr: |
NIDaqReadError / NIDaqTimeoutError
|
Surfaced from the backend. |
Source code in src/nidaqlib/tasks/session.py
close
async
¶
Stop (if needed) and close the underlying task. Idempotent.
__aexit__ always calls this; explicit call is rare. Sessions that
have opted into the every-N-samples callback bridge MUST instead use
the recorder context manager — the bridge has its own ordered
shutdown protocol (design doc §11.3.2) that this method does not
implement.
Source code in src/nidaqlib/tasks/session.py
configure
async
¶
Create the underlying task and apply channels / timing / logging / trigger.
After this method, raw_task is available and any pre-start hooks
(notably the §11.3.2 buffer-event callback registration) may run.
task.start() is not called — use :meth:start for that.
On failure, the partial task is torn down so the session does not leak NI resources.
Raises:
| Type | Description |
|---|---|
NIDaqTaskStateError
|
Already configured, started, or closed. |
Source code in src/nidaqlib/tasks/session.py
poll
async
¶
One-shot scalar read across all channels.
Valid only for sessions that are not actively buffering a sample
clock (Timing.mode == ON_DEMAND or no Timing at all). For the
live-scalar use case during a high-rate acquisition, use
:func:record and read the most recent block's last column.
Raises:
| Type | Description |
|---|---|
NIDaqTaskStateError
|
The session is buffering a sample clock (continuous or finite mode and started). |
Source code in src/nidaqlib/tasks/session.py
read_block
async
¶
Read one rectangular :class:DaqBlock.
Wraps the backend read in an run_sync so the
event loop stays responsive during the blocking NI call. Increments
the per-session first_sample_index cursor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
samples_per_channel
|
int
|
Samples per channel for this block. |
required |
timeout
|
float | None
|
Optional per-call timeout in seconds; falls back to the session-wide default. |
None
|
Raises:
| Type | Description |
|---|---|
NIDaqTaskStateError
|
The session is not started or is closed. |
NIDaqReadError / NIDaqTimeoutError
|
Surfaced from the backend. |
Source code in src/nidaqlib/tasks/session.py
snapshot
async
¶
Return an :class:NIDaqSnapshot of this session's current state.
No I/O — built from the cached :class:DeviceInfo (one backend call
at configure time) and the session's own lifecycle flags. Safe to
call from any thread / event loop / callback context.
Source code in src/nidaqlib/tasks/session.py
start
async
¶
Start the configured task.
:meth:configure must have run first. This method calls NI's
task.start() and records the wall-clock anchor used for §8.7
sample-time reconstruction. Calling :meth:start again after
:meth:stop reuses the configured task and resets the
block/sample counters for a new run.
confirm=True is required for task kinds whose start call
can actuate hardware immediately (currently counter-output pulse
trains).
Raises:
| Type | Description |
|---|---|
NIDaqTaskStateError
|
Not configured, already started, or closed. |
NIDaqValidationError
|
Starting would actuate hardware without explicit confirmation. |
Source code in src/nidaqlib/tasks/session.py
stop
async
¶
Stop the underlying task. Idempotent for not-yet-started sessions.
Does NOT close the task. Use :meth:close to release NI resources.
Source code in src/nidaqlib/tasks/session.py
task_state ¶
Return the coarse :class:TaskState projection of this session.
I/O-free — derived from internal lifecycle flags.
Source code in src/nidaqlib/tasks/session.py
write
async
¶
Write one sample-per-channel to the task's output channels.
Safety gate (design doc §17):
- Keys of
valuesmust match the display names of the task's output channels (AO and/or DO). Unknown or missing keys raise :class:NIDaqValidationErrorbefore any I/O. - For analog-output channels with
safe_min/safe_maxset, values outside the resolved clamp window raise :class:NIDaqValidationError. Never silently clamped. - If any target channel has
requires_confirm=TrueandconfirmisFalse, the call raises :class:NIDaqValidationError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
Mapping[str, float | bool]
|
One value per output channel keyed by display name. |
required |
confirm
|
bool
|
Operator confirmation. Required (must be |
False
|
timeout
|
float | None
|
Per-call timeout in seconds. Falls back to the session-wide default. |
None
|
Raises:
| Type | Description |
|---|---|
NIDaqTaskStateError
|
The session is not started or is closed. |
NIDaqValidationError
|
Safety-gate or shape rejection (see above). |
NIDaqWriteError / NIDaqTimeoutError
|
Surfaced from the backend. |
Source code in src/nidaqlib/tasks/session.py
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 | |
DeviceInfo
dataclass
¶
DeviceInfo(
*,
name,
product_type,
serial_number,
ai_physical_channels,
ao_physical_channels,
di_lines,
do_lines,
ci_physical_channels,
co_physical_channels,
)
Snapshot of one NI device's identity and physical-channel inventory.
Cached on a :class:~nidaqlib.tasks.session.DaqSession at configure
time and surfaced by :meth:DaqSession.snapshot. The
:func:~nidaqlib.system.discovery.find_devices enumeration also
wraps a populated :class:DeviceInfo inside each successful
:class:DiscoveryResult.
DeviceResult
dataclass
¶
One per-task outcome from a manager group operation.
The mapping key carries the task name; this record carries only the
outcome. Use :meth:success / :meth:failure to construct.
Attributes:
| Name | Type | Description |
|---|---|---|
value |
T | None
|
The operation's success value, or |
error |
NIDaqError | None
|
The wrapped :class: |
failure
staticmethod
¶
DeviceSnapshot
dataclass
¶
DeviceSnapshot(
*,
name,
model,
firmware,
serial,
connected,
last_error,
recoverable_error_count,
captured_at,
)
Cross-instrument snapshot of one device's state, captured without I/O.
Shape-shared across sibling libraries (alicatlib, sartoriuslib,
watlowlib). NI extras sit on :class:NIDaqSnapshot.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Device / task name. |
model |
str | None
|
Hardware model string, when known. |
firmware |
str | None
|
Firmware version, when known. Always |
serial |
str | None
|
Device serial number, when known. |
connected |
bool
|
|
last_error |
ErrorContext | None
|
Most recent :class: |
recoverable_error_count |
int
|
Running count of swallowed
:class: |
captured_at |
datetime
|
UTC, tz-aware wall-clock at snapshot construction. |
DigitalEdgeReferenceTrigger
dataclass
¶
Bases: TriggerSpec
Reference trigger — capture pretrigger_samples before, the rest after.
Maps to task.triggers.reference_trigger.cfg_dig_edge_ref_trig. Only
valid for finite acquisitions; NI rejects continuous + reference
trigger combinations at configure time, and the wrapper does not
second-guess that.
Attributes:
| Name | Type | Description |
|---|---|---|
pretrigger_samples |
int
|
Number of samples per channel to retain from before the edge fires. Must be > 0. |
edge |
Edge
|
Active edge of the trigger. Rising by default. |
__post_init__ ¶
Reject zero / negative pretrigger windows up-front.
Raises:
| Type | Description |
|---|---|
NIDaqValidationError
|
|
Source code in src/nidaqlib/tasks/triggers.py
from_dict
classmethod
¶
Deserialise, restoring :class:Edge from its string value.
Source code in src/nidaqlib/tasks/triggers.py
to_dict ¶
Serialise; encode :class:Edge to its string value.
DigitalEdgeStartTrigger
dataclass
¶
Bases: TriggerSpec
Start the task on a digital edge from source.
Maps to task.triggers.start_trigger.cfg_dig_edge_start_trig. After
:meth:DaqSession.start returns, the task is armed — the first
sample is acquired only after NI sees the configured edge on
source.
Attributes:
| Name | Type | Description |
|---|---|---|
edge |
Edge
|
Active edge of the trigger. Rising by default. |
from_dict
classmethod
¶
Deserialise, restoring :class:Edge from its string value.
Source code in src/nidaqlib/tasks/triggers.py
to_dict ¶
Serialise enums to .value so the payload is JSON-encodable.
DigitalInput
dataclass
¶
DigitalInput(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
line_grouping_per_line=True,
)
Bases: ChannelSpec
Digital-input line or port.
Maps to Task.di_channels.add_di_chan on the NI side. physical_channel
accepts NI's line / port grammar (Dev1/port0/line0,
Dev1/port0:7, ...).
Attributes:
| Name | Type | Description |
|---|---|---|
line_grouping_per_line |
bool
|
When |
DigitalOutput
dataclass
¶
DigitalOutput(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
requires_confirm=True,
line_grouping_per_line=True,
)
Bases: ChannelSpec
Digital-output line or port.
Maps to Task.do_channels.add_do_chan on the NI side. Writes are gated
through :meth:DaqSession.write, which requires confirm=True
whenever any target channel sets requires_confirm (design doc §17.1).
Attributes:
| Name | Type | Description |
|---|---|---|
requires_confirm |
bool
|
When |
line_grouping_per_line |
bool
|
When |
DiscoveryResult
dataclass
¶
DiscoveryResult(
*,
ok,
port,
address=None,
baudrate=None,
protocol=None,
device_info=None,
error=None,
elapsed_s=0.0,
)
One enumeration row from :func:find_devices.
Shape-shared across sibling libraries (alicatlib,
sartoriuslib, watlowlib) so cross-instrument tooling can join
on a common discovery record.
Attributes:
| Name | Type | Description |
|---|---|---|
ok |
bool
|
|
port |
str
|
NI device name ( |
address |
str | int | None
|
Always |
baudrate |
int | None
|
Always |
protocol |
ProtocolKind | None
|
Always |
device_info |
DeviceInfo | None
|
Populated only when |
error |
NIDaqError | None
|
Populated only when |
elapsed_s |
float
|
Wall-clock seconds spent enumerating this entry. |
Edge ¶
Bases: StrEnum
Active edge for the sample clock or a trigger.
Mirrors nidaqmx.constants.Edge.
ErrorContext
dataclass
¶
ErrorContext(
port=None,
address=None,
command_name=None,
protocol=None,
task_name=None,
channel_name=None,
physical_channel=None,
ni_error_code=None,
extra=_empty_extra(),
)
Structured context attached to every :class:NIDaqError.
Base fields are shared across the sibling libraries so cross-instrument
log readers can join exceptions on a common shape. NI extras
(task_name, physical_channel, ni_error_code) sit alongside.
extra accepts any Mapping and is always frozen into a read-only
:class:types.MappingProxyType at construction so the shared empty
sentinel can never be mutated through error.context.extra[k] = v.
Base fields (shape-shared with sibling libs):
port: NI device name (Dev1, cDAQ1Mod3), or None.
address: Always None for NI (no multi-drop address concept).
command_name: Logical operation name ("read", "start",
"configure_timing", ...). The unified name; sibling libs
also call this command_name.
protocol: Always None for NI (no wire protocol).
extra: Free-form additional context.
NI extras
task_name: TaskSpec.name of the task at fault.
channel_name: Display name of the at-fault channel (optional).
physical_channel: NI physical-channel string (e.g. Dev1/ai0).
ni_error_code: NI DAQmx error code, when known.
merged ¶
Return a new context with updates overlaid. Unknown keys go to extra.
Source code in src/nidaqlib/errors.py
ErrorPolicy ¶
Bases: StrEnum
How recorders react to wrapped NI errors during a read.
RAISE
class-attribute
instance-attribute
¶
Cancel the recorder's task group and re-raise the error.
RETURN
class-attribute
instance-attribute
¶
Emit a :class:DaqBlock (or :class:DaqReading) with .error set,
then continue.
The recorder MUST advance timing counters (block_index /
first_sample_index / t_mono_ns) on error records so consumers
can detect dropped intervals. Consumers MUST gate on error is None
before reading data.
NIDaqBackendError ¶
Bases: NIDaqError
The backend rejected an operation or surfaced a generic NI failure.
Used when the failure is not a clean fit for the more specific subclasses
(read, timeout, validation, state). Wraps :class:nidaqmx.errors.DaqError
via __cause__.
Source code in src/nidaqlib/errors.py
NIDaqConfigurationError ¶
Bases: NIDaqError
Configuration-level error (bad spec, missing required field, ...).
Source code in src/nidaqlib/errors.py
NIDaqConfirmationRequiredError ¶
Bases: NIDaqConfigurationError
A safety-gated start was attempted without confirm=True.
Raised by :func:open_device and :meth:DaqManager.start when a task
that drives hardware (counter pulse outputs, analog outputs) is started
without the explicit confirm=True opt-in. See design §5.10 (safe-start
gate) and the ecosystem ConfirmationRequiredError convention shared
with :mod:watlowlib and :mod:sartoriuslib.
Source code in src/nidaqlib/errors.py
NIDaqConnectionError ¶
Bases: NIDaqError
Communication with the NI backend was lost or could not be established.
Aligns with the ecosystem ConnectionError convention (matching
:class:watlowlib.WatlowConnectionError,
:class:alicatlib.AlicatConnectionError,
:class:sartoriuslib.SartoriusConnectionError). NI's backend rarely
distinguishes "connection lost" from generic backend errors at the
driver layer; this class is the family seam for those that do.
Source code in src/nidaqlib/errors.py
NIDaqDependencyError ¶
Bases: NIDaqError
A required dependency (driver, optional extra) is unavailable.
Source code in src/nidaqlib/errors.py
NIDaqDiscoveryResult
dataclass
¶
NIDaqDiscoveryResult(
*,
ok,
port,
address=None,
baudrate=None,
protocol=None,
device_info=None,
error=None,
elapsed_s=0.0,
product_type=None,
serial_number=None,
chassis=None,
physical_module=None,
)
Bases: DiscoveryResult
NI-specific discovery row carrying product / chassis identity.
Subclasses :class:DiscoveryResult without renaming any base fields.
The NI extras (product_type, serial_number, chassis,
physical_module) sit alongside the shape-shared base.
NIDaqError ¶
Bases: Exception
Base class for every :mod:nidaqlib exception.
Carries a typed :class:ErrorContext. The message is the human-readable
summary; the context is the machine-readable detail.
Initialise with a human-readable message and optional context.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
Short, human-readable summary suitable for logs. |
''
|
context
|
ErrorContext | None
|
Structured fields about the failing operation. |
None
|
Source code in src/nidaqlib/errors.py
with_context ¶
Return a copy of this error with its context updated.
Useful when an inner layer raises and an outer layer wants to enrich
the context (for instance adding task_name or operation).
Source code in src/nidaqlib/errors.py
NIDaqReadError ¶
Bases: NIDaqError
A read against the underlying NI task failed.
Source code in src/nidaqlib/errors.py
NIDaqResourceError ¶
Bases: NIDaqError
A physical-channel conflict was detected by the manager preflight.
Best-effort signal — NI is the final authority. Raised by
:meth:DaqManager.add when the new task's channels overlap with one
already managed; ErrorContext.extra carries the conflicting task names
under "conflicts".
Source code in src/nidaqlib/errors.py
NIDaqSinkDependencyError ¶
Bases: NIDaqSinkError
A sink's optional dependency (pyarrow, asyncpg, ...) is missing.
Source code in src/nidaqlib/errors.py
NIDaqSinkError ¶
Bases: NIDaqError
Base class for sink-layer failures.
Source code in src/nidaqlib/errors.py
NIDaqSinkSchemaError ¶
Bases: NIDaqSinkError
A sink rejected an input record's shape.
Most commonly raised by row-oriented sinks (CsvSink, JsonlSink)
when handed a :class:~nidaqlib.tasks.DaqBlock without
accept_blocks=True — silently scalarising would surprise users with
1-GB CSV files at 10 kHz × 8 channels (design doc §14.1).
Source code in src/nidaqlib/errors.py
NIDaqSinkWriteError ¶
Bases: NIDaqSinkError
A sink failed while writing a batch (file I/O, DB error, ...).
Source code in src/nidaqlib/errors.py
NIDaqSnapshot
dataclass
¶
NIDaqTaskStateError ¶
Bases: NIDaqError
Operation invalid for the task's current lifecycle state.
Raised, for example, by :meth:DaqSession.poll when the task is buffered
and started — two consumers on the same NI buffer would race.
Source code in src/nidaqlib/errors.py
NIDaqTimeoutError ¶
Bases: NIDaqError
An NI read or write exceeded its configured timeout.
Distinct from :class:NIDaqTransientError: this is a hard timeout that
means the operation gave up. Transient errors mean "retry safe."
Source code in src/nidaqlib/errors.py
NIDaqTransientError ¶
Bases: NIDaqError
A driver-layer error that is safe to retry without rebuilding the task.
Surfaced by the backend when an NI DAQmx call fails with a code in the
documented "retry-safe" set (see
:data:nidaqlib.backend.nidaqmx_backend._TRANSIENT_NI_CODES). Common
examples: buffer-overrun under ErrorPolicy.RETURN and the
"samples still arriving" code that NI returns when a read window slid
just ahead of the producer.
Source code in src/nidaqlib/errors.py
NIDaqValidationError ¶
Bases: NIDaqConfigurationError
Request validation failed before any I/O.
Source code in src/nidaqlib/errors.py
NIDaqWriteError ¶
Bases: NIDaqError
A write against the underlying NI task failed.
Raised by :meth:DaqSession.write when the backend rejects the write.
Out-of-range values fail earlier as :class:NIDaqValidationError.
Source code in src/nidaqlib/errors.py
NidaqConfig
dataclass
¶
NidaqConfig(
*,
default_timeout_s=10.0,
default_sample_rate_hz=1000.0,
default_buffer_size=16,
default_chunk_size=1000,
eager_tasks=False,
)
Process-wide default settings.
Anything that varies per task (channel ranges, trigger source, TDMS
path) belongs on :class:~nidaqlib.tasks.TaskSpec, not here.
Attributes:
| Name | Type | Description |
|---|---|---|
default_timeout_s |
float
|
Fallback NI read/write timeout, in seconds. Used when the call site does not supply one explicitly. |
default_sample_rate_hz |
float
|
Fallback |
default_buffer_size |
int
|
AnyIO send-stream capacity for |
default_chunk_size |
int
|
Samples per channel per emitted |
eager_tasks |
bool
|
Opt-in to |
OverflowPolicy ¶
Bases: StrEnum
Behaviour when the recorder's outbound stream is full.
BLOCK
class-attribute
instance-attribute
¶
Producer awaits consumer. Risks NI buffer overrun on hardware-clocked tasks.
DROP_NEWEST
class-attribute
instance-attribute
¶
Drop the about-to-be-enqueued block. Bounds consumer latency; loses freshest data.
DROP_OLDEST
class-attribute
instance-attribute
¶
Drop the oldest queued block. Keeps newest data; loses older queued blocks.
PollSource ¶
Bases: Protocol
Anything that yields per-name :class:DeviceResult[DaqReading] per call.
Same name across all four sibling libraries. The recorder layer's
:func:record_polled accepts any :class:PollSource instance,
decoupling the polled producer from the concrete session/manager
types.
poll
async
¶
Read once across the named resources (or all, when names is None).
PollSourceAdapter ¶
Wrap a polled :class:DaqSession as a :class:PollSource.
Multi-channel by design: one DAQ task covers many channels, so the
returned mapping has exactly one entry — keyed by the task name —
carrying a multi-channel :class:DaqReading. Individual channels stay
inside reading.values.
Example::
adapter = PollSourceAdapter(session)
async with record_polled(adapter, rate_hz=2.0) as recording:
async for results in recording.stream:
reading = results[session.spec.name].value
...
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
DaqSession
|
A started :class: |
required |
Source code in src/nidaqlib/streaming/poll_source.py
poll
async
¶
Read one :class:DaqReading and wrap it as {name: DeviceResult.success(reading)}.
names is accepted for Protocol uniformity. When provided, the
adapter only emits a row if the session's name appears in
names — otherwise the returned mapping is empty.
Source code in src/nidaqlib/streaming/poll_source.py
ProtocolKind ¶
Bases: StrEnum
Wire protocol kind for cross-library :class:ErrorContext symmetry.
Has no members because NI DAQmx is not a wire-protocol library — every
NI error context carries protocol=None. The type exists for shape
parity with sibling libs (alicatlib, sartoriuslib, watlowlib)
whose ProtocolKind enums name real serial / MODBUS / RS-485
variants.
Recording
dataclass
¶
Active-recording handle returned by :func:record / :func:record_polled.
Attributes:
| Name | Type | Description |
|---|---|---|
stream |
AsyncIterator[T]
|
Async iterator of payloads. Closes when the recorder context manager exits. |
summary |
AcquisitionSummary
|
Mutable :class: |
rate_hz |
float | None
|
Configured cadence of the active recording. |
RunMetadata
dataclass
¶
RunMetadata(
*,
run_id,
started_at,
nidaqlib_version=(lambda: __version__)(),
nidaqmx_python_version=_detect_nidaqmx_version(),
ni_driver_version=_detect_ni_driver_version(),
python_version=(lambda: sys.version.split()[0])(),
platform=platform.platform(),
task_specs=_empty_task_specs(),
user_metadata=_empty_user_metadata(),
)
Provenance bundle for one acquisition run (design doc §18.2).
Attributes:
| Name | Type | Description |
|---|---|---|
run_id |
str
|
Caller-chosen identifier for the run (e.g. UUID, ISO
timestamp, experiment name). Must be unique within the
caller's storage scheme — :mod: |
started_at |
datetime
|
Wall-clock timestamp at which the run began. UTC. |
nidaqlib_version |
str
|
Version of this package. |
nidaqmx_python_version |
str
|
Version of the |
ni_driver_version |
str | None
|
NI-DAQmx driver version, or |
python_version |
str
|
Runtime Python version string. |
platform |
str
|
Platform string from :func: |
task_specs |
Mapping[str, TaskSpec]
|
One :class: |
user_metadata |
Mapping[str, object]
|
Free-form mapping the operator wants persisted alongside the run (git commit, sample ID, recipe name, ...). Values must be JSON-serialisable. |
for_run
classmethod
¶
Construct a :class:RunMetadata with auto-detected versions.
Convenience wrapper around the dataclass constructor that supplies the version / platform / timestamp defaults so callers only need to pass the run-specific fields.
Source code in src/nidaqlib/tasks/metadata.py
from_dict
classmethod
¶
Deserialise from a dict produced by :meth:to_dict.
Raises:
| Type | Description |
|---|---|
NIDaqValidationError
|
A required field is missing or malformed. |
Source code in src/nidaqlib/tasks/metadata.py
replace ¶
to_dict ¶
Serialise to a JSON-friendly dict.
task_specs round-trips through :meth:TaskSpec.to_dict, which
in turn dispatches each channel and trigger by kind.
Source code in src/nidaqlib/tasks/metadata.py
TaskBuilder ¶
Fluent builder for :class:TaskSpec.
Example
spec = ( ... TaskBuilder("ai_demo") ... .add_channel(AnalogInputVoltage(physical_channel="Dev1/ai0")) ... .with_timing(Timing(rate_hz=1000.0)) ... .build() ... )
Create a builder for a task named name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Task name. Will become :attr: |
required |
Source code in src/nidaqlib/tasks/builder.py
TaskSpec
dataclass
¶
Declarative description of one NI task.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Task name. Must be unique within an :class: |
channels |
Sequence[ChannelSpec]
|
One or more :class: |
timing |
Timing | None
|
Optional :class: |
trigger |
TriggerSpec | None
|
Optional :class: |
logging |
TdmsLogging | None
|
Optional :class: |
metadata |
Mapping[str, str | int | float | bool]
|
Free-form scalar metadata propagated into emitted records. |
__post_init__ ¶
Validate the channel list shape (the cheap, always-true invariants).
Raises:
| Type | Description |
|---|---|
NIDaqValidationError
|
|
Source code in src/nidaqlib/tasks/spec.py
from_dict
classmethod
¶
Deserialise from a dict produced by :meth:to_dict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Mapping[str, Any]
|
Mapping carrying the task-spec fields. |
required |
Raises:
| Type | Description |
|---|---|
NIDaqValidationError
|
A channel or trigger entry has an unknown
|
Source code in src/nidaqlib/tasks/spec.py
replace ¶
Return a copy of this spec with updates applied.
Mirrors dataclasses.replace but is exposed as a method for
consistency with the rest of the API.
Source code in src/nidaqlib/tasks/spec.py
to_dict ¶
Serialise to a JSON-friendly dict, dispatching channels by kind.
Source code in src/nidaqlib/tasks/spec.py
TaskState ¶
Bases: StrEnum
Coarse projection of NI DAQmx's documented task lifecycle.
NI's underlying verified/reserved/committed states all
bucket into :attr:CONFIGURED because the wrapper does not separately
track them today. The other four are 1:1 with NI's documented states.
CONFIGURED
class-attribute
instance-attribute
¶
Channels + timing applied (NI verified/reserved/committed).
CREATED
class-attribute
instance-attribute
¶
Task has been constructed; channels not yet applied.
STOPPED
class-attribute
instance-attribute
¶
Stopped but not yet closed; may transition back to RUNNING.
TdmsLogging
dataclass
¶
TdmsLogging(
*,
path,
operation=_default_logging_operation(),
mode=_default_logging_mode(),
group_name=None,
)
Driver-side TDMS logging configuration (design doc §14.6).
Attached to :attr:TaskSpec.logging. The wrapper does not write TDMS
by hand — nidaqmx-python exposes task-level driver-side logging via
task.in_stream.configure_logging(...). nidaqlib configures the
knobs and otherwise stays out of the way.
Attributes:
| Name | Type | Description |
|---|---|---|
path |
str | Path
|
Destination |
operation |
LoggingOperation
|
How NI handles a pre-existing file. Defaults to
:class: |
mode |
LoggingMode
|
Write-and-read vs. write-only. Defaults to
:class: |
group_name |
str | None
|
Optional TDMS group name. |
from_dict
classmethod
¶
Deserialise, restoring enum members from their .value ints.
Source code in src/nidaqlib/tasks/spec.py
to_dict ¶
Serialise to a JSON-friendly dict using each enum's .value.
Source code in src/nidaqlib/tasks/spec.py
ThermocoupleInput
dataclass
¶
ThermocoupleInput(
*,
physical_channel,
name=None,
unit=None,
metadata=_empty_metadata(),
adc_timing_mode=None,
adc_custom_timing_mode=None,
auto_zero_mode=None,
thermocouple_type,
min_val,
max_val,
cjc_source=None,
cjc_val=None,
units=_default_temperature_units(),
)
Bases: AnalogInputBase
Thermocouple analog-input channel.
Maps to Task.ai_channels.add_ai_thrmcpl_chan on the NI side. The
enum-typed fields are stored as int values matching
nidaqmx.constants so that to_dict/from_dict round-trips through
JSON without dragging NI's enum machinery into the serialisation layer.
Attributes:
| Name | Type | Description |
|---|---|---|
thermocouple_type |
ThermocoupleType
|
One of |
min_val |
float
|
Lower limit of the expected temperature, in |
max_val |
float
|
Upper limit of the expected temperature, in |
cjc_source |
CJCSource | None
|
Cold-junction compensation source. |
cjc_val |
float | None
|
Cold-junction reference temperature, in |
units |
TemperatureUnits
|
Temperature units for |
Inherits :attr:adc_timing_mode and :attr:adc_custom_timing_mode
from :class:AnalogInputBase — most useful here, since the NI 9213 /
9214 thermocouple modules expose the full set of timing modes.
__post_init__ ¶
Validate the temperature range.
Source code in src/nidaqlib/channels/analog_input.py
from_dict
classmethod
¶
Reconstruct, restoring enum members from their serialised .value ints.
Source code in src/nidaqlib/channels/analog_input.py
to_dict ¶
Serialise, encoding TC-specific enums via their .value ints.
Source code in src/nidaqlib/channels/analog_input.py
Timing
dataclass
¶
Timing(
*,
rate_hz,
mode=AcquisitionMode.CONTINUOUS,
samples_per_channel=None,
source=None,
active_edge=Edge.RISING,
)
Sample-clock timing configuration.
Attributes:
| Name | Type | Description |
|---|---|---|
rate_hz |
float
|
Sample clock rate, in Hz. Required for hardware-timed modes (finite / continuous). |
mode |
AcquisitionMode
|
Acquisition mode. Defaults to continuous. |
samples_per_channel |
int | None
|
For |
source |
str | None
|
Optional sample-clock source terminal (e.g. an external
terminal name); |
active_edge |
Edge
|
Sample-clock active edge. Rising by default. |
__post_init__ ¶
Validate timing parameters before they reach NI.
Source code in src/nidaqlib/tasks/spec.py
from_dict
classmethod
¶
Deserialise from a dict produced by :meth:to_dict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Mapping[str, Any]
|
Mapping carrying the timing fields. |
required |
Raises:
| Type | Description |
|---|---|
NIDaqValidationError
|
An enum field carries an unknown value. |
Source code in src/nidaqlib/tasks/spec.py
to_dict ¶
Serialise to a JSON-friendly dict.
Enum members serialise to their string values so the result is JSON-encodable without a custom encoder.
Source code in src/nidaqlib/tasks/spec.py
TriggerSpec
dataclass
¶
Base class for task-level trigger configurations.
Subclasses declare a non-empty :attr:kind and are registered via
:func:register_trigger_kind so :meth:from_dict on the base can
dispatch by discriminator.
Attributes:
| Name | Type | Description |
|---|---|---|
source |
str
|
NI terminal supplying the trigger (e.g. |
kind
class-attribute
¶
Discriminator used by :meth:from_dict. Concrete subclasses override.
from_dict
classmethod
¶
Deserialise; on the base, dispatch by kind to a registered subclass.
Raises:
| Type | Description |
|---|---|
NIDaqValidationError
|
|
Source code in src/nidaqlib/tasks/triggers.py
to_dict ¶
block_to_rows ¶
Unroll a :class:DaqBlock into one row per (channel, sample).
Per-sample timestamps reconstruct from block.t_mono_ns,
block.block_period_ns, and block.first_sample_index. For
on-demand blocks (no clock), samples are spaced uniformly within the
read window.
Each row carries:
device,task,channel— join keys.block_index,sample_index— block- and task-level indices.t_mono_ns— reconstructed monotonic nanoseconds for this sample.t_utc— reconstructed wall-clock (ISO 8601) for this sample.value— the scalar sample value.unit— engineering unit for the channel (orNone).error_type/error_message— populated only on error blocks.
Source code in src/nidaqlib/sinks/base.py
config_from_env ¶
Best-effort env loader.
Only reads well-known keys. Missing or unparseable values fall back to
:class:NidaqConfig's defaults — this function never raises.
Recognised keys (with prefix="NIDAQLIB_"):
NIDAQLIB_DEFAULT_TIMEOUT_S— float secondsNIDAQLIB_DEFAULT_SAMPLE_RATE_HZ— float HzNIDAQLIB_DEFAULT_BUFFER_SIZE— int slotsNIDAQLIB_DEFAULT_CHUNK_SIZE— int samplesNIDAQLIB_EAGER_TASKS—"1"/"true"/"yes"
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prefix
|
str
|
Prefix to prepend to each env key. Defaults to
|
DEFAULT_ENV_PREFIX
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
NidaqConfig
|
class: |
Source code in src/nidaqlib/config.py
find_devices ¶
Enumerate NI DAQ devices visible to the driver. Never raises.
Returns:
| Name | Type | Description |
|---|---|---|
One |
list[DiscoveryResult]
|
class: |
list[DiscoveryResult]
|
NI hardware is present. On enumeration-level failure (driver |
|
list[DiscoveryResult]
|
missing, system call raises), returns a single |
|
list[DiscoveryResult]
|
with |
Source code in src/nidaqlib/system/discovery.py
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | |
open_device
async
¶
Open and return a configured :class:DaqSession.
Usage forms::
async with await open_device(spec) as session:
...
session = await open_device(spec)
try:
...
finally:
await session.close()
Mirrors the ecosystem open_device shape used by alicatlib,
watlowlib, and sartoriuslib. The DAQ-specific deviation: the
spec is the declarative task description (channels, timing,
triggers) rather than a serial port string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spec
|
TaskSpec
|
Declarative :class: |
required |
backend
|
DaqBackend | None
|
Optional :class: |
None
|
timeout
|
float
|
Default per-operation timeout, in seconds. |
10.0
|
autostart
|
bool
|
When |
True
|
confirm_start
|
bool
|
Required when starting the task can actuate hardware
immediately (for example counter-output pulse trains). Only
consulted when |
False
|
Returns:
| Type | Description |
|---|---|
DaqSession
|
A configured :class: |
Source code in src/nidaqlib/tasks/__init__.py
read_sidecar ¶
Read a sidecar adjacent to tdms_path and reconstruct a :class:RunMetadata.
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
The sidecar does not exist. |
NIDaqValidationError
|
The sidecar JSON is structurally invalid. |
Source code in src/nidaqlib/tasks/metadata.py
reading_to_row ¶
Flatten a :class:DaqReading into a single row dict.
Layout:
device,task— join keys.t_mono_ns— int, canonical monotonic join key.t_utc— ISO 8601, wall-clock acquisition midpoint.t_midpoint_mono_ns— int or None (integration-window midpoint).requested_at/received_at— ISO 8601, I/O provenance.latency_s— float seconds.- one column per channel (
valueskeys), values flattened. - one
<channel>_unitcolumn per channel. error_type/error_message— populated only on error rows.
The same row layout is used by every row-oriented sink.
Source code in src/nidaqlib/sinks/base.py
record
async
¶
record(
source,
*,
chunk_size,
timeout=10.0,
buffer_size=16,
error_policy=ErrorPolicy.RAISE,
overflow=OverflowPolicy.DROP_OLDEST,
use_callback_bridge=False,
)
Hardware-clocked block acquisition.
Yields a :class:Recording[DaqBlock]. The stream is closed when this
context manager exits; summary is mutated in place during the run
and is safe to read after exit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
DaqSession
|
A configured :class:
|
required |
chunk_size
|
int
|
Samples per channel per emitted :class: |
required |
timeout
|
float
|
Per-read timeout in seconds (Option A only — Option B reads from the NI buffer with timeout 0). |
10.0
|
buffer_size
|
int
|
AnyIO memory-object stream buffer, in :class: |
16
|
error_policy
|
ErrorPolicy
|
:attr: |
RAISE
|
overflow
|
OverflowPolicy
|
Backpressure policy. |
DROP_OLDEST
|
use_callback_bridge
|
bool
|
Opt into the §11.3.2 every-N-samples callback
path. Default |
False
|
Raises:
| Type | Description |
|---|---|
NIDaqTaskStateError
|
|
ValueError
|
|
Source code in src/nidaqlib/streaming/block.py
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 | |
record_polled
async
¶
record_polled(
source,
*,
rate_hz,
error_policy=ErrorPolicy.RAISE,
overflow=OverflowPolicy.BLOCK,
buffer_size=64,
)
Software-timed scalar polling at rate_hz.
Yields a :class:Recording[T]. The per-tick payload type T depends
on source:
- :class:
DaqSession→ one :class:DaqReadingper tick. - :class:
DaqManager→Mapping[str, DeviceResult[DaqReading]]per tick (matches :meth:DaqManager.poll). - Any :class:
PollSource(including :class:PollSourceAdapter) →Mapping[str, DeviceResult[DaqReading]]returned by itspoll().
summary is updated in place during the run; a final snapshot is
frozen on exit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
DaqSession | DaqManager | PollSource
|
A started :class: |
required |
rate_hz
|
float
|
Target poll rate, in Hz. Must be > 0. |
required |
error_policy
|
ErrorPolicy
|
:attr: |
RAISE
|
overflow
|
OverflowPolicy
|
Backpressure policy. Defaults to :attr: |
BLOCK
|
buffer_size
|
int
|
AnyIO send-stream capacity in payload slots. |
64
|
Raises:
| Type | Description |
|---|---|
NIDaqTaskStateError
|
A session |
ValueError
|
|
Source code in src/nidaqlib/streaming/recorder.py
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |
sidecar_path_for ¶
Return the conventional sidecar path for tdms_path.
run.tdms → run.metadata.json. The .tdms suffix is replaced
with .metadata.json; any other extension gets .metadata.json
appended.
Source code in src/nidaqlib/tasks/metadata.py
to_pint ¶
Return a pint-compatible unit string for unit, or None.
Accepts
None→None.- A string already in pint form (
"degC","V", ...) — passed through unchanged when it's in the known set; otherwise returned as-is so unfamiliar units don't get silently dropped. - An
nidaqmx.constants.TemperatureUnitsmember → mapped through the dedicated temperature table.
Lossy by design: no tuple, no discriminator, no exception on unknown units — same contract as the sibling libraries.
Source code in src/nidaqlib/units.py
write_sidecar ¶
Write metadata next to tdms_path as <base>.metadata.json.
The TDMS file itself does not need to exist yet — the sidecar can be written before, during, or after the acquisition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tdms_path
|
str | Path
|
The TDMS file path. Determines the sidecar location via
:func: |
required |
metadata
|
RunMetadata
|
The :class: |
required |
indent
|
int | None
|
|
2
|
Returns:
| Name | Type | Description |
|---|---|---|
The |
Path
|
class: |