├── LICENSE.txt ├── README.md ├── examples ├── MQTTClient │ └── MQTTClient.ino └── WebClient │ └── WebClient.ino ├── keywords.txt ├── library.properties └── src ├── TCP_over_Serial.cpp └── TCP_over_Serial.h /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2023, Roan Brand 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino TCP over Serial Client 2 | Arduino client for the [Serial To TCP Bridge Protocol](https://github.com/RoanBrand/SerialToTCPBridgeProtocol) gateway service. 3 | 4 | Open a TCP over Serial connection to a server from the Arduino using the host. No Ethernet/WiFi shields necessary. 5 | Quickly communicate with other servers and make network apps using minimal hardware. 6 | See [this](https://github.com/RoanBrand/SerialToTCPBridgeProtocol) for more information on the protocol and for the **Protocol Gateway** you will need to run on the host PC the Arduino is connected to serially. 7 | 8 | ## Hardware Requirements: 9 | - Serial 0 (Main serial through USB) 10 | - Timer 1 11 | 12 | ## Dependencies 13 | - [NeoHWSerial](https://github.com/gicking/NeoHWSerial) v1.6.6 14 | - [CRC32](https://github.com/bakercp/CRC32) v2.0.0 15 | - [PubSubClient](https://github.com/knolleary/pubsubclient) v2.8.0 - Needed for the included **MQTT Client Example** 16 | 17 | These should install automatically when installing this library through the Arduino *Library Manager*. 18 | 19 | ## How to 20 | - Get the [Protocol Gateway](https://github.com/RoanBrand/SerialToTCPBridgeProtocol) and build it. 21 | - Change the gateway's config to listen on the Serial port connected to your Arduino and start it. 22 | - Your Arduino app can then use the `TCPOverSerialClient` API which is similar to the `EthernetClient` API to make tcp connections to servers as long as they are reachable from the host PC and the gateway service is running. 23 | 24 | ## Web Client Example 25 | - Modified version of the Ethernet shield's Web Client example. 26 | - The HTTP response data cannot be printed out over USB serial as in original example, because it is already in use by the protocol. 27 | - Instead it uses the software serial library to send it out over another serial connection. 28 | - Note that you would need to connect this second serial line to your PC if you wish to get the data or view the result in the Arduino IDE Serial Monitor. 29 | - On other boards with more hardware serial ports than the Uno the example can be modified to use those instead (Serial1,2,etc.) and remove the software serial lib. 30 | 31 | ## MQTT Client Example 32 | 1. Open and upload the example from the Arduino *Examples menu*. 33 | 2. Run the **Protocol Gateway** on the same PC, with the correct Serial port configuration. 34 | 3. After a short time, the Arduino should connect to the MQTT broker `mqtt.eclipseprojects.io`. 35 | 4. Using a MQTT client, like [MQTTX](https://mqttx.app/), connect to the same broker and subscribe to the `ArduinoOut` topic. 36 | 5. While the Arduino is connected to the MQTT broker, it will publish a message to the `ArduinoOut` topic every 5s. 37 | 6. Using the client, publish characters `1` and `2` to topic `LedIn` to toggle the led on and off on the Arduino board. 38 | 39 | ### Details 40 | - Tested only on Arduino Uno. It would probably not work for the Arduino Due. 41 | - You cannot use the same serial port in your app that is being used by the protocol, e.g. You cannot use `Serial` (Serial0) when the library uses `NeoSerial`, etc. You can modify this lib easily to use other hardware serial ports on bigger boards than the Arduino Uno if you want to free up your USB Serial connection. 42 | - Your app's instance of `TCPOverSerialClient` needs to be a pointer and created with `new()`. It doesn't work otherwise and I don't know why yet. 43 | 44 | - The protocol provides the app an in order, duplicates free and error checked byte stream by adding a CRC32 and simple retry mechanism. See [this](https://en.wikibooks.org/wiki/Serial_Programming/Error_Correction_Methods) for background. 45 | - The **Protocol Gateway** opens a real TCP connection to a set destination on behalf of the **Protocol Client** running on the Arduino, and forwards traffic bi-directionally. 46 | - `TCPOverSerialClient` is derived from the standard Arduino `Client` class. This means existing code written for Ethernet/Wi-Fi shields should work with this. 47 | - `NeoHWSerial` is an alternative for `Serial`, used to gain access to the AVR UART interrupts. 48 | - The protocol cannot run over the standard Serial API or something like software serial because it needs hardware level RX interrupts when bytes arrive. 49 | -------------------------------------------------------------------------------- /examples/MQTTClient/MQTTClient.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Basic example of MQTT Client running over the Serial To TCP Bridge Client. 3 | * 4 | * It uses the MQTT client package PubSubClient from Nick O’Leary. 5 | * Install it from the Library Manager, if not already automatically installed. 6 | * 7 | * The example connects to the public Broker at "mqtt.eclipseprojects.io". 8 | * It will publish millis() as a message every 5s to topic "ArduinoOut", while connected. 9 | * 10 | * Using a MQTT client (e.g. https://mqttx.app): 11 | * Publish character '1' to topic "LedIn" to turn on the Led on the Arduino Uno and '2' to turn it off. 12 | */ 13 | 14 | #include 15 | #include 16 | 17 | TCPOverSerialClient* s; // Protocol Client running over USB Serial 18 | PubSubClient client; // MQTT Client 19 | 20 | const char* broker = "mqtt.eclipseprojects.io"; 21 | const char* ledTopic = "LedIn"; 22 | const char* outTopic = "ArduinoOut"; 23 | 24 | uint32_t lastPub = 0; 25 | 26 | void setup() { 27 | pinMode(13, OUTPUT); 28 | s = new TCPOverSerialClient(); 29 | client.setClient(*s); 30 | 31 | // MQTT Broker running on same PC the Arduino is connected to. 32 | client.setServer(broker, 1883); 33 | client.setCallback(callback); 34 | } 35 | 36 | void loop() { 37 | // If not connected, retry indefinitely. 38 | while(!client.loop()) { 39 | while(!connectToBroker()) { 40 | 41 | // If connection attempt unsuccessful, flash led fast. 42 | for (uint8_t fc = 0; fc < 10; fc++) { 43 | digitalWrite(13, !digitalRead(13)); 44 | delay(200); 45 | } 46 | } 47 | } 48 | 49 | // Publish arduino alive time every 5s, while connected to broker. 50 | uint32_t now = millis(); 51 | if ((now - lastPub) > 5000) { 52 | String aliveTimeMsg = String(now); 53 | client.publish(outTopic, aliveTimeMsg.c_str()); 54 | lastPub = now; 55 | } 56 | } 57 | 58 | // MQTT incoming message from broker callback 59 | void callback(char* topic, byte* payload, unsigned int length) { 60 | // Only proceed if incoming message's topic matches. 61 | // Toggle led if according to message character. 62 | if ((strcmp(topic, ledTopic) == 0) && (length == 1)) { 63 | if (payload[0] == '1') { 64 | digitalWrite(13, HIGH); 65 | } else if (payload[0] == '2') { 66 | digitalWrite(13, LOW); 67 | } 68 | } 69 | } 70 | 71 | boolean connectToBroker() { 72 | if (client.connect("arduinoClient")) { 73 | // Publish first message on connect and subscribe to Led controller. 74 | client.publish(outTopic, "Arduino connected!"); 75 | client.subscribe(ledTopic); 76 | return true; 77 | } else { 78 | return false; 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /examples/WebClient/WebClient.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Web client 3 | 4 | This sketch connects to a website (http://www.google.com) 5 | using the SerialToTCPBridgeProtocol. 6 | 7 | created 18 Dec 2009 8 | by David A. Mellis 9 | modified 26 Nov 2017 10 | by Roan Brand, to work over: 11 | https://github.com/RoanBrand/SerialToTCPBridgeProtocol 12 | 13 | */ 14 | 15 | #include 16 | 17 | // if you don't want to use DNS (and reduce your sketch size) 18 | // use the numeric IP instead of the name for the server: 19 | //IPAddress server(74,125,232,128); // numeric IP for Google (no DNS) 20 | char server[] = "www.google.com"; // name address for Google (using DNS) 21 | 22 | // Initialize the Protocol client library 23 | // with the IP address and port of the server 24 | // that you want to connect to (port 80 is default for HTTP): 25 | TCPOverSerialClient* client; 26 | 27 | void setup() { 28 | client = new TCPOverSerialClient(); 29 | 30 | // Open serial communications and wait for port to open: 31 | /*Serial.begin(9600); 32 | while (!Serial) { 33 | ; // wait for serial port to connect. Needed for native USB port only 34 | }*/ 35 | 36 | //delay(1000); 37 | //Serial.println("connecting..."); 38 | 39 | if (client->connect(server, 80)) { 40 | //Serial.println("connected"); 41 | // Make a HTTP request: 42 | client->println("GET /search?q=arduino HTTP/1.1"); 43 | client->println("Host: www.google.com"); 44 | client->println("Connection: close"); 45 | client->println(); 46 | } else { 47 | // if you didn't get a connection to the server: 48 | //Serial.println("connection failed"); 49 | } 50 | } 51 | 52 | void loop() { 53 | // if there are incoming bytes available 54 | // from the server, read them and print them: 55 | if (client->available()) { 56 | char c = client->read(); 57 | //Serial.print(c); 58 | } 59 | 60 | // if the server's disconnected, stop the client: 61 | if (!client->connected()) { 62 | //Serial.println(); 63 | //Serial.println("disconnecting."); 64 | client->stop(); 65 | 66 | // do nothing forevermore: 67 | while (true); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For Arduration 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | TCPOverSerialClient KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | TCPOverSerialClient KEYWORD2 16 | 17 | ####################################### 18 | # Constants (LITERAL1) 19 | ####################################### 20 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=TCP over Serial 2 | version=1.1.1 3 | author=Roan Brand 4 | maintainer=Roan Brand 5 | sentence=TCP over Serial client connection to a server from the Arduino, using the connected host. (No Ethernet/WiFi shields necessary) 6 | paragraph=Quickly communicate with other servers and make network apps using minimal hardware. 7 | category=Communication 8 | url=https://github.com/RoanBrand/ArduinoSerialToTCPBridgeClient 9 | architectures=* 10 | includes=TCP_over_Serial.h 11 | depends=CRC32 (=2.0.0), NeoHWSerial (=1.6.6), PubSubClient (>=2.8.0) 12 | -------------------------------------------------------------------------------- /src/TCP_over_Serial.cpp: -------------------------------------------------------------------------------- 1 | #include "TCP_over_Serial.h" 2 | #include 3 | #include 4 | 5 | static TCPOverSerialClient* ser0; 6 | 7 | void rxISR0(uint8_t c) { 8 | ser0->rxCallback(c); 9 | } 10 | 11 | void ackTimeout() { 12 | ser0->stopAckTimer(); 13 | ser0->tx_retries++; 14 | if (ser0->tx_retries >= 5) { 15 | ser0->stop(); 16 | return; 17 | } 18 | if (!ser0->writePacket(ser0->lastTx_cmd, ser0->lastTx_buf, ser0->lastTx_size)) { 19 | ser0->stop(); 20 | return; 21 | } 22 | ser0->startAckTimer(); 23 | } 24 | 25 | ISR(TIMER1_COMPA_vect) { 26 | ackTimeout(); 27 | } 28 | 29 | TCPOverSerialClient::TCPOverSerialClient() { 30 | reset(); 31 | ser0 = this; 32 | NeoSerial.attachInterrupt(rxISR0); 33 | NeoSerial.begin(115200); 34 | setupAckTimer(); 35 | } 36 | 37 | /* 38 | http://forum.arduino.cc/index.php?topic=315271.msg2183707#msg2183707 39 | 40 | SUCCESS 1 41 | FAILED 0 42 | TIMED_OUT -1 43 | INVALID_SERVER -2 44 | TRUNCATED -3 45 | INVALID_RESPONSE -4 46 | DOMAIN_NOT_FOUND -5 47 | */ 48 | int TCPOverSerialClient::connect(IPAddress ip, uint16_t port) { 49 | uint8_t destination[6] = { 50 | (uint8_t) ((uint32_t) ip), 51 | (uint8_t) ((uint32_t) ip >> 8), 52 | (uint8_t) ((uint32_t) ip >> 16), 53 | (uint8_t) ((uint32_t) ip >> 24), 54 | (uint8_t) port, 55 | (uint8_t) (port >> 8) 56 | }; 57 | 58 | if (!writePacket(PROTOCOL_CONNECT, destination, 6)) { 59 | return 0; 60 | } 61 | 62 | lastInAct = millis(); 63 | while (state != STATE_CONNECTED) { 64 | uint32_t now = millis(); 65 | if (now - lastInAct >= 5000) { 66 | return -1; 67 | } 68 | } 69 | 70 | lastInAct = millis(); 71 | return 1; 72 | } 73 | 74 | // Maximum host string of 248 bytes. 75 | int TCPOverSerialClient::connect(const char *host, uint16_t port) { 76 | uint8_t destination[250]; 77 | uint8_t len = 0; 78 | 79 | while (host[len] != '\0') { 80 | if (len == 248) { 81 | // I can't find out what TRUNCATED return type means. I just use it here. 82 | return -3; 83 | } 84 | destination[len] = host[len]; 85 | len++; 86 | } 87 | 88 | destination[len++] = (uint8_t) port; 89 | destination[len++] = (uint8_t) (port >> 8); 90 | 91 | if (!writePacket(PROTOCOL_CONNECT | 0x80, destination, len)) { 92 | return 0; 93 | } 94 | 95 | lastInAct = millis(); 96 | while (state != STATE_CONNECTED) { 97 | uint32_t now = millis(); 98 | if (now - lastInAct >= 5000) { 99 | return -1; 100 | } 101 | } 102 | 103 | lastInAct = millis(); 104 | return 1; 105 | } 106 | 107 | size_t TCPOverSerialClient::write(uint8_t b) { 108 | return write(&b, 1); 109 | } 110 | 111 | size_t TCPOverSerialClient::write(const uint8_t *buf, size_t size) { 112 | size_t written = 0; 113 | 114 | while (written < size) { 115 | size_t left = size - written; 116 | uint8_t toWrite = (left > 250) ? 250 : left; 117 | 118 | while(ackOutstanding && (state == STATE_CONNECTED)); 119 | if (state != STATE_CONNECTED) 120 | return 0; 121 | 122 | uint8_t cmd = PROTOCOL_PUBLISH; 123 | if (pubSequence) 124 | cmd |= 0x80; 125 | 126 | if (!writePacket(cmd, buf + written, toWrite)) 127 | return 0; 128 | 129 | lastTx_cmd = cmd; 130 | lastTx_buf = (uint8_t*) (buf + written); 131 | lastTx_size = toWrite; 132 | 133 | ackOutstanding = true; 134 | startAckTimer(); 135 | written += toWrite; 136 | } 137 | 138 | while(ackOutstanding && (state == STATE_CONNECTED)); 139 | if (state != STATE_CONNECTED) 140 | return 0; 141 | 142 | return size; 143 | } 144 | 145 | int TCPOverSerialClient::available() { 146 | if (rxBufisFull) 147 | return 256; 148 | 149 | if (rxBufpT >= rxBufpH) { 150 | return (int) (rxBufpT - rxBufpH); 151 | } else { 152 | return 256 - (int) (rxBufpH - rxBufpT); 153 | } 154 | } 155 | 156 | int TCPOverSerialClient::read() { 157 | if (available() == 0) 158 | return -1; 159 | 160 | uint8_t ch = rxBuf[rxBufpH++]; 161 | rxBufisFull = false; 162 | return ch; 163 | } 164 | 165 | int TCPOverSerialClient::read(uint8_t *buf, size_t size) { 166 | int have = available(); 167 | if (have == 0) 168 | return -1; 169 | 170 | int toRead = (size > have) ? have : size; 171 | 172 | for (size_t i = 0; i < toRead; i++) 173 | buf[i] = rxBuf[rxBufpH++]; 174 | 175 | rxBufisFull = false; 176 | // should we return rather when number of bytes requested is read? 177 | return toRead; 178 | } 179 | 180 | int TCPOverSerialClient::peek() { 181 | return rxBuf[rxBufpH]; 182 | } 183 | 184 | void TCPOverSerialClient::flush() { 185 | NeoSerial.flush(); 186 | } 187 | 188 | void TCPOverSerialClient::stop() { 189 | writePacket(PROTOCOL_DISCONNECT, NULL, 0); 190 | flush(); 191 | reset(); 192 | //NeoSerial.end(); 193 | } 194 | 195 | uint8_t TCPOverSerialClient::connected() { 196 | return (state == STATE_CONNECTED) ? 1 : 0; 197 | } 198 | 199 | TCPOverSerialClient::operator bool() { 200 | return 1; 201 | } 202 | 203 | void TCPOverSerialClient::reset() { 204 | stopAckTimer(); 205 | state = STATE_DISCONNECTED; 206 | ackOutstanding = false; 207 | expectedRxSeqFlag = false; 208 | pubSequence = false; 209 | tx_retries = 0; 210 | rxBufpH = 0; 211 | rxBufpT = 0; 212 | rxBufisFull = false; 213 | } 214 | 215 | boolean TCPOverSerialClient::writePacket(uint8_t command, uint8_t* payload, uint8_t pLength) { 216 | if (pLength > 250) 217 | return false; 218 | 219 | CRC32 crc; 220 | uint8_t h_length = pLength + 5; 221 | 222 | while(NeoSerial.availableForWrite() < 2); 223 | NeoSerial.write(h_length); 224 | crc.update(h_length); 225 | NeoSerial.write(command); 226 | crc.update(command); 227 | 228 | if (payload != NULL) { 229 | uint8_t written = 0; 230 | 231 | while(written < pLength) { 232 | int space = NeoSerial.availableForWrite(); 233 | 234 | if (space > 0) { 235 | uint8_t left = pLength - written; 236 | uint8_t toWrite = (left > space) ? space : left; 237 | 238 | for (uint8_t i = written; i < (written + toWrite); i++) { 239 | NeoSerial.write(payload[i]); 240 | crc.update(payload[i]); 241 | } 242 | written += toWrite; 243 | } 244 | } 245 | } 246 | 247 | uint32_t checksum = crc.finalize(); 248 | 249 | while(NeoSerial.availableForWrite() < 4); 250 | NeoSerial.write(checksum); 251 | NeoSerial.write(checksum >> 8); 252 | NeoSerial.write(checksum >> 16); 253 | NeoSerial.write(checksum >> 24); 254 | 255 | return true; 256 | } 257 | 258 | void TCPOverSerialClient::rxCallback(uint8_t c) { 259 | static uint16_t byteCount = 0; 260 | static uint8_t rxState = RX_PACKET_IDLE; 261 | 262 | static uint8_t newBufPtr; 263 | static uint8_t p_length; 264 | static uint8_t p_cmd; 265 | static uint32_t p_crc; 266 | static CRC32 crc; 267 | 268 | byteCount++; 269 | 270 | switch (rxState) { 271 | 272 | case RX_PACKET_IDLE: 273 | p_length = c; 274 | crc.reset(); 275 | crc.update(c); 276 | rxState = RX_PACKET_GOTLENGTH; 277 | break; 278 | 279 | case RX_PACKET_GOTLENGTH: 280 | p_cmd = c; 281 | p_crc = 0; 282 | crc.update(c); 283 | if (p_length > 5) { 284 | newBufPtr = rxBufpT; 285 | rxState = RX_PACKET_GOTCOMMAND; 286 | } else { 287 | rxState = RX_PACKET_GOTPAYLOAD; 288 | } 289 | break; 290 | 291 | case RX_PACKET_GOTCOMMAND: 292 | if (!rxBufisFull) 293 | rxBuf[newBufPtr++] = c; 294 | rxBufisFull = (rxBufpH == newBufPtr); 295 | crc.update(c); 296 | if (byteCount == (p_length - 3)) 297 | rxState = RX_PACKET_GOTPAYLOAD; 298 | break; 299 | 300 | case RX_PACKET_GOTPAYLOAD: 301 | // Integrity checking. 302 | p_crc |= (uint32_t)c << (8 * (byteCount - p_length + 2)); 303 | 304 | if (byteCount == (uint16_t)p_length + 1) { 305 | uint32_t crcCode = crc.finalize(); 306 | byteCount = 0; 307 | rxState = RX_PACKET_IDLE; 308 | 309 | // Received packet valid. 310 | if (p_crc == crcCode) { 311 | boolean rxSeqFlag = (p_cmd & 0x80) > 0; 312 | 313 | switch (p_cmd & 0x7F) { 314 | // Connection established with destination. 315 | case PROTOCOL_CONNACK: 316 | if (p_length == 5) 317 | state = STATE_CONNECTED; 318 | break; 319 | // Upstream tcp connection closed. 320 | case PROTOCOL_DISCONNECT: 321 | if (p_length == 5 && (state == STATE_CONNECTED)) 322 | reset(); 323 | break; 324 | // Incoming data. 325 | case PROTOCOL_PUBLISH: 326 | writePacket(PROTOCOL_ACK | (p_cmd & 0x80), NULL, 0); 327 | if (rxSeqFlag == expectedRxSeqFlag) { 328 | expectedRxSeqFlag = !expectedRxSeqFlag; 329 | rxBufpT = newBufPtr; 330 | } 331 | break; 332 | // Protocol Acknowledge. 333 | case PROTOCOL_ACK: 334 | if (ackOutstanding && (rxSeqFlag == pubSequence)) { 335 | stopAckTimer(); 336 | pubSequence = !pubSequence; 337 | tx_retries = 0; 338 | ackOutstanding = false; 339 | } 340 | break; 341 | } 342 | } 343 | } 344 | break; 345 | } 346 | } 347 | 348 | // http://www.engblaze.com/microcontroller-tutorial-avr-and-arduino-timer-interrupts/ 349 | void TCPOverSerialClient::setupAckTimer() { 350 | cli(); 351 | TCCR1A = 0; 352 | TCCR1B = 0; 353 | OCR1A = 12499; // 200ms 354 | TCCR1B |= (1 << WGM12); // ctc mode 355 | TIMSK1 |= (1 << OCIE1A); 356 | sei(); 357 | } 358 | 359 | void TCPOverSerialClient::startAckTimer() { 360 | TCCR1B |= (1 << CS12); // 256 prescaler 361 | } 362 | 363 | void TCPOverSerialClient::stopAckTimer() { 364 | TCCR1B &= 0xF8; // remove clk source 365 | TCNT1 = 0; // reset counter 366 | } 367 | -------------------------------------------------------------------------------- /src/TCP_over_Serial.h: -------------------------------------------------------------------------------- 1 | #ifndef tcpoverserial_H 2 | #define tcpoverserial_H 3 | 4 | #include "Arduino.h" 5 | #include "Client.h" 6 | 7 | // Protocol Packet Headers 8 | #define PROTOCOL_CONNECT 0 9 | #define PROTOCOL_CONNACK 1 10 | #define PROTOCOL_DISCONNECT 2 11 | #define PROTOCOL_PUBLISH 3 12 | #define PROTOCOL_ACK 4 13 | 14 | // Protocol Link State 15 | #define STATE_DISCONNECTED 0 16 | #define STATE_CONNECTED 1 17 | 18 | // Protocol Packet RX State 19 | #define RX_PACKET_IDLE 0 20 | #define RX_PACKET_GOTLENGTH 1 21 | #define RX_PACKET_GOTCOMMAND 2 22 | #define RX_PACKET_GOTPAYLOAD 3 23 | 24 | class TCPOverSerialClient : public Client { 25 | public: 26 | TCPOverSerialClient(); 27 | 28 | virtual int connect(IPAddress ip, uint16_t port); 29 | virtual int connect(const char *host, uint16_t port); 30 | virtual size_t write(uint8_t b); 31 | virtual size_t write(const uint8_t *buf, size_t size); 32 | virtual int available(); 33 | virtual int read(); 34 | virtual int read(uint8_t *buf, size_t size); 35 | virtual int peek(); 36 | virtual void flush(); 37 | virtual void stop(); 38 | virtual uint8_t connected(); 39 | virtual operator bool(); 40 | 41 | private: 42 | volatile uint8_t state; 43 | volatile boolean expectedRxSeqFlag; 44 | volatile boolean pubSequence; 45 | volatile boolean ackOutstanding; 46 | volatile uint8_t tx_retries; 47 | uint32_t lastInAct; 48 | uint8_t lastTx_cmd; 49 | uint8_t *lastTx_buf; 50 | uint8_t lastTx_size; 51 | 52 | // RX buffer 53 | uint8_t rxBuf[256]; 54 | uint8_t rxBufpH, rxBufpT; 55 | boolean rxBufisFull; 56 | 57 | void reset(); 58 | boolean writePacket(uint8_t command, uint8_t* payload, uint8_t pLength); 59 | void rxCallback(uint8_t c); 60 | void setupAckTimer(); 61 | void startAckTimer(); 62 | void stopAckTimer(); 63 | 64 | friend void rxISR0(uint8_t c); 65 | friend void ackTimeout(); 66 | }; 67 | 68 | #endif 69 | --------------------------------------------------------------------------------