01 — What you are building

One Linux edge gateway box, typically a MOXA UC-series or similar industrial ARM/x86 device, sitting on the plant LAN. N3uron runs on it. N3uron polls field devices (inverters, meters, RTACs) over Modbus TCP. It publishes the resulting tag values to an MQTT broker — on-prem or in the cloud — using Sparkplug B framing. A cloud SCADA platform (Ignition, AWS IoT SiteWise, customer portal) subscribes to those topics and gets near-real-time plant data without poking holes in the plant firewall for SCADA polling.

N3uron Modbus to MQTT data path Field devices on the left publish data over Modbus TCP to the N3uron edge gateway in the middle. N3uron applies a tag model and historization, then publishes to an MQTT broker over the WAN using Sparkplug B framing. Cloud SCADA subscribes to the broker on the right. Animated arrows show the direction of data flow. Inverter Fleet Modbus TCP POI Meter Modbus TCP SEL RTAC Modbus TCP server Modbus poll N3uron Edge MOXA UC-series Modbus client Tag model Historian (local) MQTT publisher Sparkplug B TLS over WAN MQTT Broker Mosquitto / HiveMQ / AWS IoT / EMQX Cloud SCADA Ignition / SiteWise / customer portal subscribe
The plant firewall only opens outbound MQTT to the broker. No inbound holes. This is the architectural reason MQTT-via-edge-gateway has displaced polling-based cloud SCADA on most modern plants.
ℹ Why this matters

Cloud SCADA platforms used to poll plant gateways inbound, which meant opening DNP3 or OPC ports through the plant firewall and accepting whatever security risk that came with. MQTT inverts the direction: the plant initiates an outbound TLS connection to a broker and keeps it open. The plant network never accepts an inbound connection. This is the same pattern modern IoT uses, applied to industrial telemetry.

02 — N3uron in one paragraph

N3uron is a modular industrial edge platform built by N3uron S.L. (Madrid). It runs on Windows or Linux, x86 or ARM. Functionality is split into modules that you license and load individually — Modbus client, DNP3 client, OPC UA client, OPC UA server, SQL connector, MQTT publisher, Sparkplug B framing, alarm engine, web UI. You pay for what you load. The platform is designed for industrial gateways and is widely deployed on small Linux edge boxes in solar and wind plants. The configuration model is JSON files on disk that can be version-controlled, edited from a web UI, or pushed by an automation tool.

03 — Hardware: the MOXA UC-series edge gateway

The most common host for N3uron at the plant edge is a MOXA UC-series industrial computer. These are small fanless ARM Linux boxes designed for industrial environments: DIN-rail mount, wide temperature range, multiple Ethernet ports, optional cellular modem, runs Debian-based Linux from MOXA. Other vendors make equivalent boxes (Advantech, Siemens, Beckhoff), but MOXA UC has the longest field track record in solar plant integrations and is what most integrators reach for first.

ModelClassTypical use
UC-2100 seriesARM Cortex-A7/A35, 1 GB RAMVery small plants, ~50 tags, no local historization
UC-3100 seriesARM Cortex-A8, 1–2 GB RAM, cellular optionalSmall plants with cellular backhaul, ~500 tags
UC-8100 seriesARM Cortex-A8, 1 GB RAM, two LAN portsStandard mid-size plant edge box, ~1000–2000 tags
UC-8200 / 8400 seriesx86 Atom, 2–4 GB RAM, multi-LANLarger plants, multiple Modbus segments, OPC UA server role on the same box
⚠ Sizing matters more than people think

N3uron is light, but a Sparkplug B publisher buffering hours of store-and-forward data needs both RAM and disk. On a UC-2100 with 1 GB RAM and 8 GB eMMC, a 4-hour link outage on a 1000-tag plant can fill the disk and stall the gateway. Match the box class to the worst-case offline window you expect. For sites with frequent cellular dropouts, oversize one tier.

04 — Installing N3uron on Linux

MOXA UC boxes ship with MOXA's Debian variant. Other Linux hosts (Ubuntu Server 22.04, Debian 12) work identically. Install N3uron from the vendor-provided installer:

# Pull the installer (replace VERSION with the version your license covers)
wget https://downloads.n3uron.com/n3uron-VERSION-linux-arm.tar.gz

# Extract
tar xzf n3uron-VERSION-linux-arm.tar.gz
cd n3uron-VERSION

# Install as a systemd service
sudo ./install.sh

# Confirm the service is up
sudo systemctl status n3uron

# Web UI listens on 8003 by default — open from a laptop on the same LAN
# http://<gateway-ip>:8003

Default credentials on first login are admin / n3uron. Change the password immediately. The web UI is the configuration surface — everything below is configured here, then persisted to JSON files under /opt/n3uron/conf/.

05 — The four modules you need

For a Modbus-to-MQTT bridge, four modules carry the work. Other modules exist; you don't need them for this build.

ModuleWhat it doesLicense tier
Modbus ClientPolls field devices over Modbus TCP and RTUStandard
Tag HistoryLocal time-series store for store-and-forward and trendingStandard
MQTT PublisherPublishes tag values to an MQTT broker; supports Sparkplug BStandard
LoggerBuilt in. Without it you cannot debug anything. Set log level to INFO in production.Included

06 — Configuring the Modbus client

Add the Modbus Client module from the web UI: Configuration → Modules → New → Modbus Client. Name it modbus. Inside the module:

1
Create a Channel per Modbus segment
A channel groups devices that share a transport and timing. One channel for all the inverters on the plant LAN, another channel for the POI meter (often a different IP range). Channels run in parallel; devices within a channel are polled sequentially. Naming convention: ch_inverters, ch_meters, ch_rtac.
2
Add Devices under each channel
One Device per Modbus endpoint. For each: IP address, port (502 standard), unit ID (1 unless the inverter is daisy-chained over a gateway), polling timeout (1500 ms is a safe default for plant LAN), retries (2). Disable byte swap and word swap initially — turn them on only after you confirm values are wrong.
3
Define Groups for poll-rate batching
A Group is a set of tags polled together at one rate. Fast group: P, Q, V, I at 1-second polling. Medium group: status flags and breaker positions at 5 seconds. Slow group: nameplate values, daily counters at 60 seconds. Polling everything at 1 second wastes Modbus bandwidth on nameplate values that change once a year.
4
Add Tags with their register addresses
Per tag: name, function code (3 for holding registers, 4 for input registers), address (zero-based or one-based — N3uron is zero-based; check the inverter manual carefully), data type (Float32, Int16, UInt32), and scaling. Naming convention: inv01.P_kW, inv01.Q_kVAR, so the cloud subscriber can pattern-match on the device prefix.
5
Verify with the live values panel
N3uron's web UI has a live tag value panel. Open it after applying the config and watch the values update. Anything showing as NaN or stuck at zero usually means wrong address, wrong byte order, or wrong data type. -32768 on a 16-bit register that should be an active-power reading is almost always a signed/unsigned mismatch.

07 — Tag model and historization

N3uron's tag model is hierarchical. Plant1.Inverter01.P_kW is a path. The MQTT publisher uses this path to build the topic structure later, so naming matters now. Build the hierarchy: Plant1InvertersINV01 → tag. Repeat for meters, RTAC, breakers.

For historization, add the Tag History module. Configure it to log every tag at change-of-value with a deadband matching the tag's significance. Don't log every register read — log when the value moved by enough to matter. A 0.1 kW deadband on a 1000 kW inverter is too tight (event storm); a 50 kW deadband is too loose (misses ramp transients). 1 kW is a reasonable starting point. Tune from the historian.

08 — Configuring the MQTT publisher

Add the MQTT Publisher module: Configuration → Modules → New → MQTT Publisher. The interesting settings:

SettingValueNotes
Broker URLssl://broker.example.com:8883Always TLS. Plain tcp://...:1883 is for lab only.
Client IDplant1-edgeMust be unique per gateway. Brokers disconnect duplicates.
Username / PasswordPer-plant credentialsOr X.509 client cert. Never anonymous on production brokers.
Keep Alive30 secondsShort enough that the broker detects a dead client quickly, long enough that cellular keepalives don't burn data.
Clean Sessionfalse (for Sparkplug B)Sparkplug B needs persistent session state for birth/death certificates.
QoS1 (at least once)QoS 0 loses data on link bounces; QoS 2 doubles broker work. QoS 1 is the SCADA default.
Retainfalse for periodic data, true for static metadataRetained messages stay on the broker for new subscribers. Useful for nameplate values, dangerous for time-varying data.
FramingSparkplug BSee §09 for the alternative.

09 — Sparkplug B vs raw MQTT — when to use which

Sparkplug B is a payload specification on top of MQTT that adds structure: birth/death certificates, sequence numbers, type-aware payloads, and a defined topic hierarchy. Raw MQTT lets you publish any payload (JSON, CSV, binary) to any topic. Both are valid.

Decision factorSparkplug BRaw MQTT
Subscriber is Ignition or compliant SCADA✓ — auto-discovers tags, handles birth/deathWorks but you build the tag tree manually in the SCADA
Subscriber is AWS IoT / Azure IoT HubPartial — many platforms don't natively parse Sparkplug B✓ — JSON over MQTT is what these expect
You want strict typing and sequence verificationYou roll your own sequence number scheme
You want flexible payload structureConstrained by spec✓ — anything you can encode
Multi-vendor cloud subscribersSpec is open, support varies✓ — JSON is universal

Default to Sparkplug B if your subscriber is Ignition or a Sparkplug-aware SCADA. Use raw JSON over MQTT if you're publishing to a public cloud IoT platform.

10 — MQTT topic structure

Sparkplug B mandates the topic format: spBv1.0/<group_id>/<message_type>/<edge_node_id>/<device_id>. Raw MQTT lets you choose. Either way, the topic structure is hard to change after deployment — every subscriber depends on it — so it has to be designed up front. The builder below shows both styles side by side.

MQTT Topic Builder

11 — Broker selection

The MQTT broker can live anywhere with network reachability and a stable IP/DNS. Common choices:

BrokerWhere it livesWhen to pick
MosquittoSelf-hosted Linux VMYou control the broker; on-prem, single-tenant, free. Limits at ~10k concurrent connections.
HiveMQSelf-hosted or cloudEnterprise broker with strong Sparkplug B support, clustering, web console. Paid.
EMQXSelf-hosted or cloudOpen-source broker with clustering and rules engine. Free tier capable for most plant deployments.
AWS IoT CoreCloud (AWS)Managed broker, pay per message. Use raw JSON, not Sparkplug B — AWS IoT doesn't natively parse it.
Azure IoT HubCloud (Azure)Same pattern as AWS. Strong integration with Azure data services.

12 — Store-and-forward — when the link drops

Cellular WAN links drop. Satellite links drop more. A plant that loses MQTT connectivity for 6 hours and then comes back must not lose the 6 hours of data. N3uron's store-and-forward behavior handles this:

What this needs from your configuration:

13 — Security: TLS, certs, what not to expose

Three layers of MQTT security: transport (TLS), authentication (username/password or client certs), and authorization (broker ACLs).

14 — Troubleshooting

Symptom: MQTT publisher shows "Disconnected" repeatedly

What it looks like. The N3uron web UI shows the MQTT module flipping between "Connected" and "Disconnected" every few seconds. No tags ever stay published. The broker logs show the client connecting and immediately disconnecting.

How to find it. First check: client ID collision. If two N3uron instances on different plants share the same client ID, the broker will disconnect whichever one connected first when the second one connects. Each will reconnect, knock the other off, repeat forever. Look in the broker log for the same client ID appearing from multiple source IPs. Second check: keepalive too short. If keep alive is 5 seconds and your WAN round-trip latency is 2 seconds with packet loss, the broker times out before the keepalive arrives.

How to fix it. Unique client ID per gateway, including a plant identifier. Keep alive 30 seconds for cellular links, 15 seconds for fiber. Verify there's no proxy or NAT timeout shorter than your keepalive.

Symptom: Tag values arrive but timestamps are all "now"

What it looks like. After a store-and-forward replay, the cloud SCADA shows all the replayed data with the same timestamp — the moment the link came back up, not the original sample times. The historical trend is broken.

How to find it. Either the publisher is not including source timestamps in the payload, or the subscriber is overwriting them with arrival time. For Sparkplug B, check that timestamp is set on each metric in the payload. For raw MQTT, confirm your JSON schema includes a timestamp field and the subscriber actually parses it.

How to fix it. On the N3uron side, ensure the MQTT Publisher is configured to include source timestamps from the historian, not assign new ones at publish time. On the subscriber, configure the historian-ingest path to use the message's timestamp field, not server-receive time. If the subscriber is AWS IoT, this is typically done in the rule that ingests messages into Timestream or SiteWise.

Symptom: Subscriber sees tags but Ignition tag browser is empty

What it looks like. MQTT broker logs show messages flowing. Topic subscriber tools (mosquitto_sub) show payloads arriving. But the Ignition MQTT Engine tag browser is empty.

How to find it. Sparkplug B requires birth certificates. The Edge Node publishes an NBIRTH message and each device publishes a DBIRTH when they come online. These contain the tag definitions. If the subscriber connects after the birth certificates were published and the broker did not retain them, the subscriber never learns about the tags. The subsequent NDATA and DDATA messages reference metric aliases the subscriber has never seen, and Ignition silently ignores them.

How to fix it. Either configure the gateway to republish birth certificates on subscriber-rebirth-request (the Sparkplug B standard mechanism), or restart the N3uron MQTT publisher so it republishes births. Ignition can request rebirth from the tag browser. This is the single most common Sparkplug B integration issue.

15 — Commissioning checklist

ItemVerified
MOXA UC-series box racked, on plant LAN, default password changed
N3uron installed, running as systemd service, admin password changed
Modbus client configured with channels per segment, devices per endpoint
Tags defined with correct addresses, data types, scaling; live values panel shows real numbers
Tag history module enabled with sensible deadbands per tag
MQTT publisher configured against TLS broker (port 8883)
Client ID unique per plant; credentials per-plant, not reused
Sparkplug B framing (or raw JSON for cloud-native subscribers) chosen and tested
Topic structure documented and matches subscriber expectations
Broker ACL restricts this gateway to its own topic tree only
Store-and-forward tested by pulling WAN for 30 minutes; data backfilled cleanly
Source timestamps preserved through to subscriber historian
N3uron config files (/opt/n3uron/conf/) backed up to corporate Git
Disk usage monitored; alert if Tag History store grows beyond 70% of disk

→ SEL RTAC as Power Plant Controller · → Hyper-V SCADA Server Build · → DNP3 Point List Design