Connect Ignition to NI-DAQmx Without a Python/MQTT Sidecar

JNBridgePro — the fastest, easiest way to bridge Java and .NET in production. Generate proxies in minutes, call Java from C# (or C# from Java) with native syntax — trusted by enterprises worldwide. Learn more · Download free trial

Case Study Ignition 8.1 / 8.3 Java → .NET In-process, no sidecar

Plants that run Ignition SCADA often also own NI DAQ hardware from National Instruments — and those two worlds famously don’t meet. The accepted wisdom is that to connect Ignition to NI-DAQmx you must deploy a sidecar: a separate Python process reading the device and republishing data over MQTT or OPC UA. This case study shows the alternative everyone says doesn’t exist: an Ignition gateway module we built that calls the NI-DAQmx .NET API directly inside the gateway’s own JVM using JNBridgePro, and publishes readings as native Ignition tags. One process. No broker. No sidecar.

Two worlds that don’t meet

Ignition’s gateway is a Java application. NI-DAQmx — NI’s driver for its PCIe, USB, and CompactDAQ (cDAQ) hardware — exposes programmable APIs for C, .NET, and Python, but has never offered a Java API. There is no National Instruments driver module for Ignition, and the standard NI-DAQmx install contains no OPC UA server for Ignition’s OPC client to talk to. So a platform built on the JVM sits next to a driver whose supported interfaces are all somewhere else.

The Ignition community treats the direct route as a dead end. When a user asked the official forum, point-blank, whether Ignition can call a .NET assembly, the entire answer was:

“Not from inside Ignition (java vs .net).”

Inductive Automation forum, “Calling .NET assembly from Ignition”

The thread ended there. In every Ignition forum discussion we could find, bridging the gateway JVM to .NET is assumed impossible and never revisited.

What everyone builds instead: the sidecar

Because direct is “impossible,” the standing recommendation — including from Inductive Automation staff, when asked how to get NI USB-6211 data into Ignition — is middleware:

  • Python sidecar + MQTT: a separate Python program (the nidaqmx package) reads the device and republishes to a broker; Ignition subscribes. Three processes, two protocol hops, and a broker to babysit.
  • Python sidecar + OPC UA or HTTP: the same idea with a different transport — stand up a small OPC UA server or Flask app around the Python reader.
  • Commercial OPC middleware: a licensed OPC server in front of the hardware — still an extra hop, plus licensing.

These work — the forum thread above settled on Python + MQTT — but every variant adds processes to deploy and monitor, serializes every sample across a protocol boundary, and reduces the full NI-DAQmx API (timing, triggering, buffered reads) to whatever subset the sidecar chooses to expose.

The sidecar pattern (industry standard)

NI-DAQmx driver Python script MQTT broker Ignition gateway

3 processes · 2 protocol hops · every sample serialized twice

This module

Ignition tag system gateway module JNBridgePro shared memory NationalInstruments.DAQmx.dll

1 process · 0 hops — the .NET CLR runs inside the gateway JVM

The module: NI-DAQmx running inside Ignition

We built a standard Ignition gateway module (a .modl file, installed through the normal Config → Modules page) that uses JNBridgePro’s shared-memory transport to load the .NET CLR inside Ignition’s JVM, call NationalInstruments.DAQmx through generated Java proxies, and publish live readings through the Ignition SDK’s ManagedTagProvider — a native NI-DAQmx integration for Ignition, not a protocol relay. The Java-side technique is exactly the one from our NI-DAQmx from Java demo guide — same proxy JAR, same acquisition sequence — wrapped in about 300 lines of module glue.

Because the data enters Ignition through its native tag system rather than a protocol bridge, everything downstream just works: alarms, tag history, Perspective dashboards, transaction groups. The provider surfaces both the readings and the bridge’s own health:

TagMeaning
Bridge/StatusStarting / Running / Error: … / Stopped
Bridge/LastErrorMost recent error detail (walks .NET InnerExceptions)
Bridge/ReadCountCompleted polls since startup
Dev1/ProductTypeDevice model, e.g. PCIe-6363
Dev1/ai0…3/Value, Mean, Min, MaxPer-channel statistics over each 100-sample read

On a fault — device unplugged, driver error — the .NET exception detail lands in Bridge/LastError and the gateway log, value tags go stale (Bad_Stale quality), and the loop rebuilds the DAQmx task and recovers on its own.

How it works, in code

The acquisition loop is plain Java against the proxied .NET classes — recognizable to anyone who has used NI-DAQmx from C#:

DaqPollingService.java (condensed)
Task task = new Task();
task.Get_AIChannels().CreateVoltageChannel(device + "/ai0:3", "",
        AITerminalConfiguration.Differential, -10.0, 10.0, AIVoltageUnits.Volts);
task.Get_Timing().ConfigureSampleClock("", 1000.0,
        SampleClockActiveEdge.Rising, SampleQuantityMode.FiniteSamples, 100);
AnalogMultiChannelReader reader = new AnalogMultiChannelReader(task.Get_Stream());

while (running.get()) {
    task.Start();
    double[][] data = reader.ReadMultiSample(100);   // [channel][sample]
    task.Stop();
    publish(deviceName, data);                        // → Ignition tags
    Thread.sleep(pollPeriodMs);
}

Publishing is the Ignition SDK’s managed-provider API, nothing exotic:

NiDaqBridgeGatewayHook.java / TagPublisher.java (condensed)
provider = context.getTagManager().getOrCreateManagedProvider(
        ManagedTagProviderConfiguration.builder("NIDAQ")
                .persistTags(false)
                .allowTagCustomization(true)
                .build());
// per channel, each poll:
provider.configureTag("Dev1/ai0/Value", DataType.Float8);
provider.updateValue("Dev1/ai0/Value", volts, QualityCode.Good);

One architecture decision carries the whole design. JNI native libraries can be owned by only one classloader per JVM, and a loaded .NET CLR can never be unloaded — but Ignition modules are hot-reloadable, and every reinstall creates a fresh classloader. So the JNBridgePro runtime and proxy JARs do not ship inside the .modl: they sit on the gateway’s root classpath via three wrapper.java.classpath entries in ignition.conf, where they load once and live as long as the process. The .modl carries only the thin glue jar and can be upgraded freely; a system-property guard lets a reinstalled module reuse the already-loaded CLR:

ignition.conf additions
wrapper.java.additional.N=-Dignition.allowunsignedmodules=true
wrapper.java.additional.N=-Djnbridge.nidaq.config=C:/JNBridge-NIDaq/jnbridge-nidaq.properties
wrapper.java.classpath.N=C:/JNBridge-NIDaq/jnbcore.jar
wrapper.java.classpath.N=C:/JNBridge-NIDaq/bcel-6.10.0.jar
wrapper.java.classpath.N=C:/JNBridge-NIDaq/NIDAQmxProxies.jar

Verified on a live gateway

This isn’t a whiteboard exercise. The module was built against the Ignition SDK (8.3), installed on a stock Ignition 8.3.8 gateway on Windows, and run against a simulated PCIe-6363 created in NI MAX — the same no-hardware-required setup from the demo guide. The gateway log tells the story:

Ignition wrapper.log (excerpt)
NI-DAQmx bridge config (C:\JNBridge-NIDaq\jnbridge-nidaq.properties):
    device=(first found), channels=ai0:3, 100 samples @ 1000.0 Hz, poll every 1000 ms
Starting up module 'com.jnbridge.ignition.nidaq' v1.0.0 ...
NI-DAQmx acquisition started; tags publishing under provider 'NIDAQ'
NI-DAQmx bridge status: Starting
NI-DAQmx device found: Dev1 (PCIe-6363)
NI-DAQmx bridge status: Running

From there the NIDAQ provider appears in the Designer’s Tag Browser with Bridge/ReadCount incrementing once a second and live Value/Mean/Min/Max tags per channel — on our validation run the bridge polled for hours without a single error in the log. Both current Ignition lines can host it: 8.3, and 8.1 LTS from 8.1.33 (both bundle Java 17, which the full bridge pipeline passes on — verified on Azul Zulu 17, Ignition’s own JRE). The only version-specific code is five lines of provider configuration, since 8.1 uses ProviderConfiguration where 8.3 uses ManagedTagProviderConfiguration.builder — the SDK’s managed-tag-provider example shows both.

Why in-process beats a sidecar

  • Fewer moving parts. Nothing extra to deploy, monitor, or restart. No broker, no sidecar service, no per-sample serialize/deserialize.
  • Lower latency. Shared-memory in-process calls replace two network hops. Reads are block reads (100 samples per call), which is both DAQmx best practice and bridge-friendly.
  • The full API. The module speaks the complete NI-DAQmx .NET object model — timing, triggering, buffered acquisition — not the subset a sidecar happens to expose over MQTT topics.
  • Real diagnostics. DAQmx errors surface as .NET exception detail in the gateway log and a LastError tag, with tag quality going stale on faults — instead of a silently empty MQTT topic.
  • Native tags. Data enters through Ignition’s tag system, so alarming, history, and Perspective need no special handling.

A sidecar is still the right call in some architectures — if the DAQ box is remote from the gateway, a network hop is unavoidable, and our bridge vs REST vs gRPC comparison walks through that decision in general. But when the hardware and the gateway share a machine, inserting two protocol hops between a driver and its consumer is pure overhead.

💡

NI-DAQmx is the example, not the limit. The same pattern — proxy JAR on the root classpath, thin module glue, ManagedTagProvider — puts any .NET assembly in reach of an Ignition gateway: vendor SDKs that ship as DLLs, in-house .NET libraries, other instrument drivers. If Ignition needs data that only .NET can reach, it doesn’t need a sidecar to get it.

Build it yourself

Everything needed to connect NI DAQ hardware to Ignition this way is covered by existing material: the step-by-step demo guide shows how to generate the NI-DAQmx proxy JAR and run the same acquisition code from a console app (about 30 minutes, simulated device, no hardware), and the NI-DAQmx from Java overview shows the architecture interactively. From there, the module is glue: an AbstractGatewayModuleHook, a poll loop, and a ManagedTagProvider, with the JARs placed as described above. A JNBridgePro evaluation license is enough to run all of it.

Ignition talking to .NET, no sidecar Download JNBridgePro and start with the demo guide — or talk to us about the Ignition module POC.
Download JNBridgePro

FAQ

What is the best way to acquire data from an NI DAQ device in Ignition?

The standard advice is a Python sidecar republishing over MQTT or OPC UA — and when the DAQ hardware is remote from the gateway, that still makes sense. But when the device and the gateway share a machine, a gateway module that calls the NI-DAQmx .NET API in-process is simpler and faster: no broker, no extra processes, and readings land directly in Ignition’s tag system, as this case study’s module demonstrates.

Can Ignition call a .NET assembly?

Yes — despite the community’s standing answer of “not from inside Ignition.” A gateway module can use JNBridgePro to load the .NET CLR inside the gateway JVM and use any .NET DLL through generated Java proxies (JNA, the usual suggestion for native libraries in Ignition, only handles C-linkage DLLs — not .NET assemblies). This case study’s module does exactly that with NationalInstruments.DAQmx, verified on Ignition 8.3.8.

Does this need MQTT, OPC UA, or any middleware?

No. The .NET side runs in-process with the gateway over JNBridgePro’s shared-memory transport, and readings enter Ignition directly as managed tags. There is no broker, no separate server, and no additional process to operate.

Which Ignition versions can host the bridge?

Ignition 8.3 and 8.1 LTS from 8.1.33 onward — both bundle Java 17, on which the full bridge pipeline is verified. The only version-specific code is the five-line tag-provider configuration (the 8.1 and 8.3 SDKs differ there).

What happens when the module is reinstalled or upgraded?

The JNBridgePro runtime and proxy JARs live on the gateway’s root classpath, not inside the .modl, so the CLR loads once per process and survives module hot-reloads. The .modl itself is a thin glue jar that can be upgraded freely; an init guard reuses the already-loaded CLR.

Do I need real DAQ hardware to try this?

No — a simulated device created in NI MAX behaves like real hardware, produces sine-wave data on every analog-input channel, and is exactly what our validation used (a simulated PCIe-6363). Code developed against it runs unchanged on physical devices.