Merge branch 'master' into windows

pull/36/head
Benjamin Runnels 9 years ago
commit 5ad0353a8a
  1. 3
      .gitmodules
  2. 24
      Makefile
  3. 17
      README.md
  4. 179
      cmd/cmd.c
  5. 90
      cmd/cmd.h
  6. 79
      cmd/handlers.c
  7. 394
      cmd/rest.c
  8. 39
      cmd/rest.h
  9. 315
      cmd/tcpclient.c
  10. 16
      cmd/tcpclient.h
  11. 52
      html/home.html
  12. 39
      html/ui.js
  13. 6
      httpd/httpd.c
  14. 121
      serial/console.c
  15. 78
      serial/crc16.c
  16. 100
      serial/crc16.h
  17. 570
      serial/serbridge.c
  18. 30
      serial/serbridge.h
  19. 136
      serial/slip.c
  20. 2
      serial/uart.h
  21. 1
      user/cgiflash.c
  22. 2
      user/cgipins.c
  23. 74
      user/cgitcp.c
  24. 8
      user/cgitcp.h
  25. 5
      user/cgiwifi.c
  26. 2
      user/cgiwifi.h
  27. 64
      user/status.c
  28. 7
      user/user_main.c

3
.gitmodules vendored

@ -1,3 +0,0 @@
[submodule "lib/heatshrink"]
path = lib/heatshrink
url = https://github.com/atomicobject/heatshrink.git

@ -36,9 +36,9 @@ FLASH_SIZE ?= 512KB
ifeq ("$(FLASH_SIZE)","512KB")
# Winbond 25Q40 512KB flash, typ for esp-01 thru esp-11
ESP_SPI_SIZE ?= 0 # 0->512KB (256KB+256KB)
ESP_FLASH_MODE ?= 0 # 0->QIO
ESP_FLASH_FREQ_DIV ?= 0 # 0->40Mhz
ESP_FLASH_MAX ?= 241664 # max bin file for 512KB flash: 236KB
ESP_FLASH_MODE ?= 0 # 0->QIO
ESP_FLASH_FREQ_DIV ?= 0 # 0->40Mhz
ESP_FLASH_MAX ?= 241664 # max bin file for 512KB flash: 236KB
ET_FS ?= 4m # 4Mbit flash size in esptool flash command
ET_FF ?= 40m # 40Mhz flash speed in esptool flash command
ET_BLANK ?= 0x7E000 # where to flash blank.bin to erase wireless settings
@ -133,10 +133,10 @@ YUI-COMPRESSOR ?= yuicompressor-2.4.8.jar
# Output directors to store intermediate compiled files
# relative to the project directory
BUILD_BASE = build
FW_BASE = firmware
FW_BASE = firmware
# name for the target project
TARGET = httpd
TARGET = httpd
# espressif tool to concatenate sections for OTA upload using bootloader v1.2+
APPGEN_TOOL ?= gen_appbin.py
@ -146,10 +146,10 @@ MODULES = espfs httpd user serial
EXTRA_INCDIR = include .
# libraries used in this project, mainly provided by the SDK
LIBS = c gcc hal phy pp net80211 wpa main lwip
LIBS = c gcc hal phy pp net80211 wpa main lwip
# compiler flags using during compilation of source files
CFLAGS = -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \
CFLAGS = -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \
-nostdlib -mlongcalls -mtext-section-literals -ffunction-sections -fdata-sections \
-D__ets__ -DICACHE_FLASH -D_STDINT_H -Wno-address -DFIRMWARE_SIZE=$(ESP_FLASH_MAX) \
-DMCU_RESET_PIN=$(MCU_RESET_PIN) -DMCU_ISP_PIN=$(MCU_ISP_PIN) \
@ -209,11 +209,11 @@ vecho := @echo
endif
ifeq ("$(GZIP_COMPRESSION)","yes")
CFLAGS += -DGZIP_COMPRESSION
CFLAGS += -DGZIP_COMPRESSION
endif
ifeq ("$(CHANGE_TO_STA)","yes")
CFLAGS += -DCHANGE_TO_STA
CFLAGS += -DCHANGE_TO_STA
endif
vpath %.c $(SRC_DIR)
@ -294,7 +294,7 @@ yui/$(YUI-COMPRESSOR):
ifeq ($(OS),Windows_NT)
cd yui; wget --no-check-certificate https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI-COMPRESSOR) -O $(YUI-COMPRESSOR)
else
cd yui; wget https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI-COMPRESSOR)
cd yui; wget https://github.com/yui/yuicompressor/releases/download/v2.4.8/$(YUI-COMPRESSOR)
endif
ifeq ("$(COMPRESS_W_YUI)","yes")
@ -330,11 +330,11 @@ 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 '}' \

@ -1,9 +1,12 @@
ESP-LINK
========
This firmware implements a transparent bridge between Wifi and serial using an ESP8266 module.
It also provides support for flash-programming Arduino/AVR microcontrollers as well as
LPC800-series and other ARM microcontrollers via the ESP8266.
This firmware connects an attached micro-controller to the internet using a ESP8266 Wifi module.
It implements a number of features:
- transparent bridge between Wifi and serial, useful for debugging or inputting into a uC
- flash-programming attached Arduino/AVR microcontrollers as well as LPC800-series and other
ARM microcontrollers via Wifi
- outbound TCP (and thus HTTP) connections from the attached micro-controller to the internet
The firmware includes a tiny HTTP server based on
[esphttpd](http://www.esp8266.com/viewforum.php?f=34)
@ -12,6 +15,8 @@ with a simple web interface, many thanks to Jeroen Domburg for making it availab
[![Chat at https://gitter.im/jeelabs/esp-link](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jeelabs/esp-link?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
###[Latest release](https://github.com/jeelabs/esp-link/releases)
Note that the [stable V1.0 release](https://github.com/jeelabs/esp-link/releases/tag/v1.0.0) is
recommended if you do not need the outbound TCP connections and have a 512KB flash chip.
Eye Candy
---------
@ -215,6 +220,12 @@ modes are supported that can be set in the web UI (and the mode is saved in flas
Note that even if the UART log is always off the bootloader prints to uart0 whenever the
esp8266 comes out of reset. This cannot be disabled.
Outbound TCP connections
------------------------
The attached micro-controller can open outbound TCP connections using a simple
[serial protocol](https://gist.github.com/tve/a46c44bf1f6b42bc572e).
More info and sample code forthcoming...
Contact
-------
If you find problems with esp-link, please create a github issue. If you have a question, please

@ -0,0 +1,179 @@
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
//
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh
#include "esp8266.h"
#include "cmd.h"
#include "crc16.h"
#include "serbridge.h"
#include "uart.h"
extern const CmdList commands[];
//===== ESP -> Serial responses
static void ICACHE_FLASH_ATTR
CMD_ProtoWrite(uint8_t data) {
switch(data){
case SLIP_START:
case SLIP_END:
case SLIP_REPL:
uart0_write_char(SLIP_REPL);
uart0_write_char(SLIP_ESC(data));
break;
default:
uart0_write_char(data);
}
}
static void ICACHE_FLASH_ATTR
CMD_ProtoWriteBuf(uint8_t *data, short len) {
while (len--) CMD_ProtoWrite(*data++);
}
// Start a response, returns the partial CRC
uint16_t ICACHE_FLASH_ATTR
CMD_ResponseStart(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc) {
uint16_t crc = 0;
uart0_write_char(SLIP_START);
CMD_ProtoWriteBuf((uint8_t*)&cmd, 2);
crc = crc16_data((uint8_t*)&cmd, 2, crc);
CMD_ProtoWriteBuf((uint8_t*)&callback, 4);
crc = crc16_data((uint8_t*)&callback, 4, crc);
CMD_ProtoWriteBuf((uint8_t*)&_return, 4);
crc = crc16_data((uint8_t*)&_return, 4, crc);
CMD_ProtoWriteBuf((uint8_t*)&argc, 2);
crc = crc16_data((uint8_t*)&argc, 2, crc);
return crc;
}
// Adds data to a response, returns the partial CRC
uint16_t ICACHE_FLASH_ATTR
CMD_ResponseBody(uint16_t crc_in, uint8_t* data, short len) {
short pad_len = len+3 - (len+3)%4; // round up to multiple of 4
CMD_ProtoWriteBuf((uint8_t*)&pad_len, 2);
crc_in = crc16_data((uint8_t*)&pad_len, 2, crc_in);
CMD_ProtoWriteBuf(data, len);
crc_in = crc16_data(data, len, crc_in);
if (pad_len > len) {
uint32_t temp = 0;
CMD_ProtoWriteBuf((uint8_t*)&temp, pad_len-len);
crc_in = crc16_data((uint8_t*)&temp, pad_len-len, crc_in);
}
return crc_in;
}
// Ends a response
void ICACHE_FLASH_ATTR
CMD_ResponseEnd(uint16_t crc) {
CMD_ProtoWriteBuf((uint8_t*)&crc, 2);
uart0_write_char(SLIP_END);
}
//===== serial -> ESP commands
// Execute a parsed command
static uint32_t ICACHE_FLASH_ATTR
CMD_Exec(const CmdList *scp, CmdPacket *packet) {
uint16_t crc = 0;
// Iterate through the command table and call the appropriate function
while (scp->sc_function != NULL) {
if(scp->sc_name == packet->cmd) {
//os_printf("CMD: Dispatching cmd=%d\n", packet->cmd);
// call command function
uint32_t ret = scp->sc_function(packet);
// if requestor asked for a response, send it
if (packet->_return){
os_printf("CMD: Response: %lu, cmd: %d\r\n", ret, packet->cmd);
crc = CMD_ResponseStart(packet->cmd, 0, ret, 0);
CMD_ResponseEnd(crc);
} else {
//os_printf("CMD: no response (%lu)\n", packet->_return);
}
return ret;
}
scp++;
}
os_printf("CMD: cmd=%d not found\n", packet->cmd);
return 0;
}
// Parse a packet and print info about it
void ICACHE_FLASH_ATTR
CMD_parse_packet(uint8_t *buf, short len) {
// minimum command length
if (len < 12) return;
// init pointers into buffer
CmdPacket *packet = (CmdPacket*)buf;
uint8_t *data_ptr = (uint8_t*)&packet->args;
uint8_t *data_limit = data_ptr+len;
uint16_t argc = packet->argc;
uint16_t argn = 0;
os_printf("CMD: cmd=%d argc=%d cb=%p ret=%lu\n",
packet->cmd, packet->argc, (void *)packet->callback, packet->_return);
// print out arguments
while (data_ptr+2 < data_limit && argc--) {
short l = *(uint16_t*)data_ptr;
os_printf("CMD: arg[%d] len=%d:", argn++, l);
data_ptr += 2;
while (data_ptr < data_limit && l--) {
os_printf(" %02X", *data_ptr++);
}
os_printf("\n");
}
if (data_ptr <= data_limit) {
CMD_Exec(commands, packet);
} else {
os_printf("CMD: packet length overrun, parsing arg %d\n", argn-1);
}
}
//===== Helpers to parse a command packet
// Fill out a CmdRequest struct given a CmdPacket
void ICACHE_FLASH_ATTR
CMD_Request(CmdRequest *req, CmdPacket* cmd) {
req->cmd = cmd;
req->arg_num = 0;
req->arg_ptr = (uint8_t*)&cmd->args;
}
// Return the number of arguments given a command struct
uint32_t ICACHE_FLASH_ATTR
CMD_GetArgc(CmdRequest *req) {
return req->cmd->argc;
}
// Copy the next argument from a command structure into the data pointer, returns 0 on success
// -1 on error
int32_t ICACHE_FLASH_ATTR
CMD_PopArg(CmdRequest *req, void *data, uint16_t len) {
uint16_t length;
if (req->arg_num >= req->cmd->argc)
return -1;
length = *(uint16_t*)req->arg_ptr;
if (length != len) return -1; // safety check
req->arg_ptr += 2;
os_memcpy(data, req->arg_ptr, length);
req->arg_ptr += length;
req->arg_num ++;
return 0;
}
// Return the length of the next argument
uint16_t ICACHE_FLASH_ATTR
CMD_ArgLen(CmdRequest *req) {
return *(uint16_t*)req->arg_ptr;
}

@ -0,0 +1,90 @@
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
//
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh
#ifndef CMD_H
#define CMD_H
// Escape chars used by tuanpmt, dunno why he didn't use std ones...
#define SLIP_START 0x7E
#define SLIP_END 0x7F
#define SLIP_REPL 0x7D
#define SLIP_ESC(x) (x ^ 0x20)
#if 0
// Proper SLIP escape chars from RFC
#define SLIP_END 0300 // indicates end of packet
#define SLIP_ESC 0333 // indicates byte stuffing
#define SLIP_ESC_END 0334 // ESC ESC_END means END data byte
#define SLIP_ESC_ESC 0335 // ESC ESC_ESC means ESC data byte
#endif
typedef struct __attribute((__packed__)) {
uint16_t len; // length of data
uint8_t data[0]; // really data[len]
} CmdArg;
typedef struct __attribute((__packed__)) {
uint16_t cmd; // command to perform, from CmdName enum
uint32_t callback; // callback pointer to embed in response
uint32_t _return; // return value to embed in response (?)
uint16_t argc; // number of arguments to command
CmdArg args[0]; // really args[argc]
} CmdPacket;
typedef struct {
CmdPacket *cmd; // command packet header
uint32_t arg_num; // number of args parsed
uint8_t *arg_ptr; // pointer to ??
} CmdRequest;
typedef enum {
CMD_NULL = 0,
CMD_RESET, // reset esp (not honored in this implementation)
CMD_IS_READY, // health-check
CMD_WIFI_CONNECT, // (3) connect to AP (not honored in this implementation)
CMD_MQTT_SETUP,
CMD_MQTT_CONNECT,
CMD_MQTT_DISCONNECT,
CMD_MQTT_PUBLISH,
CMD_MQTT_SUBSCRIBE,
CMD_MQTT_LWT,
CMD_MQTT_EVENTS,
CMD_REST_SETUP, // (11)
CMD_REST_REQUEST,
CMD_REST_SETHEADER,
CMD_REST_EVENTS
} CmdName;
typedef uint32_t (*cmdfunc_t)(CmdPacket *cmd);
typedef struct {
CmdName sc_name;
cmdfunc_t sc_function;
} CmdList;
void CMD_parse_packet(uint8_t *buf, short len);
// Responses
// Start a response, returns the partial CRC
uint16_t CMD_ResponseStart(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc);
// Adds data to a response, returns the partial CRC
uint16_t CMD_ResponseBody(uint16_t crc_in, uint8_t* data, short len);
// Ends a response
void CMD_ResponseEnd(uint16_t crc);
void CMD_Response(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc, CmdArg* args[]);
// Requests
// Fill out a CmdRequest struct given a CmdPacket
void CMD_Request(CmdRequest *req, CmdPacket* cmd);
// Return the number of arguments given a request
uint32_t CMD_GetArgc(CmdRequest *req);
// Return the length of the next argument
uint16_t CMD_ArgLen(CmdRequest *req);
// Copy next arg from request into the data pointer, returns 0 on success, -1 on error
int32_t CMD_PopArg(CmdRequest *req, void *data, uint16_t len);
#endif

@ -0,0 +1,79 @@
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
//
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh
#include "esp8266.h"
#include "cmd.h"
#include "rest.h"
#include "crc16.h"
#include "serbridge.h"
#include "uart.h"
#include "cgiwifi.h"
static uint32_t ICACHE_FLASH_ATTR CMD_Null(CmdPacket *cmd);
static uint32_t ICACHE_FLASH_ATTR CMD_IsReady(CmdPacket *cmd);
static uint32_t ICACHE_FLASH_ATTR CMD_WifiConnect(CmdPacket *cmd);
// Command dispatch table for serial -> ESP commands
const CmdList commands[] = {
{CMD_NULL, CMD_Null},
{CMD_RESET, CMD_Null},
{CMD_IS_READY, CMD_IsReady},
{CMD_WIFI_CONNECT, CMD_WifiConnect},
/*
{CMD_MQTT_SETUP, MQTTAPP_Setup},
{CMD_MQTT_CONNECT, MQTTAPP_Connect},
{CMD_MQTT_DISCONNECT, MQTTAPP_Disconnect},
{CMD_MQTT_PUBLISH, MQTTAPP_Publish},
{CMD_MQTT_SUBSCRIBE , MQTTAPP_Subscribe},
{CMD_MQTT_LWT, MQTTAPP_Lwt},
*/
{CMD_REST_SETUP, REST_Setup},
{CMD_REST_REQUEST, REST_Request},
{CMD_REST_SETHEADER, REST_SetHeader},
{CMD_NULL, NULL}
};
// Command handler for IsReady (healthcheck) command
static uint32_t ICACHE_FLASH_ATTR
CMD_IsReady(CmdPacket *cmd) {
os_printf("CMD: Check ready\n");
return 1;
}
// Command handler for Null command
static uint32_t ICACHE_FLASH_ATTR
CMD_Null(CmdPacket *cmd) {
os_printf("CMD: NULL/unsupported command\n");
return 1;
}
static uint8_t lastWifiStatus;
static uint32_t wifiCallback;
// Callback from wifi subsystem to notify us of status changes
static void ICACHE_FLASH_ATTR
CMD_WifiCb(uint8_t wifiStatus) {
if (wifiStatus != lastWifiStatus){
lastWifiStatus = wifiStatus;
if (wifiCallback) {
uint8_t status = wifiStatus == wifiGotIP ? 5 : 1;
uint16_t crc = CMD_ResponseStart(CMD_WIFI_CONNECT, wifiCallback, 0, 1);
crc = CMD_ResponseBody(crc, (uint8_t*)&status, 1);
CMD_ResponseEnd(crc);
}
}
}
// Command handler for Wifi connect command
static uint32_t ICACHE_FLASH_ATTR
CMD_WifiConnect(CmdPacket *cmd) {
if(cmd->argc != 2 || cmd->callback == 0)
return 0xFFFFFFFF;
wifiStatusCb = CMD_WifiCb; // register our callback with wifi subsystem
wifiCallback = cmd->callback; // save the MCU's callback
return 1;
}

@ -0,0 +1,394 @@
/*
* api.c
*
* Created on: Mar 4, 2015
* Author: Minh
*/
#include "esp8266.h"
#include "rest.h"
#include "cmd.h"
extern uint8_t ICACHE_FLASH_ATTR UTILS_StrToIP(const char* str, void *ip);
static void ICACHE_FLASH_ATTR
tcpclient_discon_cb(void *arg) {
//struct espconn *pespconn = (struct espconn *)arg;
//RestClient* client = (RestClient *)pespconn->reverse;
}
static void ICACHE_FLASH_ATTR
tcpclient_recv(void *arg, char *pdata, unsigned short len) {
uint8_t currentLineIsBlank = 0;
uint8_t httpBody = 0;
uint8_t inStatus = 0;
char statusCode[4];
int i = 0, j;
uint32_t code = 0;
uint16_t crc;
struct espconn *pCon = (struct espconn*)arg;
RestClient *client = (RestClient *)pCon->reverse;
for(j=0 ;j<len; j++){
char c = pdata[j];
if(c == ' ' && !inStatus){
inStatus = 1;
}
if(inStatus && i < 3 && c != ' '){
statusCode[i] = c;
i++;
}
if(i == 3){
statusCode[i] = '\0';
code = atoi(statusCode);
}
if(httpBody){
//only write response if its not null
uint32_t body_len = len - j;
os_printf("REST: status=%ld, body=%ld\n", code, body_len);
if(body_len == 0){
crc = CMD_ResponseStart(CMD_REST_EVENTS, client->resp_cb, code, 0);
} else {
crc = CMD_ResponseStart(CMD_REST_EVENTS, client->resp_cb, code, 1);
crc = CMD_ResponseBody(crc, (uint8_t*)(pdata+j), body_len);
}
CMD_ResponseEnd(crc);
break;
} else {
if (c == '\n' && currentLineIsBlank) {
httpBody = true;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
} else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
//if(client->security)
// espconn_secure_disconnect(client->pCon);
//else
espconn_disconnect(client->pCon);
}
static void ICACHE_FLASH_ATTR
tcpclient_sent_cb(void *arg) {
//struct espconn *pCon = (struct espconn *)arg;
//RestClient* client = (RestClient *)pCon->reverse;
os_printf("REST: Sent\n");
}
static void ICACHE_FLASH_ATTR
tcpclient_connect_cb(void *arg) {
struct espconn *pCon = (struct espconn *)arg;
RestClient* client = (RestClient *)pCon->reverse;
espconn_regist_disconcb(client->pCon, tcpclient_discon_cb);
espconn_regist_recvcb(client->pCon, tcpclient_recv);////////
espconn_regist_sentcb(client->pCon, tcpclient_sent_cb);///////
//if(client->security){
// espconn_secure_sent(client->pCon, client->data, client->data_len);
//}
//else{
espconn_sent(client->pCon, client->data, client->data_len);
//}
}
static void ICACHE_FLASH_ATTR
tcpclient_recon_cb(void *arg, sint8 errType) {
//struct espconn *pCon = (struct espconn *)arg;
//RestClient* client = (RestClient *)pCon->reverse;
}
static void ICACHE_FLASH_ATTR
rest_dns_found(const char *name, ip_addr_t *ipaddr, void *arg) {
struct espconn *pConn = (struct espconn *)arg;
RestClient* client = (RestClient *)pConn->reverse;
if(ipaddr == NULL) {
os_printf("REST DNS: Got no ip, try to reconnect\n");
return;
}
os_printf("REST DNS: found ip %d.%d.%d.%d\n",
*((uint8 *) &ipaddr->addr),
*((uint8 *) &ipaddr->addr + 1),
*((uint8 *) &ipaddr->addr + 2),
*((uint8 *) &ipaddr->addr + 3));
if(client->ip.addr == 0 && ipaddr->addr != 0) {
os_memcpy(client->pCon->proto.tcp->remote_ip, &ipaddr->addr, 4);
//if(client->security){
// espconn_secure_connect(client->pCon);
//}
//else {
espconn_connect(client->pCon);
//}
os_printf("REST: connecting...\n");
}
}
uint32_t ICACHE_FLASH_ATTR
REST_Setup(CmdPacket *cmd) {
CmdRequest req;
RestClient *client;
uint8_t *rest_host;
uint16_t len;
uint32_t port, security;
// start parsing the command
CMD_Request(&req, cmd);
os_printf("REST: setup argc=%ld\n", CMD_GetArgc(&req));
if(CMD_GetArgc(&req) != 3)
return 0;
// get the hostname
len = CMD_ArgLen(&req);
os_printf("REST: len=%d\n", len);
if (len > 128) return 0; // safety check
rest_host = (uint8_t*)os_zalloc(len + 1);
if (CMD_PopArg(&req, rest_host, len)) return 0;
rest_host[len] = 0;
os_printf("REST: setup host=%s", rest_host);
// get the port
if (CMD_PopArg(&req, (uint8_t*)&port, 4)) {
os_free(rest_host);
return 0;
}
os_printf(" port=%ld", port);
// get the security mode
if (CMD_PopArg(&req, (uint8_t*)&security, 4)) {
os_free(rest_host);
return 0;
}
os_printf(" security=%ld\n", security);
// allocate a connection structure
client = (RestClient*)os_zalloc(sizeof(RestClient));
os_memset(client, 0, sizeof(RestClient));
if(client == NULL)
return 0;
os_printf("REST: setup host=%s port=%ld security=%ld\n", rest_host, port, security);
client->resp_cb = cmd->callback;
client->host = rest_host;
client->port = port;
client->security = security;
client->ip.addr = 0;
client->data = (uint8_t*)os_zalloc(1024);
client->header = (uint8_t*)os_zalloc(4);
client->header[0] = 0;
client->content_type = (uint8_t*)os_zalloc(22);
os_sprintf((char *)client->content_type, "x-www-form-urlencoded");
client->content_type[21] = 0;
client->user_agent = (uint8_t*)os_zalloc(9);
os_sprintf((char *)client->user_agent, "esp-link");
client->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn));
client->pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp));
client->pCon->type = ESPCONN_TCP;
client->pCon->state = ESPCONN_NONE;
client->pCon->proto.tcp->local_port = espconn_port();
client->pCon->proto.tcp->remote_port = client->port;
client->pCon->reverse = client;
return (uint32_t)client;
}
uint32_t ICACHE_FLASH_ATTR
REST_SetHeader(CmdPacket *cmd) {
CmdRequest req;
RestClient *client;
uint16_t len;
uint32_t header_index, client_ptr = 0;
CMD_Request(&req, cmd);
if(CMD_GetArgc(&req) != 3)
return 0;
// Get client -- TODO: Whoa, this is totally unsafe!
if (CMD_PopArg(&req, (uint8_t*)&client_ptr, 4)) return 0;
client = (RestClient*)client_ptr;
// Get header selector
if (CMD_PopArg(&req, (uint8_t*)&header_index, 4)) return 0;
// Get header value
len = CMD_ArgLen(&req);
if (len > 256) return 0; //safety check
switch(header_index) {
case HEADER_GENERIC:
if(client->header)
os_free(client->header);
client->header = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, (uint8_t*)client->header, len);
client->header[len] = 0;
os_printf("REST: Set header: %s\r\n", client->header);
break;
case HEADER_CONTENT_TYPE:
if(client->content_type)
os_free(client->content_type);
client->content_type = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, (uint8_t*)client->content_type, len);
client->content_type[len] = 0;
os_printf("REST: Set content_type: %s\r\n", client->content_type);
break;
case HEADER_USER_AGENT:
if(client->user_agent)
os_free(client->user_agent);
client->user_agent = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, (uint8_t*)client->user_agent, len);
client->user_agent[len] = 0;
os_printf("REST: Set user_agent: %s\r\n", client->user_agent);
break;
}
return 1;
}
uint32_t ICACHE_FLASH_ATTR
REST_Request(CmdPacket *cmd) {
CmdRequest req;
RestClient *client;
uint16_t len, realLen = 0;
uint32_t client_ptr;
uint8_t *body = NULL;
uint8_t method[16];
uint8_t path[1024];
CMD_Request(&req, cmd);
if(CMD_GetArgc(&req) <3)
return 0;
// Get client -- TODO: Whoa, this is totally unsafe!
if(CMD_PopArg(&req, (uint8_t*)&client_ptr, 4)) return 0;
client = (RestClient*)client_ptr;
// Get HTTP method
len = CMD_ArgLen(&req);
if (len > 15) return 0;
CMD_PopArg(&req, method, len);
method[len] = 0;
// Get HTTP path
len = CMD_ArgLen(&req);
if (len > 1023) return 0;
CMD_PopArg(&req, path, len);
path[len] = 0;
// Get HTTP body
if (CMD_GetArgc(&req) == 3){
realLen = 0;
len = 0;
} else {
CMD_PopArg(&req, (uint8_t*)&realLen, 4);
len = CMD_ArgLen(&req);
if (len > 2048 || realLen > len) return 0;
body = (uint8_t*)os_zalloc(len + 1);
CMD_PopArg(&req, body, len);
body[len] = 0;
}
client->pCon->state = ESPCONN_NONE;
os_printf("REST: method: %s, path: %s\n", method, path);
client->data_len = os_sprintf((char*)client->data, "%s %s HTTP/1.1\r\n"
"Host: %s\r\n"
"%s"
"Content-Length: %d\r\n"
"Connection: close\r\n"
"Content-Type: %s\r\n"
"User-Agent: %s\r\n\r\n",
method, path,
client->host,
client->header,
realLen,
client->content_type,
client->user_agent);
if (realLen > 0){
os_memcpy(client->data + client->data_len, body, realLen);
client->data_len += realLen;
//os_sprintf(client->data + client->data_len, "\r\n\r\n");
//client->data_len += 4;
}
client->pCon->state = ESPCONN_NONE;
espconn_regist_connectcb(client->pCon, tcpclient_connect_cb);
espconn_regist_reconcb(client->pCon, tcpclient_recon_cb);
if(UTILS_StrToIP((char *)client->host, &client->pCon->proto.tcp->remote_ip)) {
os_printf("REST: Connect to ip %s:%ld\n",client->host, client->port);
//if(client->security){
// espconn_secure_connect(client->pCon);
//}
//else {
espconn_connect(client->pCon);
//}
} else {
os_printf("REST: Connect to host %s:%ld\n", client->host, client->port);
espconn_gethostbyname(client->pCon, (char *)client->host, &client->ip, rest_dns_found);
}
if(body) os_free(body);
return 1;
}
uint8_t ICACHE_FLASH_ATTR
UTILS_StrToIP(const char* str, void *ip)
{
/* The count of the number of bytes processed. */
int i;
/* A pointer to the next digit to process. */
const char * start;
start = str;
for (i = 0; i < 4; i++) {
/* The digit being processed. */
char c;
/* The value of this byte. */
int n = 0;
while (1) {
c = * start;
start++;
if (c >= '0' && c <= '9') {
n *= 10;
n += c - '0';
}
/* We insist on stopping at "." if we are still parsing
the first, second, or third numbers. If we have reached
the end of the numbers, we will allow any character. */
else if ((i < 3 && c == '.') || i == 3) {
break;
}
else {
return 0;
}
}
if (n >= 256) {
return 0;
}
((uint8_t*)ip)[i] = n;
}
return 1;
}

@ -0,0 +1,39 @@
/*
* api.h
*
* Created on: Mar 4, 2015
* Author: Minh
*/
#ifndef MODULES_API_H_
#define MODULES_API_H_
#include "c_types.h"
#include "ip_addr.h"
#include "cmd.h"
typedef enum {
HEADER_GENERIC = 0,
HEADER_CONTENT_TYPE,
HEADER_USER_AGENT
} HEADER_TYPE;
typedef struct {
uint8_t *host;
uint32_t port;
uint32_t security;
ip_addr_t ip;
struct espconn *pCon;
uint8_t *header;
uint8_t *data;
uint32_t data_len;
uint8_t *content_type;
uint8_t *user_agent;
uint32_t resp_cb;
} RestClient;
uint32_t REST_Setup(CmdPacket *cmd);
uint32_t REST_Request(CmdPacket *cmd);
uint32_t REST_SetHeader(CmdPacket *cmd);
#endif /* MODULES_INCLUDE_API_H_ */

@ -0,0 +1,315 @@
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
//
// TCP client library allowing uControllers attached to the serial port to send commands
// to open/close TCP connections and send/recv data.
// The serial protocol is described in https://gist.github.com/tve/a46c44bf1f6b42bc572e
#include <esp8266.h>
#include "config.h"
#include "uart.h"
#include "serled.h"
#include "tcpclient.h"
// max number of channels the client can open
#define MAX_CHAN MAX_TCP_CHAN
// 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;
static TcpConn tcpConn[MAX_CHAN];
// forward declarations
static void tcpConnFree(TcpConn* tci);
static TcpConn* tcpConnAlloc(uint8_t chan);
static void tcpDoSend(TcpConn *tci);
static void tcpConnectCb(void *arg);
static void tcpDisconCb(void *arg);
static void tcpResetCb(void *arg, sint8 err);
static void tcpSentCb(void *arg);
static void tcpRecvCb(void *arg, char *data, uint16_t len);
//===== allocate / free connections
// Allocate a new connection dynamically and return it. Returns NULL if buf alloc failed
static TcpConn* ICACHE_FLASH_ATTR
tcpConnAlloc(uint8_t chan) {
TcpConn *tci = tcpConn+chan;
if (tci->state != TCP_idle && tci->conn != NULL) return tci;
// malloc and return espconn struct
tci->conn = os_malloc(sizeof(struct espconn));
if (tci->conn == NULL) goto fail;
memset(tci->conn, 0, sizeof(struct espconn));
// malloc esp_tcp struct
tci->tcp = os_malloc(sizeof(esp_tcp));
if (tci->tcp == NULL) goto fail;
memset(tci->tcp, 0, sizeof(esp_tcp));
// common init
tci->state = TCP_dns;
tci->conn->type = ESPCONN_TCP;
tci->conn->state = ESPCONN_NONE;
tci->conn->proto.tcp = tci->tcp;
tci->tcp->remote_port = 80;
espconn_regist_connectcb(tci->conn, tcpConnectCb);
espconn_regist_reconcb(tci->conn, tcpResetCb);
espconn_regist_sentcb(tci->conn, tcpSentCb);
espconn_regist_recvcb(tci->conn, tcpRecvCb);
espconn_regist_disconcb(tci->conn, tcpDisconCb);
tci->conn->reverse = tci;
return tci;
fail:
tcpConnFree(tci);
return NULL;
}
// Free a connection dynamically.
static void ICACHE_FLASH_ATTR
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
// 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);
}
//===== Connect / disconnect
// Connected callback
static void ICACHE_FLASH_ATTR
tcpConnectCb(void *arg) {
struct espconn *conn = arg;
TcpConn *tci = conn->reverse;
os_printf("TCP connect CB (%p %p)\n", arg, tci);
tci->state = TCP_data;
// send any buffered data
if (tci->txBuf != NULL && tci->txBufLen > 0) tcpDoSend(tci);
// reply to serial
char buf[6];
short l = os_sprintf(buf, "\n~@%dC\n", tci-tcpConn);
uart0_tx_buffer(buf, l);
}
// Disconnect callback
static void ICACHE_FLASH_ATTR tcpDisconCb(void *arg) {
struct espconn *conn = arg;
TcpConn *tci = conn->reverse;
os_printf("TCP disconnect CB (%p %p)\n", arg, tci);
// notify to serial
char buf[6];
short l = os_sprintf(buf, "\n~@%dZ\n", tci-tcpConn);
uart0_tx_buffer(buf, l);
// free
tcpConnFree(tci);
}
// Connection reset callback
static void ICACHE_FLASH_ATTR tcpResetCb(void *arg, sint8 err) {
struct espconn *conn = arg;
TcpConn *tci = conn->reverse;
os_printf("TCP reset CB (%p %p) err=%d\n", arg, tci, err);
// notify to serial
char buf[6];
short l = os_sprintf(buf, "\n~@%dZ\n", tci-tcpConn);
uart0_tx_buffer(buf, l);
// free
tcpConnFree(tci);
}
//===== Sending and receiving
// Send the next buffer (assumes that the connection is in a state that allows it)
static void ICACHE_FLASH_ATTR
tcpDoSend(TcpConn *tci) {
sint8 err = espconn_sent(tci->conn, (uint8*)tci->txBuf, tci->txBufLen);
if (err == ESPCONN_OK) {
// send successful
os_printf("TCP sent (%p %p)\n", tci->conn, tci);
tci->txBuf[tci->txBufLen] = 0; os_printf("TCP data: %s\n", tci->txBuf);
tci->txBufSent = tci->txBuf;
tci->txBuf = NULL;
tci->txBufLen = 0;
} else {
// send error, leave as-is and try again later...
os_printf("TCP send err (%p %p) %d\n", tci->conn, tci, err);
}
}
// 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) {
uint8_t chan;
for (chan=0; chan<MAX_CHAN && tcpConn+chan!=tci; chan++)
if (chan >= MAX_CHAN) return; // oops!?
char buf[6];
short l = os_sprintf(buf, "\n~%d", chan);
uart0_tx_buffer(buf, l);
uart0_tx_buffer(data, len);
uart0_tx_buffer("\0\n", 2);
}
serledFlash(50); // short blink on serial LED
}
void ICACHE_FLASH_ATTR
tcpClientSendChar(uint8_t chan, char c) {
TcpConn *tci = tcpConn+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
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;
}
}
void ICACHE_FLASH_ATTR
tcpClientSendPush(uint8_t chan) {
TcpConn *tci = tcpConn+chan;
if (tci->state != TCP_data) return; // no active connection on this channel
if (tci->txBuf == NULL || tci->txBufLen == 0) return; // no chars accumulated to send
if (tci->txBufSent != NULL) return; // already got a send in progress
tcpDoSend(tci);
}
//===== Command parsing
// Perform a TCP command: parse the command and do the right thing.
// Returns true on success.
bool ICACHE_FLASH_ATTR
tcpClientCommand(uint8_t chan, char cmd, char *cmdBuf) {
TcpConn *tci;
char *hostname;
char *port;
// copy the command so we can modify it
char buf[128];
os_strncpy(buf, cmdBuf, 128);
buf[127] = 0;
switch (cmd) {
//== TCP Connect command
case 'T':
hostname = buf;
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(chan);
if (tci == NULL) break;
tci->state = TCP_dns;
tci->tcp->remote_port = portInt;
// start the DNS resolution
os_printf("TCP %p resolving %s for chan %d (conn=%p)\n", tci, hostname, chan ,tci->conn);
ip_addr_t ip;
err_t err = espconn_gethostbyname(tci->conn, hostname, &ip, tcpClientHostnameCb);
if (err == ESPCONN_OK) {
// 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;
}
return true;
//== TCP Close/disconnect command
case 'C':
os_printf("TCP closing chan %d\n", chan);
tci = tcpConn+chan;
if (tci->state > TCP_idle) {
tci->state = TCP_idle; // hackish...
espconn_disconnect(tci->conn);
}
break;
}
return false;
}

@ -0,0 +1,16 @@
#ifndef __TCP_CLIENT_H__
#define __TCP_CLIENT_H__
// max number of channels the client can open
#define MAX_TCP_CHAN 8
// Parse and perform the command, 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);
// Enqueue the buffered characters for transmission on the specified channel
void tcpClientSendPush(uint8_t chan);
#endif /* __TCP_CLIENT_H__ */

@ -26,18 +26,42 @@
</div></div>
</div>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2"><div class="card">
<h1>Wifi summary</h1>
<div id="wifi-spinner" class="spinner spinner-small"></div>
<table id="wifi-table" class="pure-table pure-table-horizontal" hidden><tbody>
<tr><td>WiFi mode</td><td id="wifi-mode"></td></tr>
<tr><td>Wifi channel</td><td id="wifi-chan"></td></tr>
<tr><td>Configured network</td><td id="wifi-ssid"></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>Configured hostname</td><td id="wifi-hostname"></td></tr>
</tbody> </table>
</div></div>
<div class="pure-u-1 pure-u-md-1-2">
<div class="card">
<h1>Wifi summary</h1>
<div id="wifi-spinner" class="spinner spinner-small"></div>
<table id="wifi-table" class="pure-table pure-table-horizontal" hidden><tbody>
<tr><td>WiFi mode</td><td id="wifi-mode"></td></tr>
<tr><td>Configured network</td><td id="wifi-ssid"></td></tr>
<tr><td>Wifi channel</td><td id="wifi-chan"></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>Configured hostname</td><td id="wifi-hostname"></td></tr>
</tbody> </table>
</div>
<div class="card">
<h1>TCP client</h1>
<form action="#" id="tcpform" class="pure-form">
<legend>TCP client support in esp-link</legend>
<div class="form-horizontal">
<input type="checkbox" name="tcp_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>
</div>
<div class="pure-form-stacked">
<label>API key/passwd</label>
<input type="password" name="api_key"/>
</div>
<button id="tcp-button" type="submit"
class="pure-button button-primary">Change!</button>
</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>
@ -46,6 +70,8 @@
</fieldset>
</div></div>
</div>
<div class="pure-g">
</div>
</div>
</div>
</div>
@ -54,6 +80,8 @@
onLoad(function() {
fetchPins();
getWifiInfo();
fetchTcpClient();
bnd($("#tcpform"), "submit", changeTcpClient);
});
</script>
</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.
}
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.
}
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
//the result headers and data.
@ -419,7 +419,7 @@ static void ICACHE_FLASH_ATTR httpdParseHeader(char *h, HttpdConnData *conn) {
if (conn->getArgs!=0) {
*conn->getArgs=0;
conn->getArgs++;
os_printf("%s GET args = %s\n", connStr, conn->getArgs);
os_printf("%s args = %s\n", connStr, conn->getArgs);
} else {
conn->getArgs=NULL;
}

@ -25,64 +25,70 @@ static int console_pos; // offset since reset of buffer
static void ICACHE_FLASH_ATTR
console_write(char c) {
console_buf[console_wr] = c;
console_wr = (console_wr+1) % BUF_MAX;
if (console_wr == console_rd) {
// full, we write anyway and loose the oldest char
console_rd = (console_rd+1) % BUF_MAX; // full, eat first char
console_pos++;
}
console_buf[console_wr] = c;
console_wr = (console_wr+1) % BUF_MAX;
if (console_wr == console_rd) {
// full, we write anyway and loose the oldest char
console_rd = (console_rd+1) % BUF_MAX; // full, eat first char
console_pos++;
}
}
#if 0
// return previous character in console, 0 if at start
static char ICACHE_FLASH_ATTR
console_prev(void) {
if (console_wr == console_rd) return 0;
return console_buf[(console_wr-1+BUF_MAX)%BUF_MAX];
if (console_wr == console_rd) return 0;
return console_buf[(console_wr-1+BUF_MAX)%BUF_MAX];
}
#endif
void ICACHE_FLASH_ATTR
console_write_char(char c) {
<<<<<<< HEAD
//if (c == '\n' && console_prev() != '\r') console_write('\r'); // does more harm than good
console_write(c);
=======
//if (c == '\n' && console_prev() != '\r') console_write('\r'); // does more harm than good
console_write(c);
>>>>>>> master
}
int ICACHE_FLASH_ATTR
ajaxConsoleReset(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
jsonHeader(connData, 200);
console_rd = console_wr = console_pos = 0;
serbridgeReset();
return HTTPD_CGI_DONE;
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
jsonHeader(connData, 200);
console_rd = console_wr = console_pos = 0;
serbridgeReset();
return HTTPD_CGI_DONE;
}
int ICACHE_FLASH_ATTR
ajaxConsoleBaud(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
char buff[512];
int len, status = 400;
len = httpdFindArg(connData->getArgs, "rate", buff, sizeof(buff));
if (len > 0) {
int rate = atoi(buff);
if (rate >= 9600 && rate <= 1000000) {
uart0_baud(rate);
flashConfig.baud_rate = rate;
status = configSave() ? 200 : 400;
}
} else if (connData->requestType == HTTPD_METHOD_GET) {
status = 200;
}
jsonHeader(connData, status);
os_sprintf(buff, "{\"rate\": %ld}", flashConfig.baud_rate);
httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE;
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
char buff[512];
int len, status = 400;
len = httpdFindArg(connData->getArgs, "rate", buff, sizeof(buff));
if (len > 0) {
int rate = atoi(buff);
if (rate >= 9600 && rate <= 1000000) {
uart0_baud(rate);
flashConfig.baud_rate = rate;
status = configSave() ? 200 : 400;
}
} else if (connData->requestType == HTTPD_METHOD_GET) {
status = 200;
}
jsonHeader(connData, status);
os_sprintf(buff, "{\"rate\": %ld}", flashConfig.baud_rate);
httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE;
}
int ICACHE_FLASH_ATTR
ajaxConsole(HttpdConnData *connData) {
<<<<<<< HEAD
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
char buff[2048];
int len; // length of text in buff
@ -126,11 +132,56 @@ ajaxConsole(HttpdConnData *connData) {
os_strcpy(buff+len, "\"}"); len+=2;
httpdSend(connData, buff, len);
return HTTPD_CGI_DONE;
=======
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
char buff[2048];
int len; // length of text in buff
int console_len = (console_wr+BUF_MAX-console_rd) % BUF_MAX; // num chars in console_buf
int start = 0; // offset onto console_wr to start sending out chars
jsonHeader(connData, 200);
// figure out where to start in buffer based on URI param
len = httpdFindArg(connData->getArgs, "start", buff, sizeof(buff));
if (len > 0) {
start = atoi(buff);
if (start < console_pos) {
start = 0;
} else if (start >= console_pos+console_len) {
start = console_len;
} else {
start = start - console_pos;
}
}
// start outputting
len = os_sprintf(buff, "{\"len\":%d, \"start\":%d, \"text\": \"",
console_len-start, console_pos+start);
int rd = (console_rd+start) % BUF_MAX;
while (len < 2040 && rd != console_wr) {
uint8_t c = console_buf[rd];
if (c == '\\' || c == '"') {
buff[len++] = '\\';
buff[len++] = c;
} else if (c == '\r') {
// this is crummy, but browsers display a newline for \r\n sequences
} else if (c < ' ') {
len += os_sprintf(buff+len, "\\u%04x", c);
} else {
buff[len++] = c;
}
rd = (rd + 1) % BUF_MAX;
}
os_strcpy(buff+len, "\"}"); len+=2;
httpdSend(connData, buff, len);
return HTTPD_CGI_DONE;
>>>>>>> master
}
void ICACHE_FLASH_ATTR consoleInit() {
console_wr = 0;
console_rd = 0;
console_wr = 0;
console_rd = 0;
}

@ -0,0 +1,78 @@
/*
* Copyright (c) 2005, Swedish Institute of Computer Science
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the Contiki operating system.
*
*/
/** \addtogroup crc16
* @{ */
/**
* \file
* Implementation of the CRC16 calculcation
* \author
* Adam Dunkels <adam@sics.se>
*
*/
/* CITT CRC16 polynomial ^16 + ^12 + ^5 + 1 */
/*---------------------------------------------------------------------------*/
unsigned short
crc16_add(unsigned char b, unsigned short acc)
{
/*
acc = (unsigned char)(acc >> 8) | (acc << 8);
acc ^= b;
acc ^= (unsigned char)(acc & 0xff) >> 4;
acc ^= (acc << 8) << 4;
acc ^= ((acc & 0xff) << 4) << 1;
*/
acc ^= b;
acc = (acc >> 8) | (acc << 8);
acc ^= (acc & 0xff00) << 4;
acc ^= (acc >> 8) >> 4;
acc ^= (acc & 0xff00) >> 5;
return acc;
}
/*---------------------------------------------------------------------------*/
unsigned short
crc16_data(const unsigned char *data, int len, unsigned short acc)
{
int i;
for(i = 0; i < len; ++i) {
acc = crc16_add(*data, acc);
++data;
}
return acc;
}
/*---------------------------------------------------------------------------*/
/** @} */

@ -0,0 +1,100 @@
/*
* Copyright (c) 2005, Swedish Institute of Computer Science
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This file is part of the Contiki operating system.
*
*/
/**
* \file
* Header file for the CRC16 calculcation
* \author
* Adam Dunkels <adam@sics.se>
*
*/
/** \addtogroup lib
* @{ */
/**
* \defgroup crc16 Cyclic Redundancy Check 16 (CRC16) calculation
*
* The Cyclic Redundancy Check 16 is a hash function that produces a
* checksum that is used to detect errors in transmissions. The CRC16
* calculation module is an iterative CRC calculator that can be used
* to cumulatively update a CRC checksum for every incoming byte.
*
* @{
*/
#ifndef CRC16_H_
#define CRC16_H_
#ifdef __cplusplus
extern "C" {
#endif
/**
* \brief Update an accumulated CRC16 checksum with one byte.
* \param b The byte to be added to the checksum
* \param crc The accumulated CRC that is to be updated.
* \return The updated CRC checksum.
*
* This function updates an accumulated CRC16 checksum
* with one byte. It can be used as a running checksum, or
* to checksum an entire data block.
*
* \note The algorithm used in this implementation is
* tailored for a running checksum and does not perform as
* well as a table-driven algorithm when checksumming an
* entire data block.
*
*/
unsigned short crc16_add(unsigned char b, unsigned short crc);
/**
* \brief Calculate the CRC16 over a data area
* \param data Pointer to the data
* \param datalen The length of the data
* \param acc The accumulated CRC that is to be updated (or zero).
* \return The CRC16 checksum.
*
* This function calculates the CRC16 checksum of a data area.
*
* \note The algorithm used in this implementation is
* tailored for a running checksum and does not perform as
* well as a table-driven algorithm when checksumming an
* entire data block.
*/
unsigned short crc16_data(const unsigned char *data, int datalen,
unsigned short acc);
#ifdef __cplusplus
}
#endif
#endif /* CRC16_H_ */
/** @} */
/** @} */

@ -9,79 +9,30 @@
#include "gpio.h"
#include "uart.h"
#include "crc16.h"
#include "serbridge.h"
#include "serled.h"
#include "config.h"
#include "console.h"
#include "cmd.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);
extern uint8_t slip_disabled; // disable slip to allow flashing of attached MCU
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) {
for (int i=0; i<MAX_CONN; i++) {
if (connData[i].conn == (struct espconn *)arg) {
return &connData[i];
}
}
//os_printf("FindConnData: Huh? Couldn't find connection for %p\n", arg);
return NULL; // not found, may be closed already...
}
// 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;
struct espconn *conn = arg;
return arg == NULL ? NULL : (serbridgeConnData *)conn->reverse;
}
//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
@ -102,224 +53,288 @@ enum { TN_normal, TN_iac, TN_will, TN_start, TN_end, TN_comPort, TN_setControl }
static uint8_t ICACHE_FLASH_ATTR
telnetUnwrap(uint8_t *inBuf, int len, uint8_t state)
{
for (int i=0; i<len; i++) {
uint8_t c = inBuf[i];
switch (state) {
default:
case TN_normal:
if (c == IAC) state = TN_iac; // escape char: see what's next
else uart0_write_char(c); // regular char
break;
case TN_iac:
switch (c) {
case IAC: // second escape -> write one to outbuf and go normal again
state = TN_normal;
uart0_write_char(c);
break;
case WILL: // negotiation
state = TN_will;
break;
case SB: // command sequence begin
state = TN_start;
break;
case SE: // command sequence end
state = TN_normal;
break;
default: // not sure... let's ignore
uart0_write_char(IAC);
uart0_write_char(c);
}
break;
case TN_will:
state = TN_normal; // yes, we do COM port options, let's go back to normal
break;
case TN_start: // in command seq, now comes the type of cmd
if (c == ComPortOpt) state = TN_comPort;
else state = TN_end; // an option we don't know, skip 'til the end seq
break;
case TN_end: // wait for end seq
if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq
break;
case TN_comPort:
if (c == SetControl) state = TN_setControl;
else state = TN_end;
break;
case TN_setControl: // switch control line and delay a tad
switch (c) {
case DTR_ON:
if (mcu_reset_pin >= 0) {
os_printf("MCU reset gpio%d\n", mcu_reset_pin);
GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L);
} else os_printf("MCU reset: no pin\n");
break;
case DTR_OFF:
if (mcu_reset_pin >= 0) {
GPIO_OUTPUT_SET(mcu_reset_pin, 1);
os_delay_us(100L);
}
break;
case RTS_ON:
if (mcu_isp_pin >= 0) {
os_printf("MCU ISP gpio%d\n", mcu_isp_pin);
GPIO_OUTPUT_SET(mcu_isp_pin, 0);
os_delay_us(100L);
} else os_printf("MCU isp: no pin\n");
break;
case RTS_OFF:
if (mcu_isp_pin >= 0) {
GPIO_OUTPUT_SET(mcu_isp_pin, 1);
os_delay_us(100L);
}
break;
}
state = TN_end;
break;
}
}
return state;
for (int i=0; i<len; i++) {
uint8_t c = inBuf[i];
switch (state) {
default:
case TN_normal:
if (c == IAC) state = TN_iac; // escape char: see what's next
else uart0_write_char(c); // regular char
break;
case TN_iac:
switch (c) {
case IAC: // second escape -> write one to outbuf and go normal again
state = TN_normal;
uart0_write_char(c);
break;
case WILL: // negotiation
state = TN_will;
break;
case SB: // command sequence begin
state = TN_start;
break;
case SE: // command sequence end
state = TN_normal;
break;
default: // not sure... let's ignore
uart0_write_char(IAC);
uart0_write_char(c);
}
break;
case TN_will:
state = TN_normal; // yes, we do COM port options, let's go back to normal
break;
case TN_start: // in command seq, now comes the type of cmd
if (c == ComPortOpt) state = TN_comPort;
else state = TN_end; // an option we don't know, skip 'til the end seq
break;
case TN_end: // wait for end seq
if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq
break;
case TN_comPort:
if (c == SetControl) state = TN_setControl;
else state = TN_end;
break;
case TN_setControl: // switch control line and delay a tad
switch (c) {
case DTR_ON:
if (mcu_reset_pin >= 0) {
os_printf("MCU reset gpio%d\n", mcu_reset_pin);
GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L);
} else os_printf("MCU reset: no pin\n");
break;
case DTR_OFF:
if (mcu_reset_pin >= 0) {
GPIO_OUTPUT_SET(mcu_reset_pin, 1);
os_delay_us(100L);
}
break;
case RTS_ON:
if (mcu_isp_pin >= 0) {
os_printf("MCU ISP gpio%d\n", mcu_isp_pin);
GPIO_OUTPUT_SET(mcu_isp_pin, 0);
os_delay_us(100L);
} else os_printf("MCU isp: no pin\n");
slip_disabled++;
break;
case RTS_OFF:
if (mcu_isp_pin >= 0) {
GPIO_OUTPUT_SET(mcu_isp_pin, 1);
os_delay_us(100L);
}
if (slip_disabled > 0) slip_disabled--;
break;
}
state = TN_end;
break;
}
}
return state;
}
// Generate a reset pulse for the attached microcontroller
void ICACHE_FLASH_ATTR serbridgeReset() {
if (mcu_reset_pin >= 0) {
os_printf("MCU reset gpio%d\n", mcu_reset_pin);
GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L);
GPIO_OUTPUT_SET(mcu_reset_pin, 1);
} else os_printf("MCU reset: no pin\n");
if (mcu_reset_pin >= 0) {
os_printf("MCU reset gpio%d\n", mcu_reset_pin);
GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L);
GPIO_OUTPUT_SET(mcu_reset_pin, 1);
} 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);
//os_printf("Receive callback on conn %p\n", conn);
if (conn == NULL) return;
// at the start of a connection we're in cmInit mode and we wait for the first few characters
// to arrive in order to decide what type of connection this is.. The following if statements
// do this dispatch. An issue here is that we assume that the first few characters all arrive
// in the same TCP packet, which is true if the sender is a program, but not necessarily
// if the sender is a person typing (although in that case the line-oriented TTY input seems
// to make it work too). If this becomes a problem we need to buffer the first few chars...
if (conn->conn_mode == cmInit) {
// If the connection starts with the Arduino or ARM reset sequence we perform a RESET
if ((len == 2 && strncmp(data, "0 ", 2) == 0) ||
(len == 2 && strncmp(data, "?\n", 2) == 0) ||
(len == 3 && strncmp(data, "?\r\n", 3) == 0)) {
os_printf("MCU Reset=%d ISP=%d\n", mcu_reset_pin, mcu_isp_pin);
os_delay_us(2*1000L); // time for os_printf to happen
// send reset to arduino/ARM
if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L);
if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 0);
os_delay_us(100L);
if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1);
os_delay_us(100L);
if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1);
os_delay_us(1000L);
conn->conn_mode = cmAVR;
// If the connection starts with a telnet negotiation we will do telnet
} else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) {
conn->conn_mode = cmTelnet;
conn->telnet_state = TN_normal;
// note that the three negotiation chars will be gobbled-up by telnetUnwrap
os_printf("telnet mode\n");
// looks like a plain-vanilla connection!
} else {
conn->conn_mode = cmTransparent;
}
}
serbridgeConnData *conn = serbridgeFindConnData(arg);
//os_printf("Receive callback on conn %p\n", conn);
if (conn == NULL) return;
// at the start of a connection we're in cmInit mode and we wait for the first few characters
// to arrive in order to decide what type of connection this is.. The following if statements
// do this dispatch. An issue here is that we assume that the first few characters all arrive
// in the same TCP packet, which is true if the sender is a program, but not necessarily
// if the sender is a person typing (although in that case the line-oriented TTY input seems
// to make it work too). If this becomes a problem we need to buffer the first few chars...
if (conn->conn_mode == cmInit) {
// If the connection starts with the Arduino or ARM reset sequence we perform a RESET
if ((len == 2 && strncmp(data, "0 ", 2) == 0) ||
(len == 2 && strncmp(data, "?\n", 2) == 0) ||
(len == 3 && strncmp(data, "?\r\n", 3) == 0)) {
os_printf("MCU Reset=gpio%d ISP=gpio%d\n", mcu_reset_pin, mcu_isp_pin);
os_delay_us(2*1000L); // time for os_printf to happen
// send reset to arduino/ARM
if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L);
if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 0);
os_delay_us(100L);
if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1);
os_delay_us(100L);
if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1);
os_delay_us(1000L);
conn->conn_mode = cmAVR;
slip_disabled++; // disable SLIP so it doesn't interfere with flashing
// If the connection starts with a telnet negotiation we will do telnet
} else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) {
conn->conn_mode = cmTelnet;
conn->telnet_state = TN_normal;
// note that the three negotiation chars will be gobbled-up by telnetUnwrap
os_printf("telnet mode\n");
// looks like a plain-vanilla connection!
} else {
conn->conn_mode = cmTransparent;
}
// Process return data on TCP client connections
} else if (conn->conn_mode == cmTcpClient) {
}
// write the buffer to the uart
if (conn->conn_mode == cmTelnet) {
conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state);
} else {
uart0_tx_buffer(data, len);
}
serledFlash(50); // short blink on serial LED
}
// write the buffer to the uart
if (conn->conn_mode == cmTelnet) {
conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state);
} else {
uart0_tx_buffer(data, len);
}
//===== UART -> TCP
serledFlash(50); // short blink on serial LED
// 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;
}
void ICACHE_FLASH_ATTR
console_process(char *buf, short len) {
// push buffer into web-console
for (short i=0; i<len; i++)
console_write_char(buf[i]);
// push the buffer into each open connection
for (short i=0; i<MAX_CONN; i++) {
if (connData[i].conn && connData[i].conn_mode != cmTcpClient) {
espbuffsend(&connData[i], buf, len);
}
}
}
//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
}
//===== 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;
// Close the connection
espconn_disconnect(conn->conn);
conn->conn = NULL;
serbridgeConnData *sbConn = serbridgeFindConnData(arg);
if (sbConn == NULL) return;
if (sbConn->conn_mode == cmAVR) {
if (slip_disabled > 0) slip_disabled--;
}
// Close the connection
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) {
// 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) {
if (slip_disabled > 0) slip_disabled--;
if (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
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);
if (i==MAX_CONN) {
os_printf("Aiee, conn pool overflow!\n");
espconn_disconnect(conn);
return;
}
connData[i].conn=conn;
connData[i].txbufferlen = 0;
connData[i].readytosend = true;
connData[i].telnet_state = 0;
connData[i].conn_mode = cmInit;
espconn_regist_recvcb(conn, serbridgeRecvCb);
espconn_regist_reconcb(conn, serbridgeReconCb);
espconn_regist_disconcb(conn, serbridgeDisconCb);
espconn_regist_sentcb(conn, serbridgeSentCb);
espconn_set_opt(conn, ESPCONN_REUSEADDR|ESPCONN_NODELAY);
struct espconn *conn = arg;
// 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);
if (i==MAX_CONN) {
os_printf("Aiee, conn pool overflow!\n");
espconn_disconnect(conn);
return;
}
conn->reverse = connData+i;
connData[i].conn = conn;
connData[i].txbufferlen = 0;
connData[i].readytosend = true;
connData[i].telnet_state = 0;
connData[i].conn_mode = cmInit;
espconn_regist_recvcb(conn, serbridgeRecvCb);
espconn_regist_reconcb(conn, serbridgeReconCb);
espconn_regist_disconcb(conn, serbridgeDisconCb);
espconn_regist_sentcb(conn, serbridgeSentCb);
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() {
<<<<<<< HEAD
mcu_reset_pin = flashConfig.reset_pin;
mcu_isp_pin = flashConfig.isp_pin;
os_printf("Serbridge pins: reset=%d isp=%d swap=%d\n",
@ -343,24 +358,49 @@ void ICACHE_FLASH_ATTR serbridgeInitPins() {
// switch pin mux to make these pins GPIO pins
if (mcu_reset_pin >= 0) makeGpio(mcu_reset_pin);
if (mcu_isp_pin >= 0) makeGpio(mcu_isp_pin);
=======
mcu_reset_pin = flashConfig.reset_pin;
mcu_isp_pin = flashConfig.isp_pin;
os_printf("Serbridge pins: reset=%d isp=%d swap=%d\n",
mcu_reset_pin, mcu_isp_pin, flashConfig.swap_uart);
if (flashConfig.swap_uart) {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 4);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 4);
PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U);
PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U);
system_uart_swap();
} else {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, 0);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, 0);
system_uart_de_swap();
}
// set both pins to 1 before turning them on so we don't cause a reset
if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1);
if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1);
// switch pin mux to make these pins GPIO pins
if (mcu_reset_pin >= 0) makeGpio(mcu_reset_pin);
if (mcu_isp_pin >= 0) makeGpio(mcu_isp_pin);
>>>>>>> master
}
// Start transparent serial bridge TCP server on specified port (typ. 23)
void ICACHE_FLASH_ATTR serbridgeInit(int port) {
serbridgeInitPins();
int i;
for (i = 0; i < MAX_CONN; i++) {
connData[i].conn = NULL;
connData[i].txbuffer = txbuffer[i];
}
serbridgeConn.type = ESPCONN_TCP;
serbridgeConn.state = ESPCONN_NONE;
serbridgeTcp.local_port = port;
serbridgeConn.proto.tcp = &serbridgeTcp;
espconn_regist_connectcb(&serbridgeConn, serbridgeConnectCb);
espconn_accept(&serbridgeConn);
espconn_tcp_set_max_con_allow(&serbridgeConn, MAX_CONN);
espconn_regist_time(&serbridgeConn, SER_BRIDGE_TIMEOUT, 0);
serbridgeInitPins();
int i;
for (i = 0; i < MAX_CONN; i++) {
connData[i].conn = NULL;
connData[i].txbuffer = txbuffer[i];
}
serbridgeConn.type = ESPCONN_TCP;
serbridgeConn.state = ESPCONN_NONE;
serbridgeTcp.local_port = port;
serbridgeConn.proto.tcp = &serbridgeTcp;
espconn_regist_connectcb(&serbridgeConn, serbridgeConnectCb);
espconn_accept(&serbridgeConn);
espconn_tcp_set_max_con_allow(&serbridgeConn, MAX_CONN);
espconn_regist_time(&serbridgeConn, SER_BRIDGE_TIMEOUT, 0);
}

@ -11,30 +11,28 @@
//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
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)
cmTelnet, // use telnet escape sequences for programming mode
cmTcpClient, // client connection (initiated via serial)
};
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
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);
void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, int len);
void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, short len);
void ICACHE_FLASH_ATTR serbridgeReset();
#endif /* __SER_BRIDGE_H__ */

@ -0,0 +1,136 @@
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
#include "esp8266.h"
#include "uart.h"
#include "crc16.h"
#include "serbridge.h"
#include "serled.h"
#include "console.h"
#include "cmd.h"
uint8_t slip_disabled; // disable slip to allow flashing of attached MCU
extern void ICACHE_FLASH_ATTR console_process(char *buf, short len);
// This SLIP parser does not conform to RFC 1055 https://tools.ietf.org/html/rfc1055,
// instead, it implements the framing implemented in https://github.com/tuanpmt/esp_bridge
// It accumulates each packet into a static buffer and calls cmd_parse() when the end
// of a packet is reached. It expects cmd_parse() to copy anything it needs from the
// buffer elsewhere as the buffer is immediately reused.
// One special feature is that if the first two characters of a packet are both printable or
// \n or \r then the parser assumes it's dealing with console debug output and calls
// slip_console(c) for each character and does not accumulate chars in the buffer until the
// next SLIP_END marker is seen. This allows random console debug output to come in between
// packets as long as each packet starts *and* ends with SLIP_END (which is an official
// variation on the SLIP protocol).
static bool slip_escaped; // true when prev char received is escape
static bool slip_inpkt; // true when we're after SLIP_START and before SLIP_END
#define SLIP_MAX 1024 // max length of SLIP packet
static char slip_buf[SLIP_MAX]; // buffer for current SLIP packet
static short slip_len; // accumulated length in slip_buf
// SLIP process a packet or a bunch of debug console chars
static void ICACHE_FLASH_ATTR
slip_process() {
if (slip_len < 1) return;
if (!slip_inpkt) {
// debug console stuff
console_process(slip_buf, slip_len);
} else {
// proper SLIP packet, invoke command processor after checking CRC
//os_printf("SLIP: rcv %d\n", slip_len);
if (slip_len > 2) {
uint16_t crc = crc16_data((uint8_t*)slip_buf, slip_len-2, 0);
uint16_t rcv = ((uint16_t)slip_buf[slip_len-2]) | ((uint16_t)slip_buf[slip_len-1] << 8);
if (crc == rcv) {
CMD_parse_packet((uint8_t*)slip_buf, slip_len-2);
} else {
os_printf("SLIP: bad CRC, crc=%x rcv=%x\n", crc, rcv);
for (short i=0; i<slip_len; i++) {
if (slip_buf[i] >= ' ' && slip_buf[i] <= '~')
os_printf("%c", slip_buf[i]);
else
os_printf("\\%02X", slip_buf[i]);
}
os_printf("\n");
}
}
}
}
#if 0
// determine whether a character is printable or not (or \r \n)
static bool ICACHE_FLASH_ATTR
slip_printable(char c) {
return (c >= ' ' && c <= '~') || c == '\n' || c == '\r';
}
#endif
static void ICACHE_FLASH_ATTR
slip_reset() {
slip_inpkt = false;
slip_escaped = false;
slip_len = 0;
}
// SLIP parse a single character
static void ICACHE_FLASH_ATTR
slip_parse_char(char c) {
if (!slip_inpkt) {
if (c == SLIP_START) {
if (slip_len > 0) console_process(slip_buf, slip_len);
slip_reset();
slip_inpkt = true;
return;
}
} else if (slip_escaped) {
// prev char was SLIP_REPL
c = SLIP_ESC(c);
slip_escaped = false;
} else {
switch (c) {
case SLIP_REPL:
slip_escaped = true;
return;
case SLIP_END:
// end of packet, process it and get ready for next one
if (slip_len > 0) slip_process();
slip_reset();
return;
case SLIP_START:
os_printf("SLIP: got SLIP_START while in packet?\n");
slip_reset();
return;
}
}
if (slip_len < SLIP_MAX) slip_buf[slip_len++] = c;
}
// callback with a buffer of characters that have arrived on the uart
void ICACHE_FLASH_ATTR
serbridgeUartCb(char *buf, short length) {
if (slip_disabled > 0) {
//os_printf("SLIP: disabled got %d\n", length);
console_process(buf, length);
for (short i=0; i<length; i++)
if (buf[i] == SLIP_START) {
os_printf("SLIP: START while disabled=%d\n", slip_disabled);
break;
}
return;
}
// do SLIP parsing
for (short i=0; i<length; i++)
slip_parse_char(buf[i]);
// if we're in-between packets (debug console) then print it now
if (!slip_inpkt && length > 0) {
slip_process();
slip_reset();
}
serledFlash(50); // short blink on serial LED
}

@ -4,7 +4,7 @@
#include "uart_hw.h"
// Receive callback function signature
typedef void (*UartRecv_cb)(char *buf, int len);
typedef void (*UartRecv_cb)(char *buf, short len);
// Initialize UARTs to the provided baud rates (115200 recommended). This also makes the os_printf
// calls use uart1 for output (for debugging purposes)

@ -129,6 +129,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) {

@ -99,11 +99,9 @@ int ICACHE_FLASH_ATTR cgiPinsSet(HttpdConnData *connData) {
statusInit();
if (configSave()) {
os_printf("New config saved\n");
httpdStartResponse(connData, 200);
httpdEndHeaders(connData);
} else {
os_printf("*** Failed to save config ***\n");
httpdStartResponse(connData, 500);
httpdEndHeaders(connData);
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

@ -40,6 +40,8 @@ static char *wifiReasons[] = {
static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" };
static char *wifiPhy[] = { 0, "11b", "11g", "11n" };
void (*wifiStatusCb)(uint8_t); // callback when wifi status changes
static char* ICACHE_FLASH_ATTR wifiGetReason(void) {
if (wifiReason <= 24) return wifiReasons[wifiReason];
if (wifiReason >= 200 && wifiReason <= 201) return wifiReasons[wifiReason-200+24];
@ -86,6 +88,7 @@ static void ICACHE_FLASH_ATTR wifiHandleEventCb(System_Event_t *evt) {
default:
break;
}
if (wifiStatusCb) (*wifiStatusCb)(wifiState);
}
// ===== wifi scanning
@ -478,7 +481,7 @@ int ICACHE_FLASH_ATTR printWifiInfo(char *buff) {
char *mode = wifiMode[op];
char *status = "unknown";
int st = wifi_station_get_connect_status();
if (st > 0 && st < sizeof(connStatuses)) status = connStatuses[st];
if (st >= 0 && st < sizeof(connStatuses)) status = connStatuses[st];
int p = wifi_get_phy_mode();
char *phy = wifiPhy[p&3];
char *warn = wifiWarn[op];

@ -14,4 +14,6 @@ int cgiWiFiConnStatus(HttpdConnData *connData);
int cgiWiFiSpecial(HttpdConnData *connData);
void wifiInit(void);
extern void (*wifiStatusCb)(uint8_t); // callback when wifi status changes
#endif

@ -4,9 +4,13 @@
#include "config.h"
#include "serled.h"
#include "cgiwifi.h"
#include "tcpclient.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 +25,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 +38,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 +58,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 +67,55 @@ 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;
#define GS_STREAM "rssi"
// Timer callback to send an RSSI update to a monitoring system
static void ICACHE_FLASH_ATTR rssiTimerCb(void *v) {
if (!flashConfig.rssi_enable || !flashConfig.tcp_enable || flashConfig.api_key[0]==0)
return;
sint8 rssi = wifi_station_get_rssi();
os_printf("timer rssi=%d\n", rssi);
if (rssi >= 0) return; // not connected or other error
// compose TCP command
uint8_t chan = MAX_TCP_CHAN-1;
tcpClientCommand(chan, 'T', "grovestreams.com:80");
// compose http header
char buf[1024];
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",
flashConfig.api_key);
// http body
int dataLen = os_sprintf(buf+hdrLen,
"[{\"compId\":\"%s\", \"streamId\":\"%s\", \"data\":%d}]\r",
flashConfig.hostname, GS_STREAM, rssi);
buf[hdrLen+dataLen++] = 0;
buf[hdrLen+dataLen++] = '\n';
// hackish way to fill in the content-length
os_sprintf(buf+hdrLen-9, "%5d", dataLen);
buf[hdrLen-4] = '\r'; // fix-up the \0 inserted by sprintf (hack!)
// send the request off and forget about it...
for (short i=0; i<hdrLen+dataLen; i++) {
tcpClientSendChar(chan, buf[i]);
}
tcpClientSendPush(chan);
}
//===== Init status stuff
void ICACHE_FLASH_ATTR statusInit(void) {
if (flashConfig.conn_led_pin >= 0) {
makeGpio(flashConfig.conn_led_pin);
@ -72,6 +126,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
}

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

Loading…
Cancel
Save