/* * This file is part of Espruino, a JavaScript interpreter for Microcontrollers * * Copyright (C) 2013 Gordon Williams * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * ---------------------------------------------------------------------------- * Implementation of JsNetwork for ESP8266 devices * * DEPRECATED - YOU SHOULD NOW USE THE ESP8266.js MODULE * ---------------------------------------------------------------------------- */ #include "jsinteractive.h" #include "network.h" #include "network_esp8266.h" #include "jswrap_stream.h" #define INVALID_SOCKET ((SOCKET)(-1)) #define SOCKET_ERROR (-1) #define ESP8266_IPD_NAME JS_HIDDEN_CHAR_STR"ID" #define ESP8266_IPD_NAME_LEN 3 // strlen(ESP8266_IPD_NAME) #define ESP8266_DNS_NAME JS_HIDDEN_CHAR_STR"DNS" #define ESP8266_DEBUG // --------------------------------------------------------------------------- typedef enum { ESP8266_IDLE, ESP8266_DELAY, // just delay and execute after the timeout ESP8266_WAIT_OK, ESP8266_WAIT_OK_THEN_CWMODE_THEN_RESET_THEN_READY_THEN_CIPMUX, ESP8266_WAIT_CWMODE_THEN_RESET_THEN_READY_THEN_CIPMUX, ESP8266_WAIT_RESET_THEN_READY_THEN_CIPMUX, ESP8266_WAIT_READY_THEN_CIPMUX, ESP8266_WAIT_OK_THEN_LINKED, ESP8266_WAIT_OK_THEN_DELAY_10S, ESP8266_WAIT_LINKED, ESP8266_WAIT_OK_THEN_WAIT_CONNECTED, ESP8266_WAIT_SEND_OK, ESP8266_WAIT_SEND_OK_THEN_CLOSE, ESP8266_WAIT_CONNECTED_RESPONSE, // wait until we're connected (we can get an IP) ESP8266_DELAY_1S_WAIT_CONNECTED, // after ESP8266_WAIT_CONNECTED_RESPONSE fails, it waits 1s and tried again /*ESP8266_WAIT_OK_THEN_READY, ESP8266_WAIT_READY,*/ } ESP8266State; ESP8266State esp8266State = ESP8266_IDLE; // current state - see above JsSysTime stateTimeout; // the timout to allow const char *stateTimeoutMessage; // the message to be displayed if we get a timeout JsVar *stateSuccessCallback = 0; // if all goes well, this is what gets called char currentSocket = -1; // if esp8266State!=IDLE, this could be a socket on which to report errors unsigned char socketErrors = 0; // bit mask of errors on sockets unsigned char socketUsage = 0; // bit mask of what sockets are used #define SOCKET_COUNT 5 // 0..4 const char *esp8266_idle_compare = 0; char esp8266_ipd_buffer_sckt = 0; size_t esp8266_ipd_buffer_size = 0; bool esp8266_idle_compare_only_start = false; unsigned char connectionCheckCount; void net_esp8266_setState(ESP8266State state, int timeout, JsVar *callback, const char *timeoutMessage) { esp8266State = state; if (timeout) { stateTimeout = jshGetSystemTime() + jshGetTimeFromMilliseconds(timeout); stateTimeoutMessage = timeoutMessage; } else { stateTimeout = -1; stateTimeoutMessage = 0; } callback = jsvLockAgainSafe(callback); if (stateSuccessCallback) jsvUnLock(stateSuccessCallback); stateSuccessCallback = callback; } void net_esp8266_execute_and_set_idle() { JsVar *cb = jsvLockAgainSafe(stateSuccessCallback); net_esp8266_setState(ESP8266_IDLE, 0, 0, 0); if (cb) { jsiQueueEvents(0, cb, 0, 0); jsvUnLock(cb); } } ESP8266State net_esp8266_getState() { return esp8266State; } // --------------------------------------------------------------------------- void esp8266_send(JsVar *msg) { #ifdef ESP8266_DEBUG jsiConsolePrintf(">>> %q\n", msg); #endif JsvStringIterator it; jsvStringIteratorNew(&it, msg, 0); while (jsvStringIteratorHasChar(&it)) { char ch = jsvStringIteratorGetChar(&it); jshTransmit(networkGetCurrent()->data.device, (unsigned char)ch); jsvStringIteratorNext(&it); } jsvStringIteratorFree(&it); } void esp8266_got_data(int sckt, JsVar *data) { #ifdef ESP8266_DEBUG jsiConsolePrintf("%d<<< %q\n", sckt, data); #endif if (sckt<0 || sckt>=SOCKET_COUNT) { // sanity check assert(0); return; } if (socketUsage & (1<0) { if (nChars > esp8266_ipd_buffer_size) nChars = esp8266_ipd_buffer_size; esp8266_ipd_buffer_size -= nChars; JsVar *data = jsvNewFromStringVar(buf,0,(size_t)nChars); JsVar *newBuf = jsvNewFromStringVar(buf, (size_t)(nChars+1), JSVAPPENDSTRINGVAR_MAXLENGTH); //jsiConsolePrintf("%d,%d %q -> %q + %q\n", nChars,esp8266_ipd_buffer_size, buf, data, newBuf); jsvUnLock(buf); buf = newBuf; hasChanged = true; esp8266_got_data(esp8266_ipd_buffer_sckt, data); } } else { char chars[20]; jsvGetStringChars(buf, 0, chars, sizeof(chars)); if (chars[0]=='+' && chars[1]=='I' && chars[2]=='P' && chars[3]=='D' && chars[4]==',') { size_t sckt_idx = 5; while (sckt_idx len_idx) { size_t nChars = len-(len_idx+1); if (nChars > esp8266_ipd_buffer_size) nChars = esp8266_ipd_buffer_size; esp8266_ipd_buffer_size -= nChars; JsVar *data = jsvNewFromStringVar(buf,(size_t)(len_idx+1),(size_t)nChars); JsVar *newBuf = jsvNewFromStringVar(buf, (size_t)(len_idx+nChars+1), JSVAPPENDSTRINGVAR_MAXLENGTH); //jsiConsolePrintf("%d,%d %q -> %q + %q\n", nChars,esp8266_ipd_buffer_size, buf, data, newBuf); jsvUnLock(buf); buf = newBuf; hasChanged = true; esp8266_got_data(esp8266_ipd_buffer_sckt, data); jsvUnLock(data); } else { // Do nothing - our string isn't big enough } } else if (chars[len_idx]!=0) { // invalid data. Kill it. jsWarn("ESP8266 expecting +IPD string, got %q", buf); jsvUnLock(buf); buf = 0; hasChanged = true; } } else { // string doesn't start with '+IPD' int idx = jsvGetStringIndexOf(buf, '\r'); if (idx<0 && esp8266_idle_compare && esp8266_idle_compare[0]=='>' && esp8266_idle_compare[1]==0 && buf && buf->varData.str[0]=='>') idx=2; // pretend we had /r/n - this only works because right now we're only expecting one char while ((!found) && idx>=0) { hasChanged = true; JsVar *line = idx ? jsvNewFromStringVar(buf,0,(size_t)(idx - 1)) : // \r\n - so idx is of '\n' and we want to remove '\r' too jsvNewFromEmptyString(); #ifdef ESP8266_DEBUG jsiConsoleRemoveInputLine(); jsiConsolePrintf("ESP8266> %q\n", line); #endif switch (net_esp8266_getState()) { case ESP8266_IDLE: break; // ignore? case ESP8266_DELAY: break; // ignore here - see stateTimeout below case ESP8266_DELAY_1S_WAIT_CONNECTED: break; // ignore here - see stateTimeout below case ESP8266_WAIT_OK: if (jsvIsStringEqualOrStartsWith(line, "OK", false)) { net_esp8266_execute_and_set_idle(); } break; case ESP8266_WAIT_OK_THEN_CWMODE_THEN_RESET_THEN_READY_THEN_CIPMUX: if (jsvIsStringEqualOrStartsWith(line, "OK", false)) { JsVar *cmd = jsvNewFromString("AT+CWMODE=1\r\n"); // set mode to 1 (client). 3 (both) works too esp8266_send(cmd); jsvUnLock(cmd); net_esp8266_setState(ESP8266_WAIT_CWMODE_THEN_RESET_THEN_READY_THEN_CIPMUX, 500, stateSuccessCallback, stateTimeoutMessage); } break; case ESP8266_WAIT_CWMODE_THEN_RESET_THEN_READY_THEN_CIPMUX: if (jsvIsStringEqualOrStartsWith(line, "OK", false) || jsvIsStringEqualOrStartsWith(line, "no change", false)) { JsVar *cmd = jsvNewFromString("AT+RST\r\n"); // sent reset command esp8266_send(cmd); jsvUnLock(cmd); net_esp8266_setState(ESP8266_WAIT_RESET_THEN_READY_THEN_CIPMUX, 500, stateSuccessCallback, stateTimeoutMessage); } break; case ESP8266_WAIT_RESET_THEN_READY_THEN_CIPMUX: if (jsvIsStringEqualOrStartsWith(line, "OK", false)) { net_esp8266_setState(ESP8266_WAIT_READY_THEN_CIPMUX, 6000, stateSuccessCallback, stateTimeoutMessage); } break; case ESP8266_WAIT_READY_THEN_CIPMUX: if (jsvIsStringEqualOrStartsWith(line, "ready", false)) { JsVar *cmd = jsvNewFromString("AT+CIPMUX=1\r\n"); // sent reset command esp8266_send(cmd); jsvUnLock(cmd); net_esp8266_setState(ESP8266_WAIT_OK, 500, stateSuccessCallback, stateTimeoutMessage); } break; case ESP8266_WAIT_OK_THEN_DELAY_10S: if (jsvIsStringEqualOrStartsWith(line, "OK", false)) { // we got ok, now just delay for 5S net_esp8266_setState(ESP8266_DELAY, 10000, stateSuccessCallback, stateTimeoutMessage); } break; case ESP8266_WAIT_OK_THEN_LINKED: if (jsvIsStringEqualOrStartsWith(line, "OK", false)) { net_esp8266_setState(ESP8266_WAIT_LINKED, 5000, stateSuccessCallback, stateTimeoutMessage); // FIXME what now? } break; case ESP8266_WAIT_LINKED: if (jsvIsStringEqualOrStartsWith(line, "OK", false)) { net_esp8266_execute_and_set_idle(); } break; case ESP8266_WAIT_OK_THEN_WAIT_CONNECTED: if (jsvIsStringEqualOrStartsWith(line, "OK", false)) { // wait 1s and then ask for an IP address connectionCheckCount = 60; // wait 60 seconds net_esp8266_setState(ESP8266_DELAY_1S_WAIT_CONNECTED, 3000, stateSuccessCallback, "WiFi network connection failed"); } break; case ESP8266_WAIT_CONNECTED_RESPONSE: if (jsvIsStringEqualOrStartsWith(line, "ERROR", false)) { // ok, we got an error - keep waiting and polling net_esp8266_setState(ESP8266_DELAY_1S_WAIT_CONNECTED, 1000, stateSuccessCallback, stateTimeoutMessage); } else if (isNumeric(jsvGetCharInString(line,0))) { // finally - we have an IP net_esp8266_execute_and_set_idle(); } break; case ESP8266_WAIT_SEND_OK: if (jsvIsStringEqualOrStartsWith(line, "SEND OK", false)) { net_esp8266_execute_and_set_idle(); } break; case ESP8266_WAIT_SEND_OK_THEN_CLOSE: if (jsvIsStringEqualOrStartsWith(line, "SEND OK", false)) { JsVar *msg = jsvVarPrintf("AT+CIPCLOSE=%d\r\n", currentSocket); esp8266_send(msg); jsvUnLock(msg); net_esp8266_setState(ESP8266_WAIT_OK, 500, 0, "No acknowledgement"); } break; } if (esp8266_idle_compare && jsvIsStringEqualOrStartsWith(line, esp8266_idle_compare, esp8266_idle_compare_only_start)) found = true; jsvUnLock(line); JsVar *newBuf = jsvNewFromStringVar(buf, (size_t)(idx+1), JSVAPPENDSTRINGVAR_MAXLENGTH); jsvUnLock(buf); buf = newBuf; idx = jsvGetStringIndexOf(buf, '\r'); } } } } if (hasChanged) jsvObjectSetChild(usartClass, STREAM_BUFFER_NAME, buf); jsvUnLock(buf); if (net_esp8266_getState() != ESP8266_IDLE && stateTimeout>=0 && jshGetSystemTime()>stateTimeout) { if (net_esp8266_getState() == ESP8266_DELAY) { net_esp8266_execute_and_set_idle(); } else if (net_esp8266_getState() == ESP8266_DELAY_1S_WAIT_CONNECTED && connectionCheckCount) { connectionCheckCount--; // so we don't keep doing it // Keep asking for an IP address JsVar *msg = jsvNewFromString("AT+CIFSR\r\n"); esp8266_send(msg); jsvUnLock(msg); net_esp8266_setState(ESP8266_WAIT_CONNECTED_RESPONSE, 1000, stateSuccessCallback, stateTimeoutMessage); } else { if (currentSocket>=0) { socketErrors = (unsigned char)(socketErrors | 1<data.device)); if (!jsvIsObject(usartClass)) { assert(0); return false; } bool found = false; while (!found && jshGetSystemTime() < endTime) { IOEvent event; // drag any data out of the event queue while (jshPopIOEventOfType(networkGetCurrent()->data.device, &event)) { jsiHandleIOEventForUSART(usartClass, &event); } // Search for newlines esp8266_idle_compare = text; esp8266_idle_compare_only_start = justTheStart; found = esp8266_idle(usartClass); esp8266_idle_compare = 0; } jsvUnLock(usartClass); return found; } bool net_esp8266_initialise(JsVar *callback) { JsVar *cmd = jsvNewFromString("AT+CWJAP=\"\",\"\"\r\n"); esp8266_send(cmd); jsvUnLock(cmd); net_esp8266_setState(ESP8266_WAIT_OK_THEN_CWMODE_THEN_RESET_THEN_READY_THEN_CIPMUX, 500, callback, "No Acknowledgement"); socketUsage = 0; // no sockets should be used now socketErrors = 0; return true; } bool net_esp8266_connect(JsVar *vAP, JsVar *vKey, JsVar *callback) { // 'AT+CWMODE=1\r' ? seems to be the default JsVar *msg = jsvVarPrintf("AT+CWJAP=%q,%q\r\n", vAP, vKey); esp8266_send(msg); jsvUnLock(msg); net_esp8266_setState(ESP8266_WAIT_OK_THEN_WAIT_CONNECTED, 500, callback, "No Acknowledgement"); return true; } // ------------------------------------------------------------------------------------------------------------------------ /// Get an IP address from a name. Sets out_ip_addr to 0 on failure void net_esp8266_gethostbyname(JsNetwork *net, char * hostName, uint32_t* out_ip_addr) { // hacky - save the last checked name so we can put it straight into the request *out_ip_addr = 0xFFFFFFFF; jsvObjectSetChildAndUnLock(execInfo.hiddenRoot, ESP8266_DNS_NAME, jsvNewFromString(hostName)); } /// Called on idle. Do any checks required for this device void net_esp8266_idle(JsNetwork *net) { JsVar *usartClass = jsvSkipNameAndUnLock(jsiGetClassNameFromDevice(networkGetCurrent()->data.device)); if (jsvIsObject(usartClass)) { esp8266_idle(usartClass); } jsvUnLock(usartClass); } /// Call just before returning to idle loop. This checks for errors and tries to recover. Returns true if no errors. bool net_esp8266_checkError(JsNetwork *net) { return true; } /// if host=0, creates a server otherwise creates a client (and automatically connects). Returns >=0 on success int net_esp8266_createsocket(JsNetwork *net, uint32_t host, unsigned short port) { char sckt; if (host!=0) { sckt = 0; if (socketUsage&(1<=SOCKET_COUNT) { jsError("No available sockets"); return -1; } socketErrors = (unsigned char)(socketErrors & ~(1<len) ? jsvNewFromStringVar(ipd, len, JSVAPPENDSTRINGVAR_MAXLENGTH) : 0; jsvUnLock(ipd); jsvObjectSetChildAndUnLock(execInfo.hiddenRoot, ipdName, newIpd); } return (int)((chars>len) ? len : chars); } /// Send data if possible. returns nBytes on success, 0 on no data, or -1 on failure int net_esp8266_send(JsNetwork *net, int sckt, const void *buf, size_t len) { if (net_esp8266_getState() != ESP8266_IDLE) return 0; // can't send if (socketErrors & (1<",1000,true)) { size_t i; for (i=0;idata.device, ((unsigned char *)buf)[i]); currentSocket = sckt; net_esp8266_setState(ESP8266_WAIT_SEND_OK, 2000, 0, "Send failed"); return (int)len; } return -1; // broken! } // ------------------------------------------------------------------------------------------------------------------------ void netSetCallbacks_esp8266(JsNetwork *net) { net->idle = net_esp8266_idle; net->checkError = net_esp8266_checkError; net->createsocket = net_esp8266_createsocket; net->closesocket = net_esp8266_closesocket; net->accept = net_esp8266_accept; net->gethostbyname = net_esp8266_gethostbyname; net->recv = net_esp8266_recv; net->send = net_esp8266_send; }