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