TCP client support; Grovestreams RSSI submission support

pull/33/head
Thorsten von Eicken 9 years ago
parent 886ce62df0
commit 2fbd9953d5
  1. 46
      html/home.html
  2. 39
      html/ui.js
  3. 6
      httpd/httpd.c
  4. 66
      serial/serbridge.c
  5. 270
      serial/tcpclient.c
  6. 10
      serial/tcpclient.h
  7. 3
      user/cgipins.c
  8. 74
      user/cgitcp.c
  9. 8
      user/cgitcp.h
  10. 13
      user/config.c
  11. 3
      user/config.h
  12. 102
      user/status.c
  13. 7
      user/user_main.c

@ -26,7 +26,8 @@
</div></div> </div></div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2"><div class="card"> <div class="pure-u-1 pure-u-md-1-2">
<div class="card">
<h1>Wifi summary</h1> <h1>Wifi summary</h1>
<div id="wifi-spinner" class="spinner spinner-small"></div> <div id="wifi-spinner" class="spinner spinner-small"></div>
<table id="wifi-table" class="pure-table pure-table-horizontal" hidden><tbody> <table id="wifi-table" class="pure-table pure-table-horizontal" hidden><tbody>
@ -35,37 +36,40 @@
<tr><td>Wifi status</td><td id="wifi-status"></td></tr> <tr><td>Wifi status</td><td id="wifi-status"></td></tr>
<tr><td>Wifi address</td><td id="wifi-ip"></td></tr> <tr><td>Wifi address</td><td id="wifi-ip"></td></tr>
</tbody> </table> </tbody> </table>
</div></div>
<div class="pure-u-1 pure-u-md-1-2"><div class="card">
<h1>Pin assignment</h1>
<legend>Select one of the following signal/pin assignments to match your hardware</legend>
<fieldset class='radios' id='pin-mux'>
<div class="spinner spinner-small"></div>
</fieldset>
</div></div>
</div> </div>
<div class="pure-g"> <div class="card">
<div class="pure-u-1 pure-u-md-1-2"><div class="card"> <h1>TCP client</h1>
<h1>HTTP service</h1> <form action="#" id="tcpform" class="pure-form">
<form action="#" id="httpform" class="pure-form"> <legend>TCP client support in esp-link</legend>
<legend>External web service configuration</legend>
<div class="form-horizontal"> <div class="form-horizontal">
<label>Enable serial port HTTP</label> <input type="checkbox" name="tcp_enable"/>
<input type="checkbox" name="http_enable"/> <label>Enable serial port TCP client</label>
</div>
<br>
<legend>Grovestreams data push</legend>
<div class="form-horizontal">
<input type="checkbox" name="rssi_enable"/>
<label>Send RSSI</label> <label>Send RSSI</label>
<input type="checkbox" name="send_rssi"/>
</div> </div>
<div class="pure-form-stacked"> <div class="pure-form-stacked">
<label>Server hostname</label>
<input type="text" name="hostname"/>
<label>API key/passwd</label> <label>API key/passwd</label>
<input type="password" name="api_key"/> <input type="password" name="api_key"/>
</div> </div>
<button id="special-button" type="submit" <button id="tcp-button" type="submit"
class="pure-button button-primary">Change!</button> class="pure-button button-primary">Change!</button>
</form> </form>
</div>
</div>
<div class="pure-u-1 pure-u-md-1-2"><div class="card">
<h1>Pin assignment</h1>
<legend>Select one of the following signal/pin assignments to match your hardware</legend>
<fieldset class='radios' id='pin-mux'>
<div class="spinner spinner-small"></div>
</fieldset>
</div></div> </div></div>
</div> </div>
<div class="pure-g">
</div>
</div> </div>
</div> </div>
</div> </div>
@ -74,6 +78,8 @@
onLoad(function() { onLoad(function() {
fetchPins(); fetchPins();
getWifiInfo(); getWifiInfo();
fetchTcpClient();
bnd($("#tcpform"), "submit", changeTcpClient);
}); });
</script> </script>
</body></html> </body></html>

@ -356,3 +356,42 @@ function setPins(v, name) {
}); });
} }
//===== TCP client card
function tcpEn(){return document.querySelector('input[name="tcp_enable"]')}
function rssiEn(){return document.querySelector('input[name="rssi_enable"]')}
function apiKey(){return document.querySelector('input[name="api_key"]')}
function changeTcpClient(e) {
e.preventDefault();
var url = "tcpclient";
url += "?tcp_enable=" + tcpEn().checked;
url += "&rssi_enable=" + rssiEn().checked;
url += "&api_key=" + encodeURIComponent(apiKey().value);
hideWarning();
var cb = $("#tcp-button");
addClass(cb, 'pure-button-disabled');
ajaxSpin("POST", url, function(resp) {
removeClass(cb, 'pure-button-disabled');
getWifiInfo();
}, function(s, st) {
showWarning("Error: "+st);
removeClass(cb, 'pure-button-disabled');
getWifiInfo();
});
}
function displayTcpClient(resp) {
tcpEn().checked = resp.tcp_enable > 0;
rssiEn().checked = resp.rssi_enable > 0;
apiKey().value = resp.api_key;
}
function fetchTcpClient() {
ajaxJson("GET", "/tcpclient", displayTcpClient, function() {
window.setTimeout(fetchTcpClient, 1000);
});
}

@ -314,13 +314,13 @@ static void ICACHE_FLASH_ATTR httpdSentCb(void *arg) {
conn->cgi=NULL; //mark for destruction. conn->cgi=NULL; //mark for destruction.
} }
if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) { if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) {
os_printf("%s ERROR! CGI fn returns code %d after sending data! Bad CGI!\n", connStr, r); os_printf("%s ERROR! Bad CGI code %d\n", connStr, r);
conn->cgi=NULL; //mark for destruction. conn->cgi=NULL; //mark for destruction.
} }
xmitSendBuff(conn); xmitSendBuff(conn);
} }
static const char *httpNotFoundHeader="HTTP/1.0 404 Not Found\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nNot Found.\r\n"; static const char *httpNotFoundHeader="HTTP/1.0 404 Not Found\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nNot Found.\r\n";
//This is called when the headers have been received and the connection is ready to send //This is called when the headers have been received and the connection is ready to send
//the result headers and data. //the result headers and data.
@ -419,7 +419,7 @@ static void ICACHE_FLASH_ATTR httpdParseHeader(char *h, HttpdConnData *conn) {
if (conn->getArgs!=0) { if (conn->getArgs!=0) {
*conn->getArgs=0; *conn->getArgs=0;
conn->getArgs++; conn->getArgs++;
os_printf("%s GET args = %s\n", connStr, conn->getArgs); os_printf("%s args = %s\n", connStr, conn->getArgs);
} else { } else {
conn->getArgs=NULL; conn->getArgs=NULL;
} }

@ -263,9 +263,13 @@ enum {
TC_idle, // in-between commands TC_idle, // in-between commands
TC_newline, // newline seen TC_newline, // newline seen
TC_start, // start character (~) seen TC_start, // start character (~) seen
TC_cmd, // command character (0) seen TC_cmd, // command start (@) seen
TC_cmdChar, // command character seen
TC_cmdLine, // accumulating command TC_cmdLine, // accumulating command
TC_tdata, // data character (1-9) seen, and in text data mode TC_tdchan, // saw data channel character
TC_tdlen1, // saw first data length character
TC_tdata0, // accumulate data, zero-terminated
TC_tdataN, // accumulate data, length-terminated
}; };
static uint8_t tcState = TC_newline; static uint8_t tcState = TC_newline;
static uint8_t tcChan; // channel for current command (index into tcConn) static uint8_t tcChan; // channel for current command (index into tcConn)
@ -274,6 +278,7 @@ static uint8_t tcChan; // channel for current command (index into tcConn)
static char tcCmdBuf[CMD_MAX]; static char tcCmdBuf[CMD_MAX];
static short tcCmdBufLen = 0; static short tcCmdBufLen = 0;
static char tcCmdChar; static char tcCmdChar;
static short tcLen;
// scan a buffer for tcp client commands // scan a buffer for tcp client commands
static int ICACHE_FLASH_ATTR static int ICACHE_FLASH_ATTR
@ -291,17 +296,27 @@ tcpClientProcess(char *buf, int len)
if (c == '~') tcState = TC_start; if (c == '~') tcState = TC_start;
continue; // gobble up the ~ continue; // gobble up the ~
case TC_start: // saw ~, expect channel number case TC_start: // saw ~, expect channel number
if (c == '0') { if (c == '@') {
tcState = TC_cmd; tcState = TC_cmd;
continue; continue;
} else if (c >= '1' && c <= '9') { } else if (c >= '0' && c <= '9') {
tcChan = c-'1'; tcChan = c-'0';
tcState = TC_tdata; tcState = TC_tdchan;
continue; continue;
} }
*out++ = '~'; // make up for '~' we skipped *out++ = '~'; // make up for '~' we skipped
break; break;
case TC_cmd: // saw channel number 0 (command), expect command char case TC_cmd: // saw control char (@), expect channel char
if (c >= '0' && c <= '9') {
tcChan = c-'0';
tcState = TC_cmdChar;
continue;
} else {
*out++ = '~'; // make up for '~' we skipped
*out++ = '@'; // make up for '@' we skipped
break;
}
case TC_cmdChar: // saw channel number, expect command char
tcCmdChar = c; // save command character tcCmdChar = c; // save command character
tcCmdBufLen = 0; // empty the command buffer tcCmdBufLen = 0; // empty the command buffer
tcState = TC_cmdLine; tcState = TC_cmdLine;
@ -310,11 +325,36 @@ tcpClientProcess(char *buf, int len)
if (c != '\n') { if (c != '\n') {
if (tcCmdBufLen < CMD_MAX) tcCmdBuf[tcCmdBufLen++] = c; if (tcCmdBufLen < CMD_MAX) tcCmdBuf[tcCmdBufLen++] = c;
} else { } else {
tcpClientCommand(tcCmdChar, tcCmdBuf); tcpClientCommand(tcChan, tcCmdChar, tcCmdBuf);
tcState = TC_newline; tcState = TC_newline;
} }
continue; continue;
case TC_tdata: // saw channel number, getting data characters case TC_tdchan: // saw channel number, getting first length char
if (c >= '0' && c <= '9') {
tcLen = c-'0';
} else if (c >= 'A' && c <= 'F') {
tcLen = c-'A'+10;
} else {
*out++ = '~'; // make up for '~' we skipped
*out++ = '0'+tcChan;
break;
}
tcState = TC_tdlen1;
continue;
case TC_tdlen1: // saw first length char, get second
tcLen *= 16;
if (c >= '0' && c <= '9') {
tcLen += c-'0';
} else if (c >= 'A' && c <= 'F') {
tcLen += c-'A'+10;
} else {
*out++ = '~'; // make up for '~' we skipped
*out++ = '0'+tcChan;
break;
}
tcState = tcLen == 0 ? TC_tdata0 : TC_tdataN;
continue;
case TC_tdata0: // saw data length, getting data characters zero-terminated
if (c != 0) { if (c != 0) {
tcpClientSendChar(tcChan, c); tcpClientSendChar(tcChan, c);
} else { } else {
@ -322,6 +362,14 @@ tcpClientProcess(char *buf, int len)
tcState = TC_idle; tcState = TC_idle;
} }
continue; continue;
case TC_tdataN: // saw data length, getting data characters length-terminated
tcpClientSendChar(tcChan, c);
tcLen--;
if (tcLen == 0) {
tcpClientSendPush(tcChan);
tcState = TC_idle;
}
continue;
} }
*out++ = c; *out++ = c;
} }

@ -1,4 +1,8 @@
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt // 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 <esp8266.h>
#include "config.h" #include "config.h"
@ -7,7 +11,7 @@
#include "tcpclient.h" #include "tcpclient.h"
// max number of channels the client can open // max number of channels the client can open
#define MAX_CHAN 8 #define MAX_CHAN MAX_TCP_CHAN
// size of tx buffer // size of tx buffer
#define MAX_TXBUF 1024 #define MAX_TXBUF 1024
@ -28,8 +32,54 @@ typedef struct {
enum TcpState state; enum TcpState state;
} TcpConn; } TcpConn;
#define MAX_CONN (8) static TcpConn tcpConn[MAX_CHAN];
static TcpConn tcpConn[MAX_CONN];
// 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. // Free a connection dynamically.
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
@ -41,6 +91,8 @@ tcpConnFree(TcpConn* tci) {
memset(tci, 0, sizeof(TcpConn)); memset(tci, 0, sizeof(TcpConn));
} }
//===== DNS
// DNS name resolution callback // DNS name resolution callback
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
tcpClientHostnameCb(const char *name, ip_addr_t *ipaddr, void *arg) { 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); 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) // 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); sint8 err = espconn_sent(tci->conn, (uint8*)tci->txBuf, tci->txBufLen);
if (err == ESPCONN_OK) { if (err == ESPCONN_OK) {
// send successful // send successful
os_printf("TCP sent (%p %p)\n", tci->conn, tci); 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->txBufSent = tci->txBuf;
tci->txBuf = NULL; tci->txBuf = NULL;
tci->txBufLen = 0; 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 // Sent callback
static void ICACHE_FLASH_ATTR tcpSentCb(void *arg) { static void ICACHE_FLASH_ATTR
tcpSentCb(void *arg) {
struct espconn *conn = arg; struct espconn *conn = arg;
TcpConn *tci = conn->reverse; TcpConn *tci = conn->reverse;
os_printf("TCP sent CB (%p %p)\n", arg, tci); 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; TcpConn *tci = conn->reverse;
os_printf("TCP recv CB (%p %p)\n", arg, tci); os_printf("TCP recv CB (%p %p)\n", arg, tci);
if (tci->state == TCP_data) { 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(data, len);
uart0_tx_buffer("\0\n", 2);
} }
serledFlash(50); // short blink on serial LED serledFlash(50); // short blink on serial LED
} }
// Disconnect callback void ICACHE_FLASH_ATTR
static void ICACHE_FLASH_ATTR tcpDisconCb(void *arg) { tcpClientSendChar(uint8_t chan, char c) {
struct espconn *conn = arg; TcpConn *tci = tcpConn+chan;
TcpConn *tci = conn->reverse; if (tci->state == TCP_idle) return;
os_printf("TCP disconnect CB (%p %p)\n", arg, tci);
tcpConnFree(tci);
}
// Connection reset callback if (tci->txBuf != NULL) {
static void ICACHE_FLASH_ATTR tcpResetCb(void *arg, sint8 err) { // we have a buffer
struct espconn *conn = arg; if (tci->txBufLen < MAX_TXBUF) {
TcpConn *tci = conn->reverse; // buffer has space, add char and return
os_printf("TCP reset CB (%p %p) err=%d\n", arg, tci, err); tci->txBuf[tci->txBufLen++] = c;
tcpConnFree(tci); 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;
}
}
// 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;
} }
// 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 (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;
fail: void ICACHE_FLASH_ATTR
tcpConnFree(tci); tcpClientSendPush(uint8_t chan) {
return NULL; 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. // Perform a TCP command: parse the command and do the right thing.
// Returns true on success. // Returns true on success.
bool ICACHE_FLASH_ATTR bool ICACHE_FLASH_ATTR
tcpClientCommand(char cmd, char *cmdBuf) { tcpClientCommand(uint8_t chan, char cmd, char *cmdBuf) {
uint8_t tcChan;
TcpConn *tci; TcpConn *tci;
char *hostname;
char *port;
switch (cmd) { switch (cmd) {
//== TCP Connect command //== TCP Connect command
case 'T': case 'T':
if (*cmdBuf < '1' || *cmdBuf > ('0'+MAX_CHAN)) break; hostname = cmdBuf;
tcChan = *cmdBuf++ - '1'; port = hostname;
char *hostname = cmdBuf;
char *port = hostname;
while (*port != 0 && *port != ':') port++; while (*port != 0 && *port != ':') port++;
if (*port != ':') break; if (*port != ':') break;
*port = 0; *port = 0;
@ -197,13 +274,13 @@ tcpClientCommand(char cmd, char *cmdBuf) {
if (portInt < 1 || portInt > 65535) break; if (portInt < 1 || portInt > 65535) break;
// allocate a connection // allocate a connection
tci = tcpConnAlloc(); tci = tcpConnAlloc(chan);
if (tci == NULL) break; if (tci == NULL) break;
tci->state = TCP_dns; tci->state = TCP_dns;
tci->tcp->remote_port = portInt; tci->tcp->remote_port = portInt;
// start the DNS resolution // 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; ip_addr_t ip;
err_t err = espconn_gethostbyname(tci->conn, hostname, &ip, tcpClientHostnameCb); err_t err = espconn_gethostbyname(tci->conn, hostname, &ip, tcpClientHostnameCb);
if (err == ESPCONN_OK) { if (err == ESPCONN_OK) {
@ -215,14 +292,12 @@ tcpClientCommand(char cmd, char *cmdBuf) {
break; break;
} }
tcConn[tcChan] = tci;
return true; return true;
//== TCP Close/disconnect command //== TCP Close/disconnect command
case 'C': case 'C':
if (*cmdBuf < '1' || *cmdBuf > ('0'+MAX_CHAN)) break; os_printf("TCP closing chan %d\n", chan);
tcChan = *cmdBuf++ - '1'; tci = tcpConn+chan;
tci = tcConn[tcChan];
if (tci->state > TCP_idle) { if (tci->state > TCP_idle) {
tci->state = TCP_idle; // hackish... tci->state = TCP_idle; // hackish...
espconn_disconnect(tci->conn); espconn_disconnect(tci->conn);
@ -233,40 +308,3 @@ tcpClientCommand(char cmd, char *cmdBuf) {
return false; 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);
}

@ -1,8 +1,16 @@
#ifndef __TCP_CLIENT_H__ #ifndef __TCP_CLIENT_H__
#define __TCP_CLIENT_H__ #define __TCP_CLIENT_H__
bool tcpClientCommand(char cmd, char *cmdBuf); // max number of channels the client can open
#define MAX_TCP_CHAN 8
// Parse and perform the commandm cmdBuf must be null-terminated
bool tcpClientCommand(uint8_t chan, char cmd, char *cmdBuf);
// Append a character to the specified channel
void tcpClientSendChar(uint8_t chan, char c); void tcpClientSendChar(uint8_t chan, char c);
// Enqueue the buffered characters for transmission on the specified channel
void tcpClientSendPush(uint8_t chan); void tcpClientSendPush(uint8_t chan);
#endif /* __TCP_CLIENT_H__ */ #endif /* __TCP_CLIENT_H__ */

@ -1,3 +1,4 @@
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
#include <esp8266.h> #include <esp8266.h>
#include "cgi.h" #include "cgi.h"
@ -93,11 +94,9 @@ int ICACHE_FLASH_ATTR cgiPinsSet(HttpdConnData *connData) {
statusInit(); statusInit();
if (configSave()) { if (configSave()) {
os_printf("New config saved\n");
httpdStartResponse(connData, 200); httpdStartResponse(connData, 200);
httpdEndHeaders(connData); httpdEndHeaders(connData);
} else { } else {
os_printf("*** Failed to save config ***\n");
httpdStartResponse(connData, 500); httpdStartResponse(connData, 500);
httpdEndHeaders(connData); httpdEndHeaders(connData);
httpdSend(connData, "Failed to save config", -1); httpdSend(connData, "Failed to save config", -1);

@ -0,0 +1,74 @@
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
// // TCP Client settings
#include <esp8266.h>
#include "cgi.h"
#include "config.h"
#include "cgitcp.h"
// Cgi to return TCP client settings
int ICACHE_FLASH_ATTR cgiTcpGet(HttpdConnData *connData) {
char buff[1024];
int len;
if (connData->conn==NULL) return HTTPD_CGI_DONE;
len = os_sprintf(buff, "{ \"tcp_enable\":%d, \"rssi_enable\": %d, \"api_key\":\"%s\" }",
flashConfig.tcp_enable, flashConfig.rssi_enable, flashConfig.api_key);
jsonHeader(connData, 200);
httpdSend(connData, buff, len);
return HTTPD_CGI_DONE;
}
// Cgi to change choice of pin assignments
int ICACHE_FLASH_ATTR cgiTcpSet(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE;
// Handle tcp_enable flag
char buff[128];
int len = httpdFindArg(connData->getArgs, "tcp_enable", buff, sizeof(buff));
if (len <= 0) {
jsonHeader(connData, 400);
return HTTPD_CGI_DONE;
}
flashConfig.tcp_enable = os_strcmp(buff, "true") == 0;
// Handle rssi_enable flag
len = httpdFindArg(connData->getArgs, "rssi_enable", buff, sizeof(buff));
if (len <= 0) {
jsonHeader(connData, 400);
return HTTPD_CGI_DONE;
}
flashConfig.rssi_enable = os_strcmp(buff, "true") == 0;
// Handle api_key flag
len = httpdFindArg(connData->getArgs, "api_key", buff, sizeof(buff));
if (len < 0) {
jsonHeader(connData, 400);
return HTTPD_CGI_DONE;
}
buff[sizeof(flashConfig.api_key)-1] = 0; // ensure we don't get an overrun
os_strcpy(flashConfig.api_key, buff);
if (configSave()) {
httpdStartResponse(connData, 200);
httpdEndHeaders(connData);
} else {
httpdStartResponse(connData, 500);
httpdEndHeaders(connData);
httpdSend(connData, "Failed to save config", -1);
}
return HTTPD_CGI_DONE;
}
int ICACHE_FLASH_ATTR cgiTcp(HttpdConnData *connData) {
if (connData->requestType == HTTPD_METHOD_GET) {
return cgiTcpGet(connData);
} else if (connData->requestType == HTTPD_METHOD_POST) {
return cgiTcpSet(connData);
} else {
jsonHeader(connData, 404);
return HTTPD_CGI_DONE;
}
}

@ -0,0 +1,8 @@
#ifndef CGITCP_H
#define CGITCP_H
#include "httpd.h"
int cgiTcp(HttpdConnData *connData);
#endif

@ -17,6 +17,9 @@ FlashConfig flashDefault = {
"esp-link\0 ", // hostname "esp-link\0 ", // hostname
0, 0x00ffffff, 0, // static ip, netmask, gateway 0, 0x00ffffff, 0, // static ip, netmask, gateway
0, // log mode 0, // log mode
0, // swap_uart
1, 0, // tcp_enable, rssi_enable
"\0", // api_key
}; };
typedef union { typedef union {
@ -40,7 +43,6 @@ static void memDump(void *addr, int len) {
#endif #endif
bool ICACHE_FLASH_ATTR configSave(void) { bool ICACHE_FLASH_ATTR configSave(void) {
FlashFull ff; FlashFull ff;
memset(&ff, 0, sizeof(ff)); memset(&ff, 0, sizeof(ff));
memcpy(&ff, &flashConfig, sizeof(FlashConfig)); memcpy(&ff, &flashConfig, sizeof(FlashConfig));
@ -48,7 +50,7 @@ bool ICACHE_FLASH_ATTR configSave(void) {
// erase secondary // erase secondary
uint32_t addr = FLASH_ADDR + (1-flash_pri)*FLASH_SECT; uint32_t addr = FLASH_ADDR + (1-flash_pri)*FLASH_SECT;
if (spi_flash_erase_sector(addr>>12) != SPI_FLASH_RESULT_OK) if (spi_flash_erase_sector(addr>>12) != SPI_FLASH_RESULT_OK)
return false; // no harm done, give up goto fail; // no harm done, give up
// calculate CRC // calculate CRC
ff.fc.seq = seq; ff.fc.seq = seq;
ff.fc.magic = FLASH_MAGIC; ff.fc.magic = FLASH_MAGIC;
@ -60,11 +62,11 @@ bool ICACHE_FLASH_ATTR configSave(void) {
// write primary with incorrect seq // write primary with incorrect seq
ff.fc.seq = 0xffffffff; ff.fc.seq = 0xffffffff;
if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK) if (spi_flash_write(addr, (void *)&ff, sizeof(ff)) != SPI_FLASH_RESULT_OK)
return false; // no harm done, give up goto fail; // no harm done, give up
// fill in correct seq // fill in correct seq
ff.fc.seq = seq; ff.fc.seq = seq;
if (spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)) != SPI_FLASH_RESULT_OK) if (spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)) != SPI_FLASH_RESULT_OK)
return false; // most likely failed, but no harm if successful goto fail; // most likely failed, but no harm if successful
// now that we have safely written the new version, erase old primary // now that we have safely written the new version, erase old primary
addr = FLASH_ADDR + flash_pri*FLASH_SECT; addr = FLASH_ADDR + flash_pri*FLASH_SECT;
flash_pri = 1-flash_pri; flash_pri = 1-flash_pri;
@ -77,6 +79,9 @@ bool ICACHE_FLASH_ATTR configSave(void) {
ff.fc.seq = seq; ff.fc.seq = seq;
spi_flash_write(addr, (void *)&ff, sizeof(uint32_t)); spi_flash_write(addr, (void *)&ff, sizeof(uint32_t));
return true; return true;
fail:
os_printf("*** Failed to save config ***\n");
return false;
} }
void ICACHE_FLASH_ATTR configWipe(void) { void ICACHE_FLASH_ATTR configWipe(void) {

@ -9,6 +9,9 @@ typedef struct {
char hostname[32]; // if using DHCP char hostname[32]; // if using DHCP
uint32_t staticip, netmask, gateway; // using DHCP if staticip==0 uint32_t staticip, netmask, gateway; // using DHCP if staticip==0
uint8_t log_mode; // UART log debug mode uint8_t log_mode; // UART log debug mode
uint8_t swap_uart; // swap uart0 to gpio 13&15
uint8_t tcp_enable, rssi_enable; // TCP client settings
char api_key[48]; // RSSI submission API key (Grovestreams for now)
} FlashConfig; } FlashConfig;
extern FlashConfig flashConfig; extern FlashConfig flashConfig;

@ -4,6 +4,7 @@
#include "config.h" #include "config.h"
#include "serled.h" #include "serled.h"
#include "cgiwifi.h" #include "cgiwifi.h"
#include "tcpclient.h"
//===== "CONN" LED status indication //===== "CONN" LED status indication
@ -72,101 +73,44 @@ void ICACHE_FLASH_ATTR statusWifiUpdate(uint8_t state) {
static ETSTimer rssiTimer; static ETSTimer rssiTimer;
static uint8_t rssiSendState = 0;
static sint8 rssiLast = 0; //last RSSI value
static esp_tcp rssiTcp;
static struct espconn rssiConn;
#define GS_API_KEY "2eb868a8-224f-3faa-939d-c79bd605912a"
#define GS_COMP_ID "esp-link"
#define GS_STREAM "rssi" #define GS_STREAM "rssi"
// Connected callback // Timer callback to send an RSSI update to a monitoring system
static void ICACHE_FLASH_ATTR rssiConnectCb(void *arg) { static void ICACHE_FLASH_ATTR rssiTimerCb(void *v) {
struct espconn *conn = (struct espconn *)arg; if (!flashConfig.rssi_enable || !flashConfig.tcp_enable || flashConfig.api_key[0]==0)
os_printf("RSSI connect CB (%p %p)\n", arg, conn->reverse); return;
sint8 rssi = wifi_station_get_rssi();
if (rssi >= 0) return; // not connected or other error
char buf[2000]; // compose TCP command
uint8_t chan = MAX_TCP_CHAN-1;
tcpClientCommand(chan, 'T', "grovestreams.com:80");
// http header // compose http header
char buf[1024];
int hdrLen = os_sprintf(buf, int hdrLen = os_sprintf(buf,
"PUT /api/feed?api_key=%s HTTP/1.0\r\n" "PUT /api/feed?api_key=%s HTTP/1.0\r\n"
"Content-Type: application/json\r\n" "Content-Type: application/json\r\n"
"Content-Length: XXXXX\r\n\r\n", "Content-Length: XXXXX\r\n\r\n",
GS_API_KEY); flashConfig.api_key);
// http body // http body
int dataLen = os_sprintf(buf+hdrLen, int dataLen = os_sprintf(buf+hdrLen,
"[{\"compId\":\"%s\", \"streamId\":\"%s\", \"data\":%d}]", "[{\"compId\":\"%s\", \"streamId\":\"%s\", \"data\":%d}]\r",
GS_COMP_ID, GS_STREAM, rssiLast); flashConfig.hostname, GS_STREAM, rssi);
buf[hdrLen+dataLen++] = 0;
buf[hdrLen+dataLen++] = '\r';
// hackish way to fill in the content-length // hackish way to fill in the content-length
os_sprintf(buf+hdrLen-9, "%5d", dataLen); os_sprintf(buf+hdrLen-9, "%5d", dataLen);
buf[hdrLen-4] = '\r'; buf[hdrLen-4] = '\r'; // fix-up the \0 inserted by sprintf (hack!)
// send it off
if (espconn_sent(conn, (uint8*)buf, hdrLen+dataLen) == ESPCONN_OK) {
os_printf("RSSI sent rssi=%d\n", rssiLast);
os_printf("RSSI sent <<%s>>\n", buf);
}
}
// Sent callback
static void ICACHE_FLASH_ATTR rssiSentCb(void *arg) {
struct espconn *conn = (struct espconn *)arg;
os_printf("RSSI sent CB (%p %p)\n", arg, conn->reverse);
}
// Recv callback // send the request off and forget about it...
static void ICACHE_FLASH_ATTR rssiRecvCb(void *arg, char *data, uint16_t len) { for (short i=0; i<hdrLen+dataLen; i++) {
struct espconn *conn = (struct espconn *)arg; tcpClientSendChar(chan, buf[i]);
os_printf("RSSI recv CB (%p %p)\n", arg, conn->reverse);
data[len] = 0; // hack!!!
os_printf("GOT %d: <<%s>>\n", len, data);
espconn_disconnect(conn);
}
// Disconnect callback
static void ICACHE_FLASH_ATTR rssiDisconCb(void *arg) {
struct espconn *conn = (struct espconn *)arg;
os_printf("RSSI disconnect CB (%p %p)\n", arg, conn->reverse);
rssiSendState = 0;
}
// Connection reset callback
static void ICACHE_FLASH_ATTR rssiResetCb(void *arg, sint8 err) {
struct espconn *conn = (struct espconn *)arg;
os_printf("RSSI reset CB (%p %p) err=%d\n", arg, conn->reverse, err);
rssiSendState = 0;
}
// Timer callback to send an RSSI update to a monitoring system
static void ICACHE_FLASH_ATTR rssiTimerCb(void *v) {
sint8 rssi = wifi_station_get_rssi();
if (rssi >= 0) return; // not connected or other error
rssiLast = rssi;
if (rssiSendState > 0) return; // not done with previous rssi report
rssiConn.type = ESPCONN_TCP;
rssiConn.state = ESPCONN_NONE;
rssiConn.proto.tcp = &rssiTcp;
rssiTcp.remote_port = 80;
rssiTcp.remote_ip[0] = 173;
rssiTcp.remote_ip[1] = 236;
rssiTcp.remote_ip[2] = 12;
rssiTcp.remote_ip[3] = 163;
espconn_regist_connectcb(&rssiConn, rssiConnectCb);
espconn_regist_reconcb(&rssiConn, rssiResetCb);
espconn_regist_sentcb(&rssiConn, rssiSentCb);
espconn_regist_recvcb(&rssiConn, rssiRecvCb);
espconn_regist_disconcb(&rssiConn, rssiDisconCb);
rssiConn.reverse = (void *)0xdeadf00d;
os_printf("RSSI connect (%p)\n", &rssiConn);
if (espconn_connect(&rssiConn) == ESPCONN_OK) {
rssiSendState++;
} }
tcpClientSendPush(chan);
} }
//===== Init status stuff //===== Init status stuff

@ -16,6 +16,7 @@
#include "cgi.h" #include "cgi.h"
#include "cgiwifi.h" #include "cgiwifi.h"
#include "cgipins.h" #include "cgipins.h"
#include "cgitcp.h"
#include "cgiflash.h" #include "cgiflash.h"
#include "auth.h" #include "auth.h"
#include "espfs.h" #include "espfs.h"
@ -88,6 +89,7 @@ HttpdBuiltInUrl builtInUrls[]={
{"/wifi/setmode", cgiWiFiSetMode, NULL}, {"/wifi/setmode", cgiWiFiSetMode, NULL},
{"/wifi/special", cgiWiFiSpecial, NULL}, {"/wifi/special", cgiWiFiSpecial, NULL},
{"/pins", cgiPins, NULL}, {"/pins", cgiPins, NULL},
{"/tcpclient", cgiTcp, NULL},
{"*", cgiEspFsHook, NULL}, //Catch-all cgi function for the filesystem {"*", cgiEspFsHook, NULL}, //Catch-all cgi function for the filesystem
{NULL, NULL, NULL} {NULL, NULL, NULL}
@ -138,8 +140,9 @@ void user_init(void) {
// Wifi // Wifi
wifiInit(); wifiInit();
// init the flash filesystem with the html stuff // init the flash filesystem with the html stuff
EspFsInitResult res = espFsInit(&_binary_espfs_img_start); espFsInit(&_binary_espfs_img_start);
os_printf("espFsInit %s\n", res?"ERR":"ok"); //EspFsInitResult res = espFsInit(&_binary_espfs_img_start);
//os_printf("espFsInit %s\n", res?"ERR":"ok");
// mount the http handlers // mount the http handlers
httpdInit(builtInUrls, 80); httpdInit(builtInUrls, 80);
// init the wifi-serial transparent bridge (port 23) // init the wifi-serial transparent bridge (port 23)

Loading…
Cancel
Save