Skip to main content

The Python programming language is very popular for developing programs that run on Linux PC platforms as well as on embedded systems platforms like the Digi ConnectPort X2/X4 gateways (e.g. http://www.digi.com/products/wireless/), which feature an on-board Python interpreter so you can develop your program on a PC platform and then download it to run directly on the gateway. One example of this scenario is a ConnectPort X4 Zigbee Gateway communicating with one or more WattNode Modbus meters attached to a XB485 XBee-PRO ZB (extended range) Zigbee adapter. The gateway can thus serve as a data concentrator, acquiring data from multiple meters and shipping it back via wireless cellular and/or land-line links to the iDigi or Etherios cloud for easy (but secure) access from anywhere on the web.

The Python code example below shows how to request the WattNode’s Basic float registers and parse the WattNode response. Each 32-bit (4-byte or IEEE-754 single precision) float value consists of two consecutive Modbus registers. The WattNode returns these registers in the same order that many Modbus Master programs running on Intel CPU architectures expect (i.e. the so-called “Little Endian” order where the least significant part of the data comes first). However, with other software or target hardware platforms, some reordering of the raw data bytes may be needed before the WattNode float values can be converted from raw bytes into floating point variables in Python. After all of the raw data is reordered, converting it to floats is easy using Python’s “unpack” keyword that facilitates converting the binary C-language values into native Python variables.

Note that to keep this example as simple as possible, the Modbus RTU protocol’s usual message CRC computation and checking was omitted.

The code has been made platform-independent by using the sys.byteorder flag. If you don’t want the overhead of importing the sys package and you know which Endianess (Big or Little) your target platform uses, you can delete the “import sys” line, the if sys.byteorder == “little”: conditional test and the appropriate block of code below it. FYI, the ConnectPort X2’s that we have used are Big Endian.

This example uses Python version 2.4.3, but should run on later versions as well.

# This Python 2.4.3 code example shows how to parse the Modbus WattNode's Basic float registers from a reply frame.
# The lines of code that do the serial communications I/O are commented out since that part is hardware-dependent
# on the specific platform that the code runs on. Instead, the actual data bytes captured from a WattNode
# response are assigned to the reply which allows running this demo on a PC without a serial port.
# The main point of this example is to show how to parse the bytes from the WattNode and convert them to floats.
import array
import struct   # Needed for struct.unpack
import sys      # Only needed for sys.byteorder flag
#
# Open serial port 2 on Linux system
#ser = serial.Serial(port=2, baudrate=19200, bytesize=8, parity='N', stopbits=1, timeout=5, xonxoff=0, rtscts=0)
#
# Send command to WattNode at slave address 1 to read its Basic float registers
cmd = array.array('B', [0x01, 0x03, 0x03, 0xE8, 0x00, 0x22, 0x45, 0xA3])
#ser.write(cmd.tostring())
#
# Get WattNode Modbus RTU data reply
#buf = ser.read(3 + 68 + 2) # header + numBytes + CRC
#
# Simulate data reply
buf = "\x01\x03\x44\xF4\xAE\x3F\x36\x06\x42\x3F\x3A\xF4\xAE\x3F\x36\x06\x42\x3F\x3A\x38\x0A\x44\xCC\x3F" \
    + "\x5B\x44\x0A\x82\xD3\x44\x03\xAD\xE5\x44\x0A\xC0\x00\x42\x9C\xf6\xfa\x42\xf7\xf6\xbd\x42\xf8\x7b" \
    + "\xab\x42\xf8\x2e\x90\x43\x57\x2d\x31\x43\x57\x66\x9f\x43\x57\xf7\xe0\x43\x56\x40\x38\x42\x70\xd4\xab"
 
reply = array.array('B', buf[3:71]) # Remove header and trailing CRC, convert to array
numBytes = ord(buf[2])              # 3rd byte = data byte count = 2 x register count
 
# Each IEEE-754 single-precision float is 4 bytes long and comprised of two consecutive 16-bit Modbus 
# registers. Depending on whether the target machine is Little-endian or Big-endian, this "for loop";
# either swaps the bytes in each Modbus register or it swaps the registers, respectively.
for i in range(0, numBytes, 4) :
    if sys.byteorder == "little":   # Little-endian: swap bytes
        msb = reply[i]
        reply[i] = reply[i + 1]
        reply[i + 1] = msb
        msb = reply[i + 2]
        reply[i + 2] = reply[i + 3]
        reply[i + 3] = msb
    else:                           # Big-endian: swap registers
        msb = reply[i]
        lsb = reply[i + 1]
        reply[i] = reply[i + 2]
        reply[i + 1] = reply[i + 3]
        reply[i + 2] = msb
        reply[i + 3] = lsb
 
# Unpack 17 single-precision float values
EnergySum, EnergyPosSum, EnergySumNR, EnergyPosSumNR, PowerSum, PowerA, PowerB, PowerC, VoltAvgLN, VoltA, VoltB, VoltC, VoltAvgLL, \
VoltAB, VoltAC, VoltBC, Freq = struct.unpack('17f', reply)
fmt = "%.1f"
print "%.6f" %EnergySum, fmt %PowerSum, fmt %PowerA, fmt %PowerB, fmt %PowerC, fmt %VoltA, fmt %VoltB, fmt %VoltC, fmt %Freq)
# Displays: ('0.714671', '1633.8', '553.0', '526.0', '554.7', '124.0', '124.5', '124.2', '60.0')