Skip to content

Commit 7a66536

Browse files
committed
Modbus stats
Modbus stats and error reporting on "Current Status" page.
1 parent 9ff0348 commit 7a66536

File tree

6 files changed

+90
-59
lines changed

6 files changed

+90
-59
lines changed

arduino-modbus-rtu-tcp-gateway/01-interfaces.ino

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,21 +120,19 @@ void maintainUptime() {
120120
}
121121
#endif /* ENABLE_EXTRA_DIAG */
122122

123+
#ifdef ENABLE_EXTRA_DIAG
123124
void maintainCounters() {
124125
// synchronize roll-over of data counters to zero, at 0xFFFFFF00 or 0xFF00 respectively
125-
#ifdef ENABLE_EXTRA_DIAG
126126
const unsigned long rollover = 0xFFFFFF00;
127+
const unsigned int rollover = 0xFF00;
127128
if (serialTxCount > rollover || serialRxCount > rollover || ethTxCount > rollover || ethRxCount > rollover) {
128129
ethRxCount = 0;
129130
ethTxCount = 0;
130-
#else
131-
const unsigned int rollover = 0xFF00;
132-
if (serialTxCount > rollover || serialRxCount > rollover) {
133-
#endif /* ENABLE_EXTRA_DIAG */
134131
serialRxCount = 0;
135132
serialTxCount = 0;
136133
}
137134
}
135+
#endif /* ENABLE_EXTRA_DIAG */
138136

139137

140138
void generateMac() {

arduino-modbus-rtu-tcp-gateway/02-modbus-tcp.ino

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ enum status : byte {
3939
// bool arrays for storing Modbus RTU status of individual slaves
4040
uint8_t stat[STAT_NUM][(maxSlaves + 1 + 7) / 8];
4141

42+
// array for storing error counts
43+
uint16_t errorCount[STAT_NUM];
44+
uint16_t errorInvalid;
45+
4246
// bool arrays for storing Modbus RTU status (responging or not responding). Array index corresponds to slave address.
4347
// uint8_t statOk[(maxSlaves + 1 + 7) / 8];
4448
// uint8_t statError0B[(maxSlaves + 1 + 7) / 8];
@@ -174,10 +178,12 @@ void processRequests() {
174178
byte checkRequest(const byte inBuffer[], unsigned int msgLength, const IPAddress remoteIP, const unsigned int remotePort, const byte clientNum) {
175179
if (localConfig.enableRtuOverTcp) { // check CRC for Modbus RTU over TCP/UDP
176180
if (checkCRC(inBuffer, msgLength) == false) {
181+
errorInvalid++;
177182
return 0; // reject: do nothing and return no error code
178183
}
179184
} else { // check MBAP header structure for Modbus TCP/UDP
180185
if (inBuffer[2] != 0x00 || inBuffer[3] != 0x00 || inBuffer[4] != 0x00 || inBuffer[5] != msgLength - 6) {
186+
errorInvalid++;
181187
return 0; // reject: do nothing and return no error code
182188
}
183189
}
@@ -225,5 +231,6 @@ void setSlaveStatus(const uint8_t slave, byte status, const bool value) {
225231
stat[i][slave / 8] &= ~masks[slave & 7]; // set all other flags to false
226232
}
227233
stat[status][slave / 8] |= masks[slave & 7];
234+
errorCount[status]++;
228235
}
229236
}

arduino-modbus-rtu-tcp-gateway/03-modbus-rtu.ino

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ void sendSerial() {
5353
}
5454
} else if (serialState == DELAY && txDelay.isOver()) {
5555
header myHeader = queueHeaders.first();
56+
#ifdef ENABLE_EXTRA_DIAG
5657
serialTxCount += myHeader.msgLen;
5758
serialTxCount += 2;
59+
#endif
5860
#ifdef RS485_CONTROL_PIN
5961
digitalWrite(RS485_CONTROL_PIN, RS485_RECEIVE); // Disable RS485 Transmit
6062
#endif /* RS485_CONTROL_PIN */
@@ -127,7 +129,9 @@ void recvSerial() {
127129
deleteRequest();
128130
serialState = IDLE;
129131
}
132+
#ifdef ENABLE_EXTRA_DIAG
130133
serialRxCount += rxNdx;
134+
#endif /* ENABLE_EXTRA_DIAG */
131135
rxNdx = 0;
132136
rxErr = false;
133137
}

arduino-modbus-rtu-tcp-gateway/04-webserver.ino

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ void recvWeb() {
161161
// Actions that require "please wait" page
162162
if (action == WEB || action == REBOOT || action == ETH_SOFT || action == FACTORY || action == MAC) {
163163
reqPage = PAGE_WAIT;
164+
// } else if (action == RESET_COUNTERS) {
165+
// reqPage = PAGE_STATUS;
164166
}
165167

166168
// Send page
@@ -332,7 +334,15 @@ void processPost(char postParameter[]) {
332334
break;
333335
case SCAN:
334336
scanCounter = 1;
335-
memset(&stat, 0, sizeof(stat)); // clear all status flags
337+
memset(&stat, 0, sizeof(stat)); // clear all status flags
338+
memset(errorCount, 0, sizeof(errorCount));
339+
errorInvalid = 0;
340+
#ifdef ENABLE_EXTRA_DIAG
341+
ethRxCount = 0;
342+
ethTxCount = 0;
343+
serialRxCount = 0;
344+
serialTxCount = 0;
345+
#endif /* ENABLE_EXTRA_DIAG */
336346
break;
337347
default:
338348
break;

arduino-modbus-rtu-tcp-gateway/05-pages.ino

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -215,15 +215,11 @@ void contentStatus(ChunkedPrint &chunked) {
215215
chunked.print(F(" mins, "));
216216
chunked.print(mod_seconds);
217217
chunked.print(F(" secs"));
218-
#endif /* ENABLE_EXTRA_DIAG */
219-
220218
chunked.print(F("<tr><td>RS485 Data:<td>"));
221219
chunked.print(serialTxCount);
222220
chunked.print(F(" Tx bytes / "));
223221
chunked.print(serialRxCount);
224222
chunked.print(F(" Rx bytes"));
225-
226-
#ifdef ENABLE_EXTRA_DIAG
227223
chunked.print(F("<tr><td>Ethernet Data:<td>"));
228224
chunked.print(ethTxCount);
229225
chunked.print(F(" Tx bytes / "));
@@ -325,6 +321,22 @@ void contentStatus(ChunkedPrint &chunked) {
325321
chunked.print(F("</table>"));
326322
#endif /* ENABLE_EXTRA_DIAG */
327323

324+
chunked.print(F("<tr><td><br>"
325+
"<tr><td>Modbus Stats:<td>"));
326+
chunked.print(errorCount[STAT_OK]);
327+
chunked.print(F(" Slave Responded"
328+
"<tr><td><td>"));
329+
chunked.print(errorCount[STAT_ERROR_0X]);
330+
chunked.print(F(" Slave Responded with Error (Codes 1~8)"
331+
"<tr><td><td>"));
332+
chunked.print(errorCount[STAT_ERROR_0A]);
333+
chunked.print(F(" Gateway Overloaded (Code 10)"
334+
"<tr><td><td>"));
335+
chunked.print(errorCount[STAT_ERROR_0B]);
336+
chunked.print(F(" Slave Failed to Respond (Code 11)"
337+
"<tr><td><td>"));
338+
chunked.print(errorInvalid);
339+
chunked.print(F(" Invalid Request (Dropped by Gateway)"));
328340
chunked.print(F("<tr><td><br>"
329341
"<tr><td>Modbus TCP/UDP Masters:"));
330342
byte countMasters = 0;
@@ -343,13 +355,13 @@ void contentStatus(ChunkedPrint &chunked) {
343355
}
344356
}
345357
}
346-
if (countMasters == 0) chunked.print(F("<td>none"));
358+
if (countMasters == 0) chunked.print(F("<td>None"));
347359
chunked.print(F("<tr><td><br>"
348360
"<tr><td>Modbus RTU Slaves:<td><button name="));
349361
chunked.print(POST_ACTION);
350362
chunked.print(F(" value="));
351363
chunked.print(SCAN);
352-
chunked.print(F(">Scan</button>"));
364+
chunked.print(F(">Scan & Reset Stats</button>"));
353365
byte countSlaves = 0;
354366
for (int k = 1; k < maxSlaves; k++) {
355367
for (int s = 0; s < STAT_NUM; s++) {
@@ -364,16 +376,16 @@ void contentStatus(ChunkedPrint &chunked) {
364376
}
365377
switch (s) {
366378
case STAT_OK:
367-
chunked.print(F(" OK"));
379+
chunked.print(F(" Slave Responded"));
368380
break;
369381
case STAT_ERROR_0X:
370-
chunked.print(F(" Responded with Error (0x01~0x08)"));
382+
chunked.print(F(" Slave Responded with Error (Codes 1~8)"));
371383
break;
372384
case STAT_ERROR_0A:
373-
chunked.print(F(" Gateway Overloaded (0x0A)"));
385+
chunked.print(F(" Gateway Overloaded (Code 10)"));
374386
break;
375387
case STAT_ERROR_0B:
376-
chunked.print(F(" Failed to Respond (0x0B)"));
388+
chunked.print(F(" Slave Failed to Respond (Code 11)"));
377389
break;
378390
default:
379391
break;

arduino-modbus-rtu-tcp-gateway/arduino-modbus-rtu-tcp-gateway.ino

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,19 @@
3737
v3.0 2021-11-07 Improve POST parameters processing, bugfix 404 and 204 error headers.
3838
v3.1 2022-01-28 Code optimization, bugfix DHCP settings.
3939
v3.2 2022-06-04 Reduce program size (so that it fits on Nano), ethernet data counter only available when ENABLE_EXTRA_DIAG.
40-
v4.0 2022-11-26 Only store last 3 bytes of MAC in EEPROM,
40+
v4.0 2022-11-26 Optimize Modbus timeout and attempts counter, Modbus stats and error reporting on "Current Status" page
4141
4242
*/
4343

44-
const byte version[] = {4, 0};
44+
const byte version[] = { 4, 0 };
4545

4646
#include <SPI.h>
4747
#include <Ethernet.h>
4848
#include <EthernetUdp.h>
4949
#include <utility/w5100.h>
50-
#include <CircularBuffer.h> // CircularBuffer https://github.com/rlogiacco/CircularBuffer
50+
#include <CircularBuffer.h> // CircularBuffer https://github.com/rlogiacco/CircularBuffer
5151
#include <EEPROM.h>
52-
#include <StreamLib.h> // StreamLib https://github.com/jandrassy/StreamLib
52+
#include <StreamLib.h> // StreamLib https://github.com/jandrassy/StreamLib
5353

5454
// these are used by CreateTrulyRandomSeed() function
5555
#include <avr/interrupt.h>
@@ -58,13 +58,13 @@ const byte version[] = {4, 0};
5858

5959
/****** ADVANCED SETTINGS ******/
6060

61-
const byte reqQueueCount = 15; // max number of TCP or UDP requests stored in queue
62-
const int reqQueueSize = 256; // total length of TCP or UDP requests stored in queue (in bytes)
63-
const byte maxSlaves = 247; // max number of Modbus slaves (Modbus supports up to 247 slaves, the rest is for reserved addresses)
64-
const int modbusSize = 256; // size of a MODBUS RTU frame (determines size of serialInBuffer and tcpInBuffer)
65-
#define mySerial Serial // define serial port for RS485 interface, for Arduino Mega choose from Serial1, Serial2 or Serial3
66-
#define RS485_CONTROL_PIN 6 // Arduino Pin for RS485 Direction control, disable if you have module with hardware flow control
67-
const byte ethResetPin = 7; // Ethernet shield reset pin (deals with power on reset issue of the ethernet shield)
61+
const byte reqQueueCount = 10; // max number of TCP or UDP requests stored in a queue
62+
const int reqQueueSize = 256; // total length of TCP or UDP requests stored in a queue (in bytes)
63+
const byte maxSlaves = 247; // max number of Modbus slaves (Modbus supports up to 247 slaves, the rest is for reserved addresses)
64+
const int modbusSize = 256; // size of a MODBUS RTU frame (determines size of serialInBuffer and tcpInBuffer)
65+
#define mySerial Serial // define serial port for RS485 interface, for Arduino Mega choose from Serial1, Serial2 or Serial3
66+
#define RS485_CONTROL_PIN 6 // Arduino Pin for RS485 Direction control, disable if you have module with hardware flow control
67+
const byte ethResetPin = 7; // Ethernet shield reset pin (deals with power on reset issue of the ethernet shield)
6868
const byte scanCommand[] = { 0x03, 0x00, 0x00, 0x00, 0x01 }; // Command sent during Modbus RTU Scan. Slave is detected if any response (even error) is received.
6969

7070
// #define DEBUG // Main Serial (USB) is used for printing some debug info, not for Modbus RTU. At the moment, only web server related debug messages are printed.
@@ -107,10 +107,10 @@ typedef struct
107107
const config_type defaultConfig = {
108108
{}, // macEnd (last 3 bytes)
109109
false, // enableDhcp
110-
{192, 168, 1, 254}, // ip
111-
{255, 255, 255, 0}, // subnet
112-
{192, 168, 1, 1}, // gateway
113-
{192, 168, 1, 1}, // dns
110+
{ 192, 168, 1, 254 }, // ip
111+
{ 255, 255, 255, 0 }, // subnet
112+
{ 192, 168, 1, 1 }, // gateway
113+
{ 192, 168, 1, 1 }, // dns
114114
502, // tcpPort
115115
502, // udpPort
116116
80, // webPort
@@ -130,7 +130,7 @@ const byte configStart = 128;
130130
#ifdef UDP_TX_PACKET_MAX_SIZE
131131
#undef UDP_TX_PACKET_MAX_SIZE
132132
#define UDP_TX_PACKET_MAX_SIZE modbusSize
133-
#endif
133+
#endif
134134

135135
#ifdef MAX_SOCK_NUM // Ethernet.h library determines MAX_SOCK_NUM by Microcontroller RAM (not by Ethernet chip type).
136136
#undef MAX_SOCK_NUM // Ignore the RAM-based limitation on the number of sockets.
@@ -139,7 +139,9 @@ const byte configStart = 128;
139139

140140
byte maxSockNum = MAX_SOCK_NUM;
141141

142+
#ifdef ENABLE_DHCP
142143
bool dhcpSuccess = false;
144+
#endif /* ENABLE_DHCP */
143145

144146
const byte MAC_START[3] = { 0x90, 0xA2, 0xDA };
145147

@@ -152,19 +154,19 @@ EthernetServer webServer(80);
152154
#else /* DEBUG */
153155
#define dbg(x...) ;
154156
#define dbgln(x...) ;
155-
#endif /* DEBUG */
156-
#define UDP_REQUEST 0xFF // We store these codes in "header.clientNum" in order to differentiate
157-
#define SCAN_REQUEST 0xFE // between TCP requests (their clientNum is nevew higher than 0x07), UDP requests and scan requests (triggered by scan button)
157+
#endif /* DEBUG */
158+
#define UDP_REQUEST 0xFF // We store these codes in "header.clientNum" in order to differentiate
159+
#define SCAN_REQUEST 0xFE // between TCP requests (their clientNum is nevew higher than 0x07), UDP requests and scan requests (triggered by scan button)
158160

159161
/****** TIMERS AND STATE MACHINE ******/
160162

161163
class MicroTimer {
162-
private:
163-
unsigned long timestampLastHitMs;
164-
unsigned long sleepTimeMs;
165-
public:
166-
boolean isOver();
167-
void sleep(unsigned long sleepTimeMs);
164+
private:
165+
unsigned long timestampLastHitMs;
166+
unsigned long sleepTimeMs;
167+
public:
168+
boolean isOver();
169+
void sleep(unsigned long sleepTimeMs);
168170
};
169171
boolean MicroTimer::isOver() {
170172
if ((unsigned long)(micros() - timestampLastHitMs) > sleepTimeMs) {
@@ -177,12 +179,12 @@ void MicroTimer::sleep(unsigned long sleepTimeMs) {
177179
timestampLastHitMs = micros();
178180
}
179181
class Timer {
180-
private:
181-
unsigned long timestampLastHitMs;
182-
unsigned long sleepTimeMs;
183-
public:
184-
boolean isOver();
185-
void sleep(unsigned long sleepTimeMs);
182+
private:
183+
unsigned long timestampLastHitMs;
184+
unsigned long sleepTimeMs;
185+
public:
186+
boolean isOver();
187+
void sleep(unsigned long sleepTimeMs);
186188
};
187189
boolean Timer::isOver() {
188190
if ((unsigned long)(millis() - timestampLastHitMs) > sleepTimeMs) {
@@ -196,8 +198,8 @@ void Timer::sleep(unsigned long sleepTimeMs) {
196198
}
197199
Timer requestTimeout;
198200
uint16_t crc;
199-
#define RS485_TRANSMIT HIGH
200-
#define RS485_RECEIVE LOW
201+
#define RS485_TRANSMIT HIGH
202+
#define RS485_RECEIVE LOW
201203
byte scanCounter = 0;
202204
enum state : byte {
203205
IDLE,
@@ -213,25 +215,23 @@ unsigned int frameDelay;
213215

214216
volatile uint32_t seed1; // seed1 is generated by CreateTrulyRandomSeed()
215217
volatile int8_t nrot;
216-
uint32_t seed2 = 17111989; // seed2 is static
218+
uint32_t seed2 = 17111989; // seed2 is static
217219

220+
#ifdef ENABLE_EXTRA_DIAG
218221
// store uptime seconds (includes seconds counted before millis() overflow)
219222
unsigned long seconds;
220223
// store last millis() so that we can detect millis() overflow
221224
unsigned long last_milliseconds = 0;
222225
// store seconds passed until the moment of the overflow so that we can add them to "seconds" on the next call
223226
unsigned long remaining_seconds = 0;
224227
// Data counters (we only use unsigned long in ENABLE_EXTRA_DIAG, to save flash memory)
225-
#ifdef ENABLE_EXTRA_DIAG
226228
unsigned long serialTxCount = 0;
227229
unsigned long serialRxCount = 0;
228230
unsigned long ethTxCount = 0;
229231
unsigned long ethRxCount = 0;
230232
#else
231-
unsigned int serialTxCount = 0;
232-
unsigned int serialRxCount = 0;
233-
// unsigned int ethTxCount = 0;
234-
// unsigned int ethRxCount = 0;
233+
// unsigned int serialTxCount = 0;
234+
// unsigned int serialRxCount = 0;
235235
#endif /* ENABLE_EXTRA_DIAG */
236236

237237
/****** SETUP: RUNS ONCE ******/
@@ -253,8 +253,8 @@ void setup() {
253253
}
254254

255255
#ifdef DEBUG
256-
debugSerial.begin(localConfig.baud); // same baud as RS485
257-
#endif /* DEBUG */
256+
debugSerial.begin(localConfig.baud); // same baud as RS485
257+
#endif /* DEBUG */
258258

259259
startSerial();
260260
startEthernet();
@@ -274,13 +274,13 @@ void loop() {
274274
#endif /* DEBUG */
275275

276276
recvWeb();
277-
maintainCounters(); // maintain counters and synchronize their reset to zero when they overflow
278277

279278
#ifdef ENABLE_DHCP
280279
maintainDhcp();
281280
#endif /* ENABLE_DHCP */
282281

283282
#ifdef ENABLE_EXTRA_DIAG
283+
maintainCounters(); // maintain counters and synchronize their reset to zero when they overflow
284284
maintainUptime(); // maintain uptime in case of millis() overflow
285-
#endif /* ENABLE_EXTRA_DIAG */
285+
#endif /* ENABLE_EXTRA_DIAG */
286286
}

0 commit comments

Comments
 (0)