mirror of https://github.com/jeelabs/esp-link.git
commit
546b9c3774
@ -1,3 +0,0 @@ |
|||||||
[submodule "lib/heatshrink"] |
|
||||||
path = lib/heatshrink |
|
||||||
url = https://github.com/atomicobject/heatshrink.git |
|
@ -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; |
||||||
|
} |
||||||
|
|
@ -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_ */ |
||||||
|
|
||||||
|
/** @} */ |
||||||
|
/** @} */ |
@ -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; |
|
||||||
} |
|
||||||
|
|
Loading…
Reference in new issue