- 01What you are building
- 02N3uron in one paragraph
- 03Hardware: the MOXA UC-series edge gateway
- 04Installing N3uron on Linux
- 05The four modules you need
- 06Configuring the Modbus client
- 07Tag model and historization
- 08Configuring the MQTT publisher
- 09Sparkplug B vs raw MQTT — when to use which
- 10MQTT topic structure (with builder)
- 11Broker selection
- 12Store-and-forward — when the link drops
- 13Security: TLS, certs, what not to expose
- 14Troubleshooting
- 15Commissioning checklist
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.
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.
| Model | Class | Typical use |
|---|---|---|
| UC-2100 series | ARM Cortex-A7/A35, 1 GB RAM | Very small plants, ~50 tags, no local historization |
| UC-3100 series | ARM Cortex-A8, 1–2 GB RAM, cellular optional | Small plants with cellular backhaul, ~500 tags |
| UC-8100 series | ARM Cortex-A8, 1 GB RAM, two LAN ports | Standard mid-size plant edge box, ~1000–2000 tags |
| UC-8200 / 8400 series | x86 Atom, 2–4 GB RAM, multi-LAN | Larger plants, multiple Modbus segments, OPC UA server role on the same box |
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.
| Module | What it does | License tier |
|---|---|---|
| Modbus Client | Polls field devices over Modbus TCP and RTU | Standard |
| Tag History | Local time-series store for store-and-forward and trending | Standard |
| MQTT Publisher | Publishes tag values to an MQTT broker; supports Sparkplug B | Standard |
| Logger | Built 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:
ch_inverters, ch_meters, ch_rtac.inv01.P_kW, inv01.Q_kVAR, so the cloud subscriber can pattern-match on the device prefix.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: Plant1 → Inverters → INV01 → 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:
| Setting | Value | Notes |
|---|---|---|
| Broker URL | ssl://broker.example.com:8883 | Always TLS. Plain tcp://...:1883 is for lab only. |
| Client ID | plant1-edge | Must be unique per gateway. Brokers disconnect duplicates. |
| Username / Password | Per-plant credentials | Or X.509 client cert. Never anonymous on production brokers. |
| Keep Alive | 30 seconds | Short enough that the broker detects a dead client quickly, long enough that cellular keepalives don't burn data. |
| Clean Session | false (for Sparkplug B) | Sparkplug B needs persistent session state for birth/death certificates. |
| QoS | 1 (at least once) | QoS 0 loses data on link bounces; QoS 2 doubles broker work. QoS 1 is the SCADA default. |
| Retain | false for periodic data, true for static metadata | Retained messages stay on the broker for new subscribers. Useful for nameplate values, dangerous for time-varying data. |
| Framing | Sparkplug B | See §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 factor | Sparkplug B | Raw MQTT |
|---|---|---|
| Subscriber is Ignition or compliant SCADA | ✓ — auto-discovers tags, handles birth/death | Works but you build the tag tree manually in the SCADA |
| Subscriber is AWS IoT / Azure IoT Hub | Partial — many platforms don't natively parse Sparkplug B | ✓ — JSON over MQTT is what these expect |
| You want strict typing and sequence verification | ✓ | You roll your own sequence number scheme |
| You want flexible payload structure | Constrained by spec | ✓ — anything you can encode |
| Multi-vendor cloud subscribers | Spec 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:
| Broker | Where it lives | When to pick |
|---|---|---|
| Mosquitto | Self-hosted Linux VM | You control the broker; on-prem, single-tenant, free. Limits at ~10k concurrent connections. |
| HiveMQ | Self-hosted or cloud | Enterprise broker with strong Sparkplug B support, clustering, web console. Paid. |
| EMQX | Self-hosted or cloud | Open-source broker with clustering and rules engine. Free tier capable for most plant deployments. |
| AWS IoT Core | Cloud (AWS) | Managed broker, pay per message. Use raw JSON, not Sparkplug B — AWS IoT doesn't natively parse it. |
| Azure IoT Hub | Cloud (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:
- When the MQTT publisher cannot reach the broker, tag updates queue locally in the Tag History store.
- When the connection re-establishes, queued updates are published in order with their original timestamps.
- The broker and downstream SCADA see a burst of historical data followed by current data.
What this needs from your configuration:
- Sufficient local disk for the worst-case outage window. 1000 tags × 1 sample/second × 6 hours ≈ 22 million samples. At ~50 bytes per sample with timestamp and quality, that's ~1 GB. The MOXA UC-2100 with 8 GB eMMC will run out of space on a 48-hour outage; UC-8100 with 32 GB eMMC handles a week.
- Subscriber that understands out-of-order or historical timestamps. Sparkplug B carries timestamps; the SCADA historian must store by source timestamp, not by arrival time, or your historical trend will show a flat line followed by a vertical line.
- Tested by actually pulling the WAN. Most store-and-forward bugs are discovered the first time it's tested in production. Pull the WAN cable for 30 minutes during commissioning, verify the data comes through cleanly when reconnected.
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).
- TLS, always. Connect over port 8883 with TLS, never port 1883. Verify the server certificate; the gateway must reject the connection if the broker's cert doesn't validate against a trusted CA. Self-signed broker certs are acceptable on-prem if the gateway has the CA pinned; never on a cloud broker.
- Per-plant credentials. Every plant gateway has its own username and password (or its own client certificate). Reusing credentials across plants means one compromised gateway gives access to every plant's topics.
- Broker ACLs. A plant gateway should only be allowed to publish to its own topic tree (
spBv1.0/Plant_A/#) and not read or write anyone else's. Most brokers support this; Mosquitto viaacl_file, HiveMQ via the permissions API. - Never expose the broker on the public internet without authentication. Default-install Mosquitto on a public IP with anonymous enabled is a target within minutes. Shodan has indexed thousands of these. If you must put the broker on the public internet, require client certificates.
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
| Item | Verified |
|---|---|
| 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