Go Down

Topic: Modbus TCP master library (Read 3033 times) previous topic - next topic

Watcher

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

Juraj

#16
Jan 12, 2018, 04:20 pm Last Edit: Jan 12, 2018, 04:24 pm by Juraj
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
Code: [Select]

#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

LamySae

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

LamySae

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 ?

Juraj

remove them. those are other parts of my project

Watcher

#20
Jan 12, 2018, 05:26 pm Last Edit: Jan 12, 2018, 05:28 pm by Watcher
Thanks Juraj!

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

eg. what is this ?

Code: [Select]

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!

Juraj

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

Watcher

#22
Jan 12, 2018, 05:36 pm Last Edit: Jan 12, 2018, 05:36 pm by Watcher
Thanks Juraj!

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

eg. what is this ?

Code: [Select]

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!
OK, now removed NetClient above and added a global

EthernetClient  modbus;

and also some Global vars for various vars that are missing and it now compiles..


Still not sure how to use it though :)

Juraj

you must know the modbus registers of your system and understand Modbus TCP protocol.
for example readInverter request two registers from address 40083 described in inverter specs and uses them to calculate the inverter AC output

Watcher

YEs, I understood that... My inverter is an SMA one... going through the modbus spec now.

Where does the inverter IP go ?

Juraj

Where does the inverter IP go ?
it is there in the snippet as SymoAddress

Watcher

YEs got it ..Thanks you...

Sorry to have to ask all this.... but just to be sure :


modbusRequest(METER_UID, 40490, 1, regs);

METER_UID is the inverter's modbus address (in my case 126)
40490 is the register number
1 is the number of registrers to read
regs is where to store the result, in this case of length 1 Int (2 bytes)


Is the above correct ?

Cant get the inverter to respond for some reason...



Juraj

uid is a device address. in my case the inverter has address 1 and smart meter has address METER_UID. on Fronius I needed to enable the Modbus TCP (check box in web panel)

Watcher

#28
Jan 12, 2018, 06:42 pm Last Edit: Jan 12, 2018, 06:43 pm by Watcher
Thanks ..

Could you also please explain this packet frame :

 byte request[] = {0, 1, 0, 0, 0, 6, uid, 3, (byte) (addr / 256), (byte) (addr % 256), 0, len};


in particular the first 5 bytes ...




Juraj

Transaction identifier   2 bytes   For synchronization between messages of server and client

Protocol identifier   2 bytes   0 for Modbus/TCP

Length field   2 bytes   Number of remaining bytes in this frame

Unit identifier   1 byte   Slave address (255 if not used)

Function code   1 byte   Function codes as in other variants

Data bytes   n byted   Data as response or commands

Go Up