nidaqlib.backend¶
nidaqlib.backend ¶
Backend layer — protocol + real and fake implementations.
See design doc §10.
CallbackHandle
module-attribute
¶
Backend-defined receipt for a registered every-N-samples callback.
Real :class:~nidaqlib.backend.nidaqmx_backend.NidaqmxBackend returns the
underlying nidaqmx task object (NI exposes registration as a method on
the task with no separate handle). The fake backend returns its own opaque
handle. Callers treat this as opaque — pass it back to
:meth:DaqBackend.unregister_every_n_samples.
DaqBackend ¶
Bases: Protocol
Operations the rest of :mod:nidaqlib needs from the NI driver layer.
Implementations must wrap nidaqmx.errors.DaqError (and equivalents)
into :class:~nidaqlib.errors.NIDaqError subclasses, preserving the
original via __cause__ and populating
:class:~nidaqlib.errors.ErrorContext.
add_channel ¶
close_task ¶
configure_logging ¶
Configure driver-side TDMS logging on task (design doc §14.6).
Maps to task.in_stream.configure_logging(...) on the real backend.
Called once, after channels are added and before
:meth:configure_timing.
Source code in src/nidaqlib/backend/base.py
configure_timing ¶
configure_trigger ¶
Configure a start- or reference-trigger on task.
Implementations dispatch on the concrete :class:TriggerSpec
subclass:
- :class:
~nidaqlib.tasks.triggers.DigitalEdgeStartTrigger→task.triggers.start_trigger.cfg_dig_edge_start_trig. - :class:
~nidaqlib.tasks.triggers.AnalogEdgeStartTrigger→task.triggers.start_trigger.cfg_anlg_edge_start_trig. - :class:
~nidaqlib.tasks.triggers.DigitalEdgeReferenceTrigger→task.triggers.reference_trigger.cfg_dig_edge_ref_trig.
Called once, after :meth:configure_timing (NI requires the
sample clock to be configured before a reference trigger is set).
Source code in src/nidaqlib/backend/base.py
create_task ¶
device_info ¶
Return product / channel info for device, or None if unknown.
Used by :class:~nidaqlib.manager.DaqManager preflight to detect
module-level reservation classes (e.g. NI 9211/9212/9213/9214 TC
modules reserve the whole module per task). Implementations MAY
return None when the device is unknown to the backend or when
no such information is available (e.g. the fake backend).
Source code in src/nidaqlib/backend/base.py
read_block ¶
Block until samples_per_channel samples are available, return them.
Returns:
| Type | Description |
|---|---|
ndarray
|
|
ndarray
|
|
Source code in src/nidaqlib/backend/base.py
register_every_n_samples ¶
Register a buffer-event callback that fires every n samples.
The callback runs on a driver thread — implementations must not forward asyncio / anyio primitives to it. See design doc §11.3.2.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task
|
Any
|
Backend task handle from :meth: |
required |
n
|
int
|
Sample-count cadence. Must be > 0. |
required |
callback
|
Callable[[int], None]
|
Receives |
required |
Returns:
| Type | Description |
|---|---|
CallbackHandle
|
An opaque :data: |
CallbackHandle
|
meth: |
Source code in src/nidaqlib/backend/base.py
start_task ¶
stop_task ¶
unregister_every_n_samples ¶
Unregister a previously-registered buffer-event callback.
After this returns, the backend guarantees no further invocations of
the callback. MUST be called after :meth:stop_task on the same
task — NI rejects unregister on a running task with -200986. See the
§11.3.2 ordering invariants for the full stop → unregister →
sentinel → drain sequence.
Source code in src/nidaqlib/backend/base.py
write ¶
Write one sample-per-channel to task.
Keys of values are the channel display names declared on the
spec (ChannelSpec.display_name / NI's
name_to_assign_to_channel). Implementations dispatch on the
channel kinds present on the task — AO writes go through
AnalogMultiChannelWriter; DO writes through
DigitalMultiChannelWriter. Mixing kinds in one task is a
configuration error and SHOULD be rejected.
:meth:DaqSession.write performs all safety-gate validation
(confirm=True, safe_min / safe_max) before this call —
backends MUST NOT silently clamp or coerce.
Source code in src/nidaqlib/backend/base.py
FakeDaqBackend ¶
FakeDaqBackend(
*,
blocks=None,
read_block_default_shape=None,
read_errors=None,
write_errors=None,
)
In-memory test double for :class:~nidaqlib.backend.base.DaqBackend.
Capabilities:
- Scripted block reads, keyed by task name and consumed FIFO.
- Optional deterministic ramp generation when no script is provided.
- Scripted timeouts / errors injected by the test.
- An operation log (:attr:
operations) for asserting the §11.3.2 shutdown ordering. - A driver-thread simulator (:meth:
simulate_callbacks) that fires the registered every-N-samples callback on a privatethreading.Thread, matching the threading model of NI's real callback.
Configure the fake backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
blocks
|
dict[str, Sequence[ndarray]] | None
|
Per-task-name sequence of pre-built |
None
|
read_block_default_shape
|
tuple[int, int] | None
|
|
None
|
read_errors
|
dict[str, Iterable[Exception]] | None
|
Per-task-name iterable of exceptions to raise from
:meth: |
None
|
write_errors
|
dict[str, Iterable[Exception]] | None
|
Per-task-name iterable of exceptions to raise from
:meth: |
None
|
Source code in src/nidaqlib/backend/fake.py
operations
instance-attribute
¶
Append-only log of backend calls. Tests assert ordering against this.
add_channel ¶
Append spec to task.channels.
close_task ¶
configure_logging ¶
Record logging on task.
configure_timing ¶
Record timing on task.
configure_trigger ¶
Record trigger on task for test inspection.
Source code in src/nidaqlib/backend/fake.py
create_task ¶
Create and return a new :class:_FakeTask.
Raises:
| Type | Description |
|---|---|
NIDaqBackendError
|
A task with |
Source code in src/nidaqlib/backend/fake.py
device_info ¶
Return scripted DeviceInfo for device if registered, else None.
Tests register product types via :meth:register_device_info so the
manager's module-level preflight can be exercised against the fake.
Default behaviour (no registration) returns None, matching the
Protocol's "unknown device" semantics.
Source code in src/nidaqlib/backend/fake.py
read_block ¶
Pop the next scripted block, fall back to a deterministic ramp.
Raises:
| Type | Description |
|---|---|
NIDaqTimeoutError
|
A scripted timeout exception was queued. |
NIDaqReadError
|
A scripted read exception was queued, or the
queue is empty and no |
Source code in src/nidaqlib/backend/fake.py
register_device_info ¶
Scripted DeviceInfo for tests of the manager's module-level preflight.
Source code in src/nidaqlib/backend/fake.py
register_every_n_samples ¶
Stash callback on the task. Returns task as the handle.
Mirrors NI's ordering invariant: registration must precede
task.start(). Real NI rejects post-start registration with
-200960 ("Register all your DAQmx software events prior to starting
the task"); the fake raises an analogous
:class:NIDaqBackendError so the unit suite catches violations
that the hardware would otherwise surface only at integration.
Raises:
| Type | Description |
|---|---|
NIDaqBackendError
|
A callback is already registered, or the task has already been started. |
Source code in src/nidaqlib/backend/fake.py
simulate_callbacks ¶
Fire the registered callback firings times from a worker thread.
Models the behaviour of NI's DAQmx driver thread so the §11.3.2
bridge can be exercised end-to-end in unit tests. The callback runs
on a fresh threading.Thread (NOT the asyncio event loop).
The simulator stops early if the callback is unregistered between
firings — this models NI's "any pending events are discarded" note
on stop_task.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
task
|
_FakeTask
|
The fake task on which the callback was registered. |
required |
firings
|
int
|
Number of times to invoke the callback. |
required |
cadence_s
|
float
|
Optional sleep between firings, in seconds. Use 0 for a tight burst, > 0 to mimic a finite sample-clock cadence. |
0.0
|
Returns:
| Name | Type | Description |
|---|---|---|
The |
Thread
|
class: |
Thread
|
usually do not need to |
|
Thread
|
on the recorder's own shutdown to drain pending chunks. Joinable |
|
Thread
|
if asserting on thread liveness. |
Source code in src/nidaqlib/backend/fake.py
start_task ¶
stop_task ¶
unregister_every_n_samples ¶
Clear the buffer-event callback on task.
Mirrors NI's ordering invariant: unregistration requires the task
to be stopped. Real NI rejects post-running unregister with -200986
("DAQmx software event cannot be unregistered because the task is
running"); the fake raises an analogous
:class:NIDaqBackendError so the unit suite catches violations
that real hardware would otherwise surface only at integration.
Raises:
| Type | Description |
|---|---|
NIDaqBackendError
|
The task is still running. |
Source code in src/nidaqlib/backend/fake.py
write ¶
Record one write — for tests asserting on outputs.
Validation parity with the real backend: missing channel keys raise
:class:NIDaqConfigurationError; scripted errors raise from the
per-task write_errors queue.
Raises:
| Type | Description |
|---|---|
NIDaqConfigurationError
|
|
NIDaqWriteError / NIDaqTimeoutError
|
A scripted error was queued. |
Source code in src/nidaqlib/backend/fake.py
NidaqmxBackend ¶
Production backend wrapping nidaqmx-python.
Supported operations:
- Task creation / destruction.
- Analog, digital, and counter channel addition.
- Sample-clock timing.
- Start / stop / read / write.
- Trigger configuration.
- Every-N-samples buffer-event callbacks (the §11.3.2 bridge driver).
Per-task state held here is limited to the strong reference the
callback bridge needs: NI stores the registered callback as a raw C
function pointer (see §11.3.2 GC seam), and nidaqmx.Task uses
__slots__ so we can't stash the wrapper on the task itself. The
backend keeps it in self._callback_wrappers keyed by id(task)
until unregister_every_n_samples runs.
Source code in src/nidaqlib/backend/nidaqmx_backend.py
add_channel ¶
Dispatch on spec.kind and add the channel to task.
The dispatch table covers "ai_voltage", "thermocouple",
"ao_voltage", "di", "do", "ci_frequency",
"ci_period", "ci_edge_count", "co_pulse_frequency",
"co_pulse_time", and "co_pulse_ticks".
Raises:
| Type | Description |
|---|---|
NIDaqBackendError
|
NI rejected the channel creation, or
|
Source code in src/nidaqlib/backend/nidaqmx_backend.py
close_task ¶
Close task. Idempotent — already-closed tasks are silently OK.
Source code in src/nidaqlib/backend/nidaqmx_backend.py
configure_logging ¶
Configure driver-side TDMS logging via task.in_stream.
Raises:
| Type | Description |
|---|---|
NIDaqBackendError
|
NI rejected the configure-logging call. |
Source code in src/nidaqlib/backend/nidaqmx_backend.py
configure_timing ¶
Apply :class:Timing to task via cfg_samp_clk_timing.
Raises:
| Type | Description |
|---|---|
NIDaqBackendError
|
NI rejected the timing configuration. |
Source code in src/nidaqlib/backend/nidaqmx_backend.py
configure_trigger ¶
Dispatch trigger onto the appropriate NI triggers.* API.
Raises:
| Type | Description |
|---|---|
NIDaqBackendError
|
NI rejected the trigger configuration, or
the trigger |
NIDaqConfigurationError
|
The trigger spec is structurally
invalid (e.g. zero pretrigger samples — already caught by
|
Source code in src/nidaqlib/backend/nidaqmx_backend.py
create_task ¶
Create an nidaqmx.Task with name.
Raises:
| Type | Description |
|---|---|
NIDaqBackendError
|
NI rejected the task creation. |
Source code in src/nidaqlib/backend/nidaqmx_backend.py
device_info ¶
Return product info for device via nidaqmx.system.Device.
Direct lookup — does not enumerate the whole system. Returns
None if NI does not recognise the device alias. Used by the
manager's preflight to detect module-level reservation (e.g. TC
modules reserve the whole module per task).
Source code in src/nidaqlib/backend/nidaqmx_backend.py
read_block ¶
Block-read samples_per_channel samples per channel.
Uses :class:AnalogMultiChannelReader so the result is
np.ndarray of shape (n_channels, samples_per_channel) rather
than Task.read's list-of-lists. Allocates a fresh buffer per call
because clarity is worth a few microseconds of allocator pressure here.
Raises:
| Type | Description |
|---|---|
NIDaqTimeoutError
|
|
NIDaqReadError
|
NI returned any other read failure. |
Source code in src/nidaqlib/backend/nidaqmx_backend.py
register_every_n_samples ¶
Register a buffer-event callback for task.
Wraps NI's four-argument C-style callback into the Protocol's
single-argument Callable[[int], None]. Returns task itself —
NI tracks at most one such callback per task, so the unregister side
reuses the same handle.
The caller MUST keep a strong reference to callback for the
lifetime of the registration. NI stores the wrapper as a raw C
function pointer and Python GC will silently break the seam.
Raises:
| Type | Description |
|---|---|
NIDaqBackendError
|
NI rejected the registration. |
Source code in src/nidaqlib/backend/nidaqmx_backend.py
start_task ¶
Start task.
Source code in src/nidaqlib/backend/nidaqmx_backend.py
stop_task ¶
Stop task.
Source code in src/nidaqlib/backend/nidaqmx_backend.py
unregister_every_n_samples ¶
Unregister the buffer-event callback on task.
Per NI's API, registering with None clears the callback.
Raises:
| Type | Description |
|---|---|
NIDaqBackendError
|
NI rejected the unregister call. |
Source code in src/nidaqlib/backend/nidaqmx_backend.py
write ¶
Dispatch one-sample-per-channel write across AO / DO channels.
Inspects the underlying NI task's channel collections — AO writes go
through AnalogMultiChannelWriter; DO writes through
DigitalMultiChannelWriter. Mixing AO and DO on a single task is
rejected as :class:NIDaqConfigurationError. Per-channel ordering
follows task.channel_names so the caller's mapping does not need
to match NI's internal order.
Raises:
| Type | Description |
|---|---|
NIDaqConfigurationError
|
Task mixes AO and DO, or the keys of
|
NIDaqWriteError / NIDaqTimeoutError
|
Surfaced from the backend. |
Source code in src/nidaqlib/backend/nidaqmx_backend.py
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 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 | |