Connection Reset

I am using a ESP8266/NodeMCU controller to monitor DC voltage and current of a battery via Modbus. It is being logged using Telegraph into an Influx database on a Raspberry Pi. The code is below. It works fantastic for weeks at a time. The only problem I have is when I need to reboot the raspberry Pi the connection is lost. To fix it all I have to do is reboot the NodeMCU which is a bit of a hassle to get to based on where I have it physically located.

Is there some code I can add that could reset the connection without me having to intervene?

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

// Import required libraries
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <Ticker.h>
#include <ModbusMaster.h>
#include <SoftwareSerial.h>

ModbusMaster node;

SoftwareSerial mySerial(D1, D2); // RX, TX

/*************************************************************
Modbus Definitions
 *************************************************************/
 
#define maxInputRegister 20
#define maxHoldingRegister 20
 
#define MB_FC_NONE 0
#define MB_FC_READ_REGISTERS 3 //implemented
#define MB_FC_WRITE_REGISTER 6 //implemented
#define MB_FC_WRITE_MULTIPLE_REGISTERS 16 //implemented
//
// MODBUS Error Codes
//
#define MB_EC_NONE 0
#define MB_EC_ILLEGAL_FUNCTION 1
#define MB_EC_ILLEGAL_DATA_ADDRESS 2
#define MB_EC_ILLEGAL_DATA_VALUE 3
#define MB_EC_SLAVE_DEVICE_FAILURE 4
//
// MODBUS MBAP offsets
//
#define MB_TCP_TID 0
#define MB_TCP_PID 2
#define MB_TCP_LEN 4
#define MB_TCP_UID 6
#define MB_TCP_FUNC 7
#define MB_TCP_REGISTER_START 8
#define MB_TCP_REGISTER_NUMBER 10
 
byte ByteArray[260];
unsigned int MBHoldingRegister[maxHoldingRegister];

int ModbusTCP_port = 502;

int voltage;
int current;
int power;
int energy;

WiFiServer MBServer(ModbusTCP_port);

Ticker MeterReadTimer;

// Replace with your network credentials
const char* ssid = "********";
const char* password = "***********";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);


// Replaces placeholders with values
String processor(const String& var){
  if(var == "VOLTAGE"){
    return String(voltage);
  }
  else if (var == "CURRENT"){
    return String(current);
  }
  else if (var == "POWER"){
    return String(power);
  }
  else if (var == "ENERGY"){
    return String(energy);
  }  
}
 
void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  mySerial.begin(9600);
  
  // Modbus slave ID 1
  node.begin(1, mySerial);

  MeterReadTimer.attach(1, getMeterReadings);

  // Initialize SPIFFS
  if(!SPIFFS.begin()){
    Serial.println("An Error has occurred while mounting SPIFFS");
    return;
  }

   // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP32 Local IP Address
  Serial.println(WiFi.localIP());

  MBServer.begin();
  Serial.println("Connected ");
  Serial.print("ESP8266 Slave Modbus TCP/IP ");
  Serial.print(WiFi.localIP());
  Serial.print(":");
  Serial.println(String(ModbusTCP_port));
  Serial.println("Modbus TCP/IP Online");


  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/index.html", String(), false, processor);
  });
  
  // Route to load style.css file
  server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/style.css", "text/css");
  });

  server.on("/voltage", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(voltage).c_str());
  });
  
  server.on("/current", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(current).c_str());
  });
  
  server.on("/power", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(power).c_str());
  });

  server.on("/energy", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(energy).c_str());
  });

   // Start server
  server.begin();
}

void getMeterReadings()
{
  uint8_t result;
  node.clearResponseBuffer();
   result = node.readInputRegisters(0x00000,6);
  if (result == node.ku8MBSuccess)
  {
    voltage = node.getResponseBuffer(0);  //1LSB = 0.01V
    current = node.getResponseBuffer(1);  //1LSB = 0.01A
    power = (node.getResponseBuffer(2)*256) + node.getResponseBuffer(3);  //Merging two registers 1LSB = 0.1W
    energy = (node.getResponseBuffer(4)*256) + node.getResponseBuffer(5);  //Merging two registers 1LSB = 1Wh
  }

  if(result==node.ku8MBResponseTimedOut)
  {
    Serial.println("Inside Timeout block ");
  }

  if(result==node.ku8MBInvalidCRC)
  {
    Serial.println("Inside ku8MBInvalidCRC block ");
  }

  if(result==node.ku8MBInvalidFunction)
  {
    Serial.println("Inside ku8MBInvalidFunction block ");
  }

  if(result==node.ku8MBInvalidSlaveID)
  {
    Serial.println("Inside ku8MBInvalidFunction block ");
  }
  
  if(result==node.ku8MBSlaveDeviceFailure)
  {
    Serial.println("Inside ku8MBSlaveDeviceFailure block ");
  }

  if(result==node.ku8MBIllegalDataValue)
  {
    Serial.println("Inside ku8MBIllegalDataValue block ");
  }
}
 
void loop(){

  // Check if a client has connected // Modbus TCP/IP
 WiFiClient client = MBServer.available();
 if (!client) {
 return;
 }
 
 
 boolean flagClientConnected = 0;
 byte byteFN = MB_FC_NONE;
 int Start;
 int WordDataLength;
 int ByteDataLength;
 int MessageLength;
 
 // Modbus TCP/IP
 while (client.connected()) {
 
 if(client.available())
 {
 flagClientConnected = 1;
 int i = 0;
 while(client.available())
 {
 ByteArray[i] = client.read();
 i++;
 }
 
 client.flush();
 
 
 
///// code here --- codigo aqui
 
 ///////// Holding Register [0] A [9] = 10 Holding Registers Escritura
 ///////// Holding Register [0] A [9] = 10 Holding Registers Writing
 
 MBHoldingRegister[0] = voltage; 
 MBHoldingRegister[1] = current; 
 MBHoldingRegister[2] = power; 
 MBHoldingRegister[3] = energy;
 MBHoldingRegister[4] = random(0,12);
 MBHoldingRegister[5] = random(0,12);
 MBHoldingRegister[6] = random(0,12);
 MBHoldingRegister[7] = random(0,12);
 MBHoldingRegister[8] = random(0,12);
 MBHoldingRegister[9] = random(0,12);
 
 
 
 
 ///////// Holding Register [10] A [19] = 10 Holding Registers Lectura
 ///// Holding Register [10] A [19] = 10 Holding Registers Reading
 
 int Temporal[10];
 
 Temporal[0] = MBHoldingRegister[10];
 Temporal[1] = MBHoldingRegister[11];
 Temporal[2] = MBHoldingRegister[12];
 Temporal[3] = MBHoldingRegister[13];
 Temporal[4] = MBHoldingRegister[14];
 Temporal[5] = MBHoldingRegister[15];
 Temporal[6] = MBHoldingRegister[16];
 Temporal[7] = MBHoldingRegister[17];
 Temporal[8] = MBHoldingRegister[18];
 Temporal[9] = MBHoldingRegister[19];
 
 /// Enable Output 14
 digitalWrite(14, MBHoldingRegister[14] );
 
 
 //// debug
 
 for (int i = 0; i < 10; i++) {
 
 Serial.print("[");
 Serial.print(i);
 Serial.print("] ");
 Serial.print(Temporal[i]);
 
 }
 Serial.println("");
 
 
 
 
//// end code - fin 
 
 
 //// rutine Modbus TCP
 byteFN = ByteArray[MB_TCP_FUNC];
 Start = word(ByteArray[MB_TCP_REGISTER_START],ByteArray[MB_TCP_REGISTER_START+1]);
 WordDataLength = word(ByteArray[MB_TCP_REGISTER_NUMBER],ByteArray[MB_TCP_REGISTER_NUMBER+1]);
 }
 
 // Handle request
 
 switch(byteFN) {
 case MB_FC_NONE:
 break;
 
 case MB_FC_READ_REGISTERS: // 03 Read Holding Registers
 ByteDataLength = WordDataLength * 2;
 ByteArray[5] = ByteDataLength + 3; //Number of bytes after this one.
 ByteArray[8] = ByteDataLength; //Number of bytes after this one (or number of bytes of data).
 for(int i = 0; i < WordDataLength; i++)
 {
 ByteArray[ 9 + i * 2] = highByte(MBHoldingRegister[Start + i]);
 ByteArray[10 + i * 2] = lowByte(MBHoldingRegister[Start + i]);
 }
 MessageLength = ByteDataLength + 9;
 client.write((const uint8_t *)ByteArray,MessageLength);
 
 byteFN = MB_FC_NONE;
 
 break;
 
 
 case MB_FC_WRITE_REGISTER: // 06 Write Holding Register
 MBHoldingRegister[Start] = word(ByteArray[MB_TCP_REGISTER_NUMBER],ByteArray[MB_TCP_REGISTER_NUMBER+1]);
 ByteArray[5] = 6; //Number of bytes after this one.
 MessageLength = 12;
 client.write((const uint8_t *)ByteArray,MessageLength);
 byteFN = MB_FC_NONE;
 break;
 
 case MB_FC_WRITE_MULTIPLE_REGISTERS: //16 Write Holding Registers
 ByteDataLength = WordDataLength * 2;
 ByteArray[5] = ByteDataLength + 3; //Number of bytes after this one.
 for(int i = 0; i < WordDataLength; i++)
 {
 MBHoldingRegister[Start + i] = word(ByteArray[ 13 + i * 2],ByteArray[14 + i * 2]);
 }
 MessageLength = 12;
 client.write((const uint8_t *)ByteArray,MessageLength); 
 byteFN = MB_FC_NONE;
 
 break;
 }
 }
 
 
 
 
}

Would you be willing to rewrite your code a bit to fix your issue? I do not think this can be fixed with just a line of code.

Of course.

I would recommend you restructure your code with the following in mind:

  • make sure you do not have any code in setup except the simplest initialization. Anything that you might need to restart should be controlled from loop.

  • do not use any while loops, your main loop should run as often as possible, functions should be non-blocking meaning no delay() no waiting for anything

  • your loop() should have very little code as your project grows, move everything into functions called from loop()

  • the functions called from main should be major tasks e.g. wireless communication, reading sensors, display … use more smaller functions to separate your code similar to what you did with processor()

  • inside these tasks I like to use state machines (based on switch-case) to do one thing at a time, a state variable is used to remember what the current sub task is, if something is wrong, I use the default state to close everything and set the state machine to restart

I would also recommend you change your variable and function names to make the code easier to understand and keep a consistent naming scheme. Most Arduino code uses camelCase for variables and functions, ALL_CAPS_WITH_UNDERSCORE for constants and Classes start with a capital letter. e.g.

  • Start, ByteArray ByteDataLength … are not good variable name, what is in the ByteArray
  • mySerial should be modbusSerial if I understand correctly that there is some modbus module connected

For your WiFi SSID and password use a separate file. Arduino original WiFi examples use a file called arduino_secrets.h with some defines. That way you do not accidentally post your personal data and others can test your code with their network without modifications.

Can you provide a full link to the tutorial?

Is there a second hardware serial available on your board?

I agree the code could use a lot of cleanup. It’s a compilation from three different sources. Your comment on eliminating while loops was helpful though. I changed:

while(client.available())

to:

if(client.available())

That seems to have taken care of the problem.