Pymate-Modbus-basic: Difference between revisions
(→Code) |
|||
| Line 216: | Line 216: | ||
The module slave address and baudrate can be changed with special RS485 calls. | The module slave address and baudrate can be changed with special RS485 calls. | ||
[[File:Pymate-Modbus-basic-30. | [[File:Pymate-Modbus-basic-30.jpg|800px]] | ||
=== Code === | === Code === | ||
Latest revision as of 21:17, 5 July 2026
Introduction
Modbus is a standardized protocol over serial bus. The half-duplex RS485 bus can be used for Modbus transmission.
The Modbus over RS485 bus is available on the A+, B- terminals.
Example 1: Wind Direction
The following example shows how to grab the data from a RS485 Wind Direction device. The Wind Direction is a Modbus device.
So the example script will rely on Modbus RTU Master (umodbus library) to read the value.
The master send the request to read register 0x0000 on slave 0x02.
The function code 0x03 = "read holding register".
From that request we do expect the following response from the device.
The data contains the angular position in degrees * 10 the value is encoded as an uint16.
The angle can be converted to wind direction thanks to simple rule:
Code
The example is articulated around the:
- Modbus Instance : creating a ModbusRTUMaster instance via Board.modbus() to communnicates of the RS485 bus.
- Read Holding Register : Access the Slave #2 at address 0x00000 with Modbus function #3 (see ModbusRTUMaster.read_holding_registers() ).
- Get value : received value (uint16) and transform it in degree angle
from pymate.board import Board
import time, struct
DIR_TO_TEXT = ['North', 'Northeast by North', 'Northeast', 'Northeast by east', 'East',
'Southeast by east', 'Southeast', 'Southeast by south', 'South', 'Southwest by south',
'Southwest', 'Southwest by west', 'West', 'Northwest by west', 'Northwest', 'Northwest by north' ]
def dir_as_text( value ):
if 0<=value<=len(DIR_TO_TEXT):
return DIR_TO_TEXT[value]
return '???'
# === Main Program ===
brd = Board()
modbus = brd.modbus(baudrate=9600)
while True:
# function #3: READ Holding Registers
reg_value = modbus.read_holding_registers( slave_addr=0x02, starting_addr=0x0000, register_qty=1, signed=False)
# Returns tuple of uint16. Ex: (2427,)
degree = reg_value[0] / 10 # According to WindDirection docs, value = angle*10
direction = int(degree/22.5) # from angle to 16 possible direction
label = dir_as_text( direction )
print( "Direction:",direction,'=', label, "| Degree:", degree )
time.sleep_ms( 1000 )Example 2: Power Meter
The following example shows how to grab the data from a Modbus Single-Phase Power-Meter device from Etek.
This module monitor the power and many other power grid parameters (see example script).
The scripts rely on:
- the etek library (made available to customers) and
- umodbus library (part of Pymate.IO standard libray).
The module slave address can be displayed and changed from the module LCD.
Code : Single-Phase
from pymate.board import Board
from ekem import EKEM2D
import time
BAUDRATE = 9600
SLAVE_ADDR = 2
brd = Board()
modbus = brd.modbus( baudrate=BAUDRATE )
meter = EKEM2D( modbus, SLAVE_ADDR )
print( '==== Instantaneous values ======================' )
print( 'Voltage :', meter.voltage, 'Volts' )
print( 'Current :', meter.current, 'Amps' )
print( 'Active Power :', meter.active_power, 'kW' )
print( 'Reactive Power:', meter.reactive_power, 'kvar' )
print( 'Apparent Power:', meter.apparent_power, 'kVA' )
print( 'Power factor :', meter.power_factor )
print( 'Frequency :', meter.frequency, 'Hz' )
print( )
print( '==== combined active Energy ====================' )
print( 'Show current value of active energy.' )
print( 'Those are combined values' )
print( 'Total active energy :', meter.active.combined.total , 'kWh' )
print( 'Spike active energy :', meter.active.combined.spike , 'kWh' )
print( 'Peak active energy :', meter.active.combined.peak , 'kWh' )
print( 'Flat active energy :', meter.active.combined.flat , 'kWh' )
print( 'valley active energy :', meter.active.combined.valley, 'kWh' )
print( )
print( '==== Reversing Active Energy ====================' )
print( 'Total active energy :', meter.active.reversing.total , 'kWh' )
print( 'Spike active energy :', meter.active.reversing.spike , 'kWh' )
print( 'Peak active energy :', meter.active.reversing.peak , 'kWh' )
print( 'Flat active energy :', meter.active.reversing.flat , 'kWh' )
print( 'valley active energy :', meter.active.reversing.valley, 'kWh' )
print( )
print( '==== Forward Active Energy ======================' )
print( 'Total active energy :', meter.active.forward.total , 'kWh' )
print( 'Spike active energy :', meter.active.forward.spike , 'kWh' )
print( 'Peak active energy :', meter.active.forward.peak , 'kWh' )
print( 'Flat active energy :', meter.active.forward.flat , 'kWh' )
print( 'valley active energy :', meter.active.forward.valley, 'kWh' )
print( )
print( '==== Reactive Energy ==============================' )
print( 'Total reactive energy :', meter.reactive.total , 'kVarh' )
print( 'Spike reactive energy :', meter.reactive.spike , 'kVarh' )
print( 'Peak reactive energy :', meter.reactive.peak , 'kVarh' )
print( 'Flat reactive energy :', meter.reactive.flat , 'kVarh' )
print( 'valley reactive energy :', meter.reactive.valley, 'kVarh' )
print( )
print( '==== Reversing Reactive Energy ====================' )
print( 'Total reactive energy :', meter.reactive.reversing.total , 'kVarh' )
print( 'Spike reactive energy :', meter.reactive.reversing.spike , 'kVarh' )
print( 'Peak reactive energy :', meter.reactive.reversing.peak , 'kVarh' )
print( 'Flat reactive energy :', meter.reactive.reversing.flat , 'kVarh' )
print( 'valley reactive energy :', meter.reactive.reversing.valley, 'kVarh' )
print( )
print( '==== Forward Reactive Energy ======================' )
print( 'Total reactive energy :', meter.reactive.forward.total , 'kVarh' )
print( 'Spike reactive energy :', meter.reactive.forward.spike , 'kVarh' )
print( 'Peak reactive energy :', meter.reactive.forward.peak , 'kVarh' )
print( 'Flat reactive energy :', meter.reactive.forward.flat , 'kVarh' )
print( 'valley reactive energy :', meter.reactive.forward.valley, 'kVarh' )Code : Three-Phase
The etek library is ready for single-phase reading and three-phase reading.
Additional power-grid parameters can be retreived when using the three-phase power-meter.
from pymate.board import Board
from ekem import EKEM4D
import time
BAUDRATE = 9600
SLAVE_ADDR = 2
brd = Board()
modbus = brd.modbus( baudrate=BAUDRATE )
meter = EKEM4D( modbus, SLAVE_ADDR )
# See read_meter_values.py examples
#print( '==== Instantaneous values ======================' )
#print( 'Voltage :', meter.voltage, 'Volts' )
#print( 'Current :', meter.current, 'Amps' )
#print( 'Active Power :', meter.active_power, 'kW' )
#print( 'Reactive Power:', meter.reactive_power, 'kvar' )
#print( 'Apparent Power:', meter.apparent_power, 'kVA' )
#print( 'Power factor :', meter.power_factor )
#print( 'Frequency :', meter.frequency, 'Hz' )
#print( )
# ...
print( '==== ThreePhase Voltages ===================' )
print( 'L1 Phase Voltage :', meter.voltages.L1 , 'Volts' )
print( 'L2 Phase Voltage :', meter.voltages.L2 , 'Volts' )
print( 'L3 Phase Voltage :', meter.voltages.L3 , 'Volts' )
print( 'Average Phase voltage :', meter.voltages.average, 'volts' )
print( )
print( '==== ThreePhase Currents ===================' )
print( 'L1 Phase Current :', meter.currents.L1 , 'Amps' )
print( 'L2 Phase Current :', meter.currents.L2 , 'Amps' )
print( 'L3 Phase Current :', meter.currents.L3 , 'Amps' )
print( 'Average Phase current :', meter.currents.average, 'Amps' )
print( 'Total Currents :', meter.total_currents, 'Amps' )
print( )
print( '==== Other values ==========================' )
print( 'Total active power :', meter.total_active_power , 'W' )
print( 'Total active energy :', meter.total_active_energy , 'kWh' )
print( )
print( 'Line voltage 12 :', meter.line_voltage_l12, 'Volts' )
print( 'Line voltage 23 :', meter.line_voltage_l23, 'Volts' )
print( 'Line voltage 31 :', meter.line_voltage_l31, 'Volts' )
print( 'Line voltage Average:', meter.line_voltage_avg, 'Volts' )
print( )
print( 'Maximum Voltage :', meter.max_voltage, 'Volts' )
print( 'Maximum Current :', meter.max_current, 'Amps' )Example 3: Light/Humidity/Temperature sensor
The following example shows how to grab the data from a ACE-THL-MB Modbus device from ACE-Automation.
This IP65 box monitor the Temperature, Humidity and Luminosity (Lux) at a given position (see example script).
The scripts rely on:
- the acethl library (made available to customers) and
- umodbus library (part of Pymate.IO standard libray).
The module slave address and baudrate can be changed with special RS485 calls.
Code
from pymate.board import Board
from acethl import ACETHL
import time
BAUDRATE = 9600
SLAVE_ADDR = 1
brd = Board()
modbus = brd.modbus( baudrate=BAUDRATE )
meter = ACETHL( modbus, SLAVE_ADDR )
print( 'Humidity :', meter.humidity, '%' )
print( 'Temperature :', meter.temperature, 'Celcius' )
print( 'Lux :', meter.lux, 'Lux' )Modbus Cheat Sheet
As a programmer, there is a chance for you to develop your own code to access a Modbus device. We can also provide support for this.
In such case, you will have to rely on the umodbus library (see umodbus project, umodbus documentation).
Create an modbus instance object.
from pymate.board import Board brd = Board() modbus = brd.modbus(baudrate=9600)
Reading the holding register is made as follow:
reg_value = modbus.read_holding_registers( slave_addr=0x02, starting_addr=0x0000, register_qty=1, signed=False) # Returns tuple of uint16. Ex: (2427,) # Grabbing the value is made as the following value = reg_value[0]
Supported Modbus functions:
| ID | Description | Method |
|---|---|---|
| 1 | Read coils | host.read_coils( slave_addr=0x01, starting_addr= 123, coil_qty=1 ) |
| 2 | Read discrete inputs | read_discrete_inputs( slave_addr=0x01, starting_addr=67, input_qty=1) |
| 3 | Read holding registers | read_holding_registers( slave_addr=0x02, starting_addr=0x0000, register_qty=1, signed=False) |
| 4 | Read input registers | read_input_registers( slave_addr=0x01, starting_addr=10, register_qty=1, signed=False) |
| 5 | Write single coil | write_single_coil( slave_addr=0x01, output_address=123, output_value=0 ) |
| 6 | Write single register | write_single_register( slave_addr=0x01, register_address=93, register_value=44, signed=False) |
| 15 | Write multiple coils | See documentation |
| 16 | Write multiple registers | See documentation |
The library documentation is available on ReadTheDoc.
Unsupported functions:
- DIAGNOSTIC = 8
- REPORT_SLAVE_ID = 17
- READ_WRITE_MULTIPLE_REGISTERS = 23
- DEVICE_INFO = 43
Modbus error
Unable to communicate over ModBus
Sometime, it is necessary to give a little push to the RS485 bus to improve the signal quality.
When the 120 Ω terminator resistor is not enough to a communication, you can add a 1 KΩ Pull-Up Resistor from A+ to 5V as well as a 1 KΩ pull-Down resistor to GND.
Here an example based on Power-Meter.
OSError: invalid response CRC
It exists two use-case for this:
- The CRC is really invalid
- The remote device is not up and running (and doesn't respond)
Credit
The Modbus access is based on the brainelectronics/micropython-modbus library where changes are published here.
The library documentation is available on ReadTheDoc
PymateIO all right reserved © 2026 - Written by MCHobby for PymateIO




