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