From ddd2af1a65b02d2a9352a664635f6f2825f0f7d4 Mon Sep 17 00:00:00 2001 From: Thorsten von Eicken Date: Mon, 24 Aug 2015 10:25:24 -0700 Subject: [PATCH] incorporated tuanpmt's slip protocol and rest library --- Makefile | 2 +- cmd/cmd.c | 179 ++++++++++ cmd/cmd.h | 90 +++++ cmd/handlers.c | 79 +++++ cmd/rest.c | 394 +++++++++++++++++++++ cmd/rest.h | 39 +++ {serial => cmd}/tcpclient.c | 0 {serial => cmd}/tcpclient.h | 2 +- serial/crc16.c | 78 +++++ serial/crc16.h | 100 ++++++ serial/serbridge.c | 670 +++++++++++++++--------------------- serial/serbridge.h | 2 +- serial/slip.c | 136 ++++++++ serial/uart.h | 2 +- user/cgiwifi.c | 5 +- user/cgiwifi.h | 2 + 16 files changed, 1382 insertions(+), 398 deletions(-) create mode 100644 cmd/cmd.c create mode 100644 cmd/cmd.h create mode 100644 cmd/handlers.c create mode 100644 cmd/rest.c create mode 100644 cmd/rest.h rename {serial => cmd}/tcpclient.c (100%) rename {serial => cmd}/tcpclient.h (86%) create mode 100644 serial/crc16.c create mode 100644 serial/crc16.h create mode 100644 serial/slip.c diff --git a/Makefile b/Makefile index 5b694ae..647ba20 100644 --- a/Makefile +++ b/Makefile @@ -146,7 +146,7 @@ TARGET = httpd APPGEN_TOOL ?= gen_appbin.py # which modules (subdirectories) of the project to include in compiling -MODULES = espfs httpd user serial +MODULES = espfs httpd user serial cmd EXTRA_INCDIR = include . # lib/heatshrink/ # libraries used in this project, mainly provided by the SDK diff --git a/cmd/cmd.c b/cmd/cmd.c new file mode 100644 index 0000000..06430ca --- /dev/null +++ b/cmd/cmd.c @@ -0,0 +1,179 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +// +// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh + +#include "esp8266.h" +#include "cmd.h" +#include "crc16.h" +#include "serbridge.h" +#include "uart.h" + +extern const CmdList commands[]; + +//===== ESP -> Serial responses + +static void ICACHE_FLASH_ATTR +CMD_ProtoWrite(uint8_t data) { + switch(data){ + case SLIP_START: + case SLIP_END: + case SLIP_REPL: + uart0_write_char(SLIP_REPL); + uart0_write_char(SLIP_ESC(data)); + break; + default: + uart0_write_char(data); + } +} + +static void ICACHE_FLASH_ATTR +CMD_ProtoWriteBuf(uint8_t *data, short len) { + while (len--) CMD_ProtoWrite(*data++); +} + +// Start a response, returns the partial CRC +uint16_t ICACHE_FLASH_ATTR +CMD_ResponseStart(uint16_t cmd, uint32_t callback, uint32_t _return, uint16_t argc) { + uint16_t crc = 0; + + uart0_write_char(SLIP_START); + CMD_ProtoWriteBuf((uint8_t*)&cmd, 2); + crc = crc16_data((uint8_t*)&cmd, 2, crc); + CMD_ProtoWriteBuf((uint8_t*)&callback, 4); + crc = crc16_data((uint8_t*)&callback, 4, crc); + CMD_ProtoWriteBuf((uint8_t*)&_return, 4); + crc = crc16_data((uint8_t*)&_return, 4, crc); + CMD_ProtoWriteBuf((uint8_t*)&argc, 2); + crc = crc16_data((uint8_t*)&argc, 2, crc); + return crc; +} + +// Adds data to a response, returns the partial CRC +uint16_t ICACHE_FLASH_ATTR +CMD_ResponseBody(uint16_t crc_in, uint8_t* data, short len) { + short pad_len = len+3 - (len+3)%4; // round up to multiple of 4 + CMD_ProtoWriteBuf((uint8_t*)&pad_len, 2); + crc_in = crc16_data((uint8_t*)&pad_len, 2, crc_in); + + CMD_ProtoWriteBuf(data, len); + crc_in = crc16_data(data, len, crc_in); + + if (pad_len > len) { + uint32_t temp = 0; + CMD_ProtoWriteBuf((uint8_t*)&temp, pad_len-len); + crc_in = crc16_data((uint8_t*)&temp, pad_len-len, crc_in); + } + + return crc_in; +} + +// Ends a response +void ICACHE_FLASH_ATTR +CMD_ResponseEnd(uint16_t crc) { + CMD_ProtoWriteBuf((uint8_t*)&crc, 2); + uart0_write_char(SLIP_END); +} + +//===== serial -> ESP commands + +// Execute a parsed command +static uint32_t ICACHE_FLASH_ATTR +CMD_Exec(const CmdList *scp, CmdPacket *packet) { + uint16_t crc = 0; + // Iterate through the command table and call the appropriate function + while (scp->sc_function != NULL) { + if(scp->sc_name == packet->cmd) { + //os_printf("CMD: Dispatching cmd=%d\n", packet->cmd); + // call command function + uint32_t ret = scp->sc_function(packet); + // if requestor asked for a response, send it + if (packet->_return){ + os_printf("CMD: Response: %lu, cmd: %d\r\n", ret, packet->cmd); + crc = CMD_ResponseStart(packet->cmd, 0, ret, 0); + CMD_ResponseEnd(crc); + } else { + //os_printf("CMD: no response (%lu)\n", packet->_return); + } + return ret; + } + scp++; + } + os_printf("CMD: cmd=%d not found\n", packet->cmd); + return 0; +} + +// Parse a packet and print info about it +void ICACHE_FLASH_ATTR +CMD_parse_packet(uint8_t *buf, short len) { + // minimum command length + if (len < 12) return; + + // init pointers into buffer + CmdPacket *packet = (CmdPacket*)buf; + uint8_t *data_ptr = (uint8_t*)&packet->args; + uint8_t *data_limit = data_ptr+len; + uint16_t argc = packet->argc; + uint16_t argn = 0; + + os_printf("CMD: cmd=%d argc=%d cb=%p ret=%lu\n", + packet->cmd, packet->argc, (void *)packet->callback, packet->_return); + + // print out arguments + while (data_ptr+2 < data_limit && argc--) { + short l = *(uint16_t*)data_ptr; + os_printf("CMD: arg[%d] len=%d:", argn++, l); + data_ptr += 2; + while (data_ptr < data_limit && l--) { + os_printf(" %02X", *data_ptr++); + } + os_printf("\n"); + } + + if (data_ptr <= data_limit) { + CMD_Exec(commands, packet); + } else { + os_printf("CMD: packet length overrun, parsing arg %d\n", argn-1); + } +} + +//===== Helpers to parse a command packet + +// Fill out a CmdRequest struct given a CmdPacket +void ICACHE_FLASH_ATTR +CMD_Request(CmdRequest *req, CmdPacket* cmd) { + req->cmd = cmd; + req->arg_num = 0; + req->arg_ptr = (uint8_t*)&cmd->args; +} + +// Return the number of arguments given a command struct +uint32_t ICACHE_FLASH_ATTR +CMD_GetArgc(CmdRequest *req) { + return req->cmd->argc; +} + +// Copy the next argument from a command structure into the data pointer, returns 0 on success +// -1 on error +int32_t ICACHE_FLASH_ATTR +CMD_PopArg(CmdRequest *req, void *data, uint16_t len) { + uint16_t length; + + if (req->arg_num >= req->cmd->argc) + return -1; + + length = *(uint16_t*)req->arg_ptr; + if (length != len) return -1; // safety check + + req->arg_ptr += 2; + os_memcpy(data, req->arg_ptr, length); + req->arg_ptr += length; + + req->arg_num ++; + return 0; +} + +// Return the length of the next argument +uint16_t ICACHE_FLASH_ATTR +CMD_ArgLen(CmdRequest *req) { + return *(uint16_t*)req->arg_ptr; +} diff --git a/cmd/cmd.h b/cmd/cmd.h new file mode 100644 index 0000000..5a66bc7 --- /dev/null +++ b/cmd/cmd.h @@ -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 diff --git a/cmd/handlers.c b/cmd/handlers.c new file mode 100644 index 0000000..73370dd --- /dev/null +++ b/cmd/handlers.c @@ -0,0 +1,79 @@ +// Copyright 2015 by Thorsten von Eicken, see LICENSE.txt +// +// Adapted from: github.com/tuanpmt/esp_bridge, Created on: Jan 9, 2015, Author: Minh + +#include "esp8266.h" +#include "cmd.h" +#include "rest.h" +#include "crc16.h" +#include "serbridge.h" +#include "uart.h" +#include "cgiwifi.h" + +static uint32_t ICACHE_FLASH_ATTR CMD_Null(CmdPacket *cmd); +static uint32_t ICACHE_FLASH_ATTR CMD_IsReady(CmdPacket *cmd); +static uint32_t ICACHE_FLASH_ATTR CMD_WifiConnect(CmdPacket *cmd); + +// Command dispatch table for serial -> ESP commands +const CmdList commands[] = { + {CMD_NULL, CMD_Null}, + {CMD_RESET, CMD_Null}, + {CMD_IS_READY, CMD_IsReady}, + {CMD_WIFI_CONNECT, CMD_WifiConnect}, + +/* + {CMD_MQTT_SETUP, MQTTAPP_Setup}, + {CMD_MQTT_CONNECT, MQTTAPP_Connect}, + {CMD_MQTT_DISCONNECT, MQTTAPP_Disconnect}, + {CMD_MQTT_PUBLISH, MQTTAPP_Publish}, + {CMD_MQTT_SUBSCRIBE , MQTTAPP_Subscribe}, + {CMD_MQTT_LWT, MQTTAPP_Lwt}, + */ + + {CMD_REST_SETUP, REST_Setup}, + {CMD_REST_REQUEST, REST_Request}, + {CMD_REST_SETHEADER, REST_SetHeader}, + {CMD_NULL, NULL} +}; + +// Command handler for IsReady (healthcheck) command +static uint32_t ICACHE_FLASH_ATTR +CMD_IsReady(CmdPacket *cmd) { + os_printf("CMD: Check ready\n"); + return 1; +} + +// Command handler for Null command +static uint32_t ICACHE_FLASH_ATTR +CMD_Null(CmdPacket *cmd) { + os_printf("CMD: NULL/unsupported command\n"); + return 1; +} + +static uint8_t lastWifiStatus; +static uint32_t wifiCallback; + +// Callback from wifi subsystem to notify us of status changes +static void ICACHE_FLASH_ATTR +CMD_WifiCb(uint8_t wifiStatus) { + if (wifiStatus != lastWifiStatus){ + lastWifiStatus = wifiStatus; + if (wifiCallback) { + uint8_t status = wifiStatus == wifiGotIP ? 5 : 1; + uint16_t crc = CMD_ResponseStart(CMD_WIFI_CONNECT, wifiCallback, 0, 1); + crc = CMD_ResponseBody(crc, (uint8_t*)&status, 1); + CMD_ResponseEnd(crc); + } + } +} + +// Command handler for Wifi connect command +static uint32_t ICACHE_FLASH_ATTR +CMD_WifiConnect(CmdPacket *cmd) { + if(cmd->argc != 2 || cmd->callback == 0) + return 0xFFFFFFFF; + + wifiStatusCb = CMD_WifiCb; // register our callback with wifi subsystem + wifiCallback = cmd->callback; // save the MCU's callback + return 1; +} diff --git a/cmd/rest.c b/cmd/rest.c new file mode 100644 index 0000000..133bf2f --- /dev/null +++ b/cmd/rest.c @@ -0,0 +1,394 @@ +/* + * api.c + * + * Created on: Mar 4, 2015 + * Author: Minh + */ +#include "esp8266.h" +#include "rest.h" +#include "cmd.h" + +extern uint8_t ICACHE_FLASH_ATTR UTILS_StrToIP(const char* str, void *ip); + +static void ICACHE_FLASH_ATTR +tcpclient_discon_cb(void *arg) { + //struct espconn *pespconn = (struct espconn *)arg; + //RestClient* client = (RestClient *)pespconn->reverse; +} + +static void ICACHE_FLASH_ATTR +tcpclient_recv(void *arg, char *pdata, unsigned short len) { + uint8_t currentLineIsBlank = 0; + uint8_t httpBody = 0; + uint8_t inStatus = 0; + char statusCode[4]; + int i = 0, j; + uint32_t code = 0; + uint16_t crc; + + struct espconn *pCon = (struct espconn*)arg; + RestClient *client = (RestClient *)pCon->reverse; + + for(j=0 ;jresp_cb, code, 0); + } else { + crc = CMD_ResponseStart(CMD_REST_EVENTS, client->resp_cb, code, 1); + crc = CMD_ResponseBody(crc, (uint8_t*)(pdata+j), body_len); + } + CMD_ResponseEnd(crc); + break; + } else { + if (c == '\n' && currentLineIsBlank) { + httpBody = true; + } + if (c == '\n') { + // you're starting a new line + currentLineIsBlank = true; + } else if (c != '\r') { + // you've gotten a character on the current line + currentLineIsBlank = false; + } + } + } + //if(client->security) + // espconn_secure_disconnect(client->pCon); + //else + espconn_disconnect(client->pCon); + +} + +static void ICACHE_FLASH_ATTR +tcpclient_sent_cb(void *arg) { + //struct espconn *pCon = (struct espconn *)arg; + //RestClient* client = (RestClient *)pCon->reverse; + os_printf("REST: Sent\n"); +} + +static void ICACHE_FLASH_ATTR +tcpclient_connect_cb(void *arg) { + struct espconn *pCon = (struct espconn *)arg; + RestClient* client = (RestClient *)pCon->reverse; + + espconn_regist_disconcb(client->pCon, tcpclient_discon_cb); + espconn_regist_recvcb(client->pCon, tcpclient_recv);//////// + espconn_regist_sentcb(client->pCon, tcpclient_sent_cb);/////// + + //if(client->security){ + // espconn_secure_sent(client->pCon, client->data, client->data_len); + //} + //else{ + espconn_sent(client->pCon, client->data, client->data_len); + //} +} + +static void ICACHE_FLASH_ATTR +tcpclient_recon_cb(void *arg, sint8 errType) { + //struct espconn *pCon = (struct espconn *)arg; + //RestClient* client = (RestClient *)pCon->reverse; +} + +static void ICACHE_FLASH_ATTR +rest_dns_found(const char *name, ip_addr_t *ipaddr, void *arg) { + struct espconn *pConn = (struct espconn *)arg; + RestClient* client = (RestClient *)pConn->reverse; + + if(ipaddr == NULL) { + os_printf("REST DNS: Got no ip, try to reconnect\n"); + return; + } + + os_printf("REST DNS: found ip %d.%d.%d.%d\n", + *((uint8 *) &ipaddr->addr), + *((uint8 *) &ipaddr->addr + 1), + *((uint8 *) &ipaddr->addr + 2), + *((uint8 *) &ipaddr->addr + 3)); + + if(client->ip.addr == 0 && ipaddr->addr != 0) { + os_memcpy(client->pCon->proto.tcp->remote_ip, &ipaddr->addr, 4); + //if(client->security){ + // espconn_secure_connect(client->pCon); + //} + //else { + espconn_connect(client->pCon); + //} + os_printf("REST: connecting...\n"); + } +} + +uint32_t ICACHE_FLASH_ATTR +REST_Setup(CmdPacket *cmd) { + CmdRequest req; + RestClient *client; + uint8_t *rest_host; + uint16_t len; + uint32_t port, security; + + + // start parsing the command + CMD_Request(&req, cmd); + os_printf("REST: setup argc=%ld\n", CMD_GetArgc(&req)); + if(CMD_GetArgc(&req) != 3) + return 0; + + // get the hostname + len = CMD_ArgLen(&req); + os_printf("REST: len=%d\n", len); + if (len > 128) return 0; // safety check + rest_host = (uint8_t*)os_zalloc(len + 1); + if (CMD_PopArg(&req, rest_host, len)) return 0; + rest_host[len] = 0; + os_printf("REST: setup host=%s", rest_host); + + // get the port + if (CMD_PopArg(&req, (uint8_t*)&port, 4)) { + os_free(rest_host); + return 0; + } + os_printf(" port=%ld", port); + + // get the security mode + if (CMD_PopArg(&req, (uint8_t*)&security, 4)) { + os_free(rest_host); + return 0; + } + os_printf(" security=%ld\n", security); + + // allocate a connection structure + client = (RestClient*)os_zalloc(sizeof(RestClient)); + os_memset(client, 0, sizeof(RestClient)); + if(client == NULL) + return 0; + + os_printf("REST: setup host=%s port=%ld security=%ld\n", rest_host, port, security); + + client->resp_cb = cmd->callback; + + client->host = rest_host; + client->port = port; + client->security = security; + client->ip.addr = 0; + + client->data = (uint8_t*)os_zalloc(1024); + + client->header = (uint8_t*)os_zalloc(4); + client->header[0] = 0; + + client->content_type = (uint8_t*)os_zalloc(22); + os_sprintf((char *)client->content_type, "x-www-form-urlencoded"); + client->content_type[21] = 0; + + client->user_agent = (uint8_t*)os_zalloc(9); + os_sprintf((char *)client->user_agent, "esp-link"); + + client->pCon = (struct espconn *)os_zalloc(sizeof(struct espconn)); + client->pCon->proto.tcp = (esp_tcp *)os_zalloc(sizeof(esp_tcp)); + + client->pCon->type = ESPCONN_TCP; + client->pCon->state = ESPCONN_NONE; + client->pCon->proto.tcp->local_port = espconn_port(); + client->pCon->proto.tcp->remote_port = client->port; + + client->pCon->reverse = client; + + return (uint32_t)client; +} + +uint32_t ICACHE_FLASH_ATTR +REST_SetHeader(CmdPacket *cmd) { + CmdRequest req; + RestClient *client; + uint16_t len; + uint32_t header_index, client_ptr = 0; + + CMD_Request(&req, cmd); + + if(CMD_GetArgc(&req) != 3) + return 0; + + // Get client -- TODO: Whoa, this is totally unsafe! + if (CMD_PopArg(&req, (uint8_t*)&client_ptr, 4)) return 0; + client = (RestClient*)client_ptr; + + // Get header selector + if (CMD_PopArg(&req, (uint8_t*)&header_index, 4)) return 0; + + // Get header value + len = CMD_ArgLen(&req); + if (len > 256) return 0; //safety check + switch(header_index) { + case HEADER_GENERIC: + if(client->header) + os_free(client->header); + client->header = (uint8_t*)os_zalloc(len + 1); + CMD_PopArg(&req, (uint8_t*)client->header, len); + client->header[len] = 0; + os_printf("REST: Set header: %s\r\n", client->header); + break; + case HEADER_CONTENT_TYPE: + if(client->content_type) + os_free(client->content_type); + client->content_type = (uint8_t*)os_zalloc(len + 1); + CMD_PopArg(&req, (uint8_t*)client->content_type, len); + client->content_type[len] = 0; + os_printf("REST: Set content_type: %s\r\n", client->content_type); + break; + case HEADER_USER_AGENT: + if(client->user_agent) + os_free(client->user_agent); + client->user_agent = (uint8_t*)os_zalloc(len + 1); + CMD_PopArg(&req, (uint8_t*)client->user_agent, len); + client->user_agent[len] = 0; + os_printf("REST: Set user_agent: %s\r\n", client->user_agent); + break; + } + return 1; +} + +uint32_t ICACHE_FLASH_ATTR +REST_Request(CmdPacket *cmd) { + CmdRequest req; + RestClient *client; + uint16_t len, realLen = 0; + uint32_t client_ptr; + uint8_t *body = NULL; + uint8_t method[16]; + uint8_t path[1024]; + + CMD_Request(&req, cmd); + + if(CMD_GetArgc(&req) <3) + return 0; + + // Get client -- TODO: Whoa, this is totally unsafe! + if(CMD_PopArg(&req, (uint8_t*)&client_ptr, 4)) return 0; + client = (RestClient*)client_ptr; + + // Get HTTP method + len = CMD_ArgLen(&req); + if (len > 15) return 0; + CMD_PopArg(&req, method, len); + method[len] = 0; + + // Get HTTP path + len = CMD_ArgLen(&req); + if (len > 1023) return 0; + CMD_PopArg(&req, path, len); + path[len] = 0; + + // Get HTTP body + if (CMD_GetArgc(&req) == 3){ + realLen = 0; + len = 0; + } else { + CMD_PopArg(&req, (uint8_t*)&realLen, 4); + + len = CMD_ArgLen(&req); + if (len > 2048 || realLen > len) return 0; + body = (uint8_t*)os_zalloc(len + 1); + CMD_PopArg(&req, body, len); + body[len] = 0; + } + + client->pCon->state = ESPCONN_NONE; + + os_printf("REST: method: %s, path: %s\n", method, path); + + client->data_len = os_sprintf((char*)client->data, "%s %s HTTP/1.1\r\n" + "Host: %s\r\n" + "%s" + "Content-Length: %d\r\n" + "Connection: close\r\n" + "Content-Type: %s\r\n" + "User-Agent: %s\r\n\r\n", + method, path, + client->host, + client->header, + realLen, + client->content_type, + client->user_agent); + + if (realLen > 0){ + os_memcpy(client->data + client->data_len, body, realLen); + client->data_len += realLen; + //os_sprintf(client->data + client->data_len, "\r\n\r\n"); + //client->data_len += 4; + } + + client->pCon->state = ESPCONN_NONE; + espconn_regist_connectcb(client->pCon, tcpclient_connect_cb); + espconn_regist_reconcb(client->pCon, tcpclient_recon_cb); + + if(UTILS_StrToIP((char *)client->host, &client->pCon->proto.tcp->remote_ip)) { + os_printf("REST: Connect to ip %s:%ld\n",client->host, client->port); + //if(client->security){ + // espconn_secure_connect(client->pCon); + //} + //else { + espconn_connect(client->pCon); + //} + } else { + os_printf("REST: Connect to host %s:%ld\n", client->host, client->port); + espconn_gethostbyname(client->pCon, (char *)client->host, &client->ip, rest_dns_found); + } + + if(body) os_free(body); + return 1; +} + +uint8_t ICACHE_FLASH_ATTR +UTILS_StrToIP(const char* str, void *ip) +{ + /* The count of the number of bytes processed. */ + int i; + /* A pointer to the next digit to process. */ + const char * start; + + start = str; + for (i = 0; i < 4; i++) { + /* The digit being processed. */ + char c; + /* The value of this byte. */ + int n = 0; + while (1) { + c = * start; + start++; + if (c >= '0' && c <= '9') { + n *= 10; + n += c - '0'; + } + /* We insist on stopping at "." if we are still parsing + the first, second, or third numbers. If we have reached + the end of the numbers, we will allow any character. */ + else if ((i < 3 && c == '.') || i == 3) { + break; + } + else { + return 0; + } + } + if (n >= 256) { + return 0; + } + ((uint8_t*)ip)[i] = n; + } + return 1; +} diff --git a/cmd/rest.h b/cmd/rest.h new file mode 100644 index 0000000..13159ea --- /dev/null +++ b/cmd/rest.h @@ -0,0 +1,39 @@ +/* + * api.h + * + * Created on: Mar 4, 2015 + * Author: Minh + */ + +#ifndef MODULES_API_H_ +#define MODULES_API_H_ + +#include "c_types.h" +#include "ip_addr.h" +#include "cmd.h" + +typedef enum { + HEADER_GENERIC = 0, + HEADER_CONTENT_TYPE, + HEADER_USER_AGENT +} HEADER_TYPE; + +typedef struct { + uint8_t *host; + uint32_t port; + uint32_t security; + ip_addr_t ip; + struct espconn *pCon; + uint8_t *header; + uint8_t *data; + uint32_t data_len; + uint8_t *content_type; + uint8_t *user_agent; + uint32_t resp_cb; +} RestClient; + +uint32_t REST_Setup(CmdPacket *cmd); +uint32_t REST_Request(CmdPacket *cmd); +uint32_t REST_SetHeader(CmdPacket *cmd); + +#endif /* MODULES_INCLUDE_API_H_ */ diff --git a/serial/tcpclient.c b/cmd/tcpclient.c similarity index 100% rename from serial/tcpclient.c rename to cmd/tcpclient.c diff --git a/serial/tcpclient.h b/cmd/tcpclient.h similarity index 86% rename from serial/tcpclient.h rename to cmd/tcpclient.h index 9bfeb01..ff0ff9d 100644 --- a/serial/tcpclient.h +++ b/cmd/tcpclient.h @@ -4,7 +4,7 @@ // max number of channels the client can open #define MAX_TCP_CHAN 8 -// Parse and perform the commandm cmdBuf must be null-terminated +// Parse and perform the command, cmdBuf must be null-terminated bool tcpClientCommand(uint8_t chan, char cmd, char *cmdBuf); // Append a character to the specified channel diff --git a/serial/crc16.c b/serial/crc16.c new file mode 100644 index 0000000..22a6651 --- /dev/null +++ b/serial/crc16.c @@ -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 + * + */ + +/* 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; +} +/*---------------------------------------------------------------------------*/ + +/** @} */ diff --git a/serial/crc16.h b/serial/crc16.h new file mode 100644 index 0000000..bd4c52e --- /dev/null +++ b/serial/crc16.h @@ -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 + * + */ + +/** \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_ */ + +/** @} */ +/** @} */ diff --git a/serial/serbridge.c b/serial/serbridge.c index 5c00175..415772b 100644 --- a/serial/serbridge.c +++ b/serial/serbridge.c @@ -9,24 +9,27 @@ #include "gpio.h" #include "uart.h" +#include "crc16.h" #include "serbridge.h" #include "serled.h" #include "config.h" #include "console.h" -#include "tcpclient.h" +#include "cmd.h" static struct espconn serbridgeConn; static esp_tcp serbridgeTcp; static int8_t mcu_reset_pin, mcu_isp_pin; +extern uint8_t slip_disabled; // disable slip to allow flashing of attached MCU + static sint8 ICACHE_FLASH_ATTR espbuffsend(serbridgeConnData *conn, const char *data, uint16 len); // Connection pool serbridgeConnData connData[MAX_CONN]; // Given a pointer to an espconn struct find the connection that correcponds to it static serbridgeConnData ICACHE_FLASH_ATTR *serbridgeFindConnData(void *arg) { - struct espconn *conn = arg; - return arg == NULL ? NULL : (serbridgeConnData *)conn->reverse; + struct espconn *conn = arg; + return arg == NULL ? NULL : (serbridgeConnData *)conn->reverse; } //===== TCP -> UART @@ -50,149 +53,152 @@ enum { TN_normal, TN_iac, TN_will, TN_start, TN_end, TN_comPort, TN_setControl } static uint8_t ICACHE_FLASH_ATTR telnetUnwrap(uint8_t *inBuf, int len, uint8_t state) { - for (int i=0; i write one to outbuf and go normal again - state = TN_normal; - uart0_write_char(c); - break; - case WILL: // negotiation - state = TN_will; - break; - case SB: // command sequence begin - state = TN_start; - break; - case SE: // command sequence end - state = TN_normal; - break; - default: // not sure... let's ignore - uart0_write_char(IAC); - uart0_write_char(c); - } - break; - case TN_will: - state = TN_normal; // yes, we do COM port options, let's go back to normal - break; - case TN_start: // in command seq, now comes the type of cmd - if (c == ComPortOpt) state = TN_comPort; - else state = TN_end; // an option we don't know, skip 'til the end seq - break; - case TN_end: // wait for end seq - if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq - break; - case TN_comPort: - if (c == SetControl) state = TN_setControl; - else state = TN_end; - break; - case TN_setControl: // switch control line and delay a tad - switch (c) { - case DTR_ON: - if (mcu_reset_pin >= 0) { - os_printf("MCU reset gpio%d\n", mcu_reset_pin); - GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - } else os_printf("MCU reset: no pin\n"); - break; - case DTR_OFF: - if (mcu_reset_pin >= 0) { - GPIO_OUTPUT_SET(mcu_reset_pin, 1); - os_delay_us(100L); - } - break; - case RTS_ON: - if (mcu_isp_pin >= 0) { - os_printf("MCU ISP gpio%d\n", mcu_isp_pin); - GPIO_OUTPUT_SET(mcu_isp_pin, 0); - os_delay_us(100L); - } else os_printf("MCU isp: no pin\n"); - break; - case RTS_OFF: - if (mcu_isp_pin >= 0) { - GPIO_OUTPUT_SET(mcu_isp_pin, 1); - os_delay_us(100L); - } - break; - } - state = TN_end; - break; - } - } - return state; + for (int i=0; i write one to outbuf and go normal again + state = TN_normal; + uart0_write_char(c); + break; + case WILL: // negotiation + state = TN_will; + break; + case SB: // command sequence begin + state = TN_start; + break; + case SE: // command sequence end + state = TN_normal; + break; + default: // not sure... let's ignore + uart0_write_char(IAC); + uart0_write_char(c); + } + break; + case TN_will: + state = TN_normal; // yes, we do COM port options, let's go back to normal + break; + case TN_start: // in command seq, now comes the type of cmd + if (c == ComPortOpt) state = TN_comPort; + else state = TN_end; // an option we don't know, skip 'til the end seq + break; + case TN_end: // wait for end seq + if (c == IAC) state = TN_iac; // simple wait to accept end or next escape seq + break; + case TN_comPort: + if (c == SetControl) state = TN_setControl; + else state = TN_end; + break; + case TN_setControl: // switch control line and delay a tad + switch (c) { + case DTR_ON: + if (mcu_reset_pin >= 0) { + os_printf("MCU reset gpio%d\n", mcu_reset_pin); + GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + } else os_printf("MCU reset: no pin\n"); + break; + case DTR_OFF: + if (mcu_reset_pin >= 0) { + GPIO_OUTPUT_SET(mcu_reset_pin, 1); + os_delay_us(100L); + } + break; + case RTS_ON: + if (mcu_isp_pin >= 0) { + os_printf("MCU ISP gpio%d\n", mcu_isp_pin); + GPIO_OUTPUT_SET(mcu_isp_pin, 0); + os_delay_us(100L); + } else os_printf("MCU isp: no pin\n"); + slip_disabled++; + break; + case RTS_OFF: + if (mcu_isp_pin >= 0) { + GPIO_OUTPUT_SET(mcu_isp_pin, 1); + os_delay_us(100L); + } + if (slip_disabled > 0) slip_disabled--; + break; + } + state = TN_end; + break; + } + } + return state; } +// Generate a reset pulse for the attached microcontroller void ICACHE_FLASH_ATTR serbridgeReset() { - if (mcu_reset_pin >= 0) { - os_printf("MCU reset gpio%d\n", mcu_reset_pin); - GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - GPIO_OUTPUT_SET(mcu_reset_pin, 1); - } else os_printf("MCU reset: no pin\n"); + if (mcu_reset_pin >= 0) { + os_printf("MCU reset gpio%d\n", mcu_reset_pin); + GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + GPIO_OUTPUT_SET(mcu_reset_pin, 1); + } else os_printf("MCU reset: no pin\n"); } // Receive callback static void ICACHE_FLASH_ATTR serbridgeRecvCb(void *arg, char *data, unsigned short len) { - serbridgeConnData *conn = serbridgeFindConnData(arg); - //os_printf("Receive callback on conn %p\n", conn); - if (conn == NULL) return; - - // at the start of a connection we're in cmInit mode and we wait for the first few characters - // to arrive in order to decide what type of connection this is.. The following if statements - // do this dispatch. An issue here is that we assume that the first few characters all arrive - // in the same TCP packet, which is true if the sender is a program, but not necessarily - // if the sender is a person typing (although in that case the line-oriented TTY input seems - // to make it work too). If this becomes a problem we need to buffer the first few chars... - if (conn->conn_mode == cmInit) { - - // If the connection starts with the Arduino or ARM reset sequence we perform a RESET - if ((len == 2 && strncmp(data, "0 ", 2) == 0) || - (len == 2 && strncmp(data, "?\n", 2) == 0) || - (len == 3 && strncmp(data, "?\r\n", 3) == 0)) { - os_printf("MCU Reset=%d ISP=%d\n", mcu_reset_pin, mcu_isp_pin); - os_delay_us(2*1000L); // time for os_printf to happen - // send reset to arduino/ARM - if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 0); - os_delay_us(100L); - if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); - os_delay_us(100L); - if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); - os_delay_us(1000L); - conn->conn_mode = cmAVR; - - - // If the connection starts with a telnet negotiation we will do telnet - } else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) { - conn->conn_mode = cmTelnet; - conn->telnet_state = TN_normal; - // note that the three negotiation chars will be gobbled-up by telnetUnwrap - os_printf("telnet mode\n"); - - // looks like a plain-vanilla connection! - } else { - conn->conn_mode = cmTransparent; - } - - // Process return data on TCP client connections - } else if (conn->conn_mode == cmTcpClient) { - } - - // write the buffer to the uart - if (conn->conn_mode == cmTelnet) { - conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state); - } else { - uart0_tx_buffer(data, len); - } - - serledFlash(50); // short blink on serial LED + serbridgeConnData *conn = serbridgeFindConnData(arg); + //os_printf("Receive callback on conn %p\n", conn); + if (conn == NULL) return; + + // at the start of a connection we're in cmInit mode and we wait for the first few characters + // to arrive in order to decide what type of connection this is.. The following if statements + // do this dispatch. An issue here is that we assume that the first few characters all arrive + // in the same TCP packet, which is true if the sender is a program, but not necessarily + // if the sender is a person typing (although in that case the line-oriented TTY input seems + // to make it work too). If this becomes a problem we need to buffer the first few chars... + if (conn->conn_mode == cmInit) { + + // If the connection starts with the Arduino or ARM reset sequence we perform a RESET + if ((len == 2 && strncmp(data, "0 ", 2) == 0) || + (len == 2 && strncmp(data, "?\n", 2) == 0) || + (len == 3 && strncmp(data, "?\r\n", 3) == 0)) { + os_printf("MCU Reset=gpio%d ISP=gpio%d\n", mcu_reset_pin, mcu_isp_pin); + os_delay_us(2*1000L); // time for os_printf to happen + // send reset to arduino/ARM + if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 0); + os_delay_us(100L); + if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); + os_delay_us(100L); + if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); + os_delay_us(1000L); + conn->conn_mode = cmAVR; + slip_disabled++; // disable SLIP so it doesn't interfere with flashing + + // If the connection starts with a telnet negotiation we will do telnet + } else if (len >= 3 && strncmp(data, (char[]){IAC, WILL, ComPortOpt}, 3) == 0) { + conn->conn_mode = cmTelnet; + conn->telnet_state = TN_normal; + // note that the three negotiation chars will be gobbled-up by telnetUnwrap + os_printf("telnet mode\n"); + + // looks like a plain-vanilla connection! + } else { + conn->conn_mode = cmTransparent; + } + + // Process return data on TCP client connections + } else if (conn->conn_mode == cmTcpClient) { + } + + // write the buffer to the uart + if (conn->conn_mode == cmTelnet) { + conn->telnet_state = telnetUnwrap((uint8_t *)data, len, conn->telnet_state); + } else { + uart0_tx_buffer(data, len); + } + + serledFlash(50); // short blink on serial LED } //===== UART -> TCP @@ -203,18 +209,19 @@ static char txbuffer[MAX_CONN][MAX_TXBUFFER]; // Send all data in conn->txbuffer // returns result from espconn_sent if data in buffer or ESPCONN_OK (0) // Use only internally from espbuffsend and serbridgeSentCb -static sint8 ICACHE_FLASH_ATTR sendtxbuffer(serbridgeConnData *conn) { - sint8 result = ESPCONN_OK; - if (conn->txbufferlen != 0) { - //os_printf("%d TX %d\n", system_get_time(), conn->txbufferlen); - conn->readytosend = false; - result = espconn_sent(conn->conn, (uint8_t*)conn->txbuffer, conn->txbufferlen); - conn->txbufferlen = 0; - if (result != ESPCONN_OK) { - os_printf("sendtxbuffer: espconn_sent error %d on conn %p\n", result, conn); - } - } - return result; +static sint8 ICACHE_FLASH_ATTR +sendtxbuffer(serbridgeConnData *conn) { + sint8 result = ESPCONN_OK; + if (conn->txbufferlen != 0) { + //os_printf("%d TX %d\n", system_get_time(), conn->txbufferlen); + conn->readytosend = false; + result = espconn_sent(conn->conn, (uint8_t*)conn->txbuffer, conn->txbufferlen); + conn->txbufferlen = 0; + if (result != ESPCONN_OK) { + os_printf("sendtxbuffer: espconn_sent error %d on conn %p\n", result, conn); + } + } + return result; } // espbuffsend adds data to the send buffer. If the previous send was completed it calls @@ -222,173 +229,44 @@ static sint8 ICACHE_FLASH_ATTR sendtxbuffer(serbridgeConnData *conn) { // Returns ESPCONN_OK (0) for success, -128 if buffer is full or error from espconn_sent // Use espbuffsend instead of espconn_sent as it solves the problem that espconn_sent must // only be called *after* receiving an espconn_sent_callback for the previous packet. -static sint8 ICACHE_FLASH_ATTR espbuffsend(serbridgeConnData *conn, const char *data, uint16 len) { - if (conn->txbufferlen + len > MAX_TXBUFFER) { - os_printf("espbuffsend: txbuffer full on conn %p\n", conn); - return -128; - } - os_memcpy(conn->txbuffer + conn->txbufferlen, data, len); - conn->txbufferlen += len; - if (conn->readytosend) { - return sendtxbuffer(conn); - } else { - //os_printf("%d QU %d\n", system_get_time(), conn->txbufferlen); - } - return ESPCONN_OK; +static sint8 ICACHE_FLASH_ATTR +espbuffsend(serbridgeConnData *conn, const char *data, uint16 len) { + if (conn->txbufferlen + len > MAX_TXBUFFER) { + os_printf("espbuffsend: txbuffer full on conn %p\n", conn); + return -128; + } + os_memcpy(conn->txbuffer + conn->txbufferlen, data, len); + conn->txbufferlen += len; + if (conn->readytosend) { + return sendtxbuffer(conn); + } else { + //os_printf("%d QU %d\n", system_get_time(), conn->txbufferlen); + } + return ESPCONN_OK; +} + +void ICACHE_FLASH_ATTR +console_process(char *buf, short len) { + // push buffer into web-console + for (short i=0; ireadytosend = true; - sendtxbuffer(conn); // send possible new data in txbuffer -} - -// TCP client connection state machine -// This processes commands from the attached uC to open outboud TCP connections -enum { - TC_idle, // in-between commands - TC_newline, // newline seen - TC_start, // start character (~) seen - TC_cmd, // command start (@) seen - TC_cmdChar, // command character seen - TC_cmdLine, // accumulating command - TC_tdchan, // saw data channel character - TC_tdlen1, // saw first data length character - TC_tdata0, // accumulate data, zero-terminated - TC_tdataN, // accumulate data, length-terminated -}; -static uint8_t tcState = TC_newline; -static uint8_t tcChan; // channel for current command (index into tcConn) - -#define CMD_MAX 256 -static char tcCmdBuf[CMD_MAX]; -static short tcCmdBufLen = 0; -static char tcCmdChar; -static short tcLen; - -// scan a buffer for tcp client commands -static int ICACHE_FLASH_ATTR -tcpClientProcess(char *buf, int len) -{ - char *in=buf, *out=buf; - for (short i=0; i= '0' && c <= '9') { - tcChan = c-'0'; - tcState = TC_tdchan; - continue; - } - *out++ = '~'; // make up for '~' we skipped - break; - case TC_cmd: // saw control char (@), expect channel char - if (c >= '0' && c <= '9') { - tcChan = c-'0'; - tcState = TC_cmdChar; - continue; - } else { - *out++ = '~'; // make up for '~' we skipped - *out++ = '@'; // make up for '@' we skipped - break; - } - case TC_cmdChar: // saw channel number, expect command char - tcCmdChar = c; // save command character - tcCmdBufLen = 0; // empty the command buffer - tcState = TC_cmdLine; - continue; - case TC_cmdLine: // accumulating command in buffer - if (c != '\n') { - if (tcCmdBufLen < CMD_MAX) tcCmdBuf[tcCmdBufLen++] = c; - } else { - tcpClientCommand(tcChan, tcCmdChar, tcCmdBuf); - tcState = TC_newline; - } - continue; - case TC_tdchan: // saw channel number, getting first length char - if (c >= '0' && c <= '9') { - tcLen = c-'0'; - } else if (c >= 'A' && c <= 'F') { - tcLen = c-'A'+10; - } else { - *out++ = '~'; // make up for '~' we skipped - *out++ = '0'+tcChan; - break; - } - tcState = TC_tdlen1; - continue; - case TC_tdlen1: // saw first length char, get second - tcLen *= 16; - if (c >= '0' && c <= '9') { - tcLen += c-'0'; - } else if (c >= 'A' && c <= 'F') { - tcLen += c-'A'+10; - } else { - *out++ = '~'; // make up for '~' we skipped - *out++ = '0'+tcChan; - break; - } - tcState = tcLen == 0 ? TC_tdata0 : TC_tdataN; - continue; - case TC_tdata0: // saw data length, getting data characters zero-terminated - if (c != 0) { - tcpClientSendChar(tcChan, c); - } else { - tcpClientSendPush(tcChan); - tcState = TC_idle; - } - continue; - case TC_tdataN: // saw data length, getting data characters length-terminated - tcpClientSendChar(tcChan, c); - tcLen--; - if (tcLen == 0) { - tcpClientSendPush(tcChan); - tcState = TC_idle; - } - continue; - } - *out++ = c; - } - //if (tcState != TC_idle) os_printf("tcState=%d\n", tcState); - return out-buf; -} - -// callback with a buffer of characters that have arrived on the uart -void ICACHE_FLASH_ATTR -serbridgeUartCb(char *buf, int length) { - // push the buffer into the microcontroller console - for (int i=0; i 0) { - for (int i=0; ireadytosend = true; + sendtxbuffer(conn); // send possible new data in txbuffer } //===== Connect / disconnect @@ -396,101 +274,107 @@ serbridgeUartCb(char *buf, int length) { // Error callback (it's really poorly named, it's not a "connection reconnected" callback, // it's really a "connection broken, please reconnect" callback) static void ICACHE_FLASH_ATTR serbridgeReconCb(void *arg, sint8 err) { - serbridgeConnData *sbConn = serbridgeFindConnData(arg); - if (sbConn == NULL) return; - // Close the connection - espconn_disconnect(sbConn->conn); - // free connection slot - sbConn->conn = NULL; + serbridgeConnData *sbConn = serbridgeFindConnData(arg); + if (sbConn == NULL) return; + if (sbConn->conn_mode == cmAVR) { + if (slip_disabled > 0) slip_disabled--; + } + // Close the connection + espconn_disconnect(sbConn->conn); + // free connection slot + sbConn->conn = NULL; } // Disconnection callback static void ICACHE_FLASH_ATTR serbridgeDisconCb(void *arg) { - serbridgeConnData *sbConn = serbridgeFindConnData(arg); - if (sbConn == NULL) return; - // send reset to arduino/ARM - if (sbConn->conn_mode == cmAVR && mcu_reset_pin >= 0) { - GPIO_OUTPUT_SET(mcu_reset_pin, 0); - os_delay_us(100L); - GPIO_OUTPUT_SET(mcu_reset_pin, 1); - } - // free connection slot - sbConn->conn = NULL; + serbridgeConnData *sbConn = serbridgeFindConnData(arg); + if (sbConn == NULL) return; + // send reset to arduino/ARM + if (sbConn->conn_mode == cmAVR) { + if (slip_disabled > 0) slip_disabled--; + if (mcu_reset_pin >= 0) { + GPIO_OUTPUT_SET(mcu_reset_pin, 0); + os_delay_us(100L); + GPIO_OUTPUT_SET(mcu_reset_pin, 1); + } + } + // free connection slot + sbConn->conn = NULL; } // New connection callback, use one of the connection descriptors, if we have one left. static void ICACHE_FLASH_ATTR serbridgeConnectCb(void *arg) { - struct espconn *conn = arg; - // Find empty conndata in pool - int i; - for (i=0; ireverse = connData+i; - connData[i].conn = conn; - connData[i].txbufferlen = 0; - connData[i].readytosend = true; - connData[i].telnet_state = 0; - connData[i].conn_mode = cmInit; - - espconn_regist_recvcb(conn, serbridgeRecvCb); - espconn_regist_reconcb(conn, serbridgeReconCb); - espconn_regist_disconcb(conn, serbridgeDisconCb); - espconn_regist_sentcb(conn, serbridgeSentCb); - - espconn_set_opt(conn, ESPCONN_REUSEADDR|ESPCONN_NODELAY); + struct espconn *conn = arg; + // Find empty conndata in pool + int i; + for (i=0; ireverse = connData+i; + connData[i].conn = conn; + connData[i].txbufferlen = 0; + connData[i].readytosend = true; + connData[i].telnet_state = 0; + connData[i].conn_mode = cmInit; + + espconn_regist_recvcb(conn, serbridgeRecvCb); + espconn_regist_reconcb(conn, serbridgeReconCb); + espconn_regist_disconcb(conn, serbridgeDisconCb); + espconn_regist_sentcb(conn, serbridgeSentCb); + + espconn_set_opt(conn, ESPCONN_REUSEADDR|ESPCONN_NODELAY); } //===== Initialization void ICACHE_FLASH_ATTR serbridgeInitPins() { - mcu_reset_pin = flashConfig.reset_pin; - mcu_isp_pin = flashConfig.isp_pin; - os_printf("Serbridge pins: reset=%d isp=%d swap=%d\n", - mcu_reset_pin, mcu_isp_pin, flashConfig.swap_uart); - - if (flashConfig.swap_uart) { - PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 4); - PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 4); - PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U); - PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U); - system_uart_swap(); - } else { - PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, 0); - PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, 0); - system_uart_de_swap(); - } - - // set both pins to 1 before turning them on so we don't cause a reset - if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); - if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); - // switch pin mux to make these pins GPIO pins - if (mcu_reset_pin >= 0) makeGpio(mcu_reset_pin); - if (mcu_isp_pin >= 0) makeGpio(mcu_isp_pin); + mcu_reset_pin = flashConfig.reset_pin; + mcu_isp_pin = flashConfig.isp_pin; + os_printf("Serbridge pins: reset=%d isp=%d swap=%d\n", + mcu_reset_pin, mcu_isp_pin, flashConfig.swap_uart); + + if (flashConfig.swap_uart) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, 4); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDO_U, 4); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTCK_U); + PIN_PULLUP_DIS(PERIPHS_IO_MUX_MTDO_U); + system_uart_swap(); + } else { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, 0); + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, 0); + system_uart_de_swap(); + } + + // set both pins to 1 before turning them on so we don't cause a reset + if (mcu_isp_pin >= 0) GPIO_OUTPUT_SET(mcu_isp_pin, 1); + if (mcu_reset_pin >= 0) GPIO_OUTPUT_SET(mcu_reset_pin, 1); + // switch pin mux to make these pins GPIO pins + if (mcu_reset_pin >= 0) makeGpio(mcu_reset_pin); + if (mcu_isp_pin >= 0) makeGpio(mcu_isp_pin); } // Start transparent serial bridge TCP server on specified port (typ. 23) void ICACHE_FLASH_ATTR serbridgeInit(int port) { - serbridgeInitPins(); - - int i; - for (i = 0; i < MAX_CONN; i++) { - connData[i].conn = NULL; - connData[i].txbuffer = txbuffer[i]; - } - serbridgeConn.type = ESPCONN_TCP; - serbridgeConn.state = ESPCONN_NONE; - serbridgeTcp.local_port = port; - serbridgeConn.proto.tcp = &serbridgeTcp; - - espconn_regist_connectcb(&serbridgeConn, serbridgeConnectCb); - espconn_accept(&serbridgeConn); - espconn_tcp_set_max_con_allow(&serbridgeConn, MAX_CONN); - espconn_regist_time(&serbridgeConn, SER_BRIDGE_TIMEOUT, 0); + serbridgeInitPins(); + + int i; + for (i = 0; i < MAX_CONN; i++) { + connData[i].conn = NULL; + connData[i].txbuffer = txbuffer[i]; + } + serbridgeConn.type = ESPCONN_TCP; + serbridgeConn.state = ESPCONN_NONE; + serbridgeTcp.local_port = port; + serbridgeConn.proto.tcp = &serbridgeTcp; + + espconn_regist_connectcb(&serbridgeConn, serbridgeConnectCb); + espconn_accept(&serbridgeConn); + espconn_tcp_set_max_con_allow(&serbridgeConn, MAX_CONN); + espconn_regist_time(&serbridgeConn, SER_BRIDGE_TIMEOUT, 0); } diff --git a/serial/serbridge.h b/serial/serbridge.h index 33c96a2..ce11906 100644 --- a/serial/serbridge.h +++ b/serial/serbridge.h @@ -32,7 +32,7 @@ typedef struct serbridgeConnData { void ICACHE_FLASH_ATTR serbridgeInit(int port); void ICACHE_FLASH_ATTR serbridgeInitPins(void); -void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, int len); +void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, short len); void ICACHE_FLASH_ATTR serbridgeReset(); #endif /* __SER_BRIDGE_H__ */ diff --git a/serial/slip.c b/serial/slip.c new file mode 100644 index 0000000..0c76103 --- /dev/null +++ b/serial/slip.c @@ -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_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 0) { + slip_process(); + slip_reset(); + } + + serledFlash(50); // short blink on serial LED +} diff --git a/serial/uart.h b/serial/uart.h index 2d1d432..3553155 100644 --- a/serial/uart.h +++ b/serial/uart.h @@ -4,7 +4,7 @@ #include "uart_hw.h" // Receive callback function signature -typedef void (*UartRecv_cb)(char *buf, int len); +typedef void (*UartRecv_cb)(char *buf, short len); // Initialize UARTs to the provided baud rates (115200 recommended). This also makes the os_printf // calls use uart1 for output (for debugging purposes) diff --git a/user/cgiwifi.c b/user/cgiwifi.c index 0d96e96..42ddf24 100644 --- a/user/cgiwifi.c +++ b/user/cgiwifi.c @@ -40,6 +40,8 @@ static char *wifiReasons[] = { static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" }; static char *wifiPhy[] = { 0, "11b", "11g", "11n" }; +void (*wifiStatusCb)(uint8_t); // callback when wifi status changes + static char* ICACHE_FLASH_ATTR wifiGetReason(void) { if (wifiReason <= 24) return wifiReasons[wifiReason]; if (wifiReason >= 200 && wifiReason <= 201) return wifiReasons[wifiReason-200+24]; @@ -86,6 +88,7 @@ static void ICACHE_FLASH_ATTR wifiHandleEventCb(System_Event_t *evt) { default: break; } + if (wifiStatusCb) (*wifiStatusCb)(wifiState); } // ===== wifi scanning @@ -478,7 +481,7 @@ int ICACHE_FLASH_ATTR printWifiInfo(char *buff) { char *mode = wifiMode[op]; char *status = "unknown"; int st = wifi_station_get_connect_status(); - if (st > 0 && st < sizeof(connStatuses)) status = connStatuses[st]; + if (st >= 0 && st < sizeof(connStatuses)) status = connStatuses[st]; int p = wifi_get_phy_mode(); char *phy = wifiPhy[p&3]; char *warn = wifiWarn[op]; diff --git a/user/cgiwifi.h b/user/cgiwifi.h index 557c31a..c78a793 100644 --- a/user/cgiwifi.h +++ b/user/cgiwifi.h @@ -14,4 +14,6 @@ int cgiWiFiConnStatus(HttpdConnData *connData); int cgiWiFiSpecial(HttpdConnData *connData); void wifiInit(void); +extern void (*wifiStatusCb)(uint8_t); // callback when wifi status changes + #endif