Merge pull request #32 from jeelabs/http

Add TCP client support
pull/39/head
Thorsten von Eicken 9 years ago
commit 77baa64eb5
  1. 51
      Makefile
  2. BIN
      html/favicon.ico
  3. 24
      html/home.html
  4. BIN
      html/jl-200x55.png
  5. 7
      html/style.css
  6. 2
      html/ui.js
  7. 253
      serial/serbridge.c
  8. 8
      serial/serbridge.h
  9. 272
      serial/tcpclient.c
  10. 8
      serial/tcpclient.h
  11. 5
      user/cgiflash.c
  12. 119
      user/status.c
  13. 1
      user/user_main.c

@ -29,11 +29,28 @@ ESPBAUD ?= 460800
# --------------- chipset configuration ---------------
# Pick your flash size: "512KB" or "4MB"
FLASH_SIZE ?= 512KB
ifeq ("$(FLASH_SIZE)","512KB")
# Winbond 25Q40 512KB flash, typ for esp-01 thru esp-11
ESP_SPI_SIZE ?= 0 # 0->512KB
ESP_FLASH_MODE ?= 0 # 0->QIO
ESP_FLASH_FREQ_DIV ?= 0 # 0->40Mhz
ESP_FLASH_MAX ?= 241664 # max bin file for 512KB flash: 236KB
else
# Winbond 25Q32 4MB flash, typ for esp-12
# Here we're using two partitions of approx 0.5MB because that's what's easily available in terms
# of linker scripts in the SDK. Ideally we'd use two partitions of approx 1MB, the remaining 2MB
# cannot be used for code.
ESP_SPI_SIZE ?= 4 # 6->4MB (1MB+1MB) or 4->4MB (512KB+512KB)
ESP_FLASH_MODE ?= 0 # 0->QIO, 2->DIO
ESP_FLASH_FREQ_DIV ?= 15 # 15->80Mhz
ESP_FLASH_MAX ?= 503808 # max bin file for 512KB flash partition: 492KB
#ESP_FLASH_MAX ?= 1028096 # max bin file for 1MB flash partition: 1004KB
endif
# hostname or IP address for wifi flashing
ESP_HOSTNAME ?= esp-link
@ -163,7 +180,6 @@ SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c))
OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(SRC)) $(BUILD_BASE)/espfs_img.o
LIBS := $(addprefix -l,$(LIBS))
APP_AR := $(addprefix $(BUILD_BASE)/,$(TARGET)_app.a)
TARGET_OUT := $(addprefix $(BUILD_BASE)/,$(TARGET).out)
USER1_OUT := $(addprefix $(BUILD_BASE)/,$(TARGET).user1.out)
USER2_OUT := $(addprefix $(BUILD_BASE)/,$(TARGET).user2.out)
@ -202,16 +218,11 @@ endef
.PHONY: all checkdirs clean webpages.espfs wiflash
all: echo_version checkdirs $(FW_BASE) firmware/user1.bin firmware/user2.bin
all: echo_version checkdirs $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin
echo_version:
@echo VERSION: $(VERSION)
$(TARGET_OUT): $(APP_AR) $(LD_SCRIPT)
$(vecho) "LD $@"
$(Q) $(LD) -L$(SDK_LIBDIR) -T$(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group $(LIBS) $(APP_AR) -Wl,--end-group -o $@
# $(OBJDP) -x $(TARGET_OUT) | egrep '(espfs_img)'
$(USER1_OUT): $(APP_AR) $(LD_SCRIPT1)
$(vecho) "LD $@"
$(Q) $(LD) -L$(SDK_LIBDIR) -T$(LD_SCRIPT1) $(LDFLAGS) -Wl,--start-group $(LIBS) $(APP_AR) -Wl,--end-group -o $@
@ -224,12 +235,11 @@ $(USER2_OUT): $(APP_AR) $(LD_SCRIPT2)
$(Q) $(LD) -L$(SDK_LIBDIR) -T$(LD_SCRIPT2) $(LDFLAGS) -Wl,--start-group $(LIBS) $(APP_AR) -Wl,--end-group -o $@
# $(Q) $(OBJDP) -x $(TARGET_OUT) | egrep espfs_img
$(FW_BASE): $(TARGET_OUT)
$(FW_BASE):
$(vecho) "FW $@"
$(Q) mkdir -p $@
$(Q) $(ESPTOOL) elf2image $(TARGET_OUT) --output $@/
firmware/user1.bin: $(USER1_OUT)
$(FW_BASE)/user1.bin: $(USER1_OUT) $(FW_BASE)
$(Q) $(OBJCP) --only-section .text -O binary $(USER1_OUT) eagle.app.v6.text.bin
$(Q) $(OBJCP) --only-section .data -O binary $(USER1_OUT) eagle.app.v6.data.bin
$(Q) $(OBJCP) --only-section .rodata -O binary $(USER1_OUT) eagle.app.v6.rodata.bin
@ -241,7 +251,7 @@ firmware/user1.bin: $(USER1_OUT)
@echo "** user1.bin uses $$(stat -c '%s' $@) bytes of" $(ESP_FLASH_MAX) "available"
$(Q) if [ $$(stat -c '%s' $@) -gt $$(( $(ESP_FLASH_MAX) )) ]; then echo "$@ too big!"; false; fi
firmware/user2.bin: $(USER2_OUT)
$(FW_BASE)/user2.bin: $(USER2_OUT) $(FW_BASE)
$(Q) $(OBJCP) --only-section .text -O binary $(USER2_OUT) eagle.app.v6.text.bin
$(Q) $(OBJCP) --only-section .data -O binary $(USER2_OUT) eagle.app.v6.data.bin
$(Q) $(OBJCP) --only-section .rodata -O binary $(USER2_OUT) eagle.app.v6.rodata.bin
@ -251,7 +261,6 @@ firmware/user2.bin: $(USER2_OUT)
$(Q) mv eagle.app.flash.bin $@
$(Q) if [ $$(stat -c '%s' $@) -gt $$(( $(ESP_FLASH_MAX) )) ]; then echo "$@ too big!"; false; fi
$(APP_AR): $(OBJ)
$(vecho) "AR $@"
$(Q) $(AR) cru $@ $^
@ -262,7 +271,7 @@ $(BUILD_DIR):
$(Q) mkdir -p $@
wiflash: all
./wiflash $(ESP_HOSTNAME) firmware/user1.bin firmware/user2.bin
./wiflash $(ESP_HOSTNAME) $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin
flash: all
$(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) write_flash \
@ -302,9 +311,7 @@ endif
# we also adjust the sizes of the segments 'cause we need more irom0
# in the end the only thing that matters wrt size is that the whole shebang fits into the
# 236KB available (in a 512KB flash)
build/eagle.esphttpd.v6.ld: $(SDK_LDDIR)/eagle.app.v6.ld
$(Q) sed -e '/\.irom\.text/{' -e 'a . = ALIGN (4);' -e 'a *(.espfs)' -e '}' \
$(SDK_LDDIR)/eagle.app.v6.ld >$@
ifeq ("$(FLASH_SIZE)","512KB")
build/eagle.esphttpd1.v6.ld: $(SDK_LDDIR)/eagle.app.v6.new.512.app1.ld
$(Q) sed -e '/\.irom\.text/{' -e 'a . = ALIGN (4);' -e 'a *(.espfs)' -e '}' \
-e '/^ irom0_0_seg/ s/2B000/38000/' \
@ -313,13 +320,23 @@ build/eagle.esphttpd2.v6.ld: $(SDK_LDDIR)/eagle.app.v6.new.512.app2.ld
$(Q) sed -e '/\.irom\.text/{' -e 'a . = ALIGN (4);' -e 'a *(.espfs)' -e '}' \
-e '/^ irom0_0_seg/ s/2B000/38000/' \
$(SDK_LDDIR)/eagle.app.v6.new.512.app2.ld >$@
else
build/eagle.esphttpd1.v6.ld: $(SDK_LDDIR)/eagle.app.v6.new.1024.app1.ld
$(Q) sed -e '/\.irom\.text/{' -e 'a . = ALIGN (4);' -e 'a *(.espfs)' -e '}' \
-e '/^ irom0_0_seg/ s/6B000/7C000/' \
$(SDK_LDDIR)/eagle.app.v6.new.1024.app1.ld >$@
build/eagle.esphttpd2.v6.ld: $(SDK_LDDIR)/eagle.app.v6.new.1024.app2.ld
$(Q) sed -e '/\.irom\.text/{' -e 'a . = ALIGN (4);' -e 'a *(.espfs)' -e '}' \
-e '/^ irom0_0_seg/ s/6B000/7C000/' \
$(SDK_LDDIR)/eagle.app.v6.new.1024.app2.ld >$@
endif
espfs/mkespfsimage/mkespfsimage: espfs/mkespfsimage/
$(Q) $(MAKE) -C espfs/mkespfsimage USE_HEATSHRINK="$(USE_HEATSHRINK)" GZIP_COMPRESSION="$(GZIP_COMPRESSION)"
release: all
$(Q) rm -rf release; mkdir -p release/esp-link
$(Q) cp firmware/user1.bin firmware/user2.bin $(SDK_BASE)/bin/blank.bin \
$(Q) cp $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin $(SDK_BASE)/bin/blank.bin \
"$(SDK_BASE)/bin/boot_v1.4(b1).bin" wiflash release/esp-link
$(Q) tar zcf esp-link.tgz -C release esp-link
$(Q) rm -rf release

Binary file not shown.

Before

Width:  |  Height:  |  Size: 622 B

After

Width:  |  Height:  |  Size: 874 B

@ -1,6 +1,6 @@
<div id="main">
<div class="header">
<div><img src="jl-200x55.png" height="55"></div>
<div><img src="favicon.ico" height="64"><span class="jl">JEELABS</span></div>
<h1 style="margin-top:0"><span class="esp">esp</span>-link</h1>
<h2 id="version"></h2>
</div>
@ -46,6 +46,28 @@
</fieldset>
</div></div>
</div>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2"><div class="card">
<h1>HTTP service</h1>
<form action="#" id="httpform" class="pure-form">
<legend>External web service configuration</legend>
<div class="form-horizontal">
<label>Enable serial port HTTP</label>
<input type="checkbox" name="http_enable"/>
<label>Send RSSI</label>
<input type="checkbox" name="send_rssi"/>
</div>
<div class="pure-form-stacked">
<label>Server hostname</label>
<input type="text" name="hostname"/>
<label>API key/passwd</label>
<input type="password" name="api_key"/>
</div>
<button id="special-button" type="submit"
class="pure-button button-primary">Change!</button>
</form>
</div></div>
</div>
</div>
</div>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

@ -114,6 +114,13 @@ div.tt {
.header h1 .esp {
font-size: 1.25em;
}
.jl {
font: normal 800 1.5em sans-serif;
position: relative;
bottom: 19px;
color: #9d1414;
margin-left: 3px;
}
.content-subhead {
margin: 50px 0 20px 0;

@ -225,7 +225,7 @@ onLoad(function() {
'<div id="menu">\
<div class="pure-menu">\
<a class="pure-menu-heading" href="https://github.com/jeelabs/esp-link">\
<img src="/favicon.ico">&nbsp;esp-link</a>\
<img src="/favicon.ico" height="32">&nbsp;esp-link</a>\
<ul id="menu-list" class="pure-menu-list"></ul>\
</div>\
</div>\

@ -13,20 +13,21 @@
#include "serled.h"
#include "config.h"
#include "console.h"
#include "tcpclient.h"
static struct espconn serbridgeConn;
static esp_tcp serbridgeTcp;
static int8_t mcu_reset_pin, mcu_isp_pin;
sint8 ICACHE_FLASH_ATTR espbuffsend(serbridgeConnData *conn, const char *data, uint16 len);
static sint8 ICACHE_FLASH_ATTR espbuffsend(serbridgeConnData *conn, const char *data, uint16 len);
// Connection pool
serbridgeConnData connData[MAX_CONN];
// Transmit buffers for the connection pool
static char txbuffer[MAX_CONN][MAX_TXBUFFER];
// Given a pointer to an espconn struct find the connection that correcponds to it
static serbridgeConnData ICACHE_FLASH_ATTR *serbridgeFindConnData(void *arg) {
struct espconn *conn = arg;
return (serbridgeConnData *)conn->reverse;
#if 0
for (int i=0; i<MAX_CONN; i++) {
if (connData[i].conn == (struct espconn *)arg) {
return &connData[i];
@ -34,54 +35,10 @@ static serbridgeConnData ICACHE_FLASH_ATTR *serbridgeFindConnData(void *arg) {
}
//os_printf("FindConnData: Huh? Couldn't find connection for %p\n", arg);
return NULL; // not found, may be closed already...
#endif
}
// Send all data in conn->txbuffer
// returns result from espconn_sent if data in buffer or ESPCONN_OK (0)
// Use only internally from espbuffsend and serbridgeSentCb
static sint8 ICACHE_FLASH_ATTR sendtxbuffer(serbridgeConnData *conn) {
sint8 result = ESPCONN_OK;
if (conn->txbufferlen != 0) {
//os_printf("%d TX %d\n", system_get_time(), conn->txbufferlen);
conn->readytosend = false;
result = espconn_sent(conn->conn, (uint8_t*)conn->txbuffer, conn->txbufferlen);
conn->txbufferlen = 0;
if (result != ESPCONN_OK) {
os_printf("sendtxbuffer: espconn_sent error %d on conn %p\n", result, conn);
}
}
return result;
}
// espbuffsend adds data to the send buffer. If the previous send was completed it calls
// sendtxbuffer and espconn_sent.
// Returns ESPCONN_OK (0) for success, -128 if buffer is full or error from espconn_sent
// Use espbuffsend instead of espconn_sent as it solves the problem that espconn_sent must
// only be called *after* receiving an espconn_sent_callback for the previous packet.
sint8 ICACHE_FLASH_ATTR espbuffsend(serbridgeConnData *conn, const char *data, uint16 len) {
if (conn->txbufferlen + len > MAX_TXBUFFER) {
os_printf("espbuffsend: txbuffer full on conn %p\n", conn);
return -128;
}
os_memcpy(conn->txbuffer + conn->txbufferlen, data, len);
conn->txbufferlen += len;
if (conn->readytosend) {
return sendtxbuffer(conn);
} else {
//os_printf("%d QU %d\n", system_get_time(), conn->txbufferlen);
}
return ESPCONN_OK;
}
//callback after the data are sent
static void ICACHE_FLASH_ATTR serbridgeSentCb(void *arg) {
serbridgeConnData *conn = serbridgeFindConnData(arg);
//os_printf("Sent callback on conn %p\n", conn);
if (conn == NULL) return;
//os_printf("%d ST\n", system_get_time());
conn->readytosend = true;
sendtxbuffer(conn); // send possible new data in txbuffer
}
//===== TCP -> UART
// Telnet protocol characters
#define IAC 255 // escape
@ -189,7 +146,6 @@ void ICACHE_FLASH_ATTR serbridgeReset() {
} else os_printf("MCU reset: no pin\n");
}
// Receive callback
static void ICACHE_FLASH_ATTR serbridgeRecvCb(void *arg, char *data, unsigned short len) {
serbridgeConnData *conn = serbridgeFindConnData(arg);
@ -233,6 +189,9 @@ static void ICACHE_FLASH_ATTR serbridgeRecvCb(void *arg, char *data, unsigned sh
} else {
conn->conn_mode = cmTransparent;
}
// Process return data on TCP client connections
} else if (conn->conn_mode == cmTcpClient) {
}
// write the buffer to the uart
@ -245,41 +204,181 @@ static void ICACHE_FLASH_ATTR serbridgeRecvCb(void *arg, char *data, unsigned sh
serledFlash(50); // short blink on serial LED
}
//===== UART -> TCP
// Transmit buffers for the connection pool
static char txbuffer[MAX_CONN][MAX_TXBUFFER];
// Send all data in conn->txbuffer
// returns result from espconn_sent if data in buffer or ESPCONN_OK (0)
// Use only internally from espbuffsend and serbridgeSentCb
static sint8 ICACHE_FLASH_ATTR sendtxbuffer(serbridgeConnData *conn) {
sint8 result = ESPCONN_OK;
if (conn->txbufferlen != 0) {
//os_printf("%d TX %d\n", system_get_time(), conn->txbufferlen);
conn->readytosend = false;
result = espconn_sent(conn->conn, (uint8_t*)conn->txbuffer, conn->txbufferlen);
conn->txbufferlen = 0;
if (result != ESPCONN_OK) {
os_printf("sendtxbuffer: espconn_sent error %d on conn %p\n", result, conn);
}
}
return result;
}
// espbuffsend adds data to the send buffer. If the previous send was completed it calls
// sendtxbuffer and espconn_sent.
// Returns ESPCONN_OK (0) for success, -128 if buffer is full or error from espconn_sent
// Use espbuffsend instead of espconn_sent as it solves the problem that espconn_sent must
// only be called *after* receiving an espconn_sent_callback for the previous packet.
static sint8 ICACHE_FLASH_ATTR espbuffsend(serbridgeConnData *conn, const char *data, uint16 len) {
if (conn->txbufferlen + len > MAX_TXBUFFER) {
os_printf("espbuffsend: txbuffer full on conn %p\n", conn);
return -128;
}
os_memcpy(conn->txbuffer + conn->txbufferlen, data, len);
conn->txbufferlen += len;
if (conn->readytosend) {
return sendtxbuffer(conn);
} else {
//os_printf("%d QU %d\n", system_get_time(), conn->txbufferlen);
}
return ESPCONN_OK;
}
//callback after the data are sent
static void ICACHE_FLASH_ATTR
serbridgeSentCb(void *arg) {
serbridgeConnData *conn = serbridgeFindConnData(arg);
//os_printf("Sent callback on conn %p\n", conn);
if (conn == NULL) return;
//os_printf("%d ST\n", system_get_time());
conn->readytosend = true;
sendtxbuffer(conn); // send possible new data in txbuffer
}
// TCP client connection state machine
// This processes commands from the attached uC to open outboud TCP connections
enum {
TC_idle, // in-between commands
TC_newline, // newline seen
TC_start, // start character (~) seen
TC_cmd, // command character (0) seen
TC_cmdLine, // accumulating command
TC_tdata, // data character (1-9) seen, and in text data mode
};
static uint8_t tcState = TC_newline;
static uint8_t tcChan; // channel for current command (index into tcConn)
#define CMD_MAX 256
static char tcCmdBuf[CMD_MAX];
static short tcCmdBufLen = 0;
static char tcCmdChar;
// scan a buffer for tcp client commands
static int ICACHE_FLASH_ATTR
tcpClientProcess(char *buf, int len)
{
char *in=buf, *out=buf;
for (short i=0; i<len; i++) {
char c = *in++;
//os_printf("tcState=%d c=%c\n", tcState, c);
switch (tcState) {
case TC_idle:
if (c == '\n') tcState = TC_newline;
break;
case TC_newline: // saw newline, expect ~
if (c == '~') tcState = TC_start;
continue; // gobble up the ~
case TC_start: // saw ~, expect channel number
if (c == '0') {
tcState = TC_cmd;
continue;
} else if (c >= '1' && c <= '9') {
tcChan = c-'1';
tcState = TC_tdata;
continue;
}
*out++ = '~'; // make up for '~' we skipped
break;
case TC_cmd: // saw channel number 0 (command), expect command char
tcCmdChar = c; // save command character
tcCmdBufLen = 0; // empty the command buffer
tcState = TC_cmdLine;
continue;
case TC_cmdLine: // accumulating command in buffer
if (c != '\n') {
if (tcCmdBufLen < CMD_MAX) tcCmdBuf[tcCmdBufLen++] = c;
} else {
tcpClientCommand(tcCmdChar, tcCmdBuf);
tcState = TC_newline;
}
continue;
case TC_tdata: // saw channel number, getting data characters
if (c != 0) {
tcpClientSendChar(tcChan, c);
} else {
tcpClientSendPush(tcChan);
tcState = TC_idle;
}
continue;
}
*out++ = c;
}
if (tcState != TC_idle) os_printf("tcState=%d\n", tcState);
return out-buf;
}
// callback with a buffer of characters that have arrived on the uart
void ICACHE_FLASH_ATTR
serbridgeUartCb(char *buf, int length) {
// push the buffer into the microcontroller console
for (int i=0; i<length; i++)
console_write_char(buf[i]);
// parse the buffer for TCP commands, this may remove characters from the buffer
length = tcpClientProcess(buf, length);
// push the buffer into each open connection
if (length > 0) {
for (int i = 0; i < MAX_CONN; ++i) {
if (connData[i].conn && connData[i].conn_mode != cmTcpClient) {
espbuffsend(&connData[i], buf, length);
}
}
}
serledFlash(50); // short blink on serial LED
}
//===== Connect / disconnect
// Error callback (it's really poorly named, it's not a "connection reconnected" callback,
// it's really a "connection broken, please reconnect" callback)
static void ICACHE_FLASH_ATTR serbridgeReconCb(void *arg, sint8 err) {
serbridgeConnData *conn=serbridgeFindConnData(arg);
if (conn == NULL) return;
serbridgeConnData *sbConn = serbridgeFindConnData(arg);
if (sbConn == NULL) return;
// Close the connection
espconn_disconnect(conn->conn);
conn->conn = NULL;
espconn_disconnect(sbConn->conn);
// free connection slot
sbConn->conn = NULL;
}
// Disconnection callback
static void ICACHE_FLASH_ATTR serbridgeDisconCb(void *arg) {
// Iterate through all the connections and deallocate the ones that are in a state that
// indicates that they're closed
for (int i=0; i<MAX_CONN; i++) {
if (connData[i].conn != NULL &&
(connData[i].conn->state == ESPCONN_NONE || connData[i].conn->state == ESPCONN_CLOSE))
{
if (connData[i].conn_mode == cmAVR) {
serbridgeConnData *sbConn = serbridgeFindConnData(arg);
if (sbConn == NULL) return;
// send reset to arduino/ARM
if (mcu_reset_pin >= 0) {
if (sbConn->conn_mode == cmAVR && mcu_reset_pin >= 0) {
GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L);
GPIO_OUTPUT_SET(mcu_reset_pin, 1);
}
}
connData[i].conn = NULL;
}
}
// free connection slot
sbConn->conn = NULL;
}
// New connection callback, use one of the connection descriptors, if we have one left.
static void ICACHE_FLASH_ATTR serbridgeConnectCb(void *arg) {
struct espconn *conn = arg;
//Find empty conndata in pool
// Find empty conndata in pool
int i;
for (i=0; i<MAX_CONN; i++) if (connData[i].conn==NULL) break;
os_printf("Accept port 23, conn=%p, pool slot %d\n", conn, i);
@ -290,7 +389,8 @@ static void ICACHE_FLASH_ATTR serbridgeConnectCb(void *arg) {
return;
}
connData[i].conn=conn;
conn->reverse = connData+i;
connData[i].conn = conn;
connData[i].txbufferlen = 0;
connData[i].readytosend = true;
connData[i].telnet_state = 0;
@ -304,20 +404,7 @@ static void ICACHE_FLASH_ATTR serbridgeConnectCb(void *arg) {
espconn_set_opt(conn, ESPCONN_REUSEADDR|ESPCONN_NODELAY);
}
// callback with a buffer of characters that have arrived on the uart
void ICACHE_FLASH_ATTR
serbridgeUartCb(char *buf, int length) {
// push the buffer into the microcontroller console
for (int i=0; i<length; i++)
console_write_char(buf[i]);
// push the buffer into each open connection
for (int i = 0; i < MAX_CONN; ++i) {
if (connData[i].conn) {
espbuffsend(&connData[i], buf, length);
}
}
serledFlash(50); // short blink on serial LED
}
//===== Initialization
void ICACHE_FLASH_ATTR serbridgeInitPins() {
mcu_reset_pin = flashConfig.reset_pin;

@ -11,26 +11,24 @@
//Max send buffer len
#define MAX_TXBUFFER 1024
typedef struct serbridgeConnData serbridgeConnData;
enum connModes {
cmInit = 0, // initialization mode: nothing received yet
cmTransparent, // transparent mode
cmAVR, // Arduino/AVR programming mode
cmARM, // ARM (LPC8xx) programming
cmEcho, // simply echo characters (used for debugging latency)
cmCommand, // AT command mode
cmTelnet, // use telnet escape sequences for programming mode
cmTcpClient, // client connection (initiated via serial)
};
struct serbridgeConnData {
typedef struct serbridgeConnData {
struct espconn *conn;
enum connModes conn_mode; // connection mode
char *txbuffer; // buffer for the data to send
uint16 txbufferlen; // length of data in txbuffer
bool readytosend; // true, if txbuffer can send by espconn_sent
uint8_t telnet_state;
};
} serbridgeConnData;
void ICACHE_FLASH_ATTR serbridgeInit(int port);
void ICACHE_FLASH_ATTR serbridgeInitPins(void);

@ -0,0 +1,272 @@
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
#include <esp8266.h>
#include "config.h"
#include "uart.h"
#include "serled.h"
#include "tcpclient.h"
// max number of channels the client can open
#define MAX_CHAN 8
// size of tx buffer
#define MAX_TXBUF 1024
enum TcpState {
TCP_idle, // unused connection
TCP_dns, // doing gethostbyname
TCP_conn, // connecting to remote server
TCP_data, // connected
};
// Connections
typedef struct {
struct espconn *conn; // esp connection structure
esp_tcp *tcp; // esp TCP parameters
char *txBuf; // buffer to accumulate into
char *txBufSent; // buffer held by espconn
uint8_t txBufLen; // number of chars in txbuf
enum TcpState state;
} TcpConn;
#define MAX_CONN (8)
static TcpConn tcpConn[MAX_CONN];
// Free a connection dynamically.
static void ICACHE_FLASH_ATTR
tcpConnFree(TcpConn* tci) {
if (tci->conn != NULL) os_free(tci->conn);
if (tci->tcp != NULL) os_free(tci->tcp);
if (tci->txBuf != NULL) os_free(tci->txBuf);
if (tci->txBufSent != NULL) os_free(tci->txBufSent);
memset(tci, 0, sizeof(TcpConn));
}
// DNS name resolution callback
static void ICACHE_FLASH_ATTR
tcpClientHostnameCb(const char *name, ip_addr_t *ipaddr, void *arg) {
struct espconn *conn = arg;
TcpConn *tci = conn->reverse;
os_printf("TCP dns CB (%p %p)\n", arg, tci);
if (ipaddr == NULL) {
os_printf("TCP %s not found\n", name);
} else {
os_printf("TCP %s -> %d.%d.%d.%d\n", name, IP2STR(ipaddr));
tci->tcp->remote_ip[0] = ip4_addr1(ipaddr);
tci->tcp->remote_ip[1] = ip4_addr2(ipaddr);
tci->tcp->remote_ip[2] = ip4_addr3(ipaddr);
tci->tcp->remote_ip[3] = ip4_addr4(ipaddr);
os_printf("TCP connect %d.%d.%d.%d (%p)\n", IP2STR(tci->tcp->remote_ip), tci);
if (espconn_connect(tci->conn) == ESPCONN_OK) {
tci->state = TCP_conn;
return;
}
os_printf("TCP connect failure\n");
}
// oops
tcpConnFree(tci);
}
// Send the next buffer (assumes that the connection is in a state that allows it)
static void 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->txBufSent = tci->txBuf;
tci->txBuf = NULL;
tci->txBufLen = 0;
} else {
// send error, leave as-is and try again later...
os_printf("TCP send err (%p %p) %d\n", tci->conn, tci, err);
}
}
// 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) {
struct espconn *conn = arg;
TcpConn *tci = conn->reverse;
os_printf("TCP sent CB (%p %p)\n", arg, tci);
if (tci->txBufSent != NULL) os_free(tci->txBufSent);
tci->txBufSent = NULL;
if (tci->txBuf != NULL && tci->txBufLen == MAX_TXBUF) {
// next buffer is full, send it now
tcpDoSend(tci);
}
}
// Recv callback
static void ICACHE_FLASH_ATTR tcpRecvCb(void *arg, char *data, uint16_t len) {
struct espconn *conn = arg;
TcpConn *tci = conn->reverse;
os_printf("TCP recv CB (%p %p)\n", arg, tci);
if (tci->state == TCP_data) {
uart0_tx_buffer(data, len);
}
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);
}
// 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:
tcpConnFree(tci);
return NULL;
}
static TcpConn *tcConn[MAX_CHAN];
// 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;
TcpConn *tci;
switch (cmd) {
//== TCP Connect command
case 'T':
if (*cmdBuf < '1' || *cmdBuf > ('0'+MAX_CHAN)) break;
tcChan = *cmdBuf++ - '1';
char *hostname = cmdBuf;
char *port = hostname;
while (*port != 0 && *port != ':') port++;
if (*port != ':') break;
*port = 0;
port++;
int portInt = atoi(port);
if (portInt < 1 || portInt > 65535) break;
// allocate a connection
tci = tcpConnAlloc();
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);
ip_addr_t ip;
err_t err = espconn_gethostbyname(tci->conn, hostname, &ip, tcpClientHostnameCb);
if (err == ESPCONN_OK) {
// dns cache hit, got the IP address, fake the callback (sigh)
os_printf("TCP DNS hit\n");
tcpClientHostnameCb(hostname, &ip, tci->conn);
} else if (err != ESPCONN_INPROGRESS) {
tcpConnFree(tci);
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];
if (tci->state > TCP_idle) {
tci->state = TCP_idle; // hackish...
espconn_disconnect(tci->conn);
}
break;
}
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);
}

@ -0,0 +1,8 @@
#ifndef __TCP_CLIENT_H__
#define __TCP_CLIENT_H__
bool tcpClientCommand(char cmd, char *cmdBuf);
void tcpClientSendChar(uint8_t chan, char c);
void tcpClientSendPush(uint8_t chan);
#endif /* __TCP_CLIENT_H__ */

@ -22,8 +22,10 @@ Some flash handling cgi routines. Used for reading the existing flash and updati
// Check that the header of the firmware blob looks like actual firmware...
static char* ICACHE_FLASH_ATTR check_header(void *buf) {
uint8_t *cd = (uint8_t *)buf;
uint32_t *buf32 = buf;
os_printf("%p: %08lX %08lX %08lX %08lX\n", buf, buf32[0], buf32[1], buf32[2], buf32[3]);
if (cd[0] != 0xEA) return "IROM magic missing";
if (cd[1] != 4 || cd[2] > 3 || cd[3] > 0x40) return "bad flash header";
if (cd[1] != 4 || cd[2] > 3 || (cd[3]>>4) > 6) return "bad flash header";
if (((uint16_t *)buf)[3] != 0x4010) return "Invalid entry addr";
if (((uint32_t *)buf)[2] != 0) return "Invalid start offset";
return NULL;
@ -151,6 +153,7 @@ int ICACHE_FLASH_ATTR cgiRebootFirmware(HttpdConnData *connData) {
int address = id == 1 ? 4*1024 // either start after 4KB boot partition
: 4*1024 + FIRMWARE_SIZE + 16*1024 + 4*1024; // 4KB boot, fw1, 16KB user param, 4KB reserved
uint32 buf[8];
os_printf("Checking %p\n", (void *)address);
spi_flash_read(address, buf, sizeof(buf));
char *err = check_header(buf);
if (err != NULL) {

@ -5,8 +5,11 @@
#include "serled.h"
#include "cgiwifi.h"
//===== "CONN" LED status indication
static ETSTimer ledTimer;
// Set the LED on or off, respecting the defined polarity
static void ICACHE_FLASH_ATTR setLed(int on) {
int8_t pin = flashConfig.conn_led_pin;
if (pin < 0) return; // disabled
@ -21,11 +24,12 @@ static void ICACHE_FLASH_ATTR setLed(int on) {
static uint8_t ledState = 0;
static uint8_t wifiState = 0;
// Timer callback to update the LED
static void ICACHE_FLASH_ATTR ledTimerCb(void *v) {
int time = 1000;
if (wifiState == wifiGotIP) {
// connected, all is good, solid light
// connected, all is good, solid light with a short dark blip every 3 seconds
ledState = 1-ledState;
time = ledState ? 2900 : 100;
} else if (wifiState == wifiIsConnected) {
@ -33,7 +37,7 @@ static void ICACHE_FLASH_ATTR ledTimerCb(void *v) {
ledState = 1 - ledState;
time = 1000;
} else {
// idle
// not connected
switch (wifi_get_opmode()) {
case 1: // STA
ledState = 0;
@ -53,7 +57,7 @@ static void ICACHE_FLASH_ATTR ledTimerCb(void *v) {
os_timer_arm(&ledTimer, time, 0);
}
// change the wifi state
// change the wifi state indication
void ICACHE_FLASH_ATTR statusWifiUpdate(uint8_t state) {
wifiState = state;
// schedule an update (don't want to run into concurrency issues)
@ -62,6 +66,111 @@ void ICACHE_FLASH_ATTR statusWifiUpdate(uint8_t state) {
os_timer_arm(&ledTimer, 500, 0);
}
//===== RSSI Status update sent to GroveStreams
#define RSSI_INTERVAL (60*1000)
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"
// Connected callback
static void ICACHE_FLASH_ATTR rssiConnectCb(void *arg) {
struct espconn *conn = (struct espconn *)arg;
os_printf("RSSI connect CB (%p %p)\n", arg, conn->reverse);
char buf[2000];
// http header
int hdrLen = os_sprintf(buf,
"PUT /api/feed?api_key=%s HTTP/1.0\r\n"
"Content-Type: application/json\r\n"
"Content-Length: XXXXX\r\n\r\n",
GS_API_KEY);
// http body
int dataLen = os_sprintf(buf+hdrLen,
"[{\"compId\":\"%s\", \"streamId\":\"%s\", \"data\":%d}]",
GS_COMP_ID, GS_STREAM, rssiLast);
// hackish way to fill in the content-length
os_sprintf(buf+hdrLen-9, "%5d", dataLen);
buf[hdrLen-4] = '\r';
// 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
static void ICACHE_FLASH_ATTR rssiRecvCb(void *arg, char *data, uint16_t len) {
struct espconn *conn = (struct espconn *)arg;
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++;
}
}
//===== Init status stuff
void ICACHE_FLASH_ATTR statusInit(void) {
if (flashConfig.conn_led_pin >= 0) {
makeGpio(flashConfig.conn_led_pin);
@ -72,6 +181,10 @@ void ICACHE_FLASH_ATTR statusInit(void) {
os_timer_disarm(&ledTimer);
os_timer_setfn(&ledTimer, ledTimerCb, NULL);
os_timer_arm(&ledTimer, 2000, 0);
os_timer_disarm(&rssiTimer);
os_timer_setfn(&rssiTimer, rssiTimerCb, NULL);
os_timer_arm(&rssiTimer, RSSI_INTERVAL, 1); // recurring timer
}

@ -156,6 +156,7 @@ void user_init(void) {
os_printf("exccause=%d epc1=0x%x epc2=0x%x epc3=0x%x excvaddr=0x%x depc=0x%x\n",
rst_info->exccause, rst_info->epc1, rst_info->epc2, rst_info->epc3,
rst_info->excvaddr, rst_info->depc);
os_printf("Flash map %d, chip %08X\n", system_get_flash_size_map(), spi_flash_get_id());
os_printf("** esp-link ready\n");
}

Loading…
Cancel
Save