Pymate-Modbus-basic: Difference between revisions

From pymate.io wiki
Jump to navigation Jump to search
Line 79: Line 79:
The module slave address can be displayed and changed from the module LCD.
The module slave address can be displayed and changed from the module LCD.


[[File:Pymate-Modbus-basic-20.png]]
=== Code : Single-Phase ===
<syntaxhighlight lang="python" line start="1">
<syntaxhighlight lang="python" line start="1">
from pymate.board import Board
from pymate.board import Board
Line 145: Line 148:
</syntaxhighlight>
</syntaxhighlight>


{{dbox-gray| The {{fname|etek}} library is ready for '''single-phase''' reading and '''three-phase''' reading. }}
=== Code : Three-Phase ===
 
The {{fname|etek}} library is ready for '''single-phase''' reading and '''three-phase''' reading.


Additional power-grid parameters can be monitored when using the three-phase power-meter.
Additional power-grid parameters can be retreived when using the three-phase power-meter.


<syntaxhighlight lang="python" line start="1">
<syntaxhighlight lang="python" line start="1">

Revision as of 13:07, 29 May 2026


Introduction

Modbus is a standardized protocol over serial bus. The half-duplex RS485 bus can be used for Modbus transmission.

Pymate-Core-RS485.jpg

The Modbus over RS485 bus is available on the A+, B- terminals.

RS485 output can be use either for Modbus over RS485, either for Direct RS485. Both access methods cannot be used together on the RS485 output.

Practical example 1

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.

Pymate-RS485-basic-00.png

The master send the request to read register 0x0000 on slave 0x02.

The function code 0x03 = "read holding register".

Pymate-RS485-basic-01.png

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.

Pymate-RS485-basic-02.png

The angle can be converted to wind direction thanks to simple rule:

Pymate-RS485-basic-03.png

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 )

Practical example 2

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.

Pymate-Modbus-basic-20.png

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' )

Modbus Cheat Sheet

Create an modbus instance object.

from pymate.board import Board
brd = Board()
modbus = brd.modbus(baudrate=9600)

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
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] 

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.

Here an example based on Power-Meter.

Pymate-Modbus-basic-20.png

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