diff --git a/Makefile b/Makefile index cad8624..32cce5c 100644 --- a/Makefile +++ b/Makefile @@ -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,24 +311,32 @@ 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/' \ - $(SDK_LDDIR)/eagle.app.v6.new.512.app1.ld >$@ + $(SDK_LDDIR)/eagle.app.v6.new.512.app1.ld >$@ 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 >$@ + $(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 diff --git a/html/favicon.ico b/html/favicon.ico index ea4eca1..fe24a7b 100755 Binary files a/html/favicon.ico and b/html/favicon.ico differ diff --git a/html/home.html b/html/home.html index f16a0e0..926ec2c 100644 --- a/html/home.html +++ b/html/home.html @@ -1,6 +1,6 @@
-
+
JEELABS

esp-link

@@ -46,6 +46,28 @@
+
+
+

HTTP service

+
+ External web service configuration +
+ + + + +
+
+ + + + +
+ +
+
+
diff --git a/html/jl-200x55.png b/html/jl-200x55.png deleted file mode 100755 index 3bc4a42..0000000 Binary files a/html/jl-200x55.png and /dev/null differ diff --git a/html/style.css b/html/style.css index 4fa393a..29d15a3 100644 --- a/html/style.css +++ b/html/style.css @@ -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; diff --git a/html/ui.js b/html/ui.js index b67f077..5de2f4c 100644 --- a/html/ui.js +++ b/html/ui.js @@ -225,7 +225,7 @@ onLoad(function() { '\ diff --git a/serial/serbridge.c b/serial/serbridge.c index 6d45610..528c486 100644 --- a/serial/serbridge.c +++ b/serial/serbridge.c @@ -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; itxbuffer -// 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= '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 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; istate == ESPCONN_NONE || connData[i].conn->state == ESPCONN_CLOSE)) - { - if (connData[i].conn_mode == cmAVR) { - // send reset to arduino/ARM - if (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; - } + serbridgeConnData *sbConn = serbridgeFindConnData(arg); + if (sbConn == NULL) return; + // send reset to arduino/ARM + 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); } + // 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; ireverse = 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 +#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; iconn = 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); +} diff --git a/serial/tcpclient.h b/serial/tcpclient.h new file mode 100644 index 0000000..c80d2d3 --- /dev/null +++ b/serial/tcpclient.h @@ -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__ */ diff --git a/user/cgiflash.c b/user/cgiflash.c index 2c5dbd4..597b73a 100644 --- a/user/cgiflash.c +++ b/user/cgiflash.c @@ -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) { diff --git a/user/status.c b/user/status.c index 23f693f..7b46d72 100644 --- a/user/status.c +++ b/user/status.c @@ -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 } diff --git a/user/user_main.c b/user/user_main.c index 57443ae..645d215 100644 --- a/user/user_main.c +++ b/user/user_main.c @@ -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"); }