Skip to content

Commit 7ca80af

Browse files
committed
Initial Release
1 parent 479f955 commit 7ca80af

File tree

6 files changed

+359
-2
lines changed

6 files changed

+359
-2
lines changed

README.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,32 @@
1-
# ArduinoSerialToTCPBridgeClient
2-
Arduino client for the Serial To TCP Bridge Protocol PC side service
1+
# Arduino Serial to TCP Bridge Client
2+
Arduino client for the [Serial To TCP Bridge Protocol](https://github.com/RoanBrand/SerialToTCPBridgeProtocol) gateway service.
3+
4+
Open a TCP connection to a server from the Arduino using just serial. (No shields or network HW)
5+
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 the Arduino is connected to serially.
6+
7+
## Dependencies
8+
- [NeoHWSerial](https://github.com/SlashDevin/NeoHWSerial) - Install manually
9+
- [CRC32](https://github.com/bakercp/CRC32) - Can be installed from the Arduino *Library Manager*
10+
11+
After installing the dependencies and this library, make sure you have all 3 present inside your **libraries** sub-directory of your sketchbook directory.
12+
13+
## How to
14+
- Get the [Protocol Gateway](https://github.com/RoanBrand/SerialToTCPBridgeProtocol) and build it.
15+
- Change the gateway's config to listen on the COM port connected to your Arduino and start it.
16+
- Your Arduino app can then use the `ArduinoSerialToTCPBridgeClient` API which is similar to the `EthernetClient` API to make tcp connections to servers as long as they are reachable from the host and the gateway is running.
17+
18+
19+
## MQTT Client Example
20+
- Install [PubSubClient](https://github.com/knolleary/pubsubclient) - Manually, or from the Arduino *Library Manager*.
21+
- Get a MQTT Broker running on your host, listening at least on `localhost`. I used [HiveMQ](www.hivemq.com).
22+
- Run the **Protocol Gateway** on the same host, on the right COM port.
23+
- Open and load the example from the Arduino *Examples menu*.
24+
- When the Arduino is connected to the MQTT broker, it will publish a message and subscribe to the `led` topic.
25+
- You can use another MQTT client like [MQTT.fx](http://mqttfx.jfx4ee.org) to publish characters `0` and `1` to the topic `led` to toggle the led on and off on the Arduino board.
26+
27+
### Details
28+
- 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.
29+
- 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.
30+
- `ArduinoSerialToTCPBridgeClient` is derived from the standard Arduino `Client` class. This means existing code written for Ethernet/Wi-Fi shields should work with this.
31+
- `NeoHWSerial` is an alternative for `Serial`, used to gain access to the AVR UART Interrupts.
32+
- 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.

examples/MQTTClient/MQTTClient.ino

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Basic example of MQTT Client running over the Serial To TCP Bridge Client.
3+
* The Arduino makes a connection to a Protocol Gateway instance on the PC that
4+
* is listening on the COM port that is connected to the Serial-over-USB of the Arduino.
5+
* The MQTT Client connects to a Broker running on localhost on the host PC.
6+
*/
7+
8+
#include <ArduinoSerialToTCPBridgeClient.h>
9+
#include <PubSubClient.h>
10+
11+
ArduinoSerialToTCPBridgeClient* s; // Protocol Client running over USB Serial
12+
PubSubClient* client; // MQTT Client
13+
const char* ledTopic = "led";
14+
15+
void setup() {
16+
pinMode(13, OUTPUT);
17+
s = new ArduinoSerialToTCPBridgeClient();
18+
client = new PubSubClient(*s);
19+
// MQTT Broker running on same PC the Arduino is connected to.
20+
client->setServer(IPAddress(127,0,0,1), 1883);
21+
client->setCallback(callback);
22+
if (client->connect("arduinoClient")) {
23+
client->publish("outTopic", "Hello world!");
24+
client->subscribe(ledTopic);
25+
}
26+
}
27+
28+
void loop() {
29+
client->loop();
30+
}
31+
32+
void callback(char* topic, byte* payload, unsigned int length) {
33+
// Only proceed if incoming message's topic matches.
34+
if ((strcmp(topic, ledTopic) == 0) && (length == 1)) {
35+
if (payload[0] == 0x31) {
36+
digitalWrite(13, HIGH); // Turn on led if MQTT message '1'
37+
} else if (payload[0] == 0x32) {
38+
digitalWrite(13, LOW); // Turn off led if MQTT message '2'
39+
}
40+
}
41+
}
42+

keywords.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#######################################
2+
# Syntax Coloring Map For Arduration
3+
#######################################
4+
5+
#######################################
6+
# Datatypes (KEYWORD1)
7+
#######################################
8+
9+
ArduinoSerialToTCPBridgeClient KEYWORD1
10+
11+
#######################################
12+
# Methods and Functions (KEYWORD2)
13+
#######################################
14+
15+
ArduinoSerialToTCPBridgeClient KEYWORD2
16+
17+
#######################################
18+
# Constants (LITERAL1)
19+
#######################################

library.properties

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name=ArduinoSerialToTCPBridgeClient
2+
version=1.0.0
3+
author=Roan Brand <brandroan@gmail.com>
4+
maintainer=Roan Brand <brandroan@gmail.com>
5+
sentence=Open a TCP connection to a server from the Arduino using just serial. (No shields or network HW)
6+
paragraph=INSTALL DEPENDENCIES! The Protocol Gateway service runs on the host, listens on a COM port connected to the Arduino, and opens TCP connections on behalf of the Protocol Client runnning on the Arduino, forwarding traffic bi-directionally. The protocol provides the app an in order, duplicates free and error checked byte stream by adding a CRC32 and simple retry mechanism.
7+
category=Communication
8+
url=https://github.com/RoanBrand/ArduinoSerialToTCPBridgeClient
9+
architectures=*
10+
includes=ArduinoSerialToTCPBridgeClient.h
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#include "ArduinoSerialToTCPBridgeClient.h"
2+
#include <NeoHWSerial.h>
3+
#include <CRC32.h>
4+
5+
static ArduinoSerialToTCPBridgeClient* ser0;
6+
7+
void rxISR0(uint8_t c) {
8+
ser0->rxCallback(c);
9+
}
10+
11+
void ArduinoSerialToTCPBridgeClient::rxCallback(uint8_t c) {
12+
static uint8_t packetCount = 0;
13+
static uint8_t rxState = RX_PACKET_IDLE;
14+
rxBuffer[packetCount++] = c;
15+
switch (rxState) {
16+
case RX_PACKET_IDLE:
17+
rxState = RX_PACKET_GOTLENGTH;
18+
break;
19+
case RX_PACKET_GOTLENGTH:
20+
rxState = RX_PACKET_GOTCOMMAND;
21+
break;
22+
case RX_PACKET_GOTCOMMAND:
23+
uint8_t packetLength = rxBuffer[0];
24+
if (packetCount == packetLength + 1) {
25+
packetCount = 0;
26+
// Integrity checking
27+
uint32_t crcRx = (uint32_t) rxBuffer[packetLength - 3] | ((uint32_t) rxBuffer[packetLength - 2] << 8)
28+
| ((uint32_t) rxBuffer[packetLength - 1] << 16) | ((uint32_t) rxBuffer[packetLength] << 24);
29+
uint32_t crcCode = CRC32::checksum(rxBuffer, packetLength - 3);
30+
if (crcRx == crcCode) { // validate packet
31+
boolean rxSeqFlag = (rxBuffer[1] & 0x80) > 0;
32+
switch (rxBuffer[1] & 0x7F) {
33+
// Connection established with destination
34+
case PROTOCOL_CONNACK:
35+
if (rxBuffer[0] == 5) {
36+
state = STATE_CONNECTED;
37+
}
38+
break;
39+
// Incoming data
40+
case PROTOCOL_PUBLISH:
41+
writePacket(PROTOCOL_ACK | (rxBuffer[1] & 0x80), NULL, 0);
42+
if (rxSeqFlag == expectedRxSeqFlag) {
43+
expectedRxSeqFlag = !expectedRxSeqFlag;
44+
if (rxBuffer[0] > 5) {
45+
for (uint8_t i = 0; i < rxBuffer[0] - 5; i++) {
46+
readBuf[readBufpT++] = rxBuffer[2 + i];
47+
}
48+
readBufisFull = (readBufpH == readBufpT);
49+
}
50+
}
51+
break;
52+
// Protocol Acknowledge
53+
case PROTOCOL_ACK:
54+
if (ackOutstanding) {
55+
if (rxSeqFlag == expectedAckSeq) {
56+
ackOutstanding = false;
57+
}
58+
}
59+
break;
60+
}
61+
}
62+
rxState = RX_PACKET_IDLE;
63+
}
64+
break;
65+
}
66+
}
67+
68+
boolean ArduinoSerialToTCPBridgeClient::writePacket(uint8_t command, uint8_t* payload, uint8_t pLength) {
69+
workBuffer[0] = pLength + 5;
70+
workBuffer[1] = command;
71+
if (payload != NULL) {
72+
for (uint8_t i = 2; i < pLength + 2; i++) {
73+
workBuffer[i] = payload[i - 2];
74+
}
75+
}
76+
uint32_t crcCode = CRC32::checksum(workBuffer, pLength + 2);
77+
workBuffer[pLength + 2] = crcCode & 0x000000FF;
78+
workBuffer[pLength + 3] = (crcCode & 0x0000FF00) >> 8;
79+
workBuffer[pLength + 4] = (crcCode & 0x00FF0000) >> 16;
80+
workBuffer[pLength + 5] = (crcCode & 0xFF000000) >> 24;
81+
if ((int) (pLength) + 6 > NeoSerial.availableForWrite()) {
82+
return false;
83+
}
84+
for (int i = 0; i < pLength + 6; i++) {
85+
NeoSerial.write(workBuffer[i]);
86+
}
87+
return true;
88+
}
89+
90+
ArduinoSerialToTCPBridgeClient::ArduinoSerialToTCPBridgeClient() {
91+
ackOutstanding = false;
92+
expectedAckSeq = false;
93+
expectedRxSeqFlag = false;
94+
readBufpH = 0;
95+
readBufpT = 0;
96+
readBufisFull = false;
97+
state = STATE_DISCONNECTED;
98+
NeoSerial.attachInterrupt(rxISR0);
99+
NeoSerial.begin(115200);
100+
ser0 = this;
101+
}
102+
103+
int ArduinoSerialToTCPBridgeClient::connect(IPAddress ip, uint16_t port) {
104+
uint8_t destination[6] = {
105+
(uint8_t) ((uint32_t) ip),
106+
(uint8_t) ((uint32_t) ip >> 8),
107+
(uint8_t) ((uint32_t) ip >> 16),
108+
(uint8_t) ((uint32_t) ip >> 24),
109+
(uint8_t) port,
110+
(uint8_t) (port >> 8)
111+
};
112+
writePacket(PROTOCOL_CONNECT, destination, 6);
113+
lastInAct = millis();
114+
while (state != STATE_CONNECTED) {
115+
uint32_t now = millis();
116+
if (now - lastInAct >= 5000) {
117+
return -1;
118+
}
119+
}
120+
lastInAct = millis();
121+
return 1;
122+
/*
123+
SUCCESS 1
124+
TIMED_OUT -1
125+
INVALID_SERVER -2
126+
TRUNCATED -3
127+
INVALID_RESPONSE -4
128+
*/
129+
}
130+
131+
int ArduinoSerialToTCPBridgeClient::connect(const char *host, uint16_t port) {
132+
133+
}
134+
135+
size_t ArduinoSerialToTCPBridgeClient::write(uint8_t) {
136+
137+
}
138+
139+
size_t ArduinoSerialToTCPBridgeClient::write(const uint8_t *buf, size_t size) {
140+
static bool pubSequence = false;
141+
uint8_t cmd = PROTOCOL_PUBLISH;
142+
if (pubSequence) {
143+
cmd |= 0x80;
144+
}
145+
pubSequence = !pubSequence;
146+
if (!writePacket(cmd, (uint8_t*) buf, size)) {
147+
return 0;
148+
}
149+
ackOutstanding = true;
150+
return size;
151+
}
152+
153+
int ArduinoSerialToTCPBridgeClient::available() {
154+
if (readBufisFull) {
155+
return 256;
156+
}
157+
if (readBufpT >= readBufpH) {
158+
return (int) (readBufpT - readBufpH);
159+
} else {
160+
return 256 - (int) (readBufpH - readBufpT);
161+
}
162+
}
163+
164+
int ArduinoSerialToTCPBridgeClient::read() {
165+
if (!available()) {
166+
return -1;
167+
}
168+
uint8_t ch = readBuf[readBufpH++];
169+
readBufisFull = false;
170+
return ch;
171+
}
172+
173+
int ArduinoSerialToTCPBridgeClient::read(uint8_t *buf, size_t size) {
174+
175+
}
176+
177+
int ArduinoSerialToTCPBridgeClient::peek() {
178+
return readBuf[readBufpH];
179+
}
180+
181+
void ArduinoSerialToTCPBridgeClient::flush() {
182+
NeoSerial.flush();
183+
}
184+
185+
void ArduinoSerialToTCPBridgeClient::stop() {
186+
187+
}
188+
189+
uint8_t ArduinoSerialToTCPBridgeClient::connected() {
190+
if (state == STATE_CONNECTED) {
191+
return 1;
192+
}
193+
return 0;
194+
}
195+
196+
ArduinoSerialToTCPBridgeClient::operator bool() {
197+
return 1;
198+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#ifndef arduinoserialtotcpbridgeclient_H
2+
#define arduinoserialtotcpbridgeclient_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+
23+
class ArduinoSerialToTCPBridgeClient : public Client {
24+
25+
public:
26+
ArduinoSerialToTCPBridgeClient();
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);
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+
void rxCallback(uint8_t c);
43+
boolean writePacket(uint8_t command, uint8_t* payload, uint8_t pLength);
44+
uint8_t state;
45+
boolean expectedRxSeqFlag;
46+
boolean expectedAckSeq;
47+
boolean ackOutstanding;
48+
uint32_t lastInAct;
49+
uint8_t workBuffer[256];
50+
uint8_t rxBuffer[256];
51+
uint8_t readBufpH, readBufpT;
52+
boolean readBufisFull;
53+
uint8_t readBuf[256];
54+
55+
friend void rxISR0(uint8_t c);
56+
};
57+
58+
#endif

0 commit comments

Comments
 (0)