Skip to content

Commit 9ff0348

Browse files
committed
Modbus slave errors in web ui
1 parent b2fab75 commit 9ff0348

File tree

5 files changed

+151
-101
lines changed

5 files changed

+151
-101
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ void startEthernet() {
9292

9393
void (*resetFunc)(void) = 0; //declare reset function at address 0
9494

95+
#ifdef ENABLE_DHCP
9596
void maintainDhcp() {
9697
if (localConfig.enableDhcp && dhcpSuccess == true) { // only call maintain if initial DHCP request by startEthernet was successfull
9798
uint8_t maintainResult = Ethernet.maintain();
@@ -101,7 +102,9 @@ void maintainDhcp() {
101102
}
102103
}
103104
}
105+
#endif /* ENABLE_DHCP */
104106

107+
#ifdef ENABLE_EXTRA_DIAG
105108
void maintainUptime() {
106109
unsigned long milliseconds = millis();
107110
if (last_milliseconds > milliseconds) {
@@ -115,6 +118,7 @@ void maintainUptime() {
115118
//We add the "remaining_seconds", so that we can continue measuring the time passed from the last boot of the device.
116119
seconds = (milliseconds / 1000) + remaining_seconds;
117120
}
121+
#endif /* ENABLE_EXTRA_DIAG */
118122

119123
void maintainCounters() {
120124
// synchronize roll-over of data counters to zero, at 0xFFFFFF00 or 0xFF00 respectively

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

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,37 @@
2626
2727
***************************************************************** */
2828

29-
#define ADDRESS_POS (6 * !localConfig.enableRtuOverTcp) // position of slave address in the TCP/UDP message (0 for Modbus RTU over TCP/UDP and 6 for Modbus RTU over TCP/UDP)
29+
#define ADDRESS_POS (6 * !localConfig.enableRtuOverTcp) // position of slave address in the incoming TCP/UDP message (0 for Modbus RTU over TCP/UDP and 6 for Modbus RTU over TCP/UDP)
30+
31+
enum status : byte {
32+
STAT_OK,
33+
STAT_ERROR_0X,
34+
STAT_ERROR_0A,
35+
STAT_ERROR_0B,
36+
STAT_NUM // Number of status flags in this enum. Must be the last element within this enum!!
37+
};
38+
39+
// bool arrays for storing Modbus RTU status of individual slaves
40+
uint8_t stat[STAT_NUM][(maxSlaves + 1 + 7) / 8];
3041

3142
// bool arrays for storing Modbus RTU status (responging or not responding). Array index corresponds to slave address.
32-
uint8_t responding[(maxSlaves + 1 + 7) / 8];
33-
uint8_t error[(maxSlaves + 1 + 7) / 8];
43+
// uint8_t statOk[(maxSlaves + 1 + 7) / 8];
44+
// uint8_t statError0B[(maxSlaves + 1 + 7) / 8];
45+
3446
uint8_t masks[8] = { 1, 2, 4, 8, 16, 32, 64, 128 };
3547

3648
typedef struct {
3749
byte tid[2]; // MBAP Transaction ID
38-
byte uid; // MBAP Unit ID (address)
39-
byte PDUlen; // lenght of PDU (func + data) stored in queuePDUs
50+
byte msgLen; // lenght of Modbus message stored in queuePDUs
4051
IPAddress remIP; // remote IP for UDP client (UDP response is sent back to remote IP)
4152
unsigned int remPort; // remote port for UDP client (UDP response is sent back to remote port)
4253
byte clientNum; // TCP client who sent the request, UDP_REQUEST (0xFF) designates UDP client
54+
byte atts; // attempts counter
4355
} header;
4456

4557
// each request is stored in 3 queues (all queues are written to, read and deleted in sync)
4658
CircularBuffer<header, reqQueueCount> queueHeaders; // queue of requests' headers and metadata (MBAP transaction ID, MBAP unit ID, PDU length, remIP, remPort, TCP client)
4759
CircularBuffer<byte, reqQueueSize> queuePDUs; // queue of PDU data (function code, data)
48-
CircularBuffer<byte, reqQueueCount> queueRetries; // queue of retry counters
4960

5061
void recvUdp() {
5162
unsigned int msgLength = Udp.parsePacket();
@@ -128,9 +139,15 @@ void processRequests() {
128139
// Insert scan request into queue
129140
if (scanCounter != 0 && queueHeaders.available() > 1 && queuePDUs.available() > sizeof(scanCommand) + 1) {
130141
// Store scan request in request queue
131-
queueHeaders.push(header{ { 0x00, 0x00 }, scanCounter, sizeof(scanCommand) + 1, {}, 0, SCAN_REQUEST });
132-
queueRetries.push(localConfig.serialAttempts - 1); // scan requests are only sent once, so set "queueRetries" to one attempt below limit
133-
queuePDUs.push(scanCounter); // address of the scanned slave
142+
queueHeaders.push(header{
143+
{ 0x00, 0x00 }, // tid[2]
144+
sizeof(scanCommand) + 1, // msgLen
145+
{}, // remIP
146+
0, // remPort
147+
SCAN_REQUEST, // clientNum
148+
localConfig.serialAttempts - 1, // atts
149+
});
150+
queuePDUs.push(scanCounter); // address of the scanned slave
134151
for (byte i = 0; i < sizeof(scanCommand); i++) {
135152
queuePDUs.push(scanCommand[i]);
136153
}
@@ -140,24 +157,21 @@ void processRequests() {
140157
// Optimize queue (prioritize requests from responding slaves) and trigger sending via serial
141158
if (serialState == IDLE) { // send new data over serial only if we are not waiting for response
142159
if (!queueHeaders.isEmpty()) {
143-
boolean queueHasRespondingSlaves; // true if queue holds at least one request to responding slaves
144-
for (byte i = 0; i < queueHeaders.size(); i++) {
145-
if (getSlaveStatus(queueHeaders[i].uid, responding) == true) {
146-
queueHasRespondingSlaves = true;
147-
break;
148-
} else {
149-
queueHasRespondingSlaves = false;
150-
}
151-
}
160+
// boolean queueHasRespondingSlaves; // true if queue holds at least one request to responding slaves
161+
// for (byte i = 0; i < queueHeaders.size(); i++) {
162+
// if (getSlaveStatus(queueHeaders[i].uid, statOk) == true) {
163+
// queueHasRespondingSlaves = true;
164+
// break;
165+
// } else {
166+
// queueHasRespondingSlaves = false;
167+
// }
168+
// }
152169
serialState = SENDING; // trigger sendSerial()
153170
}
154171
}
155172
}
156173

157174
byte checkRequest(const byte inBuffer[], unsigned int msgLength, const IPAddress remoteIP, const unsigned int remotePort, const byte clientNum) {
158-
byte address;
159-
if (localConfig.enableRtuOverTcp) address = inBuffer[0];
160-
else address = inBuffer[6];
161175
if (localConfig.enableRtuOverTcp) { // check CRC for Modbus RTU over TCP/UDP
162176
if (checkCRC(inBuffer, msgLength) == false) {
163177
return 0; // reject: do nothing and return no error code
@@ -167,37 +181,49 @@ byte checkRequest(const byte inBuffer[], unsigned int msgLength, const IPAddress
167181
return 0; // reject: do nothing and return no error code
168182
}
169183
}
170-
msgLength = msgLength - ADDRESS_POS - (2 * localConfig.enableRtuOverTcp); // in Modbus RTU over TCP/UDP do not store CRC
184+
msgLength = msgLength - ADDRESS_POS - (2 * localConfig.enableRtuOverTcp); // in Modbus RTU over TCP/UDP do not store CRC
171185
// check if we have space in request queue
172186
if (queueHeaders.available() < 1 || queuePDUs.available() < msgLength) {
173-
return 0x06; // return modbus error 6 (Slave Device Busy) - try again later
187+
setSlaveStatus(inBuffer[ADDRESS_POS], STAT_ERROR_0A, true);
188+
return 0x0A; // return modbus error 0x0A (Gateway Overloaded)
174189
}
175190
// all checkes passed OK, we can store the incoming data in request queue
176-
// Store in request queue: 2 bytes MBAP Transaction ID (ignored in Modbus RTU over TCP); MBAP Unit ID (address); message length; remote IP; remote port; TCP client Number (socket) - 0xFF for UDP
177-
queueHeaders.push(header{ { inBuffer[0], inBuffer[1] }, inBuffer[ADDRESS_POS], (byte)msgLength, (IPAddress)remoteIP, (unsigned int)remotePort, (byte)clientNum });
178-
queueRetries.push(0);
179-
for (byte i = 0; i < msgLength; i++) {
191+
// Store in request queue
192+
queueHeaders.push(header{
193+
{ inBuffer[0], inBuffer[1] }, // tid[2] (ignored in Modbus RTU over TCP/UDP)
194+
(byte)msgLength, // msgLen
195+
(IPAddress)remoteIP, // remIP
196+
(unsigned int)remotePort, // remPort
197+
(byte)clientNum, // clientNum
198+
0, // atts
199+
});
200+
for (byte i = 0; i < msgLength; i++) {
180201
queuePDUs.push(inBuffer[i + ADDRESS_POS]);
181202
}
182203
return 0;
183204
}
184205

185206
void deleteRequest() // delete request from queue
186207
{
187-
for (byte i = 0; i < queueHeaders.first().PDUlen; i++) {
208+
for (byte i = 0; i < queueHeaders.first().msgLen; i++) {
188209
queuePDUs.shift();
189210
}
190211
queueHeaders.shift();
191-
queueRetries.shift();
192212
}
193213

194-
bool getSlaveStatus(const uint8_t index, const uint8_t status[]) {
195-
if (index >= maxSlaves) return false; // error
196-
return (status[index / 8] & masks[index & 7]) > 0;
214+
bool getSlaveStatus(const uint8_t slave, const byte status) {
215+
if (slave >= maxSlaves) return false; // error
216+
return (stat[status][slave / 8] & masks[slave & 7]) > 0;
197217
}
198218

199-
void setSlaveStatus(const uint8_t index, uint8_t status[], const bool value) {
200-
if (index >= maxSlaves) return; // error
201-
if (value == 0) status[index / 8] &= ~masks[index & 7];
202-
else status[index / 8] |= masks[index & 7];
219+
void setSlaveStatus(const uint8_t slave, byte status, const bool value) {
220+
if (slave >= maxSlaves) return; // error
221+
if (value == 0) {
222+
stat[status][slave / 8] &= ~masks[slave & 7];
223+
} else {
224+
for (byte i = 0; i < STAT_NUM; i++) {
225+
stat[i][slave / 8] &= ~masks[slave & 7]; // set all other flags to false
226+
}
227+
stat[status][slave / 8] |= masks[slave & 7];
228+
}
203229
}

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

Lines changed: 57 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,17 @@ void sendSerial() {
3333
#endif /* RS485_CONTROL_PIN */
3434
crc = 0xFFFF;
3535
}
36-
while (mySerial.availableForWrite() > 0 && txNdx < myHeader.PDUlen) {
36+
while (mySerial.availableForWrite() > 0 && txNdx < myHeader.msgLen) {
3737
mySerial.write(queuePDUs[txNdx]);
3838
calculateCRC(queuePDUs[txNdx]);
3939
txNdx++;
4040
}
41-
if (mySerial.availableForWrite() > 1 && txNdx == myHeader.PDUlen) {
41+
if (mySerial.availableForWrite() > 1 && txNdx == myHeader.msgLen) {
4242
mySerial.write(lowByte(crc)); // send CRC, low byte first
4343
mySerial.write(highByte(crc));
4444
txNdx++;
4545
}
46-
if (mySerial.availableForWrite() == SERIAL_TX_BUFFER_SIZE - 1 && txNdx > myHeader.PDUlen) {
46+
if (mySerial.availableForWrite() == SERIAL_TX_BUFFER_SIZE - 1 && txNdx > myHeader.msgLen) {
4747
// wait for last byte (incl. CRC) to be sent from serial Tx buffer
4848
// this if statement is not very reliable (too fast)
4949
// Serial.isFlushed() method is needed....see https://github.com/arduino/Arduino/pull/3737
@@ -53,7 +53,7 @@ void sendSerial() {
5353
}
5454
} else if (serialState == DELAY && txDelay.isOver()) {
5555
header myHeader = queueHeaders.first();
56-
serialTxCount += myHeader.PDUlen;
56+
serialTxCount += myHeader.msgLen;
5757
serialTxCount += 2;
5858
#ifdef RS485_CONTROL_PIN
5959
digitalWrite(RS485_CONTROL_PIN, RS485_RECEIVE); // Disable RS485 Transmit
@@ -64,7 +64,9 @@ void sendSerial() {
6464
} else {
6565
serialState = WAITING;
6666
requestTimeout.sleep(localConfig.serialTimeout); // delays next serial write
67-
queueRetries.unshift(queueRetries.shift() + 1);
67+
myHeader.atts++;
68+
queueHeaders.shift();
69+
queueHeaders.unshift(myHeader);
6870
}
6971
}
7072
}
@@ -86,13 +88,15 @@ void recvSerial() {
8688
rxTimeout.sleep(charTimeout);
8789
}
8890
if (rxDelay.isOver() && rxNdx != 0) {
89-
9091
// Process Serial data
9192
// Checks: 1) RTU frame is without errors; 2) CRC; 3) address of incoming packet against first request in queue; 4) only expected responses are forwarded to TCP/UDP
9293
header myHeader = queueHeaders.first();
9394
if (!rxErr && checkCRC(serialIn, rxNdx) == true && serialIn[0] == queuePDUs[0] && serialState == WAITING) {
94-
setSlaveStatus(serialIn[0], responding, true); // flag slave as responding
95-
setSlaveStatus(serialIn[0], error, false); // flag slave as without error
95+
if (serialIn[1] > 0x80 && myHeader.clientNum != SCAN_REQUEST) {
96+
setSlaveStatus(serialIn[0], STAT_ERROR_0X, true);
97+
} else {
98+
setSlaveStatus(serialIn[0], STAT_OK, true);
99+
}
96100
byte MBAP[] = { myHeader.tid[0], myHeader.tid[1], 0x00, 0x00, highByte(rxNdx - 2), lowByte(rxNdx - 2) };
97101
if (myHeader.clientNum == UDP_REQUEST) {
98102
Udp.beginPacket(myHeader.remIP, myHeader.remPort);
@@ -131,57 +135,58 @@ void recvSerial() {
131135
// Deal with Serial timeouts (i.e. Modbus RTU timeouts)
132136
if (serialState == WAITING && requestTimeout.isOver()) {
133137
header myHeader = queueHeaders.first();
134-
setSlaveStatus(queuePDUs[0], responding, false); // flag slave as nonresponding
135-
if (myHeader.clientNum != SCAN_REQUEST) setSlaveStatus(queuePDUs[0], error, true); // flag slave as error
136-
if (queueRetries.first() >= localConfig.serialAttempts) {
137-
// send modbus error 11 (Gateway Target Device Failed to Respond) - usually means that target device (address) is not present
138-
byte MBAP[] = { myHeader.tid[0], myHeader.tid[1], 0x00, 0x00, 0x00, 0x03 };
139-
byte PDU[] = { queuePDUs[0], (byte)(queuePDUs[1] + 0x80), 0x0B };
140-
crc = 0xFFFF;
141-
for (byte i = 0; i < sizeof(PDU); i++) {
142-
calculateCRC(PDU[i]);
143-
}
144-
switch (myHeader.clientNum) {
145-
case SCAN_REQUEST:
146-
break;
147-
case UDP_REQUEST:
148-
Udp.beginPacket(myHeader.remIP, myHeader.remPort);
149-
if (!localConfig.enableRtuOverTcp) {
150-
Udp.write(MBAP, 6);
151-
}
152-
Udp.write(PDU, 3);
153-
if (localConfig.enableRtuOverTcp) {
154-
Udp.write(lowByte(crc)); // send CRC, low byte first
155-
Udp.write(highByte(crc));
156-
}
157-
Udp.endPacket();
138+
if (myHeader.atts >= localConfig.serialAttempts) {
139+
if (myHeader.clientNum != SCAN_REQUEST) {
140+
// send modbus error 0x0B (Gateway Target Device Failed to Respond) - usually means that target device (address) is not present
141+
setSlaveStatus(queuePDUs[0], STAT_ERROR_0B, true);
142+
byte MBAP[] = { myHeader.tid[0], myHeader.tid[1], 0x00, 0x00, 0x00, 0x03 };
143+
byte PDU[] = { queuePDUs[0], (byte)(queuePDUs[1] + 0x80), 0x0B };
144+
crc = 0xFFFF;
145+
for (byte i = 0; i < sizeof(PDU); i++) {
146+
calculateCRC(PDU[i]);
147+
}
148+
switch (myHeader.clientNum) {
149+
case SCAN_REQUEST:
150+
break;
151+
case UDP_REQUEST:
152+
Udp.beginPacket(myHeader.remIP, myHeader.remPort);
153+
if (!localConfig.enableRtuOverTcp) {
154+
Udp.write(MBAP, 6);
155+
}
156+
Udp.write(PDU, 3);
157+
if (localConfig.enableRtuOverTcp) {
158+
Udp.write(lowByte(crc)); // send CRC, low byte first
159+
Udp.write(highByte(crc));
160+
}
161+
Udp.endPacket();
158162
#ifdef ENABLE_EXTRA_DIAG
159-
ethTxCount += 5;
160-
if (!localConfig.enableRtuOverTcp) ethTxCount += 4;
163+
ethTxCount += 5;
164+
if (!localConfig.enableRtuOverTcp) ethTxCount += 4;
161165
#endif /* ENABLE_EXTRA_DIAG */
162-
break;
163-
default: // Ethernet client
164-
{
165-
EthernetClient client = EthernetClient(myHeader.clientNum);
166-
if (client.connected()) {
167-
if (!localConfig.enableRtuOverTcp) {
168-
client.write(MBAP, 6);
169-
}
170-
client.write(PDU, 3);
171-
if (localConfig.enableRtuOverTcp) {
172-
client.write(lowByte(crc)); // send CRC, low byte first
173-
client.write(highByte(crc));
174-
}
166+
break;
167+
default: // Ethernet client
168+
{
169+
EthernetClient client = EthernetClient(myHeader.clientNum);
170+
if (client.connected()) {
171+
if (!localConfig.enableRtuOverTcp) {
172+
client.write(MBAP, 6);
173+
}
174+
client.write(PDU, 3);
175+
if (localConfig.enableRtuOverTcp) {
176+
client.write(lowByte(crc)); // send CRC, low byte first
177+
client.write(highByte(crc));
178+
}
175179
#ifdef ENABLE_EXTRA_DIAG
176-
ethTxCount += 5;
177-
if (!localConfig.enableRtuOverTcp) ethTxCount += 4;
180+
ethTxCount += 5;
181+
if (!localConfig.enableRtuOverTcp) ethTxCount += 4;
178182
#endif /* ENABLE_EXTRA_DIAG */
183+
}
184+
break;
179185
}
180-
break;
181-
}
186+
}
182187
}
183188
deleteRequest();
184-
} // if (queueRetries.first() >= MAX_RETRY)
189+
} // if (myHeader.atts >= MAX_RETRY)
185190
serialState = IDLE;
186191
} // if (requestTimeout.isOver() && expectingData == true)
187192
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,7 @@ void processPost(char postParameter[]) {
332332
break;
333333
case SCAN:
334334
scanCounter = 1;
335-
memset(responding, 0, sizeof(responding)); // clear status flags
336-
memset(error, 0, sizeof(error));
335+
memset(&stat, 0, sizeof(stat)); // clear all status flags
337336
break;
338337
default:
339338
break;

0 commit comments

Comments
 (0)