PymateIO-HMI
Abstract
After the Modbus basics let's learn how to use it with an HMI display like the HMI-043U2S.
For this the Pymate must act as a Modbus Slave since the HMI is working as a Modbus Master.
HMI Example Project
The HMI content is designed with the CommandHMI software. The software manages '.skm' project file that can be uploaded to the HMI.
The demonstration script is based on the "HMI_PyMateIO_Modbus_MASTER.skm" project.
It contains various controls starting with two "Numeric Display".
It also contains a "Bit Lamp" displaying the status of a bit stored on the Pymate.
Finally the "Bit Switch" have a LED controled by the HMI Display (when switch is pressed). Later, the LED is updated by the Pymate to confirm the actual on/off state on the Pymate
Pymate Script
The slave act as following:
- change the "Bit Lamp" state every 5 seconds.
- change the "Bit Switch" state (its LED) every 10 seconds.
- capture the "Bit Switch" on/off event and displays a message in REPL.
- display the number of milliseconds in the "Numeric Input" 4x1
- display the number of HMI Request in the "Numeric Input" 4x0
The modbus_slave.py script perform the following tasks:
- Declare the Modbus registers exposed by Pymate
- Implement the callback handlers attached to registers
- Pumps the Modbus messages in the MainLoop
Registers declaration
The slave will declare its registers as follow:
- The both "numeric imput" fields are stored within a single HRegs register with double Integer. The master do reads two consecutive values from the same register.
- The both "Bit Switch" and "bit Lamp" do share the same byte register (8 bits) but at different bit position.
The Modbus Slave uses a specific data structure to declare the registers.
# Registers and their default values
slave_registers = {
"COILS": {
"0x bits": {
"register": 0,
"len": 8,
# WARNING: Bit 7 on left, Bit 0 on right
# bit 1 = LED; bit 0 = Btn LED
"val": [False,False,False,False,False,False,False,False],
"on_get_cb" : get_addr0_state,
"on_set_cb" : set_addr0_state
}
},
"HREGS": {
"4x fields": {
"register": 0,
"len": 2,
# 1st-> 4x0 field = 65280 ; 2nd-> 4x1 field = 1
# Values must be uint16 0-65535
"val": [0xFF00, 1 ],
"on_get_cb" : get_counters
}
},
"ISTS": {
},
"IREGS": {
}
}Note the entries on_get_cb and on_set_cb appearing in the structure. Those entries are used to associate callback functions.
The on_get_cb callback is called when the master request register data from the slave.
The on_set_cb callback is called when the master need to update a register data on the slave.
Script Structure
The slave script is structured as follows.
from pymate.board import Board
import time, struct
SLAVE_ADDR = 0x01 # Slave Addr 1-127 of this instance
print( 'Create Modbus Slave at addr', SLAVE_ADDR )
brd = Board()
modbus_slave = brd.modbus( master=False, addr=SLAVE_ADDR, baudrate=9600 )
# Active Modbus debugging messages
modbus_slave.debug = True
# -----------------------------------------------
# Definition of the callbacks
# -----------------------------------------------
def get_addr0_state( reg_type, address, val ):
pass
def set_addr0_state( reg_type, address, val ):
pass
def get_counters( reg_type, address, val ):
pass
# -----------------------------------------------
# Definition of registers
# -----------------------------------------------
# Registers and their default values
slave_registers = {
...
}
print( 'Setup slave registers...' )
modbus_slave.setup_registers( registers=slave_registers )
# -----------------------------------------------
# Processing incoming request
# -----------------------------------------------
print( 'Start slave processing...' )
while True:
# Process incoming request
result = modbus_slave.process()
# Place your processing code here
time.sleep_ms(500)The points of interests are the following:
- The Modbus slave is created with the following instructions, a variation of a Modbus Master creation.
modbus_slave = brd.modbus( master=False, addr=SLAVE_ADDR, baudrate=9600 ) - The modbus_slave.debug = True will shows incoming Modbus Request on the REPL session. This may represent unvaluable information.
- The slave register definition must be injected within the slave with modbus_slave.setup_registers( registers=slave_registers )
- The main loop do process the request with result = modbus_slave.process() . result will be True when a request was received and processed.
Notice that Exception may be raised in case of invalid message or exception in the callback!
on_get_cb implementations
The on_get_cb callback are called with the Master request a value from the slave.
The following shows the implementation of COILS reading
def get_addr0_state( reg_type, address, val ):
# reg_type (eg: COILS)
# addr (eg: 0)
# val (eg: [False,False,False,False,False,False,True,True] )
# --- Update values ---
# Btn LED is bit 0 (the right most of the representation)
val[7]=bool( time.time()%20//10 )# Turn Btn LED on/off every 10 seconds
# Round LED if bit 1 (the second bit from right)
val[6]=bool( time.time()%10//5 )# Turn LED on/off every 5 seconds
# --- Set new values ---
# Set value for register 0
modbus_slave.set_coil(0,val)The current register byte values is received as the val parameter. That parameter is made of 8 values, one for each bit of the byte.
As stated in the comments, the val[7] is the bit0 value of the byte (the least significant bit). The bit1 is stored at val[6] .
The new values are computed and stored within the incoming val parameter... however, it is not enough to update the register value.
A call to the set_coil(0,val) method do update the coils register 0 with the new val values (8 bits at once).
Note: the "Bit Switch" state can be manipulated even without user action on the screen.
The following implementation return the value for the holding register 0.
Remind that Master will read TWO values from the register HREG 0.
_counter = 0 # Call counter
def get_counters( reg_type, address, val ):
# reg_type (eg: HREGS)
# addr (eg: 0)
# val (eg: LSBF [255,0] for initial value = 0x00FF)
# Call Counter : Increment and limit to uint16
global _counter
_counter += 1
if _counter>65535:
_counter=0
# Elapse time: limit value to uint16
_ms = time.ticks_ms()%65535
val[0] = _counter # 4x0 field
val[1] = _ms # 4x1 field
modbus_slave.set_hreg(0, val)The communicated val parameter contains two integer values (uint16). Notes that value index match the field identification.
As for the COILS, the new value is computed and stored within the val parameter.
Then the values are pushed into the HREG register 0 with another method set_hreg(0, val).
on_set_cb implementation
The on_set_cb callback is invoked when the Master wants to change a value on the Slave.
It is the case when the user press/click the "Bit Switch" button.
The following script only prints a message on the REPL session with the boolean state sents by the master.
def set_addr0_state( reg_type, address, val ):
# reg_type (eg: COILS)
# addr (eg: 0)
# val (eg: True/False )
print("ON/OFF button change", val )In the real world this callback would update a global variable to start a complex process or switch the power relay of an appliance.
Debugging message
Setting the debug attribute to True will displays the incoming Modbus request on the REPL.
The following capture shows Modbus requests traveling the RS485 bus related to this demonstration.
Modbus: COILS READ <Request fct=1 qty=8 addr=0 data=None> Modbus: HREGS READ <Request fct=3 qty=2 addr=0 data=None> Modbus: COILS READ <Request fct=1 qty=8 addr=0 data=None> Modbus: HREGS READ <Request fct=3 qty=2 addr=0 data=None> Modbus: COILS READ <Request fct=1 qty=8 addr=0 data=None> Modbus: COILS WRITE <Request fct=5 qty=None addr=0 data=bytearray(b'\xff\x00')> ON/OFF button change [True] Modbus: HREGS READ <Request fct=3 qty=2 addr=0 data=None> Modbus: COILS READ <Request fct=1 qty=8 addr=0 data=None> Modbus: COILS WRITE <Request fct=5 qty=None addr=0 data=bytearray(b'\x00\x00')> ON/OFF button change [False] Modbus: COILS WRITE <Request fct=5 qty=None addr=0 data=bytearray(b'\xff\x00')> ON/OFF button change [True] Modbus: HREGS READ <Request fct=3 qty=2 addr=0 data=None> Modbus: COILS READ <Request fct=1 qty=8 addr=0 data=None> Modbus: HREGS READ <Request fct=3 qty=2 addr=0 data=None> Modbus: COILS READ <Request fct=1 qty=8 addr=0 data=None>
Mainloop and Registers
As describes above, the Modbus Slave registers are retreives and updated via callback functions.
However, it can access them via the modbus_slave instance by using same mechanism used by on_set_cb and on_get_cb callbacks.
The following example shows an alternate main loop retrieving register values from Modbus slave instance.
print( 'Start slave processing...' )
while True:
# Process incoming request
result = modbus_slave.process()
# Spy the state of the Btn LED
# ------------------------------
# List all the coils availables
print( list(modbus_slave.coils) )
# According to definition this will print [0, 1, 2, 3, 4, 5, 6, 7]
# Remeber, the LSB bit (bit 0) is the rightmost.
bit = modbus_slave.get_coil(7)
print( "Button LED is ", bit )
# Spy the state of 4x Fields
#------------------------------
# List all the hregs availables
print( list(modbus_slave.hregs))
# According to definition this will print [0, 1]
val0 = modbus_slave.get_hreg(0)
val1 = modbus_slave.get_hreg(1)
print("val0", val0, "val1", val1)
# Place your processing code here
time.sleep_ms(500)Full script
The full modbus_slave.py script.
# Pymate Modbus Slave examples for HMI-043U2S
#
# This example demonstrate typical implementation
# for interaction with HMI-043U2S display.
#
from pymate.board import Board
import time, struct
SLAVE_ADDR = 0x01 # Slave Addr 1-127 of this instance
print( 'Create Modbus Slave at addr', SLAVE_ADDR )
brd = Board()
modbus_slave = brd.modbus( master=False, addr=SLAVE_ADDR, baudrate=9600 )
# Active Modbus debugging messages
# modbus_slave.debug = True
def get_addr0_state( reg_type, address, val ):
# reg_type (eg: COILS)
# addr (eg: 0)
# val (eg: [False,False,False,False,False,False,True,True] )
# --- Update values ---
# Btn LED is bit 0 (the right most of the representation)
val[7]=bool( time.time()%20//10 )# Turn Btn LED on/off every 10 seconds
# Round LED if bit 1 (the second bit from right)
val[6]=bool( time.time()%10//5 )# Turn LED on/off every 5 seconds
# --- Set new values ---
# Set value for register 0
modbus_slave.set_coil(0,val)
def set_addr0_state( reg_type, address, val ):
# reg_type (eg: COILS)
# addr (eg: 0)
# val (eg: True/False )
print("ON/OFF button change", val )
_counter = 0 # Call counter
def get_counters( reg_type, address, val ):
# reg_type (eg: HREGS)
# addr (eg: 0)
# val (eg: LSBF [255,0] for initial value = 0x00FF)
# Call Counter : Increment and limit to uint16
global _counter
_counter += 1
if _counter>65535:
_counter=0
# Elapse time: limit value to uint16
_ms = time.ticks_ms()%65535
val[0] = _counter # 4x0 field
val[1] = _ms # 4x1 field
modbus_slave.set_hreg(0, val)
# Registers and their default values
slave_registers = {
"COILS": {
"0x bits": {
"register": 0,
"len": 8,
# WARNING: Bit 7 on left, Bit 0 on right
# bit 1 = LED; bit 0 = Btn LED
"val": [False,False,False,False,False,False,False,False],
"on_get_cb" : get_addr0_state,
"on_set_cb" : set_addr0_state
}
},
"HREGS": {
"4x fields": {
"register": 0,
"len": 2,
# 1st-> 4x0 field = 65280 ; 2nd-> 4x1 field = 1
# Values must be uint16 0-65535
"val": [0xFF00,1 ],
"on_get_cb" : get_counters
}
},
"ISTS": {
},
"IREGS": {
}
}
print( 'Setup slave registers...' )
modbus_slave.setup_registers( registers=slave_registers )
print( 'Start slave processing...' )
while True:
# Process incoming request
result = modbus_slave.process()
# Spy the state of the Btn LED
# ------------------------------
# List all the coils availables
# print( list(modbus_slave.coils) )
# According to definition this will print [0, 1, 2, 3, 4, 5, 6, 7]
# bit = modbus_slave.get_coil(7)
# print( "Button LED is ", bit )
# Spy the state of 4x Fields
#------------------------------
# List all the hregs availables
# print( list(modbus_slave.hregs))
# According to definition this will print [0, 1]
# val0 = modbus_slave.get_hreg(0)
# val1 = modbus_slave.get_hreg(1)
# print("val0", val0, "val1", val1)
# Place your processing code here
time.sleep_ms(500)
PymateIO all right reserved © 2026 - Written by MCHobby for PymateIO






