Merge pull request #4 from jeelabs/master

merge with upstream
pull/39/head
Benjamin Runnels 9 years ago
commit 546b9c3774
  1. 3
      .gitmodules
  2. 36
      Makefile
  3. 179
      cmd/cmd.c
  4. 90
      cmd/cmd.h
  5. 83
      cmd/handlers.c
  6. 426
      cmd/rest.c
  7. 40
      cmd/rest.h
  8. 315
      cmd/tcpclient.c
  9. 2
      cmd/tcpclient.h
  10. 160
      serial/console.c
  11. 78
      serial/crc16.c
  12. 100
      serial/crc16.h
  13. 670
      serial/serbridge.c
  14. 22
      serial/serbridge.h
  15. 136
      serial/slip.c
  16. 315
      serial/tcpclient.c
  17. 2
      serial/uart.h
  18. 24
      user/cgiflash.c
  19. 5
      user/cgiwifi.c
  20. 3
      user/cgiwifi.h
  21. 14
      user/config.c
  22. 4
      user/config.h
  23. 2
      user/user_main.c

3
.gitmodules vendored

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

@ -19,7 +19,7 @@ XTENSA_TOOLS_ROOT ?= $(abspath ../esp-open-sdk/xtensa-lx106-elf/bin)/
# Base directory of the ESP8266 SDK package, absolute # Base directory of the ESP8266 SDK package, absolute
# Typically you'll download from Espressif's BBS, http://bbs.espressif.com/viewforum.php?f=5 # Typically you'll download from Espressif's BBS, http://bbs.espressif.com/viewforum.php?f=5
SDK_BASE ?= $(abspath ../esp_iot_sdk_v1.2.0) SDK_BASE ?= $(abspath ../esp_iot_sdk_v1.3.0)
# Esptool.py path and port, only used for 1-time serial flashing # Esptool.py path and port, only used for 1-time serial flashing
# Typically you'll use https://github.com/themadinventor/esptool # Typically you'll use https://github.com/themadinventor/esptool
@ -29,26 +29,42 @@ ESPBAUD ?= 460800
# --------------- chipset configuration --------------- # --------------- chipset configuration ---------------
# Pick your flash size: "512KB" or "4MB" # Pick your flash size: "512KB", "1MB", or "4MB"
FLASH_SIZE ?= 4MB FLASH_SIZE ?= 4MB
ifeq ("$(FLASH_SIZE)","512KB") ifeq ("$(FLASH_SIZE)","512KB")
# Winbond 25Q40 512KB flash, typ for esp-01 thru esp-11 # Winbond 25Q40 512KB flash, typ for esp-01 thru esp-11
ESP_SPI_SIZE ?= 0 # 0->512KB ESP_SPI_SIZE ?= 0 # 0->512KB (256KB+256KB)
ESP_FLASH_MODE ?= 0 # 0->QIO ESP_FLASH_MODE ?= 0 # 0->QIO
ESP_FLASH_FREQ_DIV ?= 0 # 0->40Mhz ESP_FLASH_FREQ_DIV ?= 0 # 0->40Mhz
ESP_FLASH_MAX ?= 241664 # max bin file for 512KB flash: 236KB 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
else ifeq ("$(FLASH_SIZE)","1MB")
# ESP-01E
ESP_SPI_SIZE ?= 2 # 2->1MB (512KB+512KB)
ESP_FLASH_MODE ?= 0 # 0->QIO
ESP_FLASH_FREQ_DIV ?= 15 # 15->80MHz
ESP_FLASH_MAX ?= 503808 # max bin file for 1MB flash: 492KB
ET_FS ?= 8m # 8Mbit flash size in esptool flash command
ET_FF ?= 80m # 80Mhz flash speed in esptool flash command
ET_BLANK ?= 0xFE000 # where to flash blank.bin to erase wireless settings
else else
# Winbond 25Q32 4MB flash, typ for esp-12 # 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 # 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 # of linker scripts in the SDK. Ideally we'd use two partitions of approx 1MB, the remaining 2MB
# cannot be used for code. # cannot be used for code (esp8266 limitation).
ESP_SPI_SIZE ?= 4 # 6->4MB (1MB+1MB) or 4->4MB (512KB+512KB) ESP_SPI_SIZE ?= 4 # 6->4MB (1MB+1MB) or 4->4MB (512KB+512KB)
ESP_FLASH_MODE ?= 0 # 0->QIO, 2->DIO ESP_FLASH_MODE ?= 0 # 0->QIO, 2->DIO
ESP_FLASH_FREQ_DIV ?= 15 # 15->80Mhz ESP_FLASH_FREQ_DIV ?= 15 # 15->80Mhz
ESP_FLASH_MAX ?= 503808 # max bin file for 512KB flash partition: 492KB ESP_FLASH_MAX ?= 503808 # max bin file for 512KB flash partition: 492KB
#ESP_FLASH_MAX ?= 1028096 # max bin file for 1MB flash partition: 1004KB #ESP_FLASH_MAX ?= 1028096 # max bin file for 1MB flash partition: 1004KB
ET_FS ?= 32m # 32Mbit flash size in esptool flash command
ET_FF ?= 80m # 80Mhz flash speed in esptool flash command
ET_BLANK ?= 0x3FE000 # where to flash blank.bin to erase wireless settings
endif endif
# hostname or IP address for wifi flashing # hostname or IP address for wifi flashing
@ -130,7 +146,7 @@ TARGET = httpd
APPGEN_TOOL ?= gen_appbin.py APPGEN_TOOL ?= gen_appbin.py
# which modules (subdirectories) of the project to include in compiling # which modules (subdirectories) of the project to include in compiling
MODULES = espfs httpd user serial MODULES = espfs httpd user serial cmd
EXTRA_INCDIR = include . # lib/heatshrink/ EXTRA_INCDIR = include . # lib/heatshrink/
# libraries used in this project, mainly provided by the SDK # libraries used in this project, mainly provided by the SDK
@ -274,9 +290,9 @@ wiflash: all
./wiflash $(ESP_HOSTNAME) $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin ./wiflash $(ESP_HOSTNAME) $(FW_BASE)/user1.bin $(FW_BASE)/user2.bin
flash: all flash: all
$(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) write_flash \ $(Q) $(ESPTOOL) --port $(ESPPORT) --baud $(ESPBAUD) -fs $(ET_FS) -ff $(ET_FF) write_flash \
0x00000 "$(SDK_BASE)/bin/boot_v1.4(b1).bin" 0x01000 $(FW_BASE)/user1.bin \ 0x00000 "$(SDK_BASE)/bin/boot_v1.4(b1).bin" 0x01000 $(FW_BASE)/user1.bin \
0x7E000 $(SDK_BASE)/bin/blank.bin $(ET_BLANK) $(SDK_BASE)/bin/blank.bin
yui/$(YUI-COMPRESSOR): yui/$(YUI-COMPRESSOR):
$(Q) mkdir -p yui $(Q) mkdir -p yui

@ -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: 0x%lx, 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,83 @@
// 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) {
os_printf("CMD: Wifi connect\n");
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
// trigger an immediate callback with the current status
lastWifiStatus = 0xff;
CMD_WifiCb(wifiState);
return 1;
}

@ -0,0 +1,426 @@
// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt
//
// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Mar 4, 2015, Author: Minh
#include "esp8266.h"
#include "rest.h"
#include "cmd.h"
// Connection pool for REST clients. Attached MCU's just call REST_setup and this allocates
// a connection, They never call any 'free' and given that the attached MCU could restart at
// any time, we cannot really rely on the attached MCU to call 'free' ever, so better do without.
// Instead, we allocate a fixed pool of connections an round-robin. What this means is that the
// attached MCU should really use at most as many REST connections as there are slots in the pool.
#define MAX_REST 4
static RestClient restClient[MAX_REST];
static uint8_t restNum = 0xff; // index into restClient for next slot to allocate
#define REST_CB 0xdead0000 // fudge added to callback for arduino so we can detect problems
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;
// free the data buffer, if we have one
if (client->data) os_free(client->data);
client->data = 0;
}
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");
if (client->data_sent != client->data_len) {
// we only sent part of the buffer, send the rest
espconn_sent(client->pCon, (uint8_t*)(client->data+client->data_sent),
client->data_len-client->data_sent);
client->data_sent = client->data_len;
} else {
// we're done sending, free the memory
if (client->data) os_free(client->data);
client->data = 0;
}
}
static void ICACHE_FLASH_ATTR
tcpclient_connect_cb(void *arg) {
struct espconn *pCon = (struct espconn *)arg;
RestClient* client = (RestClient *)pCon->reverse;
os_printf("REST #%d: connected\n", client-restClient);
espconn_regist_disconcb(client->pCon, tcpclient_discon_cb);
espconn_regist_recvcb(client->pCon, tcpclient_recv);
espconn_regist_sentcb(client->pCon, tcpclient_sent_cb);
client->data_sent = client->data_len <= 1400 ? client->data_len : 1400;
os_printf("REST #%d: sending %d\n", client-restClient, client->data_sent);
//if(client->security){
// espconn_secure_sent(client->pCon, client->data, client->data_sent);
//}
//else{
espconn_sent(client->pCon, (uint8_t*)client->data, client->data_sent);
//}
}
static void ICACHE_FLASH_ATTR
tcpclient_recon_cb(void *arg, sint8 errType) {
struct espconn *pCon = (struct espconn *)arg;
RestClient* client = (RestClient *)pCon->reverse;
os_printf("REST $%d: conn reset\n", client-restClient);
}
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;
uint32_t port, security;
// start parsing the command
CMD_Request(&req, cmd);
if(CMD_GetArgc(&req) != 3) return 0;
// get the hostname
uint16_t len = CMD_ArgLen(&req);
if (len > 128) return 0; // safety check
uint8_t *rest_host = (uint8_t*)os_zalloc(len + 1);
if (CMD_PopArg(&req, rest_host, len)) return 0;
rest_host[len] = 0;
// get the port
if (CMD_PopArg(&req, (uint8_t*)&port, 4)) {
os_free(rest_host);
return 0;
}
// get the security mode
if (CMD_PopArg(&req, (uint8_t*)&security, 4)) {
os_free(rest_host);
return 0;
}
// clear connection structures the first time
if (restNum == 0xff) {
os_memset(restClient, 0, MAX_REST * sizeof(RestClient));
restNum = 0;
}
// allocate a connection structure
RestClient *client = restClient + restNum;
uint8_t clientNum = restNum;
restNum = (restNum+1)%MAX_REST;
// free any data structure that may be left from a previous connection
if (client->header) os_free(client->header);
if (client->content_type) os_free(client->content_type);
if (client->user_agent) os_free(client->user_agent);
if (client->data) os_free(client->data);
if (client->pCon) {
if (client->pCon->proto.tcp) os_free(client->pCon->proto.tcp);
os_free(client->pCon);
}
os_memset(client, 0, sizeof(RestClient));
os_printf("REST: setup #%d host=%s port=%ld security=%ld\n", clientNum, rest_host, port, security);
client->resp_cb = cmd->callback;
client->host = (char *)rest_host;
client->port = port;
client->security = security;
client->header = (char*)os_zalloc(4);
client->header[0] = 0;
client->content_type = (char*)os_zalloc(22);
os_sprintf((char *)client->content_type, "x-www-form-urlencoded");
client->user_agent = (char*)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 REST_CB | (uint32_t)clientNum;
}
uint32_t ICACHE_FLASH_ATTR
REST_SetHeader(CmdPacket *cmd) {
CmdRequest req;
CMD_Request(&req, cmd);
if(CMD_GetArgc(&req) != 3)
return 0;
// Get client
uint32_t clientNum;
if (CMD_PopArg(&req, (uint8_t*)&clientNum, 4)) return 0;
if ((clientNum & 0xffff0000) != REST_CB) return 0;
RestClient *client = restClient + ((clientNum & 0xffff) % MAX_REST);
// Get header selector
uint32_t header_index;
if (CMD_PopArg(&req, (uint8_t*)&header_index, 4)) return 0;
// Get header value
uint16_t 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 = (char*)os_zalloc(len + 3);
CMD_PopArg(&req, (uint8_t*)client->header, len);
client->header[len] = '\r';
client->header[len+1] = '\n';
client->header[len+2] = 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 = (char*)os_zalloc(len + 3);
CMD_PopArg(&req, (uint8_t*)client->content_type, len);
client->content_type[len] = '\r';
client->content_type[len+1] = '\n';
client->content_type[len+2] = 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 = (char*)os_zalloc(len + 3);
CMD_PopArg(&req, (uint8_t*)client->user_agent, len);
client->user_agent[len] = '\r';
client->user_agent[len+1] = '\n';
client->user_agent[len+2] = 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;
CMD_Request(&req, cmd);
os_printf("REST: request");
// Get client
uint32_t clientNum;
if (CMD_PopArg(&req, (uint8_t*)&clientNum, 4)) goto fail;
if ((clientNum & 0xffff0000) != REST_CB) goto fail;
clientNum &= 0xffff;
RestClient *client = restClient + clientNum % MAX_REST;
os_printf(" #%ld", clientNum);
// Get HTTP method
uint16_t len = CMD_ArgLen(&req);
if (len > 15) goto fail;
char method[16];
CMD_PopArg(&req, method, len);
method[len] = 0;
os_printf(" method=%s", method);
// Get HTTP path
len = CMD_ArgLen(&req);
if (len > 1023) goto fail;
char path[1024];
CMD_PopArg(&req, path, len);
path[len] = 0;
os_printf(" path=%s", path);
// Get HTTP body
uint32_t realLen = 0;
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) goto fail;
}
os_printf(" bodyLen=%ld", realLen);
// we need to allocate memory for the header plus the body. First we count the length of the
// header (including some extra counted "%s" and then we add the body length. We allocate the
// whole shebang and copy everything into it.
char *headerFmt = "%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";
uint16_t headerLen = strlen(headerFmt) + strlen(method) + strlen(path) + strlen(client->host) +
strlen(client->header) + strlen(client->content_type) + strlen(client->user_agent);
os_printf(" hdrLen=%d", headerLen);
if (client->data) os_free(client->data);
client->data = (char*)os_zalloc(headerLen + realLen);
if (client->data == NULL) goto fail;
os_printf(" totLen=%ld data=%p", headerLen + realLen, client->data);
client->data_len = os_sprintf((char*)client->data, headerFmt, method, path, client->host,
client->header, realLen, client->content_type, client->user_agent);
os_printf(" hdrLen=%d", client->data_len);
if (realLen > 0) {
CMD_PopArg(&req, client->data + client->data_len, realLen);
client->data_len += realLen;
}
client->pCon->state = ESPCONN_NONE;
espconn_regist_connectcb(client->pCon, tcpclient_connect_cb);
espconn_regist_reconcb(client->pCon, tcpclient_recon_cb);
os_printf("\n");
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);
}
return 1;
fail:
os_printf("\n");
return 0;
}
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,40 @@
/*
* 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 {
char *host;
uint32_t port;
uint32_t security;
ip_addr_t ip;
struct espconn *pCon;
char *header;
char *data;
uint16_t data_len;
uint16_t data_sent;
char *content_type;
char *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;
}

@ -4,7 +4,7 @@
// max number of channels the client can open // max number of channels the client can open
#define MAX_TCP_CHAN 8 #define MAX_TCP_CHAN 8
// Parse and perform the commandm cmdBuf must be null-terminated // Parse and perform the command, cmdBuf must be null-terminated
bool tcpClientCommand(uint8_t chan, char cmd, char *cmdBuf); bool tcpClientCommand(uint8_t chan, char cmd, char *cmdBuf);
// Append a character to the specified channel // Append a character to the specified channel

@ -25,112 +25,112 @@ static int console_pos; // offset since reset of buffer
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
console_write(char c) { console_write(char c) {
console_buf[console_wr] = c; console_buf[console_wr] = c;
console_wr = (console_wr+1) % BUF_MAX; console_wr = (console_wr+1) % BUF_MAX;
if (console_wr == console_rd) { if (console_wr == console_rd) {
// full, we write anyway and loose the oldest char // full, we write anyway and loose the oldest char
console_rd = (console_rd+1) % BUF_MAX; // full, eat first char console_rd = (console_rd+1) % BUF_MAX; // full, eat first char
console_pos++; console_pos++;
} }
} }
#if 0 #if 0
// return previous character in console, 0 if at start // return previous character in console, 0 if at start
static char ICACHE_FLASH_ATTR static char ICACHE_FLASH_ATTR
console_prev(void) { console_prev(void) {
if (console_wr == console_rd) return 0; if (console_wr == console_rd) return 0;
return console_buf[(console_wr-1+BUF_MAX)%BUF_MAX]; return console_buf[(console_wr-1+BUF_MAX)%BUF_MAX];
} }
#endif #endif
void ICACHE_FLASH_ATTR void ICACHE_FLASH_ATTR
console_write_char(char c) { console_write_char(char c) {
//if (c == '\n' && console_prev() != '\r') console_write('\r'); // does more harm than good //if (c == '\n' && console_prev() != '\r') console_write('\r'); // does more harm than good
console_write(c); console_write(c);
} }
int ICACHE_FLASH_ATTR int ICACHE_FLASH_ATTR
ajaxConsoleReset(HttpdConnData *connData) { ajaxConsoleReset(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
jsonHeader(connData, 200); jsonHeader(connData, 200);
console_rd = console_wr = console_pos = 0; console_rd = console_wr = console_pos = 0;
serbridgeReset(); serbridgeReset();
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
int ICACHE_FLASH_ATTR int ICACHE_FLASH_ATTR
ajaxConsoleBaud(HttpdConnData *connData) { ajaxConsoleBaud(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
char buff[512]; char buff[512];
int len, status = 400; int len, status = 400;
len = httpdFindArg(connData->getArgs, "rate", buff, sizeof(buff)); len = httpdFindArg(connData->getArgs, "rate", buff, sizeof(buff));
if (len > 0) { if (len > 0) {
int rate = atoi(buff); int rate = atoi(buff);
if (rate >= 9600 && rate <= 1000000) { if (rate >= 9600 && rate <= 1000000) {
uart0_baud(rate); uart0_baud(rate);
flashConfig.baud_rate = rate; flashConfig.baud_rate = rate;
status = configSave() ? 200 : 400; status = configSave() ? 200 : 400;
} }
} else if (connData->requestType == HTTPD_METHOD_GET) { } else if (connData->requestType == HTTPD_METHOD_GET) {
status = 200; status = 200;
} }
jsonHeader(connData, status); jsonHeader(connData, status);
os_sprintf(buff, "{\"rate\": %ld}", flashConfig.baud_rate); os_sprintf(buff, "{\"rate\": %ld}", flashConfig.baud_rate);
httpdSend(connData, buff, -1); httpdSend(connData, buff, -1);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
int ICACHE_FLASH_ATTR int ICACHE_FLASH_ATTR
ajaxConsole(HttpdConnData *connData) { ajaxConsole(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.
char buff[2048]; char buff[2048];
int len; // length of text in buff int len; // length of text in buff
int console_len = (console_wr+BUF_MAX-console_rd) % BUF_MAX; // num chars in console_buf 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 int start = 0; // offset onto console_wr to start sending out chars
jsonHeader(connData, 200); jsonHeader(connData, 200);
// figure out where to start in buffer based on URI param // figure out where to start in buffer based on URI param
len = httpdFindArg(connData->getArgs, "start", buff, sizeof(buff)); len = httpdFindArg(connData->getArgs, "start", buff, sizeof(buff));
if (len > 0) { if (len > 0) {
start = atoi(buff); start = atoi(buff);
if (start < console_pos) { if (start < console_pos) {
start = 0; start = 0;
} else if (start >= console_pos+console_len) { } else if (start >= console_pos+console_len) {
start = console_len; start = console_len;
} else { } else {
start = start - console_pos; start = start - console_pos;
} }
} }
// start outputting // start outputting
len = os_sprintf(buff, "{\"len\":%d, \"start\":%d, \"text\": \"", len = os_sprintf(buff, "{\"len\":%d, \"start\":%d, \"text\": \"",
console_len-start, console_pos+start); console_len-start, console_pos+start);
int rd = (console_rd+start) % BUF_MAX; int rd = (console_rd+start) % BUF_MAX;
while (len < 2040 && rd != console_wr) { while (len < 2040 && rd != console_wr) {
uint8_t c = console_buf[rd]; uint8_t c = console_buf[rd];
if (c == '\\' || c == '"') { if (c == '\\' || c == '"') {
buff[len++] = '\\'; buff[len++] = '\\';
buff[len++] = c; buff[len++] = c;
} else if (c == '\r') { } else if (c == '\r') {
// this is crummy, but browsers display a newline for \r\n sequences // this is crummy, but browsers display a newline for \r\n sequences
} else if (c < ' ') { } else if (c < ' ') {
len += os_sprintf(buff+len, "\\u%04x", c); len += os_sprintf(buff+len, "\\u%04x", c);
} else { } else {
buff[len++] = c; buff[len++] = c;
} }
rd = (rd + 1) % BUF_MAX; rd = (rd + 1) % BUF_MAX;
} }
os_strcpy(buff+len, "\"}"); len+=2; os_strcpy(buff+len, "\"}"); len+=2;
httpdSend(connData, buff, len); httpdSend(connData, buff, len);
return HTTPD_CGI_DONE; return HTTPD_CGI_DONE;
} }
void ICACHE_FLASH_ATTR consoleInit() { void ICACHE_FLASH_ATTR consoleInit() {
console_wr = 0; console_wr = 0;
console_rd = 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,24 +9,27 @@
#include "gpio.h" #include "gpio.h"
#include "uart.h" #include "uart.h"
#include "crc16.h"
#include "serbridge.h" #include "serbridge.h"
#include "serled.h" #include "serled.h"
#include "config.h" #include "config.h"
#include "console.h" #include "console.h"
#include "tcpclient.h" #include "cmd.h"
static struct espconn serbridgeConn; static struct espconn serbridgeConn;
static esp_tcp serbridgeTcp; static esp_tcp serbridgeTcp;
static int8_t mcu_reset_pin, mcu_isp_pin; static int8_t mcu_reset_pin, mcu_isp_pin;
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); static sint8 ICACHE_FLASH_ATTR espbuffsend(serbridgeConnData *conn, const char *data, uint16 len);
// Connection pool // Connection pool
serbridgeConnData connData[MAX_CONN]; serbridgeConnData connData[MAX_CONN];
// Given a pointer to an espconn struct find the connection that correcponds to it // Given a pointer to an espconn struct find the connection that correcponds to it
static serbridgeConnData ICACHE_FLASH_ATTR *serbridgeFindConnData(void *arg) { static serbridgeConnData ICACHE_FLASH_ATTR *serbridgeFindConnData(void *arg) {
struct espconn *conn = arg; struct espconn *conn = arg;
return arg == NULL ? NULL : (serbridgeConnData *)conn->reverse; return arg == NULL ? NULL : (serbridgeConnData *)conn->reverse;
} }
//===== TCP -> UART //===== TCP -> UART
@ -50,149 +53,152 @@ enum { TN_normal, TN_iac, TN_will, TN_start, TN_end, TN_comPort, TN_setControl }
static uint8_t ICACHE_FLASH_ATTR static uint8_t ICACHE_FLASH_ATTR
telnetUnwrap(uint8_t *inBuf, int len, uint8_t state) telnetUnwrap(uint8_t *inBuf, int len, uint8_t state)
{ {
for (int i=0; i<len; i++) { for (int i=0; i<len; i++) {
uint8_t c = inBuf[i]; uint8_t c = inBuf[i];
switch (state) { switch (state) {
default: default:
case TN_normal: case TN_normal:
if (c == IAC) state = TN_iac; // escape char: see what's next if (c == IAC) state = TN_iac; // escape char: see what's next
else uart0_write_char(c); // regular char else uart0_write_char(c); // regular char
break; break;
case TN_iac: case TN_iac:
switch (c) { switch (c) {
case IAC: // second escape -> write one to outbuf and go normal again case IAC: // second escape -> write one to outbuf and go normal again
state = TN_normal; state = TN_normal;
uart0_write_char(c); uart0_write_char(c);
break; break;
case WILL: // negotiation case WILL: // negotiation
state = TN_will; state = TN_will;
break; break;
case SB: // command sequence begin case SB: // command sequence begin
state = TN_start; state = TN_start;
break; break;
case SE: // command sequence end case SE: // command sequence end
state = TN_normal; state = TN_normal;
break; break;
default: // not sure... let's ignore default: // not sure... let's ignore
uart0_write_char(IAC); uart0_write_char(IAC);
uart0_write_char(c); uart0_write_char(c);
} }
break; break;
case TN_will: case TN_will:
state = TN_normal; // yes, we do COM port options, let's go back to normal state = TN_normal; // yes, we do COM port options, let's go back to normal
break; break;
case TN_start: // in command seq, now comes the type of cmd case TN_start: // in command seq, now comes the type of cmd
if (c == ComPortOpt) state = TN_comPort; if (c == ComPortOpt) state = TN_comPort;
else state = TN_end; // an option we don't know, skip 'til the end seq else state = TN_end; // an option we don't know, skip 'til the end seq
break; break;
case TN_end: // wait for end seq case TN_end: // wait for end seq
if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq
break; break;
case TN_comPort: case TN_comPort:
if (c == SetControl) state = TN_setControl; if (c == SetControl) state = TN_setControl;
else state = TN_end; else state = TN_end;
break; break;
case TN_setControl: // switch control line and delay a tad case TN_setControl: // switch control line and delay a tad
switch (c) { switch (c) {
case DTR_ON: case DTR_ON:
if (mcu_reset_pin >= 0) { if (mcu_reset_pin >= 0) {
os_printf("MCU reset gpio%d\n", mcu_reset_pin); os_printf("MCU reset gpio%d\n", mcu_reset_pin);
GPIO_OUTPUT_SET(mcu_reset_pin, 0); GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L); os_delay_us(100L);
} else os_printf("MCU reset: no pin\n"); } else os_printf("MCU reset: no pin\n");
break; break;
case DTR_OFF: case DTR_OFF:
if (mcu_reset_pin >= 0) { if (mcu_reset_pin >= 0) {
GPIO_OUTPUT_SET(mcu_reset_pin, 1); GPIO_OUTPUT_SET(mcu_reset_pin, 1);
os_delay_us(100L); os_delay_us(100L);
} }
break; break;
case RTS_ON: case RTS_ON:
if (mcu_isp_pin >= 0) { if (mcu_isp_pin >= 0) {
os_printf("MCU ISP gpio%d\n", mcu_isp_pin); os_printf("MCU ISP gpio%d\n", mcu_isp_pin);
GPIO_OUTPUT_SET(mcu_isp_pin, 0); GPIO_OUTPUT_SET(mcu_isp_pin, 0);
os_delay_us(100L); os_delay_us(100L);
} else os_printf("MCU isp: no pin\n"); } else os_printf("MCU isp: no pin\n");
break; slip_disabled++;
case RTS_OFF: break;
if (mcu_isp_pin >= 0) { case RTS_OFF:
GPIO_OUTPUT_SET(mcu_isp_pin, 1); if (mcu_isp_pin >= 0) {
os_delay_us(100L); GPIO_OUTPUT_SET(mcu_isp_pin, 1);
} os_delay_us(100L);
break; }
} if (slip_disabled > 0) slip_disabled--;
state = TN_end; break;
break; }
} state = TN_end;
} break;
return state; }
}
return state;
} }
// Generate a reset pulse for the attached microcontroller
void ICACHE_FLASH_ATTR serbridgeReset() { void ICACHE_FLASH_ATTR serbridgeReset() {
if (mcu_reset_pin >= 0) { if (mcu_reset_pin >= 0) {
os_printf("MCU reset gpio%d\n", mcu_reset_pin); os_printf("MCU reset gpio%d\n", mcu_reset_pin);
GPIO_OUTPUT_SET(mcu_reset_pin, 0); GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L); os_delay_us(100L);
GPIO_OUTPUT_SET(mcu_reset_pin, 1); GPIO_OUTPUT_SET(mcu_reset_pin, 1);
} else os_printf("MCU reset: no pin\n"); } else os_printf("MCU reset: no pin\n");
} }
// Receive callback // Receive callback
static void ICACHE_FLASH_ATTR serbridgeRecvCb(void *arg, char *data, unsigned short len) { static void ICACHE_FLASH_ATTR serbridgeRecvCb(void *arg, char *data, unsigned short len) {
serbridgeConnData *conn = serbridgeFindConnData(arg); serbridgeConnData *conn = serbridgeFindConnData(arg);
//os_printf("Receive callback on conn %p\n", conn); //os_printf("Receive callback on conn %p\n", conn);
if (conn == NULL) return; if (conn == NULL) return;
// at the start of a connection we're in cmInit mode and we wait for the first few characters // 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 // 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 // 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 // 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 // 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... // to make it work too). If this becomes a problem we need to buffer the first few chars...
if (conn->conn_mode == cmInit) { if (conn->conn_mode == cmInit) {
// If the connection starts with the Arduino or ARM reset sequence we perform a RESET // If the connection starts with the Arduino or ARM reset sequence we perform a RESET
if ((len == 2 && strncmp(data, "0 ", 2) == 0) || if ((len == 2 && strncmp(data, "0 ", 2) == 0) ||
(len == 2 && strncmp(data, "?\n", 2) == 0) || (len == 2 && strncmp(data, "?\n", 2) == 0) ||
(len == 3 && strncmp(data, "?\r\n", 3) == 0)) { (len == 3 && strncmp(data, "?\r\n", 3) == 0)) {
os_printf("MCU Reset=%d ISP=%d\n", mcu_reset_pin, mcu_isp_pin); 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 os_delay_us(2*1000L); // time for os_printf to happen
// send reset to arduino/ARM // send reset to arduino/ARM
if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 0); if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 0);
os_delay_us(100L); os_delay_us(100L);
if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 0); if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 0);
os_delay_us(100L); os_delay_us(100L);
if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1);
os_delay_us(100L); os_delay_us(100L);
if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1);
os_delay_us(1000L); os_delay_us(1000L);
conn->conn_mode = cmAVR; 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 // If the connection starts with a telnet negotiation we will do telnet
} else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) { } else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) {
conn->conn_mode = cmTelnet; conn->conn_mode = cmTelnet;
conn->telnet_state = TN_normal; conn->telnet_state = TN_normal;
// note that the three negotiation chars will be gobbled-up by telnetUnwrap // note that the three negotiation chars will be gobbled-up by telnetUnwrap
os_printf("telnet mode\n"); os_printf("telnet mode\n");
// looks like a plain-vanilla connection! // looks like a plain-vanilla connection!
} else { } else {
conn->conn_mode = cmTransparent; conn->conn_mode = cmTransparent;
} }
// Process return data on TCP client connections // Process return data on TCP client connections
} else if (conn->conn_mode == cmTcpClient) { } else if (conn->conn_mode == cmTcpClient) {
} }
// write the buffer to the uart // write the buffer to the uart
if (conn->conn_mode == cmTelnet) { if (conn->conn_mode == cmTelnet) {
conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state); conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state);
} else { } else {
uart0_tx_buffer(data, len); uart0_tx_buffer(data, len);
} }
serledFlash(50); // short blink on serial LED serledFlash(50); // short blink on serial LED
} }
//===== UART -> TCP //===== UART -> TCP
@ -203,18 +209,19 @@ static char txbuffer[MAX_CONN][MAX_TXBUFFER];
// Send all data in conn->txbuffer // Send all data in conn->txbuffer
// returns result from espconn_sent if data in buffer or ESPCONN_OK (0) // returns result from espconn_sent if data in buffer or ESPCONN_OK (0)
// Use only internally from espbuffsend and serbridgeSentCb // Use only internally from espbuffsend and serbridgeSentCb
static sint8 ICACHE_FLASH_ATTR sendtxbuffer(serbridgeConnData *conn) { static sint8 ICACHE_FLASH_ATTR
sint8 result = ESPCONN_OK; sendtxbuffer(serbridgeConnData *conn) {
if (conn->txbufferlen != 0) { sint8 result = ESPCONN_OK;
//os_printf("%d TX %d\n", system_get_time(), conn->txbufferlen); if (conn->txbufferlen != 0) {
conn->readytosend = false; //os_printf("%d TX %d\n", system_get_time(), conn->txbufferlen);
result = espconn_sent(conn->conn, (uint8_t*)conn->txbuffer, conn->txbufferlen); conn->readytosend = false;
conn->txbufferlen = 0; result = espconn_sent(conn->conn, (uint8_t*)conn->txbuffer, conn->txbufferlen);
if (result != ESPCONN_OK) { conn->txbufferlen = 0;
os_printf("sendtxbuffer: espconn_sent error %d on conn %p\n", result, conn); if (result != ESPCONN_OK) {
} os_printf("sendtxbuffer: espconn_sent error %d on conn %p\n", result, conn);
} }
return result; }
return result;
} }
// espbuffsend adds data to the send buffer. If the previous send was completed it calls // espbuffsend adds data to the send buffer. If the previous send was completed it calls
@ -222,173 +229,44 @@ static sint8 ICACHE_FLASH_ATTR sendtxbuffer(serbridgeConnData *conn) {
// Returns ESPCONN_OK (0) for success, -128 if buffer is full or error from 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 // 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. // 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) { static sint8 ICACHE_FLASH_ATTR
if (conn->txbufferlen + len > MAX_TXBUFFER) { espbuffsend(serbridgeConnData *conn, const char *data, uint16 len) {
os_printf("espbuffsend: txbuffer full on conn %p\n", conn); if (conn->txbufferlen + len > MAX_TXBUFFER) {
return -128; os_printf("espbuffsend: txbuffer full on conn %p\n", conn);
} return -128;
os_memcpy(conn->txbuffer + conn->txbufferlen, data, len); }
conn->txbufferlen += len; os_memcpy(conn->txbuffer + conn->txbufferlen, data, len);
if (conn->readytosend) { conn->txbufferlen += len;
return sendtxbuffer(conn); if (conn->readytosend) {
} else { return sendtxbuffer(conn);
//os_printf("%d QU %d\n", system_get_time(), conn->txbufferlen); } else {
} //os_printf("%d QU %d\n", system_get_time(), conn->txbufferlen);
return ESPCONN_OK; }
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 //callback after the data are sent
static void ICACHE_FLASH_ATTR static void ICACHE_FLASH_ATTR
serbridgeSentCb(void *arg) { serbridgeSentCb(void *arg) {
serbridgeConnData *conn = serbridgeFindConnData(arg); serbridgeConnData *conn = serbridgeFindConnData(arg);
//os_printf("Sent callback on conn %p\n", conn); //os_printf("Sent callback on conn %p\n", conn);
if (conn == NULL) return; if (conn == NULL) return;
//os_printf("%d ST\n", system_get_time()); //os_printf("%d ST\n", system_get_time());
conn->readytosend = true; conn->readytosend = true;
sendtxbuffer(conn); // send possible new data in txbuffer 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 start (@) seen
TC_cmdChar, // command character seen
TC_cmdLine, // accumulating command
TC_tdchan, // saw data channel character
TC_tdlen1, // saw first data length character
TC_tdata0, // accumulate data, zero-terminated
TC_tdataN, // accumulate data, length-terminated
};
static uint8_t tcState = TC_newline;
static uint8_t tcChan; // channel for current command (index into tcConn)
#define CMD_MAX 256
static char tcCmdBuf[CMD_MAX];
static short tcCmdBufLen = 0;
static char tcCmdChar;
static short tcLen;
// scan a buffer for tcp client commands
static int ICACHE_FLASH_ATTR
tcpClientProcess(char *buf, int len)
{
char *in=buf, *out=buf;
for (short i=0; i<len; i++) {
char c = *in++;
//os_printf("tcState=%d c=%c\n", tcState, c);
switch (tcState) {
case TC_idle:
if (c == '\n') tcState = TC_newline;
break;
case TC_newline: // saw newline, expect ~
if (c == '~') {
tcState = TC_start;
continue; // gobble up the ~
} else {
break;
}
case TC_start: // saw ~, expect channel number
if (c == '@') {
tcState = TC_cmd;
continue;
} else if (c >= '0' && c <= '9') {
tcChan = c-'0';
tcState = TC_tdchan;
continue;
}
*out++ = '~'; // make up for '~' we skipped
break;
case TC_cmd: // saw control char (@), expect channel char
if (c >= '0' && c <= '9') {
tcChan = c-'0';
tcState = TC_cmdChar;
continue;
} else {
*out++ = '~'; // make up for '~' we skipped
*out++ = '@'; // make up for '@' we skipped
break;
}
case TC_cmdChar: // saw channel number, expect command char
tcCmdChar = c; // save command character
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(tcChan, tcCmdChar, tcCmdBuf);
tcState = TC_newline;
}
continue;
case TC_tdchan: // saw channel number, getting first length char
if (c >= '0' && c <= '9') {
tcLen = c-'0';
} else if (c >= 'A' && c <= 'F') {
tcLen = c-'A'+10;
} else {
*out++ = '~'; // make up for '~' we skipped
*out++ = '0'+tcChan;
break;
}
tcState = TC_tdlen1;
continue;
case TC_tdlen1: // saw first length char, get second
tcLen *= 16;
if (c >= '0' && c <= '9') {
tcLen += c-'0';
} else if (c >= 'A' && c <= 'F') {
tcLen += c-'A'+10;
} else {
*out++ = '~'; // make up for '~' we skipped
*out++ = '0'+tcChan;
break;
}
tcState = tcLen == 0 ? TC_tdata0 : TC_tdataN;
continue;
case TC_tdata0: // saw data length, getting data characters zero-terminated
if (c != 0) {
tcpClientSendChar(tcChan, c);
} else {
tcpClientSendPush(tcChan);
tcState = TC_idle;
}
continue;
case TC_tdataN: // saw data length, getting data characters length-terminated
tcpClientSendChar(tcChan, c);
tcLen--;
if (tcLen == 0) {
tcpClientSendPush(tcChan);
tcState = TC_idle;
}
continue;
}
*out++ = c;
}
//if (tcState != TC_idle) os_printf("tcState=%d\n", tcState);
return out-buf;
}
// callback with a buffer of characters that have arrived on the uart
void ICACHE_FLASH_ATTR
serbridgeUartCb(char *buf, int length) {
// push the buffer into the microcontroller console
for (int i=0; i<length; i++)
console_write_char(buf[i]);
// parse the buffer for TCP commands, this may remove characters from the buffer
length = tcpClientProcess(buf, length);
// push the buffer into each open connection
if (length > 0) {
for (int i=0; i<MAX_CONN; i++) {
if (connData[i].conn && connData[i].conn_mode != cmTcpClient) {
espbuffsend(&connData[i], buf, length);
}
}
}
serledFlash(50); // short blink on serial LED
} }
//===== Connect / disconnect //===== Connect / disconnect
@ -396,101 +274,107 @@ serbridgeUartCb(char *buf, int length) {
// Error callback (it's really poorly named, it's not a "connection reconnected" callback, // Error callback (it's really poorly named, it's not a "connection reconnected" callback,
// it's really a "connection broken, please reconnect" callback) // it's really a "connection broken, please reconnect" callback)
static void ICACHE_FLASH_ATTR serbridgeReconCb(void *arg, sint8 err) { static void ICACHE_FLASH_ATTR serbridgeReconCb(void *arg, sint8 err) {
serbridgeConnData *sbConn = serbridgeFindConnData(arg); serbridgeConnData *sbConn = serbridgeFindConnData(arg);
if (sbConn == NULL) return; if (sbConn == NULL) return;
// Close the connection if (sbConn->conn_mode == cmAVR) {
espconn_disconnect(sbConn->conn); if (slip_disabled > 0) slip_disabled--;
// free connection slot }
sbConn->conn = NULL; // Close the connection
espconn_disconnect(sbConn->conn);
// free connection slot
sbConn->conn = NULL;
} }
// Disconnection callback // Disconnection callback
static void ICACHE_FLASH_ATTR serbridgeDisconCb(void *arg) { static void ICACHE_FLASH_ATTR serbridgeDisconCb(void *arg) {
serbridgeConnData *sbConn = serbridgeFindConnData(arg); serbridgeConnData *sbConn = serbridgeFindConnData(arg);
if (sbConn == NULL) return; if (sbConn == NULL) return;
// send reset to arduino/ARM // send reset to arduino/ARM
if (sbConn->conn_mode == cmAVR && mcu_reset_pin >= 0) { if (sbConn->conn_mode == cmAVR) {
GPIO_OUTPUT_SET(mcu_reset_pin, 0); if (slip_disabled > 0) slip_disabled--;
os_delay_us(100L); if (mcu_reset_pin >= 0) {
GPIO_OUTPUT_SET(mcu_reset_pin, 1); GPIO_OUTPUT_SET(mcu_reset_pin, 0);
} os_delay_us(100L);
// free connection slot GPIO_OUTPUT_SET(mcu_reset_pin, 1);
sbConn->conn = NULL; }
}
// free connection slot
sbConn->conn = NULL;
} }
// New connection callback, use one of the connection descriptors, if we have one left. // New connection callback, use one of the connection descriptors, if we have one left.
static void ICACHE_FLASH_ATTR serbridgeConnectCb(void *arg) { static void ICACHE_FLASH_ATTR serbridgeConnectCb(void *arg) {
struct espconn *conn = arg; struct espconn *conn = arg;
// Find empty conndata in pool // Find empty conndata in pool
int i; int i;
for (i=0; i<MAX_CONN; i++) if (connData[i].conn==NULL) break; 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); os_printf("Accept port 23, conn=%p, pool slot %d\n", conn, i);
if (i==MAX_CONN) { if (i==MAX_CONN) {
os_printf("Aiee, conn pool overflow!\n"); os_printf("Aiee, conn pool overflow!\n");
espconn_disconnect(conn); espconn_disconnect(conn);
return; return;
} }
conn->reverse = connData+i; conn->reverse = connData+i;
connData[i].conn = conn; connData[i].conn = conn;
connData[i].txbufferlen = 0; connData[i].txbufferlen = 0;
connData[i].readytosend = true; connData[i].readytosend = true;
connData[i].telnet_state = 0; connData[i].telnet_state = 0;
connData[i].conn_mode = cmInit; connData[i].conn_mode = cmInit;
espconn_regist_recvcb(conn, serbridgeRecvCb); espconn_regist_recvcb(conn, serbridgeRecvCb);
espconn_regist_reconcb(conn, serbridgeReconCb); espconn_regist_reconcb(conn, serbridgeReconCb);
espconn_regist_disconcb(conn, serbridgeDisconCb); espconn_regist_disconcb(conn, serbridgeDisconCb);
espconn_regist_sentcb(conn, serbridgeSentCb); espconn_regist_sentcb(conn, serbridgeSentCb);
espconn_set_opt(conn, ESPCONN_REUSEADDR|ESPCONN_NODELAY); espconn_set_opt(conn, ESPCONN_REUSEADDR|ESPCONN_NODELAY);
} }
//===== Initialization //===== Initialization
void ICACHE_FLASH_ATTR serbridgeInitPins() { void ICACHE_FLASH_ATTR serbridgeInitPins() {
mcu_reset_pin = flashConfig.reset_pin; mcu_reset_pin = flashConfig.reset_pin;
mcu_isp_pin = flashConfig.isp_pin; mcu_isp_pin = flashConfig.isp_pin;
os_printf("Serbridge pins: reset=%d isp=%d swap=%d\n", os_printf("Serbridge pins: reset=%d isp=%d swap=%d\n",
mcu_reset_pin, mcu_isp_pin, flashConfig.swap_uart); mcu_reset_pin, mcu_isp_pin, flashConfig.swap_uart);
if (flashConfig.swap_uart) { if (flashConfig.swap_uart) {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 4); PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 4);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_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_MTCK_U);
PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U); PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U);
system_uart_swap(); system_uart_swap();
} else { } else {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, 0); PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, 0);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, 0); PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, 0);
system_uart_de_swap(); system_uart_de_swap();
} }
// set both pins to 1 before turning them on so we don't cause a reset // 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_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1);
if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1);
// switch pin mux to make these pins GPIO pins // switch pin mux to make these pins GPIO pins
if (mcu_reset_pin >= 0) makeGpio(mcu_reset_pin); if (mcu_reset_pin >= 0) makeGpio(mcu_reset_pin);
if (mcu_isp_pin >= 0) makeGpio(mcu_isp_pin); if (mcu_isp_pin >= 0) makeGpio(mcu_isp_pin);
} }
// Start transparent serial bridge TCP server on specified port (typ. 23) // Start transparent serial bridge TCP server on specified port (typ. 23)
void ICACHE_FLASH_ATTR serbridgeInit(int port) { void ICACHE_FLASH_ATTR serbridgeInit(int port) {
serbridgeInitPins(); serbridgeInitPins();
int i; int i;
for (i = 0; i < MAX_CONN; i++) { for (i = 0; i < MAX_CONN; i++) {
connData[i].conn = NULL; connData[i].conn = NULL;
connData[i].txbuffer = txbuffer[i]; connData[i].txbuffer = txbuffer[i];
} }
serbridgeConn.type = ESPCONN_TCP; serbridgeConn.type = ESPCONN_TCP;
serbridgeConn.state = ESPCONN_NONE; serbridgeConn.state = ESPCONN_NONE;
serbridgeTcp.local_port = port; serbridgeTcp.local_port = port;
serbridgeConn.proto.tcp = &serbridgeTcp; serbridgeConn.proto.tcp = &serbridgeTcp;
espconn_regist_connectcb(&serbridgeConn, serbridgeConnectCb); espconn_regist_connectcb(&serbridgeConn, serbridgeConnectCb);
espconn_accept(&serbridgeConn); espconn_accept(&serbridgeConn);
espconn_tcp_set_max_con_allow(&serbridgeConn, MAX_CONN); espconn_tcp_set_max_con_allow(&serbridgeConn, MAX_CONN);
espconn_regist_time(&serbridgeConn, SER_BRIDGE_TIMEOUT, 0); espconn_regist_time(&serbridgeConn, SER_BRIDGE_TIMEOUT, 0);
} }

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

@ -1,315 +0,0 @@
// 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;
}

@ -4,7 +4,7 @@
#include "uart_hw.h" #include "uart_hw.h"
// Receive callback function signature // 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 // Initialize UARTs to the provided baud rates (115200 recommended). This also makes the os_printf
// calls use uart1 for output (for debugging purposes) // calls use uart1 for output (for debugging purposes)

@ -31,30 +31,6 @@ static char* ICACHE_FLASH_ATTR check_header(void *buf) {
return NULL; return NULL;
} }
#if 0
//===== Cgi that reads the SPI flash. Assumes 512KByte flash.
int ICACHE_FLASH_ATTR cgiReadFlash(HttpdConnData *connData) {
int *pos=(int *)&connData->cgiData;
if (connData->conn==NULL) {
//Connection aborted. Clean up.
return HTTPD_CGI_DONE;
}
if (*pos==0) {
os_printf("Start flash download.\n");
httpdStartResponse(connData, 200);
httpdHeader(connData, "Content-Type", "application/bin");
httpdEndHeaders(connData);
*pos=0x40200000;
return HTTPD_CGI_MORE;
}
//Send 1K of flash per call. We will get called again if we haven't sent 512K yet.
espconn_sent(connData->conn, (uint8 *)(*pos), 1024);
*pos+=1024;
if (*pos>=0x40200000+(512*1024)) return HTTPD_CGI_DONE; else return HTTPD_CGI_MORE;
}
#endif
//===== Cgi to query which firmware needs to be uploaded next //===== Cgi to query which firmware needs to be uploaded next
int ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) { int ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) {
if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. if (connData->conn==NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up.

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

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

@ -17,7 +17,7 @@ FlashConfig flashDefault = {
"esp-link\0 ", // hostname "esp-link\0 ", // hostname
0, 0x00ffffff, 0, // static ip, netmask, gateway 0, 0x00ffffff, 0, // static ip, netmask, gateway
0, // log mode 0, // log mode
0, // swap_uart 0, // swap_uart (don't by default)
1, 0, // tcp_enable, rssi_enable 1, 0, // tcp_enable, rssi_enable
"\0", // api_key "\0", // api_key
}; };
@ -27,10 +27,16 @@ typedef union {
uint8_t block[128]; uint8_t block[128];
} FlashFull; } FlashFull;
// magic number to recognize thet these are our flash settings as opposed to some random stuff
#define FLASH_MAGIC (0xaa55) #define FLASH_MAGIC (0xaa55)
#define FLASH_ADDR (0x3E000) // size of the setting sector
#define FLASH_SECT (4096) #define FLASH_SECT (4096)
// address where to flash the settings: there are 16KB of reserved space at the end of the first
// flash partition, we use the upper 8KB (2 sectors)
#define FLASH_ADDR (FLASH_SECT + FIRMWARE_SIZE + 2*FLASH_SECT)
static int flash_pri; // primary flash sector (0 or 1, or -1 for error) static int flash_pri; // primary flash sector (0 or 1, or -1 for error)
#if 0 #if 0
@ -89,7 +95,7 @@ void ICACHE_FLASH_ATTR configWipe(void) {
spi_flash_erase_sector((FLASH_ADDR+FLASH_SECT)>>12); spi_flash_erase_sector((FLASH_ADDR+FLASH_SECT)>>12);
} }
static uint32_t ICACHE_FLASH_ATTR selectPrimary(FlashFull *fc0, FlashFull *fc1); static int ICACHE_FLASH_ATTR selectPrimary(FlashFull *fc0, FlashFull *fc1);
bool ICACHE_FLASH_ATTR configRestore(void) { bool ICACHE_FLASH_ATTR configRestore(void) {
FlashFull ff0, ff1; FlashFull ff0, ff1;
@ -111,7 +117,7 @@ bool ICACHE_FLASH_ATTR configRestore(void) {
return true; return true;
} }
static uint32_t ICACHE_FLASH_ATTR selectPrimary(FlashFull *ff0, FlashFull *ff1) { static int ICACHE_FLASH_ATTR selectPrimary(FlashFull *ff0, FlashFull *ff1) {
// check CRC of ff0 // check CRC of ff0
uint16_t crc = ff0->fc.crc; uint16_t crc = ff0->fc.crc;
ff0->fc.crc = 0; ff0->fc.crc = 0;

@ -1,6 +1,10 @@
#ifndef CONFIG_H #ifndef CONFIG_H
#define CONFIG_H #define CONFIG_H
// Flash configuration settings. When adding new items always add them at the end and formulate
// them such that a value of zero is an appropriate default or backwards compatible. Existing
// modules that are upgraded will have zero in the new fields. This ensures that an upgrade does
// not wipe out the old settings.
typedef struct { typedef struct {
uint32_t seq; // flash write sequence number uint32_t seq; // flash write sequence number
uint16_t magic, crc; uint16_t magic, crc;

@ -113,7 +113,7 @@ void user_rf_pre_init(void) {
extern uint32_t _binary_espfs_img_start; extern uint32_t _binary_espfs_img_start;
static char *rst_codes[] = { static char *rst_codes[] = {
"normal", "wdt reset", "exception", "soft wdt", "restart", "deep sleep", "???", "normal", "wdt reset", "exception", "soft wdt", "restart", "deep sleep", "external",
}; };
# define VERS_STR_STR(V) #V # define VERS_STR_STR(V) #V

Loading…
Cancel
Save