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.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.