|
|
|
@ -1,4 +1,8 @@ |
|
|
|
|
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
|
|
|
|
|
//
|
|
|
|
|
// TCP client library allowing uControllers attached to the serial port to send commands
|
|
|
|
|
// to open/close TCP connections and send/recv data.
|
|
|
|
|
// The serial protocol is described in https://gist.github.com/tve/a46c44bf1f6b42bc572e
|
|
|
|
|
|
|
|
|
|
#include <esp8266.h> |
|
|
|
|
#include "config.h" |
|
|
|
@ -7,7 +11,7 @@ |
|
|
|
|
#include "tcpclient.h" |
|
|
|
|
|
|
|
|
|
// max number of channels the client can open
|
|
|
|
|
#define MAX_CHAN 8 |
|
|
|
|
#define MAX_CHAN MAX_TCP_CHAN |
|
|
|
|
// size of tx buffer
|
|
|
|
|
#define MAX_TXBUF 1024 |
|
|
|
|
|
|
|
|
@ -28,8 +32,54 @@ typedef struct { |
|
|
|
|
enum TcpState state; |
|
|
|
|
} TcpConn; |
|
|
|
|
|
|
|
|
|
#define MAX_CONN (8) |
|
|
|
|
static TcpConn tcpConn[MAX_CONN]; |
|
|
|
|
static TcpConn tcpConn[MAX_CHAN]; |
|
|
|
|
|
|
|
|
|
// forward declarations
|
|
|
|
|
static void tcpConnFree(TcpConn* tci); |
|
|
|
|
static TcpConn* tcpConnAlloc(uint8_t chan); |
|
|
|
|
static void tcpDoSend(TcpConn *tci); |
|
|
|
|
static void tcpConnectCb(void *arg); |
|
|
|
|
static void tcpDisconCb(void *arg); |
|
|
|
|
static void tcpResetCb(void *arg, sint8 err); |
|
|
|
|
static void tcpSentCb(void *arg); |
|
|
|
|
static void tcpRecvCb(void *arg, char *data, uint16_t len); |
|
|
|
|
|
|
|
|
|
//===== allocate / free connections
|
|
|
|
|
|
|
|
|
|
// Allocate a new connection dynamically and return it. Returns NULL if buf alloc failed
|
|
|
|
|
static TcpConn* ICACHE_FLASH_ATTR |
|
|
|
|
tcpConnAlloc(uint8_t chan) { |
|
|
|
|
TcpConn *tci = tcpConn+chan; |
|
|
|
|
if (tci->state != TCP_idle && tci->conn != NULL) return tci; |
|
|
|
|
|
|
|
|
|
// malloc and return espconn struct
|
|
|
|
|
tci->conn = os_malloc(sizeof(struct espconn)); |
|
|
|
|
if (tci->conn == NULL) goto fail; |
|
|
|
|
memset(tci->conn, 0, sizeof(struct espconn)); |
|
|
|
|
// malloc esp_tcp struct
|
|
|
|
|
tci->tcp = os_malloc(sizeof(esp_tcp)); |
|
|
|
|
if (tci->tcp == NULL) goto fail; |
|
|
|
|
memset(tci->tcp, 0, sizeof(esp_tcp)); |
|
|
|
|
|
|
|
|
|
// common init
|
|
|
|
|
tci->state = TCP_dns; |
|
|
|
|
tci->conn->type = ESPCONN_TCP; |
|
|
|
|
tci->conn->state = ESPCONN_NONE; |
|
|
|
|
tci->conn->proto.tcp = tci->tcp; |
|
|
|
|
tci->tcp->remote_port = 80; |
|
|
|
|
espconn_regist_connectcb(tci->conn, tcpConnectCb); |
|
|
|
|
espconn_regist_reconcb(tci->conn, tcpResetCb); |
|
|
|
|
espconn_regist_sentcb(tci->conn, tcpSentCb); |
|
|
|
|
espconn_regist_recvcb(tci->conn, tcpRecvCb); |
|
|
|
|
espconn_regist_disconcb(tci->conn, tcpDisconCb); |
|
|
|
|
tci->conn->reverse = tci; |
|
|
|
|
|
|
|
|
|
return tci; |
|
|
|
|
|
|
|
|
|
fail: |
|
|
|
|
tcpConnFree(tci); |
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Free a connection dynamically.
|
|
|
|
|
static void ICACHE_FLASH_ATTR |
|
|
|
@ -41,6 +91,8 @@ tcpConnFree(TcpConn* tci) { |
|
|
|
|
memset(tci, 0, sizeof(TcpConn)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//===== DNS
|
|
|
|
|
|
|
|
|
|
// DNS name resolution callback
|
|
|
|
|
static void ICACHE_FLASH_ATTR |
|
|
|
|
tcpClientHostnameCb(const char *name, ip_addr_t *ipaddr, void *arg) { |
|
|
|
@ -66,12 +118,59 @@ tcpClientHostnameCb(const char *name, ip_addr_t *ipaddr, void *arg) { |
|
|
|
|
tcpConnFree(tci); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//===== Connect / disconnect
|
|
|
|
|
|
|
|
|
|
// Connected callback
|
|
|
|
|
static void ICACHE_FLASH_ATTR |
|
|
|
|
tcpConnectCb(void *arg) { |
|
|
|
|
struct espconn *conn = arg; |
|
|
|
|
TcpConn *tci = conn->reverse; |
|
|
|
|
os_printf("TCP connect CB (%p %p)\n", arg, tci); |
|
|
|
|
tci->state = TCP_data; |
|
|
|
|
// send any buffered data
|
|
|
|
|
if (tci->txBuf != NULL && tci->txBufLen > 0) tcpDoSend(tci); |
|
|
|
|
// reply to serial
|
|
|
|
|
char buf[6]; |
|
|
|
|
short l = os_sprintf(buf, "\n~@%dC\n", tci-tcpConn); |
|
|
|
|
uart0_tx_buffer(buf, l); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Disconnect callback
|
|
|
|
|
static void ICACHE_FLASH_ATTR tcpDisconCb(void *arg) { |
|
|
|
|
struct espconn *conn = arg; |
|
|
|
|
TcpConn *tci = conn->reverse; |
|
|
|
|
os_printf("TCP disconnect CB (%p %p)\n", arg, tci); |
|
|
|
|
// notify to serial
|
|
|
|
|
char buf[6]; |
|
|
|
|
short l = os_sprintf(buf, "\n~@%dZ\n", tci-tcpConn); |
|
|
|
|
uart0_tx_buffer(buf, l); |
|
|
|
|
// free
|
|
|
|
|
tcpConnFree(tci); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Connection reset callback
|
|
|
|
|
static void ICACHE_FLASH_ATTR tcpResetCb(void *arg, sint8 err) { |
|
|
|
|
struct espconn *conn = arg; |
|
|
|
|
TcpConn *tci = conn->reverse; |
|
|
|
|
os_printf("TCP reset CB (%p %p) err=%d\n", arg, tci, err); |
|
|
|
|
// notify to serial
|
|
|
|
|
char buf[6]; |
|
|
|
|
short l = os_sprintf(buf, "\n~@%dZ\n", tci-tcpConn); |
|
|
|
|
uart0_tx_buffer(buf, l); |
|
|
|
|
// free
|
|
|
|
|
tcpConnFree(tci); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//===== Sending and receiving
|
|
|
|
|
|
|
|
|
|
// Send the next buffer (assumes that the connection is in a state that allows it)
|
|
|
|
|
static void tcpDoSend(TcpConn *tci) { |
|
|
|
|
static void ICACHE_FLASH_ATTR |
|
|
|
|
tcpDoSend(TcpConn *tci) { |
|
|
|
|
sint8 err = espconn_sent(tci->conn, (uint8*)tci->txBuf, tci->txBufLen); |
|
|
|
|
if (err == ESPCONN_OK) { |
|
|
|
|
// send successful
|
|
|
|
|
os_printf("TCP sent (%p %p)\n", tci->conn, tci); |
|
|
|
|
tci->txBuf[tci->txBufLen] = 0; os_printf("TCP data: %s\n", tci->txBuf); |
|
|
|
|
tci->txBufSent = tci->txBuf; |
|
|
|
|
tci->txBuf = NULL; |
|
|
|
|
tci->txBufLen = 0; |
|
|
|
@ -81,18 +180,9 @@ static void tcpDoSend(TcpConn *tci) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Connected callback
|
|
|
|
|
static void ICACHE_FLASH_ATTR tcpConnectCb(void *arg) { |
|
|
|
|
struct espconn *conn = arg; |
|
|
|
|
TcpConn *tci = conn->reverse; |
|
|
|
|
os_printf("TCP connect CB (%p %p)\n", arg, tci); |
|
|
|
|
tci->state = TCP_data; |
|
|
|
|
// send any buffered data
|
|
|
|
|
if (tci->txBuf != NULL && tci->txBufLen > 0) tcpDoSend(tci); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Sent callback
|
|
|
|
|
static void ICACHE_FLASH_ATTR tcpSentCb(void *arg) { |
|
|
|
|
static void ICACHE_FLASH_ATTR |
|
|
|
|
tcpSentCb(void *arg) { |
|
|
|
|
struct espconn *conn = arg; |
|
|
|
|
TcpConn *tci = conn->reverse; |
|
|
|
|
os_printf("TCP sent CB (%p %p)\n", arg, tci); |
|
|
|
@ -111,84 +201,71 @@ static void ICACHE_FLASH_ATTR tcpRecvCb(void *arg, char *data, uint16_t len) { |
|
|
|
|
TcpConn *tci = conn->reverse; |
|
|
|
|
os_printf("TCP recv CB (%p %p)\n", arg, tci); |
|
|
|
|
if (tci->state == TCP_data) { |
|
|
|
|
uint8_t chan; |
|
|
|
|
for (chan=0; chan<MAX_CHAN && tcpConn+chan!=tci; chan++) |
|
|
|
|
if (chan >= MAX_CHAN) return; // oops!?
|
|
|
|
|
char buf[6]; |
|
|
|
|
short l = os_sprintf(buf, "\n~%d", chan); |
|
|
|
|
uart0_tx_buffer(buf, l); |
|
|
|
|
uart0_tx_buffer(data, len); |
|
|
|
|
uart0_tx_buffer("\0\n", 2); |
|
|
|
|
} |
|
|
|
|
serledFlash(50); // short blink on serial LED
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Disconnect callback
|
|
|
|
|
static void ICACHE_FLASH_ATTR tcpDisconCb(void *arg) { |
|
|
|
|
struct espconn *conn = arg; |
|
|
|
|
TcpConn *tci = conn->reverse; |
|
|
|
|
os_printf("TCP disconnect CB (%p %p)\n", arg, tci); |
|
|
|
|
tcpConnFree(tci); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Connection reset callback
|
|
|
|
|
static void ICACHE_FLASH_ATTR tcpResetCb(void *arg, sint8 err) { |
|
|
|
|
struct espconn *conn = arg; |
|
|
|
|
TcpConn *tci = conn->reverse; |
|
|
|
|
os_printf("TCP reset CB (%p %p) err=%d\n", arg, tci, err); |
|
|
|
|
tcpConnFree(tci); |
|
|
|
|
} |
|
|
|
|
void ICACHE_FLASH_ATTR |
|
|
|
|
tcpClientSendChar(uint8_t chan, char c) { |
|
|
|
|
TcpConn *tci = tcpConn+chan; |
|
|
|
|
if (tci->state == TCP_idle) return; |
|
|
|
|
|
|
|
|
|
// Allocate a new connection dynamically and return it. Returns NULL if no
|
|
|
|
|
// connection could be allocated.
|
|
|
|
|
static TcpConn* ICACHE_FLASH_ATTR |
|
|
|
|
tcpConnAlloc(void) { |
|
|
|
|
int i; |
|
|
|
|
for (i=0; i<MAX_CONN; i++) { |
|
|
|
|
if (tcpConn[i].state == TCP_idle && tcpConn[i].conn == NULL) break; |
|
|
|
|
if (tci->txBuf != NULL) { |
|
|
|
|
// we have a buffer
|
|
|
|
|
if (tci->txBufLen < MAX_TXBUF) { |
|
|
|
|
// buffer has space, add char and return
|
|
|
|
|
tci->txBuf[tci->txBufLen++] = c; |
|
|
|
|
return; |
|
|
|
|
} else if (tci->txBufSent == NULL) { |
|
|
|
|
// we don't have a send pending, send full buffer off
|
|
|
|
|
if (tci->state == TCP_data) tcpDoSend(tci); |
|
|
|
|
if (tci->txBuf != NULL) return; // something went wrong
|
|
|
|
|
} else { |
|
|
|
|
// buffers all backed-up, drop char
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (i == MAX_CONN) return NULL; |
|
|
|
|
|
|
|
|
|
// found an empty slot, malloc and return espconn struct
|
|
|
|
|
TcpConn *tci = tcpConn+i; |
|
|
|
|
tci->conn = os_malloc(sizeof(struct espconn)); |
|
|
|
|
if (tci->conn == NULL) goto fail; |
|
|
|
|
memset(tci->conn, 0, sizeof(struct espconn)); |
|
|
|
|
// malloc esp_tcp struct
|
|
|
|
|
tci->tcp = os_malloc(sizeof(esp_tcp)); |
|
|
|
|
if (tci->tcp == NULL) goto fail; |
|
|
|
|
memset(tci->tcp, 0, sizeof(esp_tcp)); |
|
|
|
|
|
|
|
|
|
// common init
|
|
|
|
|
tci->state = TCP_dns; |
|
|
|
|
tci->conn->type = ESPCONN_TCP; |
|
|
|
|
tci->conn->state = ESPCONN_NONE; |
|
|
|
|
tci->conn->proto.tcp = tci->tcp; |
|
|
|
|
tci->tcp->remote_port = 80; |
|
|
|
|
tci->tcp->remote_ip[0] = 173; |
|
|
|
|
espconn_regist_connectcb(tci->conn, tcpConnectCb); |
|
|
|
|
espconn_regist_reconcb(tci->conn, tcpResetCb); |
|
|
|
|
espconn_regist_sentcb(tci->conn, tcpSentCb); |
|
|
|
|
espconn_regist_recvcb(tci->conn, tcpRecvCb); |
|
|
|
|
espconn_regist_disconcb(tci->conn, tcpDisconCb); |
|
|
|
|
tci->conn->reverse = tci; |
|
|
|
|
|
|
|
|
|
return tci; |
|
|
|
|
// we do not have a buffer (either didn't have one or sent it off)
|
|
|
|
|
// allocate one
|
|
|
|
|
tci->txBuf = os_malloc(MAX_TXBUF); |
|
|
|
|
tci->txBufLen = 0; |
|
|
|
|
if (tci->txBuf != NULL) { |
|
|
|
|
tci->txBuf[tci->txBufLen++] = c; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fail: |
|
|
|
|
tcpConnFree(tci); |
|
|
|
|
return NULL; |
|
|
|
|
void ICACHE_FLASH_ATTR |
|
|
|
|
tcpClientSendPush(uint8_t chan) { |
|
|
|
|
TcpConn *tci = tcpConn+chan; |
|
|
|
|
if (tci->state != TCP_data) return; // no active connection on this channel
|
|
|
|
|
if (tci->txBuf == NULL || tci->txBufLen == 0) return; // no chars accumulated to send
|
|
|
|
|
if (tci->txBufSent != NULL) return; // already got a send in progress
|
|
|
|
|
tcpDoSend(tci); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static TcpConn *tcConn[MAX_CHAN]; |
|
|
|
|
//===== Command parsing
|
|
|
|
|
|
|
|
|
|
// Perform a TCP command: parse the command and do the right thing.
|
|
|
|
|
// Returns true on success.
|
|
|
|
|
bool ICACHE_FLASH_ATTR |
|
|
|
|
tcpClientCommand(char cmd, char *cmdBuf) { |
|
|
|
|
uint8_t tcChan; |
|
|
|
|
tcpClientCommand(uint8_t chan, char cmd, char *cmdBuf) { |
|
|
|
|
TcpConn *tci; |
|
|
|
|
char *hostname; |
|
|
|
|
char *port; |
|
|
|
|
|
|
|
|
|
switch (cmd) { |
|
|
|
|
//== TCP Connect command
|
|
|
|
|
case 'T': |
|
|
|
|
if (*cmdBuf < '1' || *cmdBuf > ('0'+MAX_CHAN)) break; |
|
|
|
|
tcChan = *cmdBuf++ - '1'; |
|
|
|
|
char *hostname = cmdBuf; |
|
|
|
|
char *port = hostname; |
|
|
|
|
hostname = cmdBuf; |
|
|
|
|
port = hostname; |
|
|
|
|
while (*port != 0 && *port != ':') port++; |
|
|
|
|
if (*port != ':') break; |
|
|
|
|
*port = 0; |
|
|
|
@ -197,13 +274,13 @@ tcpClientCommand(char cmd, char *cmdBuf) { |
|
|
|
|
if (portInt < 1 || portInt > 65535) break; |
|
|
|
|
|
|
|
|
|
// allocate a connection
|
|
|
|
|
tci = tcpConnAlloc(); |
|
|
|
|
tci = tcpConnAlloc(chan); |
|
|
|
|
if (tci == NULL) break; |
|
|
|
|
tci->state = TCP_dns; |
|
|
|
|
tci->tcp->remote_port = portInt; |
|
|
|
|
|
|
|
|
|
// start the DNS resolution
|
|
|
|
|
os_printf("TCP %p resolving %s (conn=%p)\n", tci, hostname, tci->conn); |
|
|
|
|
os_printf("TCP %p resolving %s for chan %d (conn=%p)\n", tci, hostname, chan ,tci->conn); |
|
|
|
|
ip_addr_t ip; |
|
|
|
|
err_t err = espconn_gethostbyname(tci->conn, hostname, &ip, tcpClientHostnameCb); |
|
|
|
|
if (err == ESPCONN_OK) { |
|
|
|
@ -215,14 +292,12 @@ tcpClientCommand(char cmd, char *cmdBuf) { |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
tcConn[tcChan] = tci; |
|
|
|
|
return true; |
|
|
|
|
|
|
|
|
|
//== TCP Close/disconnect command
|
|
|
|
|
case 'C': |
|
|
|
|
if (*cmdBuf < '1' || *cmdBuf > ('0'+MAX_CHAN)) break; |
|
|
|
|
tcChan = *cmdBuf++ - '1'; |
|
|
|
|
tci = tcConn[tcChan]; |
|
|
|
|
os_printf("TCP closing chan %d\n", chan); |
|
|
|
|
tci = tcpConn+chan; |
|
|
|
|
if (tci->state > TCP_idle) { |
|
|
|
|
tci->state = TCP_idle; // hackish...
|
|
|
|
|
espconn_disconnect(tci->conn); |
|
|
|
@ -233,40 +308,3 @@ tcpClientCommand(char cmd, char *cmdBuf) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void ICACHE_FLASH_ATTR |
|
|
|
|
tcpClientSendChar(uint8_t chan, char c) { |
|
|
|
|
TcpConn *tci = tcConn[chan]; |
|
|
|
|
if (tci->state == TCP_idle) return; |
|
|
|
|
|
|
|
|
|
if (tci->txBuf != NULL) { |
|
|
|
|
// we have a buffer
|
|
|
|
|
if (tci->txBufLen < MAX_TXBUF) { |
|
|
|
|
// buffer has space, add char and return
|
|
|
|
|
tci->txBuf[tci->txBufLen++] = c; |
|
|
|
|
return; |
|
|
|
|
} else if (tci->txBufSent == NULL) { |
|
|
|
|
// we don't have a send pending, send full buffer off
|
|
|
|
|
tcpDoSend(tci); |
|
|
|
|
if (tci->txBuf != NULL) return; // something went wrong
|
|
|
|
|
} else { |
|
|
|
|
// buffers all backed-up, drop char
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// we do not have a buffer (either didn't have one or sent it off)
|
|
|
|
|
// allocate one
|
|
|
|
|
tci->txBuf = os_malloc(MAX_TXBUF); |
|
|
|
|
tci->txBufLen = 0; |
|
|
|
|
if (tci->txBuf != NULL) { |
|
|
|
|
tci->txBuf[tci->txBufLen++] = c; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void ICACHE_FLASH_ATTR |
|
|
|
|
tcpClientSendPush(uint8_t chan) { |
|
|
|
|
TcpConn *tci = tcConn[chan]; |
|
|
|
|
if (tci->state == TCP_idle) return; // no active connection on this channel
|
|
|
|
|
if (tci->txBuf == NULL || tci->txBufLen == 0) return; // no chars accumulated to send
|
|
|
|
|
if (tci->txBufSent != NULL) return; // already got a send in progress
|
|
|
|
|
tcpDoSend(tci); |
|
|
|
|
} |
|
|
|
|