diff --git a/ESP8266/ESP8266.cpp b/ESP8266/ESP8266.cpp index 6751d78..4f2842f 100644 --- a/ESP8266/ESP8266.cpp +++ b/ESP8266/ESP8266.cpp @@ -16,12 +16,16 @@ #include "ESP8266.h" -ESP8266::ESP8266(PinName tx, PinName rx, bool debug) - : _serial(tx, rx, 1024), _parser(_serial) +ESP8266::ESP8266(PinName tx, PinName rx, Callback signalingCallback, bool debug) + : _serial(tx, rx, 1024), _parser(_serial), _signalingCallback(signalingCallback) , _packets(0), _packets_end(&_packets) { _serial.baud(115200); _parser.debugOn(debug); + + _in_server_mode = false; + _ipd_packet = NULL; + _global_socket_counter = 0; } int ESP8266::get_firmware_version() @@ -30,11 +34,11 @@ int ESP8266::get_firmware_version() int version; if(_parser.recv("SDK version:%d", &version) && _parser.recv("OK")) { return version; - } else { + } else { // Older firmware versions do not prefix the version with "SDK version: " return -1; } - + } bool ESP8266::startup(int mode) @@ -211,6 +215,8 @@ bool ESP8266::send(int id, const void *data, uint32_t amount) void ESP8266::_packet_handler() { + if (_in_server_mode) return; + int id; uint32_t amount; @@ -241,6 +247,13 @@ void ESP8266::_packet_handler() int32_t ESP8266::recv(int id, void *data, uint32_t amount) { + bool exit_when_not_found = false; + + // see if the underlying socket changed while in the recv() call + // if you don't do this check it might be that a CLOSED,CONNECT happens on the same esp8266 socket id + // and thus we associate the data with the wrong mbed TCPSocket + uint32_t incoming_socket_id = _incoming_socket_status[id]; + while (true) { // check if any packets are ready for us for (struct packet **p = &_packets; *p; p = &(*p)->next) { @@ -269,10 +282,37 @@ int32_t ESP8266::recv(int id, void *data, uint32_t amount) } } - // Wait for inbound packet - if (!_parser.recv("OK")) { + if (exit_when_not_found) { return -1; } + + // Here's a problem... recv() blocks for forever, but it might be that the underlying socket closes in the meantime. + // We know when it happens (due to monitoring the RX IRQ channel) + // but there's no way of signaling this thread and actually abort the request... + + if (incoming_socket_id > 0) { + int timeout = _parser.getTimeout(); + _parser.setTimeout(1000); + + if (!_parser.recv("OK")) { + _parser.setTimeout(timeout); + + // socket gone + if (incoming_socket_id != _incoming_socket_status[id]) return NSAPI_ERROR_NO_SOCKET; + + // otherwise, just continue trying to get data... + continue; + } + } + else { + // Wait for inbound packet + if (!_parser.recv("OK")) { + // so this is weird... the message just received by the parser could actually be one of ours (in TCPServer mode)... + // so do one more pass... + exit_when_not_found = true; + continue; + } + } } } @@ -304,7 +344,7 @@ bool ESP8266::writeable() return _serial.writeable(); } -void ESP8266::attach(Callback func) +void ESP8266::attach(Callback func) { _serial.attach(func); } @@ -320,3 +360,122 @@ bool ESP8266::recv_ap(nsapi_wifi_ap_t *ap) return ret; } + +bool ESP8266::bind(const SocketAddress& address) +{ + // we need an event queue to dispatch from serial RX IRQ -> non-IRQ thread + event_queue = new EventQueue(); + event_thread = new Thread(osPriorityNormal, 2048); + if (!event_queue || !event_thread) { + return NSAPI_ERROR_NO_MEMORY; + } + event_thread->start(callback(event_queue, &EventQueue::dispatch_forever)); + + // buffer to store RX data in + rx_buffer = (char*)malloc(1024); + rx_ix = 0; + if (!rx_buffer) { + return NSAPI_ERROR_NO_MEMORY; + } + + // clear incoming socket status + memset(_incoming_socket_status, 0, sizeof(_incoming_socket_status)); + + // attach to the serial + _serial.attach(callback(this, &ESP8266::attach_rx)); + + _in_server_mode = true; + + // and start the actual server + return _parser.send("AT+CIPSERVER=1,%d", address.get_port()) + && _parser.recv("OK"); +} + +void ESP8266::process_command(char* cmd, size_t size) { + if (_ipd_packet) { + memcpy(_ipd_packet_data_ptr, cmd, size); + _ipd_packet_data_ptr += size; + + _ipd_packet_data_ptr[0] = '\r'; + _ipd_packet_data_ptr[1] = '\n'; + _ipd_packet_data_ptr += 2; + + if (_ipd_packet_data_ptr == ((char*)(_ipd_packet + 1)) + _ipd_packet->len) { + // append to packet list + *_packets_end = _ipd_packet; + _packets_end = &_ipd_packet->next; + + _ipd_packet = NULL; + } + } + else if (size == 9 /* 0,CONNECT */ + && (cmd[0] >= '0' && cmd[0] <= '9') + && (cmd[1] == ',') + && (strcmp(&cmd[2], "CONNECT") == 0)) { + + _incoming_socket_status[cmd[0] - '0'] = ++_global_socket_counter; + + _signalingCallback(ESP8266_SOCKET_CONNECT, cmd[0] - '0'); + } + else if (size == 8 /* 0,CLOSED */ + && (cmd[0] >= '0' && cmd[0] <= '9') + && (cmd[1] == ',') + && (strcmp(&cmd[2], "CLOSED") == 0)) { + + _incoming_socket_status[cmd[0] - '0'] = 0; + + _signalingCallback(ESP8266_SOCKET_CLOSE, cmd[0] - '0'); + } + else if (cmd[0] == '+' && cmd[1] == 'I' && cmd[2] == 'P' && cmd[3] == 'D') { + int id = cmd[5] - '0'; + + // parse out the length param... + size_t length_ix = 6; + while (cmd[length_ix] != ':' && length_ix < size) length_ix++; + char* temp_length_buff = (char*)calloc(length_ix - 7 + 1, 1); + if (!temp_length_buff) return; + memcpy(temp_length_buff, cmd + 7, length_ix - 7); + int amount = atoi(temp_length_buff); + + // alloc a new packet (and store it in a global var. we'll get the data for this packet straight after this msg) + _ipd_packet = (struct packet*)malloc( + sizeof(struct packet) + amount); + if (!_ipd_packet) { + return; + } + + _ipd_packet->id = id; + _ipd_packet->len = amount; + _ipd_packet->next = 0; + + _ipd_packet_data_ptr = (char*)(_ipd_packet + 1); + + size_t data_len = size - length_ix - 1; + memcpy(_ipd_packet_data_ptr, cmd + length_ix + 1, data_len); + _ipd_packet_data_ptr += data_len; + + // re-add the newline \r\n again... + _ipd_packet_data_ptr[0] = '\r'; + _ipd_packet_data_ptr[1] = '\n'; + _ipd_packet_data_ptr += 2; + } + free(cmd); +} + +void ESP8266::attach_rx(int c) { + // store value in buffer + rx_buffer[rx_ix] = c; + rx_buffer[rx_ix + 1] = 0; + + if (rx_ix > 0 && c == '\n') { + // got a whole command + char* cmd = (char*)calloc(rx_ix, 1); + memcpy(cmd, rx_buffer, rx_ix - 1); + event_queue->call(callback(this, &ESP8266::process_command), cmd, rx_ix - 1); + + rx_ix = 0; + return; + } + + rx_ix++; +} diff --git a/ESP8266/ESP8266.h b/ESP8266/ESP8266.h index 2164526..34cd4ee 100644 --- a/ESP8266/ESP8266.h +++ b/ESP8266/ESP8266.h @@ -19,13 +19,18 @@ #include "ATParser.h" +enum SignalingAction { + ESP8266_SOCKET_CONNECT, + ESP8266_SOCKET_CLOSE +}; + /** ESP8266Interface class. This is an interface to a ESP8266 radio. */ class ESP8266 { public: - ESP8266(PinName tx, PinName rx, bool debug=false); + ESP8266(PinName tx, PinName rx, Callback signalingCallback, bool debug=false); /** * Check firmware version of ESP8266 @@ -33,7 +38,7 @@ class ESP8266 * @return integer firmware version or -1 if firmware query command gives outdated response */ int get_firmware_version(void); - + /** * Startup the ESP8266 * @@ -97,7 +102,7 @@ class ESP8266 /** Get the local network mask * - * @return Null-terminated representation of the local network mask + * @return Null-terminated representation of the local network mask * or null if no network mask has been recieved */ const char *getNetmask(); @@ -123,7 +128,7 @@ class ESP8266 * see @a nsapi_error */ int scan(WiFiAccessPoint *res, unsigned limit); - + /**Perform a dns query * * @param name Hostname to resolve @@ -193,7 +198,7 @@ class ESP8266 * * @param func A pointer to a void function, or 0 to set as none */ - void attach(Callback func); + void attach(Callback func); /** * Attach a function to call whenever network state has changed @@ -203,12 +208,23 @@ class ESP8266 */ template void attach(T *obj, M method) { - attach(Callback(obj, method)); + attach(Callback(obj, method)); } + /** + * Start binding to a port + * + * @param address SocketAddress instance (only port is being used) + */ + bool bind(const SocketAddress& address); + private: + void attach_rx(int); + void process_command(char*, size_t); + BufferedSerial _serial; ATParser _parser; + Callback _signalingCallback; struct packet { struct packet *next; @@ -223,6 +239,17 @@ class ESP8266 char _gateway_buffer[16]; char _netmask_buffer[16]; char _mac_buffer[18]; + + // TCPServer mode needs a separate thread to dispatch RX IRQ commands from + EventQueue* event_queue; + Thread* event_thread; + char* rx_buffer; + size_t rx_ix; + uint32_t _incoming_socket_status[5]; + uint32_t _global_socket_counter; + bool _in_server_mode; + struct packet *_ipd_packet; + char* _ipd_packet_data_ptr; }; #endif diff --git a/ESP8266Interface.cpp b/ESP8266Interface.cpp index dcf4bea..2386a4c 100644 --- a/ESP8266Interface.cpp +++ b/ESP8266Interface.cpp @@ -37,10 +37,11 @@ // ESP8266Interface implementation ESP8266Interface::ESP8266Interface(PinName tx, PinName rx, bool debug) - : _esp(tx, rx, debug) + : _esp(tx, rx, callback(this, &ESP8266Interface::signal), debug) { memset(_ids, 0, sizeof(_ids)); memset(_cbs, 0, sizeof(_cbs)); + memset(_incoming_sockets, 0, sizeof(_incoming_sockets)); _esp.attach(this, &ESP8266Interface::event); } @@ -59,19 +60,19 @@ int ESP8266Interface::connect(const char *ssid, const char *pass, nsapi_security int ESP8266Interface::connect() { _esp.setTimeout(ESP8266_CONNECT_TIMEOUT); - + if (!_esp.reset()) { return NSAPI_ERROR_DEVICE_ERROR; - } - + } + _esp.setTimeout(ESP8266_MISC_TIMEOUT); - + if (_esp.get_firmware_version() != ESP8266_VERSION) { debug("ESP8266: ERROR: Firmware incompatible with this driver.\ - \r\nUpdate to v%d - https://developer.mbed.org/teams/ESP8266/wiki/Firmware-Update\r\n",ESP8266_VERSION); + \r\nUpdate to v%d - https://developer.mbed.org/teams/ESP8266/wiki/Firmware-Update\r\n",ESP8266_VERSION); return NSAPI_ERROR_DEVICE_ERROR; } - + _esp.setTimeout(ESP8266_CONNECT_TIMEOUT); if (!_esp.startup(3)) { @@ -164,7 +165,7 @@ int ESP8266Interface::socket_open(void **handle, nsapi_protocol_t proto) { // Look for an unused socket int id = -1; - + for (int i = 0; i < ESP8266_SOCKET_COUNT; i++) { if (!_ids[i]) { id = i; @@ -172,16 +173,16 @@ int ESP8266Interface::socket_open(void **handle, nsapi_protocol_t proto) break; } } - + if (id == -1) { return NSAPI_ERROR_NO_SOCKET; } - + struct esp8266_socket *socket = new struct esp8266_socket; if (!socket) { return NSAPI_ERROR_NO_SOCKET; } - + socket->id = id; socket->proto = proto; socket->connected = false; @@ -194,7 +195,7 @@ int ESP8266Interface::socket_close(void *handle) struct esp8266_socket *socket = (struct esp8266_socket *)handle; int err = 0; _esp.setTimeout(ESP8266_MISC_TIMEOUT); - + if (socket->connected && !_esp.close(socket->id)) { err = NSAPI_ERROR_DEVICE_ERROR; } @@ -207,7 +208,11 @@ int ESP8266Interface::socket_close(void *handle) int ESP8266Interface::socket_bind(void *handle, const SocketAddress &address) { - return NSAPI_ERROR_UNSUPPORTED; + int bind_res = _esp.bind(address); + if (bind_res < -3000) return bind_res; + if (bind_res != 1) return NSAPI_ERROR_DEVICE_ERROR; + + return NSAPI_ERROR_OK; } int ESP8266Interface::socket_listen(void *handle, int backlog) @@ -224,25 +229,45 @@ int ESP8266Interface::socket_connect(void *handle, const SocketAddress &addr) if (!_esp.open(proto, socket->id, addr.get_ip_address(), addr.get_port())) { return NSAPI_ERROR_DEVICE_ERROR; } - + socket->connected = true; return 0; } - + int ESP8266Interface::socket_accept(void *server, void **socket, SocketAddress *addr) { - return NSAPI_ERROR_UNSUPPORTED; + while (1) { + // now go through the _incoming_sockets array and see if there are unattached sockets... + for (size_t ix = 0; ix < ESP8266_SOCKET_COUNT; ix++) { + // printf("_incoming_sockets[ix] socket=%p connected=%d accepted=%d\n", + // _incoming_sockets[ix].socket, _incoming_sockets[ix].socket && _incoming_sockets[ix].socket->connected, + // _incoming_sockets[ix].accepted); + if (_incoming_sockets[ix].socket && _incoming_sockets[ix].socket->connected && !_incoming_sockets[ix].accepted) { + *socket = _incoming_sockets[ix].socket; + // addr is not used here I think... + _incoming_sockets[ix].accepted = true; + return 0; + } + } + + wait_ms(20); + } + return 0; } int ESP8266Interface::socket_send(void *handle, const void *data, unsigned size) { struct esp8266_socket *socket = (struct esp8266_socket *)handle; _esp.setTimeout(ESP8266_SEND_TIMEOUT); - + + if (!socket->connected) { + return NSAPI_ERROR_NO_SOCKET; + } + if (!_esp.send(socket->id, data, size)) { return NSAPI_ERROR_DEVICE_ERROR; } - + return size; } @@ -250,12 +275,15 @@ int ESP8266Interface::socket_recv(void *handle, void *data, unsigned size) { struct esp8266_socket *socket = (struct esp8266_socket *)handle; _esp.setTimeout(ESP8266_RECV_TIMEOUT); - + int32_t recv = _esp.recv(socket->id, data, size); - if (recv < 0) { + if (recv < -3000) { + return recv; + } + else if (recv < 0) { return NSAPI_ERROR_WOULD_BLOCK; } - + return recv; } @@ -278,7 +306,7 @@ int ESP8266Interface::socket_sendto(void *handle, const SocketAddress &addr, con } socket->addr = addr; } - + return socket_send(socket, data, size); } @@ -295,15 +323,57 @@ int ESP8266Interface::socket_recvfrom(void *handle, SocketAddress *addr, void *d void ESP8266Interface::socket_attach(void *handle, void (*callback)(void *), void *data) { - struct esp8266_socket *socket = (struct esp8266_socket *)handle; + struct esp8266_socket *socket = (struct esp8266_socket *)handle; _cbs[socket->id].callback = callback; _cbs[socket->id].data = data; } -void ESP8266Interface::event() { +void ESP8266Interface::event(int) { for (int i = 0; i < ESP8266_SOCKET_COUNT; i++) { if (_cbs[i].callback) { _cbs[i].callback(_cbs[i].data); } } } + +void ESP8266Interface::signal(SignalingAction action, int socket_id) { + if (action == ESP8266_SOCKET_CONNECT) { + // printf("ESP8266::SOCKET CONNECT %d\n", socket_id); + if (_ids[socket_id]) { + // this should not be possible... + // printf("ESP8266_SOCKET_CONNECT for socket that already exists...\n"); + // return; + } + + struct esp8266_socket *socket = new struct esp8266_socket; + if (!socket) { + printf("ESP8266_SOCKET_CONNECT could not create socket\n"); + return; + } + + _ids[socket_id] = true; + + socket->id = socket_id; + socket->proto = NSAPI_TCP; + socket->connected = true; + + _incoming_sockets[socket_id].socket = socket; + _incoming_sockets[socket_id].accepted = false; + } + else if (action == ESP8266_SOCKET_CLOSE) { + // printf("ESP8266::SOCKET CLOSE %d\n", socket_id); + + // Q: should we be able to delete the socket here? probably segfaults if held in user code + struct esp8266_socket *socket = _incoming_sockets[socket_id].socket; + if (!socket || !socket->connected) { + printf("ESP8266_SOCKET_CLOSE for socket that does not exist or is already closed\n"); + return; + } + + socket->connected = false; + _ids[socket_id] = false; + _incoming_sockets[socket_id].accepted = false; + _incoming_sockets[socket_id].socket = NULL; + // delete socket; + } +} diff --git a/ESP8266Interface.h b/ESP8266Interface.h index 8376fae..e27c59b 100644 --- a/ESP8266Interface.h +++ b/ESP8266Interface.h @@ -256,6 +256,13 @@ class ESP8266Interface : public NetworkStack, public WiFiInterface } private: + /** Process a signal received from the ESP8266 driver + * Used to react to passive actions, like a socket connecting to the TCPServer + * @param action Signaling action + * @param socket The ESP8266 socket ID involved + */ + void signal(SignalingAction action, int socket); + ESP8266 _esp; bool _ids[ESP8266_SOCKET_COUNT]; @@ -264,12 +271,19 @@ class ESP8266Interface : public NetworkStack, public WiFiInterface uint8_t ap_ch; char ap_pass[64]; /* The longest allowed passphrase */ - void event(); + void event(int); struct { void (*callback)(void *); void *data; } _cbs[ESP8266_SOCKET_COUNT]; + + // for incoming sockets this driver handles the memory + struct { + struct esp8266_socket* socket; + bool accepted; + } _incoming_sockets[ESP8266_SOCKET_COUNT]; + }; #endif