|
| 1 | +/* ******************************************************************* |
| 2 | + Modbus TCP/UDP functions |
| 3 | +
|
| 4 | + recvUdp |
| 5 | + - receives Modbus UDP (or Modbus RTU over UDP) messages |
| 6 | + - calls checkRequest |
| 7 | + - stores requests in queue or replies with error |
| 8 | +
|
| 9 | + recvTcp |
| 10 | + - receives Modbus TCP (or Modbus RTU over TCP) messages |
| 11 | + - calls checkRequest |
| 12 | + - stores requests in queue or replies with error |
| 13 | +
|
| 14 | + processRequests |
| 15 | + - inserts scan request into queue |
| 16 | + - optimizes queue |
| 17 | +
|
| 18 | + checkRequest |
| 19 | + - checks Modbus TCP/UDP requests (correct MBAP header, CRC in case of Modbus RTU over TCP/UDP) |
| 20 | + - checks availability of queue |
| 21 | +
|
| 22 | + deleteRequest |
| 23 | + - deletes requests from queue |
| 24 | + |
| 25 | + ***************************************************************** */ |
| 26 | + |
| 27 | + |
| 28 | +BitBool<maxSlaves> slavesResponding; |
| 29 | + |
| 30 | +typedef struct { |
| 31 | + byte tid[2]; // MBAP Transaction ID |
| 32 | + byte uid; // MBAP Unit ID (address) |
| 33 | + byte PDUlen; // lenght of PDU (func + data) stored in queuePDUs |
| 34 | + IPAddress remIP; // remote IP for UDP client (UDP response is sent back to remote IP) |
| 35 | + unsigned int remPort; // remote port for UDP client (UDP response is sent back to remote port) |
| 36 | + byte clientNum; // TCP client who sent the request, UDP_REQUEST (0xFF) designates UDP client |
| 37 | +} header; |
| 38 | + |
| 39 | +// each request is stored in 3 queues (all queues are written to, read and deleted in sync) |
| 40 | +CircularBuffer<header, reqQueueCount> queueHeaders; // queue of requests' headers and metadata (MBAP transaction ID, MBAP unit ID, PDU length, remIP, remPort, TCP client) |
| 41 | +CircularBuffer<byte, reqQueueSize> queuePDUs; // queue of PDU data (function code, data) |
| 42 | +CircularBuffer<byte, reqQueueCount> queueRetries; // queue of retry counters |
| 43 | + |
| 44 | +void recvUdp() |
| 45 | +{ |
| 46 | + int packetSize = Udp.parsePacket(); |
| 47 | + if (packetSize) |
| 48 | + { |
| 49 | + ethRxCount += packetSize; |
| 50 | + byte udpInBuffer[modbusSize + 4]; // Modbus TCP frame is 4 bytes longer than Modbus RTU frame |
| 51 | + // Modbus TCP/UDP frame: [0][1] transaction ID, [2][3] protocol ID, [4][5] length and [6] unit ID (address)..... |
| 52 | + // Modbus RTU frame: [0] address..... |
| 53 | + Udp.read(udpInBuffer, sizeof(udpInBuffer)); |
| 54 | + Udp.flush(); |
| 55 | + |
| 56 | + int errorCode = checkRequest(udpInBuffer, packetSize); |
| 57 | + byte pduStart; // first byte of Protocol Data Unit (i.e. Function code) |
| 58 | + if (localConfig.enableRtuOverTcp) pduStart = 1; // In Modbus RTU, Function code is second byte (after address) |
| 59 | + else pduStart = 7; // In Modbus TCP/UDP, Function code is 8th byte (after address) |
| 60 | + if (errorCode == 0) { |
| 61 | + // Store in request queue: 2 bytes MBAP Transaction ID (ignored in Modbus RTU over TCP); MBAP Unit ID (address); PDUlen (func + data);remote IP; remote port; TCP client Number (socket) - 0xFF for UDP |
| 62 | + queueHeaders.push(header {{udpInBuffer[0], udpInBuffer[1]}, udpInBuffer[pduStart - 1], packetSize - pduStart, Udp.remoteIP(), Udp.remotePort(), UDP_REQUEST}); |
| 63 | + queueRetries.push(0); |
| 64 | + for (byte i = 0; i < packetSize - pduStart; i++) { |
| 65 | + queuePDUs.push(udpInBuffer[i + pduStart]); |
| 66 | + } |
| 67 | + } else if (errorCode > 0) { |
| 68 | + // send back message with error code |
| 69 | + Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); |
| 70 | + if (!localConfig.enableRtuOverTcp) { |
| 71 | + Udp.write(udpInBuffer, 5); |
| 72 | + Udp.write(0x03); |
| 73 | + } |
| 74 | + Udp.write(udpInBuffer[pduStart - 1]); // address |
| 75 | + Udp.write(udpInBuffer[pduStart] + 0x80); // function + 0x80 |
| 76 | + Udp.write(errorCode); |
| 77 | + if (localConfig.enableRtuOverTcp) { |
| 78 | + crc = 0xFFFF; |
| 79 | + calculateCRC(udpInBuffer[pduStart - 1]); |
| 80 | + calculateCRC(udpInBuffer[pduStart] + 0x80); |
| 81 | + calculateCRC(errorCode); |
| 82 | + Udp.write(lowByte(crc)); // send CRC, low byte first |
| 83 | + Udp.write(highByte(crc)); |
| 84 | + } |
| 85 | + Udp.endPacket(); |
| 86 | + ethTxCount += 5; |
| 87 | + if (!localConfig.enableRtuOverTcp) ethTxCount += 4; |
| 88 | + } |
| 89 | + } |
| 90 | +} |
| 91 | + |
| 92 | + |
| 93 | +void recvTcp() |
| 94 | +{ |
| 95 | + EthernetClient client = modbusServer.available(); |
| 96 | + if (client) { |
| 97 | + int packetSize = client.available(); |
| 98 | + ethRxCount += packetSize; |
| 99 | + byte tcpInBuffer[modbusSize + 4]; // Modbus TCP frame is 4 bytes longer than Modbus RTU frame |
| 100 | + // Modbus TCP/UDP frame: [0][1] transaction ID, [2][3] protocol ID, [4][5] length and [6] unit ID (address)..... |
| 101 | + // Modbus RTU frame: [0] address..... |
| 102 | + client.read(tcpInBuffer, sizeof(tcpInBuffer)); |
| 103 | + client.flush(); |
| 104 | + int errorCode = checkRequest(tcpInBuffer, packetSize); |
| 105 | + byte pduStart; // first byte of Protocol Data Unit (i.e. Function code) |
| 106 | + if (localConfig.enableRtuOverTcp) pduStart = 1; // In Modbus RTU, Function code is second byte (after address) |
| 107 | + else pduStart = 7; // In Modbus TCP/UDP, Function code is 8th byte (after address) |
| 108 | + if (errorCode == 0) { |
| 109 | + // Store in request queue: 2 bytes MBAP Transaction ID (ignored in Modbus RTU over TCP); MBAP Unit ID (address); PDUlen (func + data);remote IP; remote port; TCP client Number (socket) - 0xFF for UDP |
| 110 | + queueHeaders.push(header {{tcpInBuffer[0], tcpInBuffer[1]}, tcpInBuffer[pduStart - 1], packetSize - pduStart, {}, 0, client.getSocketNumber()}); |
| 111 | + queueRetries.push(0); |
| 112 | + for (byte i = 0; i < packetSize - pduStart; i++) { |
| 113 | + queuePDUs.push(tcpInBuffer[i + pduStart]); |
| 114 | + } |
| 115 | + } else if (errorCode > 0) { |
| 116 | + // send back message with error code |
| 117 | + if (!localConfig.enableRtuOverTcp) { |
| 118 | + client.write(tcpInBuffer, 5); |
| 119 | + client.write(0x03); |
| 120 | + } |
| 121 | + client.write(tcpInBuffer[pduStart - 1]); // address |
| 122 | + client.write(tcpInBuffer[pduStart] + 0x80); // function + 0x80 |
| 123 | + client.write(errorCode); |
| 124 | + if (localConfig.enableRtuOverTcp) { |
| 125 | + crc = 0xFFFF; |
| 126 | + calculateCRC(tcpInBuffer[pduStart - 1]); |
| 127 | + calculateCRC(tcpInBuffer[pduStart] + 0x80); |
| 128 | + calculateCRC(errorCode); |
| 129 | + client.write(lowByte(crc)); // send CRC, low byte first |
| 130 | + client.write(highByte(crc)); |
| 131 | + } |
| 132 | + client.stop(); |
| 133 | + ethTxCount += 5; |
| 134 | + if (!localConfig.enableRtuOverTcp) ethTxCount += 4; |
| 135 | + } |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +void processRequests() |
| 140 | +{ |
| 141 | + // Insert scan request into queue |
| 142 | + if (scanCounter != 0 && queueHeaders.available() > 1 && queuePDUs.available() > 1) { |
| 143 | + // Store scan request in request queue |
| 144 | + queueHeaders.push(header {{0x00, 0x00}, scanCounter, sizeof(scanCommand), {}, 0, SCAN_REQUEST}); |
| 145 | + queueRetries.push(localConfig.serialRetry - 1); // scan requests are only sent once, so set "queueRetries" to one attempt below limit |
| 146 | + for (byte i = 0; i < sizeof(scanCommand); i++) { |
| 147 | + queuePDUs.push(scanCommand[i]); |
| 148 | + } |
| 149 | + scanCounter++; |
| 150 | + if (scanCounter == maxSlaves + 1) scanCounter = 0; |
| 151 | + } |
| 152 | + |
| 153 | + // Optimize queue (prioritize requests from responding slaves) and trigger sending via serial |
| 154 | + if (serialState == IDLE) { // send new data over serial only if we are not waiting for response |
| 155 | + if (!queueHeaders.isEmpty()) { |
| 156 | + boolean queueHasRespondingSlaves; // true if queue holds at least one request to responding slaves |
| 157 | + for (byte i = 0; i < queueHeaders.size(); i++) { |
| 158 | + if (slavesResponding[queueHeaders[i].uid] == true) { |
| 159 | + queueHasRespondingSlaves = true; |
| 160 | + break; |
| 161 | + } else { |
| 162 | + queueHasRespondingSlaves = false; |
| 163 | + } |
| 164 | + } |
| 165 | + while (queueHasRespondingSlaves == true && slavesResponding[queueHeaders.first().uid] == false) { |
| 166 | + // move requests to non responding slaves to the tail of the queue |
| 167 | + for (byte i = 0; i < queueHeaders.first().PDUlen; i++) { |
| 168 | + queuePDUs.push(queuePDUs.shift()); |
| 169 | + } |
| 170 | + queueRetries.push(queueRetries.shift()); |
| 171 | + queueHeaders.push(queueHeaders.shift()); |
| 172 | + } |
| 173 | + serialState = SENDING; // trigger sendSerial() |
| 174 | + } |
| 175 | + } |
| 176 | +} |
| 177 | + |
| 178 | +int checkRequest(byte buffer[], int bufferSize) { |
| 179 | + byte address; |
| 180 | + if (localConfig.enableRtuOverTcp) address = buffer[0]; |
| 181 | + else address = buffer[6]; |
| 182 | + |
| 183 | + if (localConfig.enableRtuOverTcp) { // check CRC for Modbus RTU over TCP/UDP |
| 184 | + if (checkCRC(buffer, bufferSize) == false) { |
| 185 | + return -1; // reject: do nothing and return no error code |
| 186 | + } |
| 187 | + } else { // check MBAP header structure for Modbus TCP/UDP |
| 188 | + if (buffer[2] != 0x00 || buffer[3] != 0x00 || buffer[4] != 0x00 || buffer[5] + 6 != bufferSize) { |
| 189 | + return -1; // reject: do nothing and return no error code |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + if (queueHeaders.isEmpty() == false && slavesResponding[address] == false) { // allow only one request to non responding slaves |
| 194 | + for (byte j = queueHeaders.size(); j > 0 ; j--) { // start searching from tail because requests to non-responsive slaves are usually towards the tail of the queue |
| 195 | + if (queueHeaders[j - 1].uid == address) { |
| 196 | + return 0x0B; // return modbus error 11 (Gateway Target Device Failed to Respond) - usually means that target device (address) is not present |
| 197 | + } |
| 198 | + } |
| 199 | + } |
| 200 | + // check if we have space in request queue |
| 201 | + if (queueHeaders.available() < 1 || (localConfig.enableRtuOverTcp && queuePDUs.available() < bufferSize - 1) || (!localConfig.enableRtuOverTcp && queuePDUs.available() < bufferSize - 7)) { |
| 202 | + return 0x06; // return modbus error 6 (Slave Device Busy) - try again later |
| 203 | + } |
| 204 | + // al checkes passed OK, we can store the incoming data in request queue |
| 205 | + return 0; |
| 206 | +} |
| 207 | + |
| 208 | +void deleteRequest() // delete request from queue |
| 209 | +{ |
| 210 | + for (byte i = 0; i < queueHeaders.first().PDUlen; i++) { |
| 211 | + queuePDUs.shift(); |
| 212 | + } |
| 213 | + queueHeaders.shift(); |
| 214 | + queueRetries.shift(); |
| 215 | +} |
0 commit comments