Marcio Cunha

How to read legacy meters via Modbus TCP with async Python

Learn how to build an asynchronous Python script to collect and store telemetry data from legacy industrial meters via Modbus TCP in real time.

Marcio Cunha•4 min
Also available in:PortuguêsEspañol
Summary
  • Asynchronous architecture using asyncio and pymodbus prevents bottlenecks when querying multiple field devices in parallel.
  • Reducing jitter in industrial networks with managed switches ensures telemetry packets do not suffer critical delays.
  • Structured persistence in relational or time-series databases requires robust exception handling for unstable connections.
  • Physical protocol converters and media converters demand rigorous validation of register addresses in user manuals.
  • Monitoring read integrity prevents data corruption when older meters respond with elevated latency.

The challenge of integrating legacy industrial meters

Many industrial plants, commercial buildings, and electrical substations still operate with older energy, flow, and temperature meters. These devices perform their physical role with excellence, but they lack modern cloud communication interfaces or web APIs. In practice, this means engineers and maintenance teams must extract data directly from the factory floor using traditional automation protocols. Modbus TCP emerges as the ideal bridge for this task, allowing automation messages to be encapsulated within standard Ethernet network packets.

However, dealing with dozens or hundreds of legacy meters on the same network introduces a serious architectural obstacle: slow response times. If a conventional script queries each meter in a strictly sequential manner, the complete polling cycle can take minutes, rendering telemetry useless for real-time monitoring. To overcome this barrier, we must resort to asynchronous programming, a technique that allows the system to send requests and await responses without blocking the main execution flow.

Understanding the Modbus TCP protocol in practice

Modbus is an industrial communication protocol created in the 1970s, based on a master-slave architecture where a central computer requests information and field devices respond. In the Modbus TCP variant, the traditional RS-485 serial physical layer is replaced by standard TCP/IP networks, typically operating on port 502. In practice, the original Modbus message is placed inside a special header called MBAP, traveling freely through corporate switches and routers.

To ensure data traffic does not suffer delays or drops in noisy environments with media converters, it is crucial to understand how to reduce jitter in industrial networks with switches, prioritizing control packets. In software terms, each meter has a register table storing physical quantities such as voltage, current, and accumulated power. The Python script needs to know exactly which address to read, how many registers to request, and how to interpret the received bytes, converting them into understandable decimal numbers.

Structuring the asynchronous collector in Python

To implement our collector, we will use Python's native asyncio library alongside the pymodbus library, which provides robust support for asynchronous connections via TCP. The code below demonstrates how to structure a non-blocking routine to query a meter and display collected data in the console in real time, paving the way for subsequent insertion into a database.

import asyncio
from pymodbus.client import AsyncModbusTcpClient

async def read_meter(ip, port=502):
    client = AsyncModbusTcpClient(ip, port=port)
    await client.connect()
    if client.connected:
        try:
            # Read 2 holding registers starting from address 0 (offset 0)
            response = await client.read_holding_registers(address=0, count=2, slave=1)
            if not response.isError():
                print(f"Data retrieved from {ip}: {response.registers}")
            else:
                print(f"Error in meter response {ip}")
        except Exception as e:
            print(f"Communication failure with {ip}: {e}")
        finally:
            client.close()
    else:
        print(f"Could not connect to IP {ip}")

async def main():
    meter_ips = ["192.168.1.50", "192.168.1.51", "192.168.1.52"]
    tasks = [read_meter(ip) for ip in meter_ips]
    await asyncio.gather(*tasks)

if __name__ == "__main__":
    asyncio.run(main())

The script above demonstrates the great advantage of the asynchronous model: instead of waiting for meter A to respond before initiating the query for meter B, Python fires all requests almost simultaneously. While one device processes the request on the network, the processor manages other connections, drastically optimizing the total polling cycle time in environments with dozens of legacy devices.

Real-time data storage and fault handling

Collecting data is only half the job; the other half consists of storing it securely and accessibly for visualization dashboards. Depending on your plant's volume, you can choose lightweight relational databases like PostgreSQL or time-series databases like InfluxDB. When designing this layer, keep in mind that industrial networks suffer from electromagnetic interference and intermittent signal drops, requiring rigorous exception handling blocks and retry queues to prevent historical data loss.

If you also deal with analog instrumentation or more primitive field sensors connected via traditional serial buses, it is worth checking the complementary guide on calibration and reading of environmental sensors with Modbus RTU and Python, which covers delicate floating-point conversions and engineering scales. Combining these approaches guarantees comprehensive coverage of the plant's metering infrastructure, modernizing legacy systems without replacing expensive physical meters already installed.

Final considerations

Automating legacy meter readings via Modbus TCP using asynchronous Python scripts represents an elegant, cost-effective, and highly scalable solution for engineers and developers. By eliminating the sluggishness of traditional sequential approaches, we can monitor large fleets of equipment smoothly and reliably. With a well-structured code architecture, proper exception handling, and efficient persistence, it is possible to transform old hardware into a rich source of real-time operational data.