ESP32 RTU Modbus simple message transmit and receive

bogosort

Joined Sep 24, 2011
696
Converted decimals to asci I get "hello"
All I need is to convert decimal values to asci and construct a serial number. Isnt that much more simple than your previously suggested method?
They're both equivalent in terms of simplicity because they're both using the same idea: serializing data into smaller chunks. Your implementation looks visually simpler because it uses ASCII bytes as the serialization unit and so doesn't require bitwise manipulation. The trade-off is that it requires twice as many registers as my implementation. But that may be perfectly acceptable in your application. As always, the choice is entirely up to you. The point of my post wasn't to tell you what implementation to use, only to give you the idea of serializing the data across multiple registers.

From an architectural standpoint, I'd use a large fixed-size integer for the slave identifier (the serial) and avoid passing strings whenever possible. For example, if my app had a command register, I would define integer constants like CMD_START, CMD_STATUS, CMD_END in both the master and slave, and write/read those values to a single register, rather than sending strings such as "start" across multiple registers. But that's a personal preference.
 

Thread Starter

zazas321

Joined Nov 29, 2015
936
Understood. Thank you very much for good tips. Let me ask one more question thought. One of the tasks is to be able to update the quantity OR serial number of the device. So for example, if one of the ESP32 device havent got a serial number assigned to it, my raspberry PI will have to assign a number. The serial number will be whatever the user inputs in my python script on raspberry PI.

The function below updates the quantity and serial. Both values (quantity and serial) come from user input in my python script. When user selects the device, the 2 user input windwos pop up on the screen and ask user to input quantity and serial number.




Code:
def update_device_quantity_modbus(quantity,serial,Slave_ID):
    serial_to_send = []
    serial_to_send.append(3)
    serial_to_send.append(int(quantity))
    serial_to_send.append(Slave_ID)
    serial_to_send.append(len(serial))

    #function code,quantity,slave,serial
    for x in range(0,len(serial)):
        serial_to_send.append(ord(serial[x]))
    print("serial to send=",serial_to_send)
                                       
    #data_to_send = [3,int(quantity),Slave_ID,serial_to_send]
    master_modbus.execute(Slave_ID, cst.WRITE_MULTIPLE_REGISTERS, 10,4+len(serial),serial_to_send)

For the code above, I just create an empty array and fill it with data that I want to send, the first value is:
1. function_code = 3 ( just a value that I made up, it means that Raspberry PI wants to change quantity and serial number), when raspberry PI reads this function code, it immidiately understands what it needs to do.
2. user input quantity
3. Slave ID (not necessary since at this point I already know which slave I need to respond to on my raspberry)
4. Length of user input serial ( it can vary from 8 to 10 characters so I need to know on my arduino how many registers to read)
5-14. - 10 registers are allocated for serial number. I can use 8, 9 or 10 based on the serial length.


For my serial, I append every character individually and convert it to asci since I cannot just simply transfer characters.
serial_to_send.append(ord(serial[x]))


Then in my arduino, I decode the asci to an actual characters and I have my new serial code!

Do you think thats an approriate way to trasnfer data?
 

bogosort

Joined Sep 24, 2011
696
Do you think thats an approriate way to trasnfer data?
From your description it seems fine, though I would probably change one thing. Rather than writing to 8, 9, or 10 registers depending on the length of the serial ID, I would designate ten registers as the serial ID registers and always write to/read from all ten registers. If the ID is less than ten characters, write an "end of serial" value to the remaining registers. Since you're using ASCII encoding, integer zero should work well. (I wouldn't rely on the default register value being zero.)

The primary reason I prefer this is that it simplifies the protocol to have a fixed, deterministic set of registers rather than a varying set. This is why I usually write the API documentation before I implement it -- the easier it is to describe the interface, the easier it is to understand and use. Compare:
Code:
Registers 100 through 109 hold the ASCII encoding of the serial ID, which can have a length of up to ten ASCII characters.  An integer zero value (0x0) marks the end of the serial ID.
As opposed to:
Code:
Register 100 holds the first value of the serial ID.  The serial ID is an array of ASCII characters between 8 and 10 characters in length.  A request from the master to set or change the ID will include the length of the ID, which the slave must use to determine the last register of the ID.  For example, if the length is 8, then the serial ID will be stored in registers 100 through 107.
It's not a huge difference, but these kinds of details help improve the usability and maintainability of a project. Always assume some poor chap will inherit your code one day.
 

Thread Starter

zazas321

Joined Nov 29, 2015
936
Cool thank you very much for the response. I got it to work on RTU. Now I am considering if I should try to implement TCP/IP messaging or not. Just had a look at the same library and I am unsure about few things:

Code:
*
  Modbus-Arduino Example - Test Holding Register (Modbus IP ESP8266)
  Configure Holding Register (offset 100) with initial value 0xABCD
  You can get or set this holding register
  Original library
  Copyright by André Sarmento Barbosa
  http://github.com/andresarmento/modbus-arduino

  Current version
  (c)2017 Alexander Emelianov (a.m.emelianov@gmail.com)
  https://github.com/emelianov/modbus-esp8266
*/

#ifdef ESP8266
#include <ESP8266WiFi.h>
#else //ESP32
#include <WiFi.h>
#endif
#include <ModbusIP_ESP8266.h>

// Modbus Registers Offsets
const int TEST_HREG = 100;


//ModbusIP object
ModbusIP mb;

void setup() {
  Serial.begin(115200);

  WiFi.begin("Pick_to_light", "teltonika20");

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  mb.server();
  mb.addHreg(TEST_HREG, 0xABCD);
}

void loop() {
   //Call once inside loop() - all magic here
   Serial.println(mb.Hreg(TEST_HREG));
   mb.task();
   delay(1000);
}
How does this work? This is code for TCP slave device. I do not see where to set slave ID?

For my master I use the same library just isntead initialized it modbus tcp.
https://github.com/ljean/modbus-tk/blob/master/examples/tcpmaster_example.py



I am writing to the REG 100 but I am getting error message on my python script:
Connection refused.



Code:
#!/usr/bin/env python
# -*- coding: utf_8 -*-
"""
 Modbus TestKit: Implementation of Modbus protocol in python
 (C)2009 - Luc Jean - luc.jean@gmail.com
 (C)2009 - Apidev - http://www.apidev.fr
 This is distributed under GNU LGPL license, see license.txt
"""

from __future__ import print_function

import modbus_tk
import modbus_tk.defines as cst
from modbus_tk import modbus_tcp, hooks
import logging

def main():
    """main"""
    logger = modbus_tk.utils.create_logger("console", level=logging.DEBUG)

    def on_after_recv(data):
        master, bytes_data = data
        logger.info(bytes_data)

    hooks.install_hook('modbus.Master.after_recv', on_after_recv)

    try:

        def on_before_connect(args):
            master = args[0]
            logger.debug("on_before_connect {0} {1}".format(master._host, master._port))

        hooks.install_hook("modbus_tcp.TcpMaster.before_connect", on_before_connect)

        def on_after_recv(args):
            response = args[1]
            logger.debug("on_after_recv {0} bytes received".format(len(response)))

        hooks.install_hook("modbus_tcp.TcpMaster.after_recv", on_after_recv)

        # Connect to the slave
        master = modbus_tcp.TcpMaster()
        master.set_timeout(5.0)
        logger.info("connected")

        logger.info(master.execute(1, cst.WRITE_SINGLE_REGISTER, 100, output_value=54))

        # logger.info(master.execute(1, cst.READ_HOLDING_REGISTERS, 0, 2, data_format='f'))

        # Read and write floats
        # master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, starting_address=0, output_value=[3.14], data_format='>f')
        # logger.info(master.execute(1, cst.READ_HOLDING_REGISTERS, 0, 2, data_format='>f'))

        # send some queries
        # logger.info(master.execute(1, cst.READ_COILS, 0, 10))
        # logger.info(master.execute(1, cst.READ_DISCRETE_INPUTS, 0, 8))
        # logger.info(master.execute(1, cst.READ_INPUT_REGISTERS, 100, 3))
        # logger.info(master.execute(1, cst.READ_HOLDING_REGISTERS, 100, 12))
        # logger.info(master.execute(1, cst.WRITE_SINGLE_COIL, 7, output_value=1))
        # logger.info(master.execute(1, cst.WRITE_SINGLE_REGISTER, 100, output_value=54))
        # logger.info(master.execute(1, cst.WRITE_MULTIPLE_COILS, 0, output_value=[1, 1, 0, 1, 1, 0, 1, 1]))
        # logger.info(master.execute(1, cst.WRITE_MULTIPLE_REGISTERS, 100, output_value=xrange(12)))

    except modbus_tk.modbus.ModbusError as exc:
        logger.error("%s- Code=%d", exc, exc.get_exception_code())

if __name__ == "__main__":
    main()

The hardware is correct since I used it for modbus RTU and everything working fine

2020-11-10-141243_1920x1080_scrot.png
 

bogosort

Joined Sep 24, 2011
696
How does this work? This is code for TCP slave device. I do not see where to set slave ID?
The slave ID is a way to differentiate between different slaves on the same bus. An IP address serves the same function in TCP, so perhaps the library doesn't use slave ID for the Modbus TCP implementation.

I am writing to the REG 100 but I am getting error message on my python script:
Connection refused.
I don't see where you set the slave IP address in your code. Troubleshoot the network first, make sure you have everything talking to each other correctly.
 

Thread Starter

zazas321

Joined Nov 29, 2015
936
The slave ID is a way to differentiate between different slaves on the same bus. An IP address serves the same function in TCP, so perhaps the library doesn't use slave ID for the Modbus TCP implementation.


I don't see where you set the slave IP address in your code. Troubleshoot the network first, make sure you have everything talking to each other correctly.
I use the example code for TCP slave so I was under impression that there will be nothing more I need to do. I will have to look at the library API see if I can find a way to initiate tcp slave
 
Top