Emon-Dmon + Modbus
I was recently tasked with automating data collection from an industrial power monitor called an Emon-Dmon. The device’s general purpose is to monitor a power feed and collect usage information which can then be billed on. Three current sensors, one for each phase provide the main input. Additional data can be collected from other sources using an input board and dry contacts.
Reading data from the device is done using an integrated LCD, or through various communications protocols. Emon-Dmon offers four options for remote monitoring communications, EZ7, BACnet, Modbus, and LonWorks. Given the factory firmware version, my options were EZ7, a proprietary format requiring VERY expensive software, or Modbus whose documentation is widely available. Easy choice!
I am going to provide a brief overview of my work in getting data from an Emon-Dmon 3400 device using Modbus TCP.
Enabling Modbus
Modbus availability is dependent on the firmware version flashed at the factory. The most common configuration allows for EZ7 or Modbus. The device should have this information listed on the label. Two interfaces exist for each protocol, Serial (RS-485), and Ethernet. Changing the available protocol and which interface it communicates over can be done using an on-board dip switch block labeled S2.
In order to set the protocol to Modbus on both Serial and Ethernet, you would move dip switches 1 and 2 to off. After making this change, a CPU reset is required and can be done using a small white button at the top of the board. A power cycle at the upstream breaker will accomplish this as well.
Networking
The Emon-Dmon will have a Ethernet port on its main board. This will need to be plugged into a network and the device will need to be assigned an IP. This can be accomplished through a DHCP server or statically configured on the Emon-Dmon with menu navigation buttons
Modbus protocol overview
Modbus is quite easy once you understand the basics on how it works and the various interpretations of the standard. The basic idea is that data is stored in registers. You query this data by specifying a function used to access a particular register type and a register address. Lets look at all of this further.
Registers
- Modbus stores data in 1 and 16 bit wide registers.
- Modbus has 4 different register types
- Coil (Discrete Output)
- 1 bit
- Read / Write
- Discrete Input (or Status Input)
- 1 bit
- Read Only
- Input Register
- 16 bit
- Read Only
- Holding Register
- 16 bit
- Read / Write
- Coil (Discrete Output)
Addressing
- Each register type (Coil, Discrete, Input, and Holding) are addressed 0-65535
- Register addresses defined by the Modbus data model start at 1
- Register addresses defined in hardware start at 0
- This means that most listed Modbus addresses to each register need to be decremented by 1 to get the actual hardware address
- In some implementations, the register type a given address is stored in is represented by prefixing a number on the address
- Coils
- Prefix: 0
- Discrete Inputs
- Prefix: 1
- Input Registers
- Prefix: 3
- Holding Registers
- Prefix: 4
- Coils
- As an example, the documented address 41001 can be read by accessing a holding register at hardware address 1000
- Addressing start index (hardware address vs data model address) varies greatly by manufacturer implementation. Some devices account for this in hardware, some in software, while other just make note of it in documentation. For more information, see the Modbus specification document Page 6
- Prefixing a number on the address to specify the register type also varies by implementation. Reading the device’s manual will always be required to decipher how exactly the manufacturer defines access.
- As an example, here is how badly all this can differ
- Address in documentation = 40001
- Holding register at hardware address 0000
- Holding register at hardware address 0001
- Hardware address 40000 in one of the four available register types
- Address in documentation = 40001
Functions
- Function codes are sent to the Modbus device in order to define access to a specific register type.
- 1: Read Coils
- 2: Read Discrete Inputs
- 3: Read Holding Registers
- 4: Read Input Registers
- 5: Write Single Coil
- 6: Write Single Register
- 7: Read Exception Status
- 15: Write Multiple Coils
- 16: Write Multiple Registers
Exceptions
- When a packet is sent to a device with information that it does not understand, the device responds with an exception code
- 1: Function code received is not supported
- 2: The request attempted to access an invalid address
- 3: Request had incorrect data
- 4: unrecoverable error (catchall code)
Raw Data Format
- PDU: Protocol Data Unit
- This is the core of the Modbus protocol and contains a function code followed by an associated set of data
- Size and content of data are defined by function code
- Can not exceed 253 bytes
- ADU: Application Data Unit
- This is the network protocol which encapsulates the PDU
- Standard ADU formats
- RTU (Remote Terminal Unit) (Serial)
- ASCII (Serial)
- TCP/IP
Since I am using the TCP/IP ADU, I will go into further detail. A single Modbus TCP/IP packet would look like the following
- Ethernet frame start
- Modbus Application Protocol Header
- Transaction Identifier: Used to identify transactions for matching send receive which might be out of order
- Protocol Identifier: Always 0 to indicate Modbus
- Length: Number of following bytes
- Unit ID: Used when communication may be transiting an IP network but still needs this data for talk across serial on either end
- Modbus PDU
- Function code: Used to identify the function being used to access data
- Data: Data being requested or returned under prior specified function code
- Modbus Application Protocol Header
- Ethernet frame end
Emon-Dmon specific Modbus info
- The Emon-Dmon addresses specified in documentation are prefixed to show which register they belong to. They also need to be decremented by 1 in order to query the correct hardware address
- The Emon-Dmon stores the majority of its data in holding registers that are 2x registers in width, totaling 32 bits. For each address specified in the documentation, you need to query and concatenate the returned data from the address listed in the documentation with the one immediately following it. For instance, to get data at address 41001 in the documentation, you would query a holding register at address 0000 and 0001 and then combine the returned data.
- Most all data from the Emod-Dmon is in the format of a 32 bit IEEE formatted single precision float. Data collected from each 2x register must be converted to a readable number.
Code
I am using Python and the pyModbus library to query the Emon-Dmon.
Here is some basic code that returns the total energy delivered. This is found in a holding register at address 1000.
#!/usr/bin/python3
from pyModbusTCP.client import ModbusClient
from pyModbusTCP import utils
register = 1000
device_ip = "192.168.1.1"
c = ModbusClient(device_ip, port=502, auto_open=True, unit_id=1)
raw_data = c.read_holding_registers(register, 2)
if raw_data:
float_data = utils.decode_ieee(utils.word_list_to_long(raw_data)[0])
print(f"Total energy delivered: {float_data}")
else:
print(f"Did not find any data at address {register}")
c.close()
This graph was created using data at holding register addresses 1052, 1054, and 1056