Modbus TCP master library

Hello everyone,

Actually I'm working on a project based on arduino Uno and ESP8266 UART shield to connect it to a controller dynamic power as a Modbus TCP master to read some registers .

I've already tried a http requests to read data from the webpage of the controller using Ethernet shield but unfortunately the connection failed many times and if it's established, the response is too slow. Hence, I drooped this method.

Then I decided to use Modbus TCP/IP protocol but I got some troubles with the libraries and it seems that this method is not reliable as I want to get data in real time.

As I've decided to use a wireless connection , I change the ethernet shield to an ESP8266 one .
I prefer to not use a Modbus RS485 protocol, but it seems that it's the most reliable . In this case, I should add another serial device with the controller to send data to the socket, but I really don't want to add more hardware.

Could you please give me your opinions to choose the best method ?
Thank you in advance.

This post includes several very different things.

I've already tried a http requests to read data from the webpage of the CDP using Ethernet shield but unfortunately the connection failed many times and if it's established, the response is too slow. Hence, I drooped this method.

What does that mean? Don't you think it may be a programming problem? So with a corrected sketch it may be a perfect method?
Don't you think it's time to post a link to that CDP and it's manual in English?

Then I decided to use Modbus TCP/IP protocol but I got some troubles with the libraries and it seems that this method is not reliable as I want to get data in real time.

Define what "real time" means for you. For most people it simply means that the time to get the information is predictable. It sounds that you think the response must be immediate but in the computer field there is no immediate answer. So specify what time range you think is acceptable in your case. Given your description I would assume it's several seconds.
And who told you that the ModBus TCP protocol is not reliable? It was me who told you that some libraries for ModBus TCP on Arduino are not very reliable but that doesn't mean that the protocol has this deficit.

My socket should be installed far from the CDP , hence I prefer to not use a Modbus RS485 protocol, but it seems that it's the most reliable .

Where is that information from? ModBus RTU and ModBus TCP show about the same reliability in practical use. Based on my experience I would even say that you can reach higher reliability using ModBus TCP because it doesn't have a strict timing dependency.

Could you please give me your opinions to choose the best method ?

Tell us the complete specification of your project and stop starting thread by thread which simply splits up the context.
What CDP are you using (links), what distance do you have to between the components, what equipment is already in place (networks, cables, etc.)?

The English manual is available here.

Some errors I found in your sketch (HTTP):

IPAddress  server (10,3,1,15);
IPAddress ip (10,0,1,60);

The default network mask if not specified in the Ethernet library is 255.255.255.0. So this setup will only work if you have a router in your network that is listening on the IP 10.0.1.1 and is able to route traffic to the specified server. The Arduino Ethernet library doesn't know anything about class-based routing (which is not used for the last 15 years at least).

  if (client.connect(server, 80)) {
    
    client.println("connected");
    client.println("GET /10.0.1.50 HTTP/1.1");
    client.println("Connection: close");
    client.println();

  }

The GET line must be the first line sent to the server. In your code you send a "connected" to the server which will disturb it for sure.
Also I doubt that your browser will show you anything if you point it to the URL

http://10.3.1.15/10.0.1.50

That looks like a mix of different IP addresses. On which URL do you expect to get anything from the CDR?

The code that is looking for the power value might work, that depends on the page that is returned. Do you have an example page (HTML) that you may post here?

I mean by real time the instantaneous response of request , the real power at that moment ( up to 30s).

I'm absolutely sure we will match that goal with the HTTP based solution if the value are available on the web pages.

The IP adress of the server is really 10.3.1.15, the CDP isn't in the same network of the server. What should I do in this case ?

Use the 5 parameter begin method for the Ethernet object:

  void begin(uint8_t *mac_address, IPAddress local_ip, IPAddress dns_server, IPAddress gateway, IPAddress subnet);

Set the dns_server parameter equally as the gateway as your router probably has a DNS forwarder service.

A piece of html code (load power) is in the attachements.

Unfortunately that's not HTML code but a screen shot having the developer tools open. But probably the rest of the code looks similar and in that case your finder search strings won't work. Please post the complete HTML source (it looks like you're using an older version of Firefox, so use Ctrl+U and then Save As...), I will then try to find correct finder search strings.

I try the Modbus TCP protocol but it doesn't get data until I press 4 ( Fc to read input registers) , I've forced 4 in the code but the communication failed.

In that code you use 10.0.1.50 for the server IP. Do you know what IP you CDP has, in this post you told us it's a 10.3.1.15.

The library you're using has the server IP fixed in the library source code:

  byte ServerIp[] = {192,168,200,163};

So you won't get any data from your server with that library.

Thank you for the clarification.

byte ServerIp[] = {192,168,200,163};

I didn't find any serverIP in the code source(.cpp) of the library. I set all the slaveIP as 10.0.1.50

For the Modbus TCP code, I use 10.0.1.50 for the slave IP which is the CDP in this case. I don't need the server IP ( 10.3.1.15) in the Modbus code.

In ModBus TCP the server is the slave. What IP does the CDP have? If your "server" is not the CDP, what is it then?

I didn't find any serverIP in the code source(.cpp) of the library. I set all the slaveIP as 10.0.1.50

Post a link to the library you're using. I've just used Google as you didn't provide the necessary information. in that library the posted code is in line 18 of MgsModbus.cpp.

You can find it in attachments.

It's more complicated than I expected. The HTML is dynamically updated using Javascript, so you won't find the data in the HTML code the server transmits. The advantage if this is that you probably won't have to parse HTML code to find the values but you can request it in JSON format (I guess).
You have to find out reading the different Javascript files ("./cgi/list_data.cgi" is the first candidate to check I would say).

I was talking about the serverIP (10.3.1.15) in the http request code , I don't need it in the Modbus code. I'm using the MgsModbus library . Please see the attachments.

What the IP of the CDR? Is it possible that the HTML file was not delivered by the CDR but some other server? So what's behind 10.3.1.15? What's behind 10.0.1.50?

You modified the Mudbus library, did you? The fixed IP is not the only problem that library has... If you modified it, it might be the time to post your modified version.

I've moved on requesting data from the database which already reads data from CDP registers in Modbus TCP. I guess the delay of waiting database to read from CDP then respond to the Arduino would be meaningful.

Please explain how that database come into the run. Do you have additional equipment you didn't mention earlier? And if your "database" already have a ModBus TCP connection why should the Arduino open an additional one? Please give us more information about the complete project.

BTW: Never, never post code as screenshots! There is a button in the editor (</>) especially for posting code.

Sorry to intervene in the thread, but as I am also trying to interrogate a Solar Inverter with TCP modbus, could someone explain the terms Server Vs Master referring to the modbus terminology?

I find confusing explanations on the net where sometimes the term Server is used and in other cases the term Master...

My Solar inverter documentation is defines the inverter as being the Modbus Server. Obviously the arduino must then be the client. Who will be the Master?

In addition, what would be the recommended library to use?

Thanks and sorry again to hijack the thread! :slight_smile:

Hi Watcher,
According to Modbus TCP Terminology, the master is the client (arduino) and the slave is the server (solar inverter) in your case.

We use the term master/slave in Modbus RTU terminology and client/server in Modbus TCP .
I used the MgsModbus Library with ethernet shield, It works very well but actually I have to work with wifi module . Unfortunately I didn't find any library for Modbus TCP master, all the libraries work with slave.

You can get the MgsModbus library from this link My Arduino Projects - Website dedicated to my arduino projects - A ModBus TCP library for the Arduino system

Good luck!

LamySae:
Hi Watcher,
According to Modbus TCP Terminology, the master is the client (arduino) and the slave is the server (solar inverter) in your case.

We use the term master/slave in Modbus RTU terminology and client/server in Modbus TCP .
I used the MgsModbus Library with ethernet shield, It works very well but actually I have to work with wifi module . Unfortunately I didn't find any library for Modbus TCP master, all the libraries work with slave.

You can get the MgsModbus library from this link My Arduino Projects - Website dedicated to my arduino projects - A ModBus TCP library for the Arduino system

Good luck!

Thanks for the info.

The link you provided has an example (example.zip) with one arduino as master and the other as slave.
Did you check it out?

In MgsModbus.cpp file, change serverIP to the IP adress of your solar inverter.
In MgsModbus_test_master.ino, change IP adress and mac adress to the ethernet shield ones.
In MB.req method, change register adress and all the bytes of the packets that you want to send .

LamySae:
In MgsModbus.cpp file, change serverIP to the IP adress of your solar inverter.
In MgsModbus_test_master.ino, change IP adress and mac adress to the ethernet shield ones.
In MB.req method, change register adress and all the bytes of the packets that you want to send .

Do you think this will also work with wifi, ie a esp8266 module instead of an ethernet shield?

I'm still looking for another library for esp8266 modbus TCP master

If this one works ok as a master with rs485, maybe we can mkdify it to also accomodate esp8266?

code from my project for requesting inverter data over ModbusTCP. tested with Ethernet2, WiFiEsp and used with WiFiLink. it should work with any Client implementation even on the esp8266, since the WiFi Link library is only a remote interface to esp8266 WiFi library

#include <TimeLib.h>
#include "Events.h"
#include "consts.h"

// Fronius Symo Hybrid SunSpec Modbus

const long EPOCH_2000 = 946696800;

const int METER_UID = 240;

const int MODBUS_CONNECT_ERROR = -10;
const int MODBUS_NO_RESPONSE = -11;

IPAddress symoAddress(192,168,1,107);

boolean requestSymoRTC() {
  int regs[2];
  int res = modbusRequest(1, 40222, 2, regs);
  if (modbusError(res))
    return false;
  setTime(EPOCH_2000 + (unsigned int) regs[0] * 65535 + (unsigned int) regs[1]);
  return true;
}

boolean requestInverter() {
  int regs[2];
  int res = modbusRequest(1, 40083, 2, regs);
  if (modbusError(res))
    return false;
  inverterAC = regs[0] * pow(10, regs[1]); // ac power * scale
  return true;
}

boolean requestMeter() {
  int regs[16];
  int res = modbusRequest(METER_UID, 40076, 16, regs);
  if (modbusError(res))
    return false;
  voltage = regs[3] * pow(10, regs[8] + 1); // ac voltage F3 * scale
  m = -regs[11] * pow(10, regs[15]); // ac power * scale
  return true;
}

boolean requestBattery() {
  int regs[58];
  int res = modbusRequest(1, 40257, 58, regs); //MPPT reg + SF offsset
  if (modbusError(res))
    return false;
  b = (unsigned int) regs[37] * pow(10, regs[0]); // dc power * scale
  soc = regs[54] / 100; // storage register addr - mppt register addr + ChaSta offset
  switch (regs[57]) {  // charge status
    case 4:  // CHARGING
    case 6:  // HOLDING
    case 7:  // TESTING (CALIBRATION)
      break;
    default:
      b = -b;
  }
  return true;
}

boolean modbusError(int err) {

  static int modbusErrorCounter = 0;
  static int modbusErrorCode = 0;

  if (modbusErrorCode != err) {
    modbusErrorCounter = 0;
    modbusErrorCode = err;
  }
  if (err == 0)
    return false;
  modbusErrorCounter++;
  switch (modbusErrorCounter) {
    case 1:
      sprintf(msg, "modbus error %d", err);
    break;
    case 5:
      sprintf(msg, "modbus error %d %d times", err, modbusErrorCounter);
      events.write(MODBUS_EVENT, err, 0);
      stopCause = AlarmCause::MODBUS;
    break;
  }
  return true;
}

/*
 * return
 *   - 0 is success
 *   - negative is comm error
 *   - positive value is modbus protocol exception code
 */
int modbusRequest(byte uid, unsigned int addr, byte len, int *regs) {

  static NetClient modbus;
  static unsigned long lastRequest = 0;

  unsigned long d = millis() - lastRequest;
  if (d < 1000) {
  	delay(d);
  }
  lastRequest = millis();

  if (!modbus.connected()) {
    int st = modbus.status();
    modbus.stop();
    modbus.connect(symoAddress, 502);
    if (!modbus.connected()) {
      modbus.stop();
      return MODBUS_CONNECT_ERROR;
    }
    sprintf(msg, "modbus reconnect %d", st);
  }
  byte request[] = {0, 1, 0, 0, 0, 6, uid, 3, (byte) (addr / 256), (byte) (addr % 256), 0, len};
  modbus.write(request, sizeof(request));
  modbus.flush();
  if (!available(modbus)) {
    modbus.stop();
    return MODBUS_NO_RESPONSE;
  }
  for (int i = 0; i < 7; i++) { // skip 7
    if (modbus.read() == -1)
      return -3;
    if (!available(modbus))
      return -1;
  }
  switch (modbus.read()) { // code
    case 3:
      break;
    case -1:
      return -4;
    case 0x83:
      return modbus.read(); // 0x01, 0x02, 0x03 or 0x11
    default:
      return -6;
  }
  if (!available(modbus))
    return -1;
  int l = modbus.read() / 2;
  int i = 0;
  while (true) {
    if (!available(modbus))
      return -1;
    byte hi = modbus.read();
    if (!available(modbus))
      return -1;
    byte lo = modbus.read();
    regs[i++] = hi * 256 + lo;
    if (i == len || i == l)
      break;
  }
  return 0;
}

boolean available(Stream& client) {
  for (int i = 0; i < 20; i++) {
    if (client.available())
      return true;
    delay(50);
  }
  return client.available();
}

it is not a library and it contains other elements of my project like logging events, so if you decide to use it, modify it as you need

Thank you very much Mr.Juraj
I'll test it

Mr Juraj,
I didn't find the header files Events.h and consts.h in github
Could you please give us a link to download them ?

remove them. those are other parts of my project

Thanks Juraj!

I am having some troubles testing your code though. Maybe I dont understand it very well.

eg. what is this ?

static NetClient modbus;

Already removed the two library includes and added SPI.h and Ethernet.h

PS: Hay this is cool.. It looks like we are all 3 online now!

modbusRequest() is universal for type 0x03 request. the rest is for example.
NetClient is a #define in my project for the Clent type of networking library used. replace it with EthernetClient