diff --git a/cmd/cmd.h b/cmd/cmd.h index 6b4b018..769326e 100644 --- a/cmd/cmd.h +++ b/cmd/cmd.h @@ -48,6 +48,9 @@ typedef enum { CMD_REST_SETUP = 20, CMD_REST_REQUEST, CMD_REST_SETHEADER, + + CMD_WEB_DATA = 30, + CMD_WEB_REQ_CB, } CmdName; typedef void (*cmdfunc_t)(CmdPacket *cmd); diff --git a/examples/arduino/EspLinkSample/EspLink.cpp b/examples/arduino/EspLinkSample/EspLink.cpp new file mode 100644 index 0000000..7b1a21d --- /dev/null +++ b/examples/arduino/EspLinkSample/EspLink.cpp @@ -0,0 +1,207 @@ +#include "EspLink.h" + +#define READ_BUF_DFLT_SIZE 64 + +// Standard 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 + +EspLink::EspLink(Stream &streamIn, CmdRequestCB callback):stream(streamIn),requestCb(callback) +{ + readBuf = NULL; + readLastChar = 0; +} + +EspLink::~EspLink() +{ + if( readBuf != NULL ) + free( readBuf ); + readBuf = NULL; +} + +void EspLink::writeChar(uint8_t data) +{ + switch(data) + { + case SLIP_END: + stream.write(SLIP_ESC); + stream.write(SLIP_ESC_END); + break; + case SLIP_ESC: + stream.write(SLIP_ESC); + stream.write(SLIP_ESC_ESC); + break; + default: + stream.write(data); + } + + crc16_add(data, &crc16_out); +} + +/* CITT CRC16 polynomial ^16 + ^12 + ^5 + 1 */ +/*---------------------------------------------------------------------------*/ +void EspLink::crc16_add(uint8_t b, uint16_t *crc) +{ + *crc ^= b; + *crc = (*crc >> 8) | (*crc << 8); + *crc ^= (*crc & 0xff00) << 4; + *crc ^= (*crc >> 8) >> 4; + *crc ^= (*crc & 0xff00) >> 5; +} + +void EspLink::writeBuf(uint8_t * buf, uint16_t len) +{ + while(len-- > 0) + writeChar(*buf++); +} + +void EspLink::sendPacketStart(uint16_t cmd, uint32_t value, uint16_t argc) +{ + crc16_out = 0; + stream.write( SLIP_END ); + writeBuf((uint8_t*)&cmd, 2); + writeBuf((uint8_t*)&argc, 2); + writeBuf((uint8_t*)&value, 4); +} + +void EspLink::sendPacketArg(uint16_t len, uint8_t * data) +{ + writeBuf((uint8_t*)&len, 2); + writeBuf(data, len); + + uint16_t pad = (4-((len+2)&3))&3; // get to multiple of 4 + if (pad > 0) { + uint32_t temp = 0; + writeBuf((uint8_t*)&temp, pad); + } +} + +void EspLink::sendPacketEnd() { + uint16_t crc = crc16_out; + writeBuf((uint8_t*)&crc, 2); + stream.write(SLIP_END); +} + +void EspLink::parseSlipPacket() +{ + CmdRequest req; + req.cmd = (CmdPacket *)readBuf; + req.arg_num = 0; + req.arg_ptr = readBuf + sizeof(CmdPacket); + + requestCb(&req); + + free(readBuf); + readBuf = NULL; +} + +void EspLink::checkPacket() +{ + if( readBufPtr <= 3 ) + return; + uint16_t crc = 0; + for(uint16_t i=0; i < readBufPtr - 2; i++) + crc16_add(readBuf[i], &crc); + + uint16_t crcpacket = *(uint16_t*)(readBuf + readBufPtr - 2); + + if( crc == crcpacket ) + { + readBufPtr -= 2; + parseSlipPacket(); + } +} + +void EspLink::readLoop() +{ + if( stream.available() > 0 ) + { + int byt = stream.read(); + + switch(readState) + { + case WAIT_FOR_SLIP_START: + if( byt == SLIP_END ) + { + if(readBuf != NULL) + free(readBuf); + readBufPtr = 0; + readBufMax = READ_BUF_DFLT_SIZE; + readBuf = (uint8_t *)malloc(readBufMax); + readState = READ_SLIP_PACKAGE; + } + break; + case READ_SLIP_PACKAGE: + if( byt == SLIP_END ) + { + readState = WAIT_FOR_SLIP_START; + checkPacket(); + break; + } + if( byt == SLIP_ESC ) + break; + if( readLastChar == SLIP_ESC && byt == SLIP_ESC_END ) + byt = SLIP_END; + else if( readLastChar == SLIP_ESC && byt == SLIP_ESC_ESC ) + byt = SLIP_ESC; + + if( readBufPtr >= readBufMax ) + { + readBufMax = readBufMax + READ_BUF_DFLT_SIZE; + readBuf = (uint8_t *)realloc(readBuf, readBufMax); + if( readBuf == NULL ) + { + readState = WAIT_FOR_SLIP_START; // TODO + break; + } + } + readBuf[readBufPtr++] = byt; + break; + } + + readLastChar = byt; + } +} + +// Return the number of arguments given a command struct +uint32_t EspLink::cmdGetArgc(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 EspLink::cmdPopArg(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 + + memcpy(data, req->arg_ptr + 2, length); + req->arg_ptr += (length+5)&~3; // round up to multiple of 4 + + req->arg_num ++; + return 0; +} + +// Skip the next argument +void EspLink::cmdSkipArg(CmdRequest *req) { + uint16_t length; + + if (req->arg_num >= req->cmd->argc) return; + + length = *(uint16_t*)req->arg_ptr; + + req->arg_ptr += (length+5)&~3; + req->arg_num ++; +} + +// Return the length of the next argument +uint16_t EspLink::cmdArgLen(CmdRequest *req) { + return *(uint16_t*)req->arg_ptr; +} + diff --git a/examples/arduino/EspLinkSample/EspLink.h b/examples/arduino/EspLinkSample/EspLink.h new file mode 100644 index 0000000..aff43f9 --- /dev/null +++ b/examples/arduino/EspLinkSample/EspLink.h @@ -0,0 +1,91 @@ +#ifndef ESP_LINK_H +#define ESP_LINK_H + +#include +#include + +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 + uint16_t argc; // number of arguments to command + uint32_t value; // callback pointer for response or first argument + 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 void (* CmdRequestCB)(CmdRequest *); + +typedef enum { + CMD_NULL = 0, + CMD_SYNC, // synchronize and clear + CMD_RESP_V, // response with a value + CMD_RESP_CB, // response with a callback + CMD_WIFI_STATUS, // get the current wifi status + CMD_CB_ADD, + CMD_CB_EVENTS, + CMD_GET_TIME, // get current time in seconds since the unix epoch + + CMD_MQTT_SETUP = 10, // set-up callbacks + CMD_MQTT_PUBLISH, // publish a message + CMD_MQTT_SUBSCRIBE, // subscribe to a topic + CMD_MQTT_LWT, // set the last-will-topic and messge + + CMD_REST_SETUP = 20, + CMD_REST_REQUEST, + CMD_REST_SETHEADER, + + CMD_WEB_DATA = 30, + CMD_WEB_REQ_CB, +} CmdName; + +typedef enum +{ + WAIT_FOR_SLIP_START, + READ_SLIP_PACKAGE, +} ReadState; + +class EspLink +{ + private: + uint16_t crc16_out; + Stream &stream; + ReadState readState; + uint8_t * readBuf; + uint16_t readBufPtr; + uint16_t readBufMax; + int readLastChar; + CmdRequestCB requestCb; + + void crc16_add(uint8_t b, uint16_t *crc); + void writeChar(uint8_t chr); + void writeBuf(uint8_t * buf, uint16_t len); + void checkPacket(); + void parseSlipPacket(); + + public: + EspLink(Stream &stream, CmdRequestCB callback); + ~EspLink(); + + void sendPacketStart(uint16_t cmd, uint32_t value, uint16_t argc); + void sendPacketArg(uint16_t len, uint8_t * data); + void sendPacketEnd(); + + void readLoop(); + + uint32_t cmdGetArgc(CmdRequest *req); + int32_t cmdPopArg(CmdRequest *req, void *data, uint16_t len); + void cmdSkipArg(CmdRequest *req); + uint16_t cmdArgLen(CmdRequest *req); +}; + +#endif /* ESP_LINK_H */ + diff --git a/examples/arduino/EspLinkSample/EspLinkSample.ino b/examples/arduino/EspLinkSample/EspLinkSample.ino new file mode 100644 index 0000000..b1b199f --- /dev/null +++ b/examples/arduino/EspLinkSample/EspLinkSample.ino @@ -0,0 +1,70 @@ + +#include "WebServer.h" +#include "EspLink.h" + +void packetReceived(CmdRequest *req); + +EspLink espLink(Serial, packetReceived); + +void packetReceived(CmdRequest *req) +{ + Serial.println("\nReceived\n"); + uint16_t shrt, port; + espLink.cmdPopArg(req, &shrt, 2); + RequestReason reason = (RequestReason)shrt; + Serial.print("Reason: "); + Serial.println(reason); + + uint8_t ip[4]; + espLink.cmdPopArg(req, &ip, 4); + Serial.print("IP: "); + for(int i=0; i < 4; i++) + { + Serial.print(ip[i], DEC); + if( i != 3 ) + Serial.print("."); + } + Serial.println(); + + espLink.cmdPopArg(req, &port, 2); + Serial.print("Port: "); + Serial.println(port); + + { + uint16_t len = espLink.cmdArgLen(req); + char bf[len+1]; + bf[len] = 0; + espLink.cmdPopArg(req, bf, len); + Serial.print("Url: "); + Serial.println(bf); + } + + switch( reason ) + { + case BUTTON: + { + uint16_t len = espLink.cmdArgLen(req); + char bf[len+1]; + bf[len] = 0; + espLink.cmdPopArg(req, bf, len); + Serial.print("Button: "); + Serial.println(bf); + } + break; + } + +} + +void setup() { + Serial.begin(57600); + + delay(10); + espLink.sendPacketStart(CMD_CB_ADD, 100, 1); + espLink.sendPacketArg(5, (uint8_t *)"webCb"); + espLink.sendPacketEnd(); +} + +void loop() { + espLink.readLoop(); +} + diff --git a/examples/arduino/EspLinkSample/WebServer.h b/examples/arduino/EspLinkSample/WebServer.h new file mode 100644 index 0000000..d0869c1 --- /dev/null +++ b/examples/arduino/EspLinkSample/WebServer.h @@ -0,0 +1,11 @@ +#ifndef WEB_SERVER_H +#define WEB_SERVER_H + +typedef enum { + LOAD=0, + REFRESH, + BUTTON, + SUBMIT, +} RequestReason; + +#endif /* WEB_SERVER_H */ diff --git a/serial/serbridge.c b/serial/serbridge.c index 66b180c..e1f3fdc 100644 --- a/serial/serbridge.c +++ b/serial/serbridge.c @@ -504,3 +504,8 @@ serbridgeInit(int port1, int port2) espconn_tcp_set_max_con_allow(&serbridgeConn2, MAX_CONN); espconn_regist_time(&serbridgeConn2, SER_BRIDGE_TIMEOUT, 0); } + +int ICACHE_FLASH_ATTR serbridgeInProgramming() +{ + return slip_disabled; +} diff --git a/serial/serbridge.h b/serial/serbridge.h index aad593e..c24953c 100644 --- a/serial/serbridge.h +++ b/serial/serbridge.h @@ -36,6 +36,8 @@ void ICACHE_FLASH_ATTR serbridgeInitPins(void); void ICACHE_FLASH_ATTR serbridgeUartCb(char *buf, short len); void ICACHE_FLASH_ATTR serbridgeReset(); +int ICACHE_FLASH_ATTR serbridgeInProgramming(); + // callback when receiving UART chars when in programming mode extern void (*programmingCB)(char *buffer, short length); diff --git a/web-server/web-server.c b/web-server/web-server.c index 862436e..d8c55b4 100644 --- a/web-server/web-server.c +++ b/web-server/web-server.c @@ -1,8 +1,18 @@ #include "web-server.h" +#include + #include "espfs.h" #include "config.h" #include "cgi.h" +#include "cmd.h" +#include "serbridge.h" + +#define WEB_CB "webCb" + +static char* web_server_reasons[] = { + "load", "refresh", "button", "submit" +}; char * webServerPages = NULL; @@ -76,7 +86,88 @@ void ICACHE_FLASH_ATTR webServerInit() int ICACHE_FLASH_ATTR webServerProcessJsonQuery(HttpdConnData *connData) { - os_printf("URL: %s\n", connData->url); - errorResponse(connData, 400, "Slip protocol is not enabled!"); + if( !flashConfig.slip_enable ) + { + errorResponse(connData, 400, "Slip processing is disabled!"); + return HTTPD_CGI_DONE; + } + CmdCallback* cb = cmdGetCbByName( WEB_CB ); + if( cb == NULL ) + { + errorResponse(connData, 500, "No MCU callback is registered!"); + return HTTPD_CGI_DONE; + } + if( serbridgeInProgramming() ) + { + errorResponse(connData, 500, "Slip disabled at programming mode!"); + return HTTPD_CGI_DONE; + } + + char reasonBuf[16]; + int i; + int len = httpdFindArg(connData->getArgs, "reason", reasonBuf, sizeof(reasonBuf)); + if( len < 0 ) + { + errorResponse(connData, 400, "No reason specified!"); + return HTTPD_CGI_DONE; + } + + RequestReason reason = INVALID; + for(i=0; i < sizeof(web_server_reasons)/sizeof(char *); i++) + { + if( os_strcmp( web_server_reasons[i], reasonBuf ) == 0 ) + reason = (RequestReason)i; + } + + if( reason == INVALID ) + { + errorResponse(connData, 400, "Invalid reason!"); + return HTTPD_CGI_DONE; + } + + char body[1024]; + int bodyLen = -1; + + switch(reason) + { + case BUTTON: + bodyLen = httpdFindArg(connData->getArgs, "id", body, sizeof(body)); + if( bodyLen <= 0 ) + { + errorResponse(connData, 400, "No button ID specified!"); + return HTTPD_CGI_DONE; + } + break; + case SUBMIT: + { + // TODO + } + break; + case LOAD: + case REFRESH: + default: + break; + } + + os_printf("Web callback to MCU: %s\n", reasonBuf); + + cmdResponseStart(CMD_WEB_REQ_CB, (uint32_t)cb->callback, bodyLen >= 0 ? 5 : 4); + uint16_t r = (uint16_t)reason; + cmdResponseBody(&r, sizeof(uint16_t)); + cmdResponseBody(&connData->conn->proto.tcp->remote_ip, 4); + cmdResponseBody(&connData->conn->proto.tcp->remote_port, sizeof(uint16_t)); + cmdResponseBody(connData->url, os_strlen(connData->url)); + if( bodyLen >= 0 ) + cmdResponseBody(body, bodyLen); + cmdResponseEnd(); + + if( reason == SUBMIT ) + { + httpdStartResponse(connData, 204); + httpdEndHeaders(connData); + return HTTPD_CGI_DONE; + } + + // TODO return HTTPD_CGI_DONE; } diff --git a/web-server/web-server.h b/web-server/web-server.h index 2fdd236..5c4c594 100644 --- a/web-server/web-server.h +++ b/web-server/web-server.h @@ -5,6 +5,16 @@ #include "httpd.h" +typedef enum +{ + LOAD=0, + REFRESH, + BUTTON, + SUBMIT, + + INVALID=-1, +} RequestReason; + void webServerInit(); char * webServerUserPages();