Polling a Remote RTU Over Cellular Without Burning the Data Plan
Sizing polls, timeouts and keepalives for SCADA over LTE: the byte arithmetic, carrier NAT timeouts, report-by-exception, and why nobody dials in.
The bill that started it
Twelve remote lift stations, each on a 500 MB/month M2M SIM, each running a one-second Modbus TCP poll of 100 holding registers. The first invoice came back with overage on eleven of them. Nobody had done the arithmetic, and the arithmetic is not subtle.
A Modbus TCP read of 100 holding registers is 12 bytes of MBAP header plus PDU on the request, and 209 bytes on the response. Wrap each in IPv4 and TCP and you add 40 bytes per segment. Add the ACKs the stack sends back. Call it roughly 300 bytes of billable IP traffic per poll cycle, before any retries.
300 bytes/poll × 1 poll/s × 86,400 s = 25.9 MB/day
25.9 MB × 30 = ~780 MB/month, per site
The engineering answer was not a bigger data plan. It was that a lift station's wet well level does not change 86,400 times a day, and we were paying to hear the same number over and over.
Poll rate is a cost decision on a WAN link
On a plant LAN, a one-second scan class costs nothing you can measure, so nobody thinks about it. Over cellular it is a line item, and it is also a reliability decision — every poll is another chance to time out and retry, and retries are the traffic you did not budget for.
Before touching anything else, sort the remote tags into what actually needs sub-minute freshness. At a typical pump station that list is short: run/fail status, a high-high level, maybe a discharge pressure during a fill. Runtime hours, ambient temperature, and daily flow totals are fine at five or fifteen minutes. Splitting one scan class into three usually cuts the traffic by an order of magnitude with no operational argument from anyone.
The bigger win is protocol. Modbus has no report-by-exception — the master must ask, every time, for values that mostly have not moved. If you get to choose the protocol on a cellular site, choose one that pushes:
| Approach | Traffic behavior | Where it fits |
|---|---|---|
| Modbus TCP polling | Constant, proportional to poll rate. | Legacy devices you cannot replace. Add an edge concentrator. |
| DNP3 unsolicited responses (IEEE 1815) | Near zero when the process is quiet; bursts on change. | Water, wastewater, distribution — the classic fit. |
| MQTT / Sparkplug B publish-on-change | Near zero when quiet, plus small keepalives. | Newer edge gateways, sites with no static IP. |
| OPC UA subscription with a deadband | Low, but the session and keepalive overhead is heavier than MQTT. | Sites already standardized on UA. |
If the RTU only speaks Modbus, put a small gateway in the panel: poll the RTU locally at whatever rate you like over the RS-485 or the local Ethernet, and publish only changes upstream. The link cost then tracks the process, not the scan class. That gateway also becomes the place to buffer during an outage, which you will need anyway.
Deadbands do the real work
Report-by-exception with no deadband on an analog is worse than polling. A 4–20 mA level transmitter with a noisy last bit will generate a change event every scan and you have reinvented the one-second poll, with more overhead per sample.
Set the deadband from instrument noise and from what an operator can act on. On a wet well I use 2% of span; the level moves through that in seconds during a fill, so the trend still looks like a trend, and the float noise between pump cycles stops publishing. On a slow tank farm level, 0.5% is fine. Add an integrity poll — DNP3 Class 0, or a periodic full read — every few hours so a missed event cannot leave a stale value on the screen forever. Not every minute. Class 0 pulls the entire static database, and running it at poll rate throws away everything unsolicited responses bought you.
Who dials whom
Most M2M SIMs sit behind carrier-grade NAT on a dynamic address. The master cannot open a TCP connection to the RTU because the RTU has no routable address. Two ways out:
- Buy a private APN with static IPs. Works, costs more per SIM, and needs a tunnel or a carrier interconnect back to the SCADA network. This is what most legacy Modbus/DNP3-over-IP sites end up doing.
- Have the field device originate the connection. MQTT does this natively. DNP3 outstations can be configured for outstation-initiated TCP. OPC UA has reverse connect, where the server opens the socket to the client and the roles flip only for the transport.
The second option is better for every reason except inertia. There is no inbound firewall rule, no static IP to renew, no port exposed to a carrier network, and adding a site does not touch the corporate firewall. IEC 62443 zone design gets simpler when the conduit is outbound-only. If you are specifying a new remote site in 2026 and someone proposes inbound polling on a static-IP APN, ask what it buys beyond looking like the plant LAN.
NAT timeouts eat idle connections
Push architectures have a specific failure mode: the link goes quiet, and the carrier's NAT silently drops the mapping. The RTU thinks it has a session. The master thinks it has a session. Neither finds out until something tries to send, and then it fails after a full TCP retransmit timeout — sometimes minutes later.
Carrier NAT idle timeouts vary, and no operator publishes a guarantee. Assume the TCP mapping can expire somewhere in the 5–30 minute range and that UDP mappings expire far faster, often under a minute. Then keep traffic flowing more often than that.
The trap is the OS default. RFC 1122 says TCP keepalive, if implemented, must default to no less than two hours, and Linux duly ships tcp_keepalive_time at 7200 seconds. That default will never save a cellular session. Set the application-layer keepalive instead:
- MQTT keepalive: 60–120 seconds is normal on cellular. A PINGREQ/PINGRESP pair is about 4 bytes of payload, but with headers it is closer to 120 bytes of billable traffic per exchange — at 60 s that is roughly 5 MB/month. Budget it; do not be shocked by it.
- DNP3: the data link keepalive interval (link status request) should be shorter than your assumed NAT timeout.
- OPC UA: the subscription keepalive is driven by
PublishingInterval × MaxKeepAliveCount. Check the product, because the resulting interval is often several minutes by default.
Then verify it empirically. Let a site sit idle overnight with nothing else on the link and watch when the first write fails. That measurement is worth more than any number a carrier support rep gives you.
Timeouts and retries, sized for the actual link
Latency on a healthy LTE link is 40–100 ms round trip, which tempts people to leave the master's default 1000 ms timeout in place. Then the site goes to a congested tower at shift change, or the modem falls back to LTE-M, and p99 latency lands in seconds. The master declares a timeout, retries, the retry queues behind the original request that is still in flight, and the link gets worse under exactly the load that caused the problem.
What I do on a new cellular site:
- Ping the RTU from the SCADA server for 24 hours and record the distribution, not the average. Size the timeout off p99, then double it.
- Set retries to 2, not 5. If two attempts fail on a cellular link, a third one 500 ms later is not going to find a different network.
- Use a backoff on reconnect — 5 s, 15 s, 60 s, cap at a few minutes. A tight reconnect loop across twelve sites during a regional outage generates a genuinely impressive amount of billable traffic and attach signaling.
- Give the comms-failure alarm a delay longer than the worst normal recovery, or operators will learn to ignore it.
Retries are not free bytes. On a poll that already costs 300 bytes, three retries turn a bad hour into four times the traffic of a good one, and bad hours are exactly when the modem is also re-attaching.
TLS on a flapping link
If the link drops every few minutes and the client does a full TLS handshake on every reconnect, the handshakes can outweigh the data. A full mutual-auth handshake with certificate chains is a few kilobytes each way. Session resumption — a ticket or a TLS 1.3 PSK — cuts that to a few hundred bytes. Check whether the gateway actually resumes rather than assuming; some embedded stacks do a full handshake every time regardless of the ticket.
This is also the argument against very short MQTT session expiry on a marginal site. If the session expires between drops, the broker discards the subscription state and the client has to re-establish everything, not just the socket.
Things to check before you leave site
| Check | What good looks like |
|---|---|
| RSRP at the mounted antenna | Better than −100 dBm. −110 to −120 dBm works until it rains. |
| RSRQ / SINR | Record them. A strong RSRP with terrible SINR means interference, not distance, and a bigger antenna will not help. |
| Antenna cable | Every metre of thin coax costs signal. Mount the antenna where the signal is, not where the panel is. |
| 24-hour latency and loss log | Kept as commissioning evidence. You will want it during the first dispute. |
| Idle-timeout test | Session survives an overnight quiet period, or the keepalive is shortened until it does. |
| Measured monthly bytes | From the router's own counter, compared against the plan. Do this in week one, not after the invoice. |
| Modem watchdog | Reboots on sustained loss, with a backoff and a reboot counter published as a tag. |
| Power budget | Modem transmit peaks are the worst-case load on a solar site, and they happen exactly when signal is poor. |
That last watchdog point deserves a warning. A watchdog that reboots the router whenever a ping fails, with no backoff, will sit in a reboot loop through any carrier outage — and every reboot means a fresh attach, a fresh handshake, and no data. Give it a counter, publish the counter, and let it back off.
The one that catches people
If the site uses an LTE-M or NB-IoT module, check whether Power Saving Mode and eDRX are enabled. Both are 3GPP Release 13 features designed to let a battery device sleep for minutes or hours, and while the module is in PSM it is not reachable — the network does not page it, and your poll does not fail interestingly, it just times out. On a mains-powered RTU you almost always want PSM off. On a battery site you want it on, which means the architecture has to be device-originated with a scheduled wake, and "poll it now" stops being a thing anyone can do.