/* * This file is part of Espruino, a JavaScript interpreter for Microcontrollers * * Copyright (C) 2015 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/. * * ---------------------------------------------------------------------------- * This file is designed to be parsed during the build process * * Contains ESP8266 and Wifi library specific functions. * * FOR DESCRIPTIONS OF THE WIFI FUNCTIONS IN THIS FILE, SEE * libs/network/jswrap_wifi.c (or http://www.espruino.com/Reference#Wifi) * * IMPORTANT: the functions in this file have tests in ./tests/wifi-test-mode.js * please maintain these tests if you make functional changes! * ---------------------------------------------------------------------------- */ // Set WIFI_DBG to 0 to disable debug printf's, to 1 for important printf's, to 2 for verbose #ifdef RELEASE #define WIFI_DBG 0 #else #define WIFI_DBG 1 #endif // Normal debug #if WIFI_DBG > 0 #define DBG(format, ...) os_printf(format, ## __VA_ARGS__) #else #define DBG(format, ...) do { } while(0) #endif // Verbose debug #if WIFI_DBG > 1 #define DBGV(format, ...) os_printf(format, ## __VA_ARGS__) #else #define DBGV(format, ...) do { } while(0) #endif // Because the ESP8266 JS wrapper is assured to be running on an ESP8266 we // can assume that inclusion of ESP8266 headers will be acceptable. #include #include #include #include #include #include #include #include #include #define ESP8266_ON_ACCESS_POINTS "#accessPoints" #define _GCC_WRAP_STDINT_H typedef long long int64_t; #include "jswrap_esp8266_network.h" #include "jswrap_esp8266.h" #include "jswrap_modules.h" #include "jswrap_interactive.h" #include "jsinteractive.h" #include "network.h" #include "network_esp8266.h" #include "jswrap_net.h" //#define jsvUnLock(v) do { os_printf("Unlock %s @%d\n", __STRING(v), __LINE__); jsvUnLock(v); } while(0) // Forward declaration of functions. static void scanCB(void *arg, STATUS status); static void wifiEventHandler(System_Event_t *event); static void pingRecvCB(); static void startMDNS(char *hostname); static void stopMDNS(); // Some common error handling FLASH_STR(expect_cb, "Expecting callback function but got %v"); #define EXPECT_CB_EXCEPTION(jsCB) jsExceptionHere_flash(JSET_ERROR, expect_cb, jsCB) FLASH_STR(expect_opt, "Expecting options object but got %t"); #define EXPECT_OPT_EXCEPTION(jsOPT) jsExceptionHere_flash(JSET_ERROR, expect_opt, jsOPT) // #NOTE: For callback functions, be sure and unlock them in the `kill` handler. // A callback function to be invoked when we find a new access point. static JsVar *g_jsScanCallback; // A callback function to be invoked when we have an IP address. static JsVar *g_jsGotIpCallback; // A callback function to be invoked on ping responses. static JsVar *g_jsPingCallback; // A callback function to be invoked on gethostbyname responses. static JsVar *g_jsHostByNameCallback; // A callback function to be invoked on a disconnect response. static JsVar *g_jsDisconnectCallback; // Flag to tell the wifi event handler that it should turn STA off on disconnect static bool g_disconnecting; // Flag to tell the wifi event handler to ignore the next disconnect event because // we're provoking it in order to connect to something different static bool g_skipDisconnect; // Global data structure for ping request static struct ping_option pingOpt; // Global data structure for setIP and setAPIP static struct ip_info info; // Configuration save to flash typedef struct { uint16_t length, version; uint32_t crc; uint8_t mode, phyMode; uint8_t sleepType, ssidLen; uint8_t authMode, hidden; char staSsid[32], staPass[64]; char apSsid[32], apPass[64]; char dhcpHostname[64]; } Esp8266_config; static Esp8266_config esp8266Config; //===== Mapping from enums to strings // Reasons for which a connection failed // (The code here is a bit of a nightmare in order to get the strings into FLASH so they don't // eat up valuable RAM space. Sadly the FLASH_STR's __attribute__ stuff can't be applied to the // wifiReasons array as a whole.) FLASH_STR(__wr0, "0 - "); // 0 FLASH_STR(__wr1, "unspecified"); // 1 - REASON_UNSPECIFIED FLASH_STR(__wr2, "auth_expire"); // 2 - REASON_AUTH_EXPIRE FLASH_STR(__wr3, "auth_leave"); // 3 - REASON_AUTH_LEAVE FLASH_STR(__wr4, "assoc_expire"); // 4 - REASON_ASSOC_EXPIRE FLASH_STR(__wr5, "assoc_toomany"); // 5 - REASON_ASSOC_TOOMANY FLASH_STR(__wr6, "not_authed"); // 6 - REASON_NOT_AUTHED FLASH_STR(__wr7, "not_assoced"); // 7 - REASON_NOT_ASSOCED FLASH_STR(__wr8, "assoc_leave"); // 8 - REASON_ASSOC_LEAVE FLASH_STR(__wr9, "assoc_not_authed"); // 9 - REASON_ASSOC_NOT_AUTHED FLASH_STR(__wr10, "disassoc_pwrcap_bad"); // 10 - REASON_DISASSOC_PWRCAP_BAD FLASH_STR(__wr11, "disassoc_supchan_bad"); // 11 - REASON_DISASSOC_SUPCHAN_BAD FLASH_STR(__wr12, "12 - "); // 12 FLASH_STR(__wr13, "ie_invalid"); // 13 - REASON_IE_INVALID FLASH_STR(__wr14, "mic_failure"); // 14 - REASON_MIC_FAILURE FLASH_STR(__wr15, "4way_handshake_timeout"); // 15 - REASON_4WAY_HANDSHAKE_TIMEOUT FLASH_STR(__wr16, "group_key_update_timeout"); // 16 - REASON_GROUP_KEY_UPDATE_TIMEOUT FLASH_STR(__wr17, "ie_in_4way_differs"); // 17 - REASON_IE_IN_4WAY_DIFFERS FLASH_STR(__wr18, "group_cipher_invalid"); // 18 - REASON_GROUP_CIPHER_INVALID FLASH_STR(__wr19, "pairwise_cipher_invalid"); // 19 - REASON_PAIRWISE_CIPHER_INVALID FLASH_STR(__wr20, "akmp_invalid"); // 20 - REASON_AKMP_INVALID FLASH_STR(__wr21, "unsupp_rsn_ie_version"); // 21 - REASON_UNSUPP_RSN_IE_VERSION FLASH_STR(__wr22, "invalid_rsn_ie_cap"); // 22 - REASON_UNSUPP_RSN_IE_VERSION FLASH_STR(__wr23, "802_1x_auth_failed"); // 23 - REASON_802_1X_AUTH_FAILED FLASH_STR(__wr24, "cipher_suite_rejected"); // 24 - REASON_CIPHER_SUITE_REJECTED FLASH_STR(__wr200, "beacon_timeout"); // 200 - REASON_BEACON_TIMEOUT FLASH_STR(__wr201, "no_ap_found"); // 201 - REASON_NO_AP_FOUND static const char *wifiReasons[] = { __wr0, __wr1, __wr2, __wr3, __wr4, __wr5, __wr6, __wr7, __wr8, __wr9, __wr10, __wr11, __wr12, __wr13, __wr14, __wr15, __wr16, __wr17, __wr18, __wr19, __wr20, __wr21, __wr22, __wr23, __wr24, __wr200, __wr201 }; static char wifiReasonBuff[sizeof("group_key_update_timeout")+1]; // length of longest string static char *wifiGetReason(uint8 wifiReason) { const char *reason; if (wifiReason <= 24) reason = wifiReasons[wifiReason]; else if (wifiReason >= 200 && wifiReason <= 201) reason = wifiReasons[wifiReason-200+24]; else reason = wifiReasons[1]; flash_strncpy(wifiReasonBuff, reason, sizeof(wifiReasonBuff)); wifiReasonBuff[sizeof(wifiReasonBuff)-1] = 0; // force null termination return wifiReasonBuff; } // Wifi events FLASH_STR(__ev0, "#onassociated"); FLASH_STR(__ev1, "#ondisconnected"); FLASH_STR(__ev2, "#onauth_change"); FLASH_STR(__ev3, "#onconnected"); FLASH_STR(__ev4, "#ondhcp_timeout"); FLASH_STR(__ev5, "#onsta_joined"); FLASH_STR(__ev6, "#onsta_left"); FLASH_STR(__ev7, "#onprobe_recv"); static const char *wifi_events[] = { __ev0, __ev1, __ev2, __ev3, __ev4, __ev5, __ev6, __ev7 }; static char wifiEventBuff[sizeof("#ondisconnected")+1]; // length of longest string static char *wifiGetEvent(uint32 event) { flash_strncpy(wifiEventBuff, wifi_events[event], sizeof(wifiEventBuff)); wifiEventBuff[sizeof(wifiEventBuff)-1] = 0; return wifiEventBuff; } static char *wifiAuth[] = { "open", "wep", "wpa", "wpa2", "wpa_wpa2" }; static char *wifiMode[] = { "off", "sta", "ap", "sta+ap" }; static char *wifiPhy[] = { "?", "11b", "11g", "11n" }; static char *wifiConn[] = { "off", "connecting", "bad_password", "no_ap_found", "connect_failed", "connected" }; static char macFmt[] = "%02x:%02x:%02x:%02x:%02x:%02x"; //===== This file contains definitions for two classes: ESP8266 and wifi /*JSON{ "type": "library", "class": "ESP8266" } The ESP8266 library is specific to the ESP8266 version of Espruino, i.e., running Espruino on an ESP8266 module (not to be confused with using the ESP8266 as Wifi add-on to an Espruino board). This library contains functions to handle ESP8266-specific actions. For example: `var esp8266 = require('ESP8266'); esp8266.reboot();` performs a hardware reset of the module. */ /** Get the global object for the Wifi library/module, this is used in order to send the * "on event" callbacks to the handlers. */ static JsVar *getWifiModule() { JsVar *moduleName = jsvNewFromString("Wifi"); JsVar *m = jswrap_require(moduleName); jsvUnLock(moduleName); return m; } void jswrap_wifi_disconnect(JsVar *jsCallback) { DBGV("> Wifi.disconnect\n"); // Free any existing callback, then register new callback if (g_jsDisconnectCallback != NULL) jsvUnLock(g_jsDisconnectCallback); g_jsDisconnectCallback = NULL; if (jsCallback != NULL && !jsvIsUndefined(jsCallback) && !jsvIsFunction(jsCallback)) { EXPECT_CB_EXCEPTION(jsCallback); return; } g_jsDisconnectCallback = jsvLockAgainSafe(jsCallback); int8 conn = wifi_station_get_connect_status(); // Do the disconnect, we ignore errors 'cause we don't care if we're not currently connected wifi_station_disconnect(); if (conn == STATION_GOT_IP) { // If we're connected we let the event handler turn off wifi so we can cleanly disconnect // The event handler will also make the callback g_disconnecting = true; } else { // We're not really connected, so we might as well make the callback right here DBGV(" Wifi.disconnect turning STA off\n"); wifi_set_opmode(wifi_get_opmode() & SOFTAP_MODE); g_disconnecting = false; if (jsvIsFunction(jsCallback)) { jsiQueueEvents(NULL, jsCallback, NULL, 0); } } DBG("Wifi.disconnect: opmode=%s\n", wifiMode[wifi_get_opmode()]); DBGV("< Wifi.disconnect\n"); } void jswrap_wifi_stopAP(JsVar *jsCallback) { DBGV("> Wifi.stopAP\n"); // handle the callback parameter if (jsCallback != NULL && !jsvIsUndefined(jsCallback) && !jsvIsFunction(jsCallback)) { EXPECT_CB_EXCEPTION(jsCallback); return; } // Change operating mode bool ok = wifi_set_opmode(wifi_get_opmode() & STATION_MODE); // keep station mode intact if (jsvIsFunction(jsCallback)) { jsiQueueEvents(NULL, jsCallback, NULL, 0); } DBG("Wifi.stopAP: opmode=%s\n", wifiMode[wifi_get_opmode()]); DBGV("< Wifi.stopAP\n"); } void jswrap_wifi_connect( JsVar *jsSsid, JsVar *jsOptions, JsVar *jsCallback ) { // Notes: // The callback function is saved in the file local variable called g_jsGotIpCallback. The // callback will be made when the WiFi callback found in the function called wifiEventHandler. DBGV("> Wifi.connect\n"); // Check that the ssid value isn't obviously in error. if (!jsvIsString(jsSsid)) { jsExceptionHere(JSET_ERROR, "No SSID provided"); return; } // Create SSID string char ssid[33]; int len = jsvGetString(jsSsid, ssid, sizeof(ssid)-1); ssid[len]='\0'; // Make sure jsOptions is NULL or an object if (jsOptions != NULL && !jsvIsObject(jsOptions)) { EXPECT_OPT_EXCEPTION(jsOptions); return; } // Check callback if (g_jsGotIpCallback != NULL) jsvUnLock(g_jsGotIpCallback); g_jsGotIpCallback = NULL; if (jsCallback != NULL && !jsvIsUndefined(jsCallback) && !jsvIsFunction(jsCallback)) { EXPECT_CB_EXCEPTION(jsCallback); return; } // Clear disconnect callback to prevent disconnection from disabling station mode if (g_jsDisconnectCallback != NULL) jsvUnLock(g_jsDisconnectCallback); g_jsDisconnectCallback = NULL; g_disconnecting = false; // we're gonna be connecting... // Get the optional password char password[65]; os_memset(password, 0, sizeof(password)); if (jsOptions != NULL) { JsVar *jsPassword = jsvObjectGetChild(jsOptions, "password", 0); if (jsPassword != NULL && !jsvIsString(jsPassword)) { jsExceptionHere(JSET_ERROR, "Expecting options.password to be a string but got %t", jsPassword); jsvUnLock(jsPassword); return; } if (jsPassword != NULL) { len = jsvGetString(jsPassword, password, sizeof(password)-1); password[len]='\0'; } else { password[0] = '\0'; } jsvUnLock(jsPassword); } // structure for SDK call, it's a shame we need to copy ssid and password but if we placed // them straight into the stationConfig struct we wouldn't be able to printf them for debug struct station_config stationConfig; memset(&stationConfig, 0, sizeof(stationConfig)); os_strncpy((char *)stationConfig.ssid, ssid, 32); os_strncpy((char *)stationConfig.password, password, 64); DBGV(" - ssid:%s passwordLen:%d\n", ssid, strlen(password)); int8 wifiConnectStatus = wifi_station_get_connect_status(); if (wifiConnectStatus < 0) wifiConnectStatus = 0; DBGV(" - Current connect status: %s\n", wifiConn[wifiConnectStatus]); struct station_config existingConfig; wifi_station_get_config(&existingConfig); if (wifiConnectStatus == STATION_GOT_IP && os_strncmp((char *)existingConfig.ssid, (char *)stationConfig.ssid, 32) == 0 && os_strncmp((char *)existingConfig.password, (char *)stationConfig.password, 64) == 0) { // we're already happily connected to the target AP, thus we don't need to do anything if (jsvIsFunction(jsCallback)) { JsVar *params[1]; params[0] = jsvNewNull(); jsiQueueEvents(NULL, jsCallback, params, 1); // TODO: fix callback params and unlock... jsvUnLock(params[0]); } DBGV("< Wifi.connect - no action\n"); return; } else { // we're not happily connected to the right AP, so disconnect to start over wifi_station_disconnect(); // we skip the disconnect event unless we're connected (then it's legit) and unless // we're idle/off (then there is no disconnect event to start with) g_skipDisconnect = wifiConnectStatus != STATION_GOT_IP && wifiConnectStatus != STATION_IDLE; wifi_set_opmode(wifi_get_opmode() | STATION_MODE); } // set callback if (jsvIsFunction(jsCallback)) g_jsGotIpCallback = jsvLockAgainSafe(jsCallback); // Set the station configuration int8 ok = wifi_station_set_config_current(&stationConfig); // Do we have a child property called dnsServers? JsVar *jsDNSServers = jsvObjectGetChild(jsOptions, "dnsServers", 0); int count = 0; if (jsvIsArray(jsDNSServers) != false) { DBGV(" - We have DNS servers!!\n"); JsVarInt numDNSServers = jsvGetArrayLength(jsDNSServers); ip_addr_t dnsAddresses[2]; if (numDNSServers == 0) { DBGV("No servers!!"); count = 0; } if (numDNSServers > 0) { // One server count = 1; JsVar *jsCurrentDNSServer = jsvGetArrayItem(jsDNSServers, 0); char buffer[50]; size_t size = jsvGetString(jsCurrentDNSServer, buffer, sizeof(buffer)-1); buffer[size] = '\0'; jsvUnLock(jsCurrentDNSServer); dnsAddresses[0].addr = networkParseIPAddress(buffer); } if (numDNSServers > 1) { // Two servers count = 2; JsVar *jsCurrentDNSServer = jsvGetArrayItem(jsDNSServers, 1); char buffer[50]; size_t size = jsvGetString(jsCurrentDNSServer, buffer, sizeof(buffer)-1); buffer[size] = '\0'; jsvUnLock(jsCurrentDNSServer); dnsAddresses[1].addr = networkParseIPAddress(buffer); } if (numDNSServers > 2) { DBG("Ignoring DNS servers after first 2."); } if (count > 0) { espconn_dns_setserver((char)count, dnsAddresses); } } jsvUnLock(jsDNSServers); // ensure we have a default DHCP hostname char *old_hostname = wifi_station_get_hostname(); if (old_hostname == NULL || old_hostname[0] == 0) wifi_station_set_hostname("espruino"); DBGV(" - old hostname=%s, new hostname=%s\n", old_hostname, wifi_station_get_hostname()); // Set the WiFi mode of the ESP8266 wifi_set_event_handler_cb(wifiEventHandler); // this seems to get lost sometimes... // Perform the network level connection. wifi_station_connect(); DBG("Wifi.connect: ssid=%s pass_len=%d opmode=%s num_dns_srv=%d\n", ssid, strlen(password), wifiMode[wifi_get_opmode()], count); DBGV("< Wifi.connect\n"); } void jswrap_wifi_scan(JsVar *jsCallback) { DBGV("> Wifi.scan\n"); // If we have a saved scan callback function we must be scanning already if (g_jsScanCallback != NULL) { jsExceptionHere(JSET_ERROR, "A scan is already in progress."); return; } // Check and save callback if (!jsvIsFunction(jsCallback)) { EXPECT_CB_EXCEPTION(jsCallback); return; } g_jsScanCallback = jsvLockAgainSafe(jsCallback); g_disconnecting = false; // we don't want that to interfere // Ask the ESP8266 to perform a network scan after first entering // station mode. The network scan will eventually result in a callback // being executed (scanCB) which will contain the results. wifi_set_opmode_current(wifi_get_opmode() | STATION_MODE); // Request a scan of the network calling "scanCB" on completion wifi_station_scan(NULL, scanCB); DBG("Wifi.scan starting: mode=%s\n", wifiMode[wifi_get_opmode()]); DBGV("< Wifi.scan\n"); } void jswrap_wifi_startAP( JsVar *jsSsid, //!< The network SSID that we will use to listen as. JsVar *jsOptions, //!< Configuration options. JsVar *jsCallback //!< A callback to be invoked when completed. ) { DBGV("> Wifi.startAP\n"); // Check callback if (jsCallback != NULL && !jsvIsUndefined(jsCallback) && !jsvIsFunction(jsCallback)) { EXPECT_CB_EXCEPTION(jsCallback); return; } // Validate that the SSID is provided and is a string. if (!jsvIsString(jsSsid)) { jsExceptionHere(JSET_ERROR, "No SSID."); return; } // Make sure jsOptions is NULL or an object if (jsOptions != NULL && !jsvIsNull(jsOptions) && !jsvIsObject(jsOptions)) { EXPECT_OPT_EXCEPTION(jsOptions); return; } // Build our SoftAP configuration details struct softap_config softApConfig; memset(&softApConfig, 0, sizeof(softApConfig)); softApConfig.max_connection = 4; softApConfig.beacon_interval = 100; softApConfig.authmode = AUTH_OPEN; // ssid is not null terminated softApConfig.ssid_len = jsvGetString(jsSsid, (char *)softApConfig.ssid, sizeof(softApConfig.ssid)); // Handle any options that may have been supplied. if (jsvIsObject(jsOptions)) { // Handle channel JsVar *jsChan = jsvObjectGetChild(jsOptions, "channel", 0); if (jsvIsInt(jsChan)) { int chan = jsvGetInteger(jsChan); if (chan >= 1 && chan <= 13) softApConfig.channel = chan; } jsvUnLock(jsChan); // Handle password JsVar *jsPassword = jsvObjectGetChild(jsOptions, "password", 0); if (jsPassword != NULL) { // handle password:null if (jsvGetStringLength(jsPassword) != 0) { if (!jsvIsString(jsPassword) || jsvGetStringLength(jsPassword) < 8) { jsExceptionHere(JSET_ERROR, "Password must be string of at least 8 characters"); jsvUnLock(jsPassword); return; } int len = jsvGetString(jsPassword, (char *)softApConfig.password, sizeof(softApConfig.password)-1); softApConfig.password[len] = '\0'; } } jsvUnLock(jsPassword); // Handle "authMode" processing. Here we check that "authMode", if supplied, is // one of the allowed values and set the softApConfig object property appropriately. JsVar *jsAuth = jsvObjectGetChild(jsOptions, "authMode", 0); if (jsvIsString(jsAuth)) { if (jsvIsStringEqual(jsAuth, "open")) { softApConfig.authmode = AUTH_OPEN; } else if (jsvIsStringEqual(jsAuth, "wpa2")) { softApConfig.authmode = AUTH_WPA2_PSK; } else if (jsvIsStringEqual(jsAuth, "wpa")) { softApConfig.authmode = AUTH_WPA_PSK; } else if (jsvIsStringEqual(jsAuth, "wpa_wpa2")) { softApConfig.authmode = AUTH_WPA_WPA2_PSK; } else { jsvUnLock(jsAuth); jsExceptionHere(JSET_ERROR, "Unknown authMode value."); return; } } else { // no explicit auth mode, set according to presence of password softApConfig.authmode = softApConfig.password[0] == 0 ? AUTH_OPEN : AUTH_WPA2_PSK; } jsvUnLock(jsAuth); // Make sure password and authmode match if (softApConfig.authmode != AUTH_OPEN && softApConfig.password[0] == 0) { jsExceptionHere(JSET_ERROR, "Password not set but authMode not open."); return; } if (softApConfig.authmode == AUTH_OPEN && softApConfig.password[0] != 0) { jsExceptionHere(JSET_ERROR, "Auth mode set to open but password supplied."); return; } } // Define that we are in Soft AP mode including station mode if required. DBGV("Wifi: switching to soft-AP mode, authmode=%d\n", softApConfig.authmode); wifi_set_opmode(wifi_get_opmode() | SOFTAP_MODE); wifi_set_event_handler_cb(wifiEventHandler); // this seems to get lost sometimes... // Set the WiFi configuration. bool ok = wifi_softap_set_config_current(&softApConfig); // Is this still true: // We should really check that becoming an access point works, however as of SDK 1.4, we // are finding that if we are currently connected to an access point and we switch to being // an access point, it works ... but returns 1 indicating an error. //if (!rc) DBG("Error %d returned from wifi_softap_set_config, probably ignore...\n", rc); if (jsCallback != NULL) { // Set the return error as a function of the return code returned from the call to // the ESP8266 API to create the AP JsVar *params[1]; FLASH_STR(_fstr, "Error from wifi_softap_set_config"); size_t len = flash_strlen(_fstr); char buff[len+1]; flash_strncpy(buff, _fstr, len+1); params[0] = ok ? jsvNewNull() : jsvNewFromString(buff); jsiQueueEvents(NULL, jsCallback, params, 1); jsvUnLock(params[0]); } DBG("Wifi.startAP ssid=%s pass_len=%d opmode=%d auth=%d\n", softApConfig.ssid, os_strlen((char *)softApConfig.password), wifi_get_opmode(), softApConfig.authmode); DBGV("< Wifi.startAP\n"); } JsVar *jswrap_wifi_getStatus(JsVar *jsCallback) { DBGV("> Wifi.getStatus\n"); // Check callback if (jsCallback != NULL && !jsvIsNull(jsCallback) && !jsvIsFunction(jsCallback)) { EXPECT_CB_EXCEPTION(jsCallback); return NULL; } uint8 opMode = wifi_get_opmode(); uint8 phy = wifi_get_phy_mode(); uint8 sleep = wifi_get_sleep_type(); int8 conn = wifi_station_get_connect_status(); if (conn < 0) conn = 0; JsVar *jsWiFiStatus = jsvNewObject(); jsvObjectSetChildAndUnLock(jsWiFiStatus, "mode", jsvNewFromString(wifiMode[opMode])); jsvObjectSetChildAndUnLock(jsWiFiStatus, "station", jsvNewFromString((opMode&STATION_MODE) ? wifiConn[conn] : "off")); jsvObjectSetChildAndUnLock(jsWiFiStatus, "ap", jsvNewFromString((opMode & SOFTAP_MODE) ? "enabled" : "disabled")); jsvObjectSetChildAndUnLock(jsWiFiStatus, "phy", jsvNewFromString(wifiPhy[phy])); jsvObjectSetChildAndUnLock(jsWiFiStatus, "powersave", jsvNewFromString(sleep == NONE_SLEEP_T ? "none" : "ps-poll")); jsvObjectSetChildAndUnLock(jsWiFiStatus, "savedMode", jsvNewFromString("off")); // Schedule callback if a function was provided if (jsvIsFunction(jsCallback)) { DBGV(" Wifi.getStatus queuing CB\n"); JsVar *params[1]; params[0] = jsWiFiStatus; jsiQueueEvents(NULL, jsCallback, params, 1); } DBGV("< Wifi.getStatus\n"); return jsWiFiStatus; } void jswrap_wifi_setConfig(JsVar *jsSettings) { DBGV("> Wifi.setConfig\n"); // Make sure jsSetings an object if (!jsvIsObject(jsSettings)) { EXPECT_OPT_EXCEPTION(jsSettings); return; } // phy setting JsVar *jsPhy = jsvObjectGetChild(jsSettings, "phy", 0); if (jsvIsString(jsPhy)) { if (jsvIsStringEqual(jsPhy, "11b")) { wifi_set_phy_mode(PHY_MODE_11B); } else if (jsvIsStringEqual(jsPhy, "11g")) { wifi_set_phy_mode(PHY_MODE_11G); } else if (jsvIsStringEqual(jsPhy, "11n")) { wifi_set_phy_mode(PHY_MODE_11N); } else { jsvUnLock(jsPhy); jsExceptionHere(JSET_ERROR, "Unknown phy mode."); return; } } if (jsPhy != NULL) jsvUnLock(jsPhy); // powersave setting JsVar *jsPowerSave = jsvObjectGetChild(jsSettings, "powersave", 0); if (jsvIsString(jsPowerSave)) { if (jsvIsStringEqual(jsPowerSave, "none")) { wifi_set_sleep_type(NONE_SLEEP_T); } else if (jsvIsStringEqual(jsPowerSave, "ps-poll")) { wifi_set_sleep_type(MODEM_SLEEP_T); } else { jsvUnLock(jsPowerSave); jsExceptionHere(JSET_ERROR, "Unknown powersave mode."); return; } } if (jsPowerSave != NULL) jsvUnLock(jsPowerSave); DBGV("< Wifi.setConfig\n"); } JsVar *jswrap_wifi_getDetails(JsVar *jsCallback) {{ DBGV("> Wifi.getDetails\n"); // Check callback if (jsCallback != NULL && !jsvIsNull(jsCallback) && !jsvIsFunction(jsCallback)) { EXPECT_CB_EXCEPTION(jsCallback); return NULL; } uint8 opMode = wifi_get_opmode(); JsVar *jsDetails = jsvNewObject(); int8 conn = wifi_station_get_connect_status(); if (conn < 0) conn = 0; jsvObjectSetChildAndUnLock(jsDetails, "status", jsvNewFromString(wifiConn[conn])); struct station_config config; wifi_station_get_config(&config); char buf[65]; // ssid os_strncpy(buf, (char *)config.ssid, 32); buf[32] = 0; jsvObjectSetChildAndUnLock(jsDetails, "ssid", jsvNewFromString(buf)); // password os_strncpy(buf, (char *)config.password, 64); buf[64] = 0; jsvObjectSetChildAndUnLock(jsDetails, "password", jsvNewFromString((char *)config.password)); if (opMode & STATION_MODE) { int rssi = wifi_station_get_rssi(); if (rssi > 0) rssi = 0; // sanity... jsvObjectSetChildAndUnLock(jsDetails, "rssi", jsvNewFromInteger(rssi)); //jsvObjectSetChildAndUnLock(jsDetails, "authMode", jsvNewFromString(wifiAuth[config.)); } jsvObjectSetChildAndUnLock(jsDetails, "savedSsid", jsvNewNull()); // Schedule callback if a function was provided if (jsvIsFunction(jsCallback)) { JsVar *params[1]; params[0] = jsDetails; jsiQueueEvents(NULL, jsCallback, params, 1); } DBGV("< Wifi.getDetails\n"); return jsDetails; } } JsVar *jswrap_wifi_getAPDetails(JsVar *jsCallback) { DBGV("> Wifi.getAPDetails\n"); // Check callback if (jsCallback != NULL && !jsvIsNull(jsCallback) && !jsvIsFunction(jsCallback)) { EXPECT_CB_EXCEPTION(jsCallback); return NULL; } uint8 opMode = wifi_get_opmode(); JsVar *jsDetails = jsvNewObject(); jsvObjectSetChildAndUnLock(jsDetails, "status", jsvNewFromString(opMode & SOFTAP_MODE ? "enabled" : "disabled")); struct softap_config config; wifi_softap_get_config(&config); jsvObjectSetChildAndUnLock(jsDetails, "authMode", jsvNewFromString(wifiAuth[config.authmode])); jsvObjectSetChildAndUnLock(jsDetails, "hidden", jsvNewFromBool(config.ssid_hidden)); jsvObjectSetChildAndUnLock(jsDetails, "maxConn", jsvNewFromInteger(config.max_connection)); char buf[65]; // ssid os_strncpy(buf, (char *)config.ssid, 32); buf[32] = 0; jsvObjectSetChildAndUnLock(jsDetails, "ssid", jsvNewFromString(buf)); // password os_strncpy(buf, (char *)config.password, 64); buf[64] = 0; jsvObjectSetChildAndUnLock(jsDetails, "password", jsvNewFromString((char *)config.password)); jsvObjectSetChildAndUnLock(jsDetails, "savedSsid", jsvNewNull()); if (opMode & SOFTAP_MODE) { JsVar *jsArray = jsvNewArray(NULL, 0); struct station_info *station = wifi_softap_get_station_info(); while(station) { JsVar *jsSta = jsvNewObject(); jsvObjectSetChildAndUnLock(jsSta, "ip", networkGetAddressAsString((uint8_t *)&station->ip.addr, 4, 10, '.')); char macAddrString[6*3 + 1]; os_sprintf(macAddrString, macFmt, station->bssid[0], station->bssid[1], station->bssid[2], station->bssid[3], station->bssid[4], station->bssid[5]); jsvObjectSetChildAndUnLock(jsSta, "mac", jsvNewFromString(macAddrString)); jsvArrayPush(jsArray, jsSta); jsvUnLock(jsSta); station = STAILQ_NEXT(station, next); } wifi_softap_free_station_info(); jsvObjectSetChildAndUnLock(jsDetails, "stations", jsArray); } // Schedule callback if a function was provided if (jsvIsFunction(jsCallback)) { JsVar *params[1]; params[0] = jsDetails; jsiQueueEvents(NULL, jsCallback, params, 1); } DBGV("< Wifi.getAPDetails\n"); return jsDetails; } void jswrap_wifi_save(JsVar *what) { DBGV("> Wifi.save\n"); uint32_t flashBlock[256]; Esp8266_config *conf=(Esp8266_config *)flashBlock; os_memset(flashBlock, 0, sizeof(flashBlock)); uint32_t map = system_get_flash_size_map(); conf->length = 1024; conf->version = 24; if (jsvIsString(what) && jsvIsStringEqual(what, "clear")) { conf->mode = 0; // disable conf->phyMode = PHY_MODE_11N; conf->sleepType = MODEM_SLEEP_T; // ssids, passwords, and hostname are set to zero thanks to memset above DBG("Wifi.save(clear)\n"); } else { conf->mode = wifi_get_opmode(); conf->phyMode = wifi_get_phy_mode(); conf->sleepType = wifi_get_sleep_type(); DBG("Wifi.save: len=%d phy=%d sleep=%d opmode=%d\n", sizeof(*conf), conf->phyMode, conf->sleepType, conf->mode); struct station_config sta_config; wifi_station_get_config(&sta_config); os_strncpy(conf->staSsid, (char *)sta_config.ssid, 32); os_strncpy(conf->staPass, (char *)sta_config.password, 64); struct softap_config ap_config; wifi_softap_get_config(&ap_config); conf->authMode = ap_config.authmode; conf->hidden = ap_config.ssid_hidden; conf->ssidLen = ap_config.ssid_len; os_strncpy(conf->apSsid, (char *)ap_config.ssid, 32); os_strncpy(conf->apPass, (char *)ap_config.password, 64); DBG("Wifi.save: AP=%s STA=%s\n", ap_config.ssid, sta_config.ssid); char *hostname = wifi_station_get_hostname(); if (hostname) os_strncpy(conf->dhcpHostname, hostname, 64); } conf->crc = crc32((uint8_t*)flashBlock, sizeof(flashBlock)); DBG("Wifi.save: len=%d vers=%d crc=0x%08lx\n", conf->length, conf->version, (long unsigned int) conf->crc); if (map == 6 ) { jshFlashErasePage( 0x3FB000); jshFlashWrite(conf,0x3FB000, sizeof(flashBlock)); } else { jshFlashErasePage(0x7B000); jshFlashWrite(conf, 0x7B000, sizeof(flashBlock)); } DBGV("< Wifi.save: write completed\n"); } void jswrap_wifi_restore(void) { DBG("Wifi.restore\n"); uint32_t flashBlock[256]; Esp8266_config *conf=(Esp8266_config *)flashBlock; os_memset(flashBlock, 0, sizeof(flashBlock)); uint32_t map = system_get_flash_size_map(); if (map == 6 ) { jshFlashRead(flashBlock, 0x3FB000, sizeof(flashBlock)); } else { jshFlashRead(flashBlock, 0x7B000, sizeof(flashBlock)); } DBG("Wifi.restore: len=%d vers=%d crc=0x%08lx\n", conf->length, conf->version, (long unsigned int) conf->crc); uint32_t crcRd = conf->crc; conf->crc = 0; uint32_t crcCalc = crc32((uint8_t*)flashBlock, sizeof(flashBlock)); wifi_set_opmode(0); // check that we have a good flash config if (conf->length != 1024 || conf->version != 24 || crcRd != crcCalc || conf->phyMode > PHY_MODE_11N || conf->sleepType > MODEM_SLEEP_T || conf->mode > STATIONAP_MODE) { DBG("Wifi.restore cannot restore: version read=%d exp=%d, crc read=0x%08lx cacl=0x%08lx\n", conf->version, 24, (long unsigned int) crcRd, (long unsigned int) crcCalc); wifi_set_phy_mode(PHY_MODE_11N); wifi_set_opmode_current(SOFTAP_MODE); return; } DBG("Wifi.restore: phy=%d sleep=%d opmode=%d\n", conf->phyMode, conf->sleepType, conf->mode); wifi_set_phy_mode(conf->phyMode); wifi_set_sleep_type(conf->sleepType); wifi_set_opmode_current(conf->mode); if (conf->mode & SOFTAP_MODE) { struct softap_config ap_config; os_memset(&ap_config, 0, sizeof(ap_config)); ap_config.authmode = conf->authMode; ap_config.ssid_hidden = conf->hidden; ap_config.ssid_len = conf->ssidLen; os_strncpy((char *)ap_config.ssid, conf->apSsid, 32); os_strncpy((char *)ap_config.password, conf->apPass, 64); ap_config.channel = 1; ap_config.max_connection = 4; ap_config.beacon_interval = 100; wifi_softap_set_config_current(&ap_config); DBG("Wifi.restore: AP=%s\n", ap_config.ssid); } if (conf->mode & STATION_MODE) { if (conf->dhcpHostname[0] != 0 && os_strlen(conf->dhcpHostname) < 64) { DBG("Wifi.restore: hostname=%s\n", conf->dhcpHostname); wifi_station_set_hostname(conf->dhcpHostname); } struct station_config sta_config; os_memset(&sta_config, 0, sizeof(sta_config)); os_strncpy((char *)sta_config.ssid, conf->staSsid, 32); os_strncpy((char *)sta_config.password, conf->staPass, 64); wifi_station_set_config_current(&sta_config); DBG("Wifi.restore: STA=%s\n", sta_config.ssid); wifi_station_connect(); // we're not supposed to call this from user_init but it doesn't harm // and we need it when invoked from JS } } /** * Get the ip info for the given interface. The interfaces are: * * 0 - Station * * 1 - Access Point */ static JsVar *getIPInfo(JsVar *jsCallback, int interface) { // Check callback if (jsCallback != NULL && !jsvIsNull(jsCallback) && !jsvIsFunction(jsCallback)) { EXPECT_CB_EXCEPTION(jsCallback); return NULL; } // first get IP address info, this may fail if we're not connected struct ip_info info; bool ok = wifi_get_ip_info(interface, &info); JsVar *jsIpInfo = jsvNewObject(); if (ok) { jsvObjectSetChildAndUnLock(jsIpInfo, "ip", networkGetAddressAsString((uint8_t *)&info.ip.addr, 4, 10, '.')); jsvObjectSetChildAndUnLock(jsIpInfo, "netmask", networkGetAddressAsString((uint8_t *)&info.netmask.addr, 4, 10, '.')); jsvObjectSetChildAndUnLock(jsIpInfo, "gw", networkGetAddressAsString((uint8_t *)&info.gw.addr, 4, 10, '.')); } // now get MAC address (which always succeeds) uint8 macAddr[6]; wifi_get_macaddr(interface, macAddr); char macAddrString[6*3 + 1]; os_sprintf(macAddrString, macFmt, macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]); jsvObjectSetChildAndUnLock(jsIpInfo, "mac", jsvNewFromString(macAddrString)); // Schedule callback if a function was provided if (jsvIsFunction(jsCallback)) { JsVar *params[1]; params[0] = jsIpInfo; jsiQueueEvents(NULL, jsCallback, params, 1); } return jsIpInfo; } JsVar *jswrap_wifi_getIP(JsVar *jsCallback) { DBGV("> Wifi.getIP\n"); JsVar *jsIP = getIPInfo(jsCallback, 0); DBGV("< Wifi.getIP\n"); return jsIP; } JsVar *jswrap_wifi_getAPIP(JsVar *jsCallback) { DBGV("> Wifi.getAPIP\n"); JsVar *jsIP = getIPInfo(jsCallback, 1); DBGV("< Wifi.getAPIP\n"); return jsIP; } /** * Handle a response from espconn_gethostbyname. * Invoke the callback function to inform the caller that a hostname has been converted to * an IP address. The callback function should take a parameter that is the IP address. */ static void dnsFoundCallback( const char *hostname, //!< The hostname that was converted to an IP address. ip_addr_t *ipAddr, //!< The ip address retrieved. This may be 0. void *arg //!< Parameter passed in from espconn_gethostbyname. ) { DBG(">> Wifi.getHostByName CB - %s %x\n", hostname, ipAddr ? ipAddr->addr : 0); if (g_jsHostByNameCallback != NULL) { JsVar *params[1]; if (ipAddr == NULL) { params[0] = jsvNewNull(); } else { params[0] = networkGetAddressAsString((uint8_t *)&ipAddr->addr, 4, 10, '.'); } jsiQueueEvents(NULL, g_jsHostByNameCallback, params, 1); jsvUnLock(params[0]); jsvUnLock(g_jsHostByNameCallback); g_jsHostByNameCallback = NULL; } DBGV("<< Wifi.getHostByName CB\n"); } void jswrap_wifi_getHostByName( JsVar *jsHostname, JsVar *jsCallback ) { ip_addr_t ipAddr; char hostname[256]; DBGV("> Wifi.getHostByName\n"); if (!jsvIsString(jsHostname)) { jsExceptionHere(JSET_ERROR, "Hostname parameter is not a string"); return; } if (!jsvIsFunction(jsCallback)) { jsExceptionHere(JSET_ERROR, "Callback is not a function"); return; } // Save the callback unlocking an old callback if needed. if (g_jsHostByNameCallback != NULL) jsvUnLock(g_jsHostByNameCallback); g_jsHostByNameCallback = jsCallback; jsvLockAgainSafe(g_jsHostByNameCallback); jsvGetString(jsHostname, hostname, sizeof(hostname)); DBG(" Wifi.getHostByName: %s\n", hostname); err_t err = espconn_gethostbyname(NULL, hostname, &ipAddr, dnsFoundCallback); if (err == ESPCONN_OK) { DBGV("Already resolved\n"); dnsFoundCallback(hostname, &ipAddr, NULL); } else if (err != ESPCONN_INPROGRESS) { os_printf("Error: %d from espconn_gethostbyname\n", err); dnsFoundCallback(hostname, NULL, NULL); } DBGV("< Wifi.getHostByName\n"); } JsVar *jswrap_wifi_getHostname(JsVar *jsCallback) { char *hostname = wifi_station_get_hostname(); if (hostname == NULL) { hostname = ""; } return jsvNewFromString(hostname); } void jswrap_wifi_setHostname( JsVar *jsHostname //!< The hostname to set for device. ) { char hostname[256]; jsvGetString(jsHostname, hostname, sizeof(hostname)); DBG("Wifi.setHostname: %s\n", hostname); wifi_station_set_hostname(hostname); // now start/restart DHCP for this to take effect if (wifi_station_dhcpc_status() == DHCP_STARTED) wifi_station_dhcpc_stop(); wifi_station_dhcpc_start(); // now update mDNS startMDNS(hostname); } //===== mDNS static bool mdns_started; // FIXME: To be removed in favor of using mDNS.js module instead void startMDNS(char *hostname) { return; // FIXME: debug if (mdns_started) stopMDNS(); // find our IP address struct ip_info info; bool ok = wifi_get_ip_info(0, &info); if (!ok || info.ip.addr == 0) return; // no IP address // start mDNS struct mdns_info *mdns_info = (struct mdns_info *)os_zalloc(sizeof(struct mdns_info)); mdns_info->host_name = hostname; mdns_info->server_name = "espruino"; mdns_info->server_port = 23; mdns_info->ipAddr = info.ip.addr; espconn_mdns_init(mdns_info); mdns_started = true; } void stopMDNS() { espconn_mdns_server_unregister(); espconn_mdns_close(); mdns_started = false; } //===== SNTP static os_timer_t sntpTimer; static void sntpSync(void *arg) { uint32_t sysTime = (uint32_t)((jshGetSystemTime() + 500000) / 1000000); uint32_t ntpTime = sntp_get_current_timestamp(); if (!ntpTime) { DBG("NTP time: null\n"); } else { if (ntpTime-sysTime != 0) { DBG("NTP time: %ld delta=%ld %s\n", (long unsigned int) ntpTime, (long unsigned int)ntpTime-sysTime, sntp_get_real_time(ntpTime)); } jswrap_interactive_setTime((JsVarFloat)ntpTime); } os_timer_disarm(&sntpTimer); os_timer_arm(&sntpTimer, 30*1000, 0); } void jswrap_wifi_setSNTP(JsVar *jsServer, JsVar *jsZone) { if (!jsvIsNumeric(jsZone)) { jsExceptionHere(JSET_ERROR, "Zone is not a number"); return; } int zone = jsvGetInteger(jsZone); if (zone < -11 || zone > 13) { jsExceptionHere(JSET_ERROR, "Zone must be in range -11..13"); return; } if (!jsvIsString(jsServer)) { jsExceptionHere(JSET_ERROR, "Server is not a string"); return; } char server[64]; jsvGetString(jsServer, server, 64); sntp_stop(); if (sntp_set_timezone(zone)) { sntp_setservername(0, server); sntp_init(); os_timer_disarm(&sntpTimer); os_timer_setfn(&sntpTimer, sntpSync, 0); os_timer_arm(&sntpTimer, 100, 0); // 100ms } DBG("SNTP: %s %s%d\n", server, zone>=0?"+":"", zone); } // When the Espruino environment is reset (e.g. the reset() function), this callback function // will be invoked. // The purpose is to reset the environment by cleaning up whatever might be needed // to be cleaned up. This does not actually touch the wifi itself: we want the IDE to remain // connected! void jswrap_ESP8266_wifi_reset() { DBGV("> Wifi reset\n"); g_jsGotIpCallback = NULL; g_jsPingCallback = NULL; g_jsScanCallback = NULL; g_jsHostByNameCallback = NULL; g_jsDisconnectCallback = NULL; g_disconnecting = false; DBGV("< Wifi reset\n"); } // This function is called in the user_main's user_init() to set-up the wifi based on what // was saved in flash. This will restore the settings from flash into the SDK so the SDK // fires-up the right AP/STA modes and connections. void jswrap_ESP8266_wifi_init1() { DBGV("> Wifi.init1\n"); jswrap_wifi_restore(); // register the state change handler so we get debug printout for sure wifi_set_event_handler_cb(wifiEventHandler); // tell the SDK to let us have 10 connections espconn_tcp_set_max_con(MAX_SOCKETS); DBG("< Wifi init1, phy=%d mode=%d\n", wifi_get_phy_mode(), wifi_get_opmode()); } /*JSON{ "type":"init", "generate":"jswrap_ESP8266_wifi_soft_init" } // This function is called in soft_init to hook-up the network. This happens from user_main's // init_done() and also from `reset()` in order to re-hook-up the network. */ void jswrap_ESP8266_wifi_soft_init() { DBGV("> Wifi.soft_init\n"); // initialize the network stack netInit_esp8266_board(); JsNetwork net; networkCreate(&net, JSNETWORKTYPE_ESP8266_BOARD); networkState = NETWORKSTATE_ONLINE; DBGV("< Wifi.soft_init\n"); } /*JSON{ "type" : "staticmethod", "class" : "ESP8266", "name" : "ping", "generate" : "jswrap_ESP8266_ping", "params" : [ ["ipAddr", "JsVar", "A string representation of an IP address."], ["pingCallback", "JsVar", "Optional callback function."] ] } Perform a network ping request. The parameter can be either a String or a numeric IP address. **Note:** This function should probably be removed, or should it be part of the wifi library? */ void jswrap_ESP8266_ping( JsVar *ipAddr, //!< A string or integer representation of an IP address. JsVar *pingCallback //!< Optional callback function. ) { memset(&pingOpt, 0, sizeof(pingOpt)); // If the parameter is a string, get the IP address from the string // representation. if (jsvIsString(ipAddr)) { char ipString[20]; int len = jsvGetString(ipAddr, ipString, sizeof(ipString)-1); ipString[len] = '\0'; pingOpt.ip = networkParseIPAddress(ipString); if (pingOpt.ip == 0) { jsExceptionHere(JSET_ERROR, "Not a valid IP address."); return; } } else // If the parameter is an integer, treat it as an IP address. if (jsvIsInt(ipAddr)) { pingOpt.ip = jsvGetInteger(ipAddr); } else // The parameter was neither a string nor an IP address and hence we don't // know how to get the IP address of the partner to ping so throw an // exception. { jsExceptionHere(JSET_ERROR, "IP address must be string or integer."); return; } if (jsvIsUndefined(pingCallback) || jsvIsNull(pingCallback)) { if (g_jsPingCallback != NULL) { jsvUnLock(g_jsPingCallback); } g_jsPingCallback = NULL; } else if (!jsvIsFunction(pingCallback)) { jsExceptionHere(JSET_ERROR, "Callback is not a function."); return; } else { if (g_jsPingCallback != NULL) { jsvUnLock(g_jsPingCallback); } g_jsPingCallback = pingCallback; jsvLockAgainSafe(g_jsPingCallback); } // We now have an IP address to ping ... so ping. pingOpt.count = 5; pingOpt.recv_function = pingRecvCB; ping_start(&pingOpt); } /** * Handle receiving a response from a ping reply. * If a callback function has been supplied we invoked that callback by queuing it for future * execution. A parameter is supplied to the callback which is a JavaScript object that contains: * - totalCount * - totalBytes * - totalTime * - respTime * - seqNo * - timeoutCount * - bytes * - error */ static void pingRecvCB(void *pingOpt, void *pingResponse) { struct ping_resp *pingResp = (struct ping_resp *)pingResponse; os_printf("Received a ping response!\n"); if (g_jsPingCallback != NULL) { JsVar *jsPingResponse = jsvNewObject(); jsvObjectSetChildAndUnLock(jsPingResponse, "totalCount", jsvNewFromInteger(pingResp->total_count)); jsvObjectSetChildAndUnLock(jsPingResponse, "totalBytes", jsvNewFromInteger(pingResp->total_bytes)); jsvObjectSetChildAndUnLock(jsPingResponse, "totalTime", jsvNewFromInteger(pingResp->total_time)); jsvObjectSetChildAndUnLock(jsPingResponse, "respTime", jsvNewFromInteger(pingResp->resp_time)); jsvObjectSetChildAndUnLock(jsPingResponse, "seqNo", jsvNewFromInteger(pingResp->seqno)); jsvObjectSetChildAndUnLock(jsPingResponse, "timeoutCount", jsvNewFromInteger(pingResp->timeout_count)); jsvObjectSetChildAndUnLock(jsPingResponse, "bytes", jsvNewFromInteger(pingResp->bytes)); jsvObjectSetChildAndUnLock(jsPingResponse, "error", jsvNewFromInteger(pingResp->ping_err)); JsVar *params[1]; params[0] = jsPingResponse; jsiQueueEvents(NULL, g_jsPingCallback, params, 1); } } // worker for jswrap_wifi_setIP and jswrap_wifi_setAPIP static void setIP(JsVar *jsSettings, JsVar *jsCallback, int interface) { DBGV("> setIP\n"); char ipTmp[20]; int len = 0; bool rc = false; memset(&info, 0, sizeof(info)); // first check parameter if (!jsvIsObject(jsSettings)) { EXPECT_OPT_EXCEPTION(jsSettings); return; } // get,check and store ip JsVar *jsIP = jsvObjectGetChild(jsSettings, "ip", 0); if (jsIP != NULL && !jsvIsString(jsIP)) { EXPECT_OPT_EXCEPTION(jsIP); jsvUnLock(jsIP); return; } jsvGetString(jsIP, ipTmp, sizeof(ipTmp)-1); //DBG(">> ip: %s\n",ipTmp); info.ip.addr = networkParseIPAddress(ipTmp); if ( info.ip.addr == 0) { jsExceptionHere(JSET_ERROR, "Not a valid IP address."); jsvUnLock(jsIP); return; } jsvUnLock(jsIP); // get, check and store gw JsVar *jsGW = jsvObjectGetChild(jsSettings, "gw", 0); if (jsGW != NULL && !jsvIsString(jsGW)) { EXPECT_OPT_EXCEPTION(jsGW); jsvUnLock(jsGW); return ; } jsvGetString(jsGW, ipTmp, sizeof(ipTmp)-1); //DBG(">> gw: %s\n",ipTmp); info.gw.addr = networkParseIPAddress(ipTmp); if (info.gw.addr == 0) { jsExceptionHere(JSET_ERROR, "Not a valid Gateway address."); jsvUnLock(jsGW); return; } jsvUnLock(jsGW); // netmask setting JsVar *jsNM = jsvObjectGetChild(jsSettings, "netmask", 0); if (jsNM != NULL && !jsvIsString(jsNM)) { EXPECT_OPT_EXCEPTION(jsNM); jsvUnLock(jsNM); return; } jsvGetString(jsNM, ipTmp, sizeof(ipTmp)-1); //DBG(">> netmask: %s\n",ipTmp); info.netmask.addr = networkParseIPAddress(ipTmp); if (info.netmask.addr == 0) { jsExceptionHere(JSET_ERROR, "Not a valid Netmask."); jsvUnLock(jsNM); return; } jsvUnLock(jsNM); // set IP for station if (interface == STATION_IF ) { wifi_station_dhcpc_stop(); rc = wifi_set_ip_info(STATION_IF, &info); } // set IP for access point else { wifi_softap_dhcps_stop(); rc = wifi_set_ip_info(SOFTAP_IF, &info); wifi_softap_dhcps_start(); } DBG(">> rc: %s\n", rc ? "true" : "false"); // Schedule callback if (jsvIsFunction(jsCallback)) { JsVar *jsRC = jsvNewObject(); jsvObjectSetChildAndUnLock(jsRC, "success",jsvNewFromBool(rc)); JsVar *params[1]; params[0] = jsRC; jsiQueueEvents(NULL, jsCallback, params, 1); jsvUnLock(params[0]); jsvUnLock(jsRC); } else { jsExceptionHere(JSET_ERROR, "Callback is not a function."); } DBGV("< setIP\n"); return ; }; void jswrap_wifi_setIP(JsVar *jsSettings, JsVar *jsCallback) { setIP(jsSettings, jsCallback, STATION_IF); return ; } void jswrap_wifi_setAPIP(JsVar *jsSettings, JsVar *jsCallback) { setIP(jsSettings, jsCallback, SOFTAP_IF); return ; } /** * Callback function that is invoked at the culmination of a scan. */ static void scanCB(void *arg, STATUS status) { /** * Create a JsVar that is an array of JS objects where each JS object represents a * retrieved access point set of information. The structure of a record will be: * o authMode * o isHidden * o rssi * o channel * o ssid * When the array has been built, invoke the callback function passing in the array * of records. */ DBGV(">> Wifi.scanCB\n"); if (g_jsScanCallback == NULL) { DBGV("<< Wifi.scanCB\n"); return; } // Create the Empty JS array that will be passed as a parameter to the callback. JsVar *jsAccessPointArray = jsvNewArray(NULL, 0); struct bss_info *bssInfo; bssInfo = (struct bss_info *)arg; short count = 0; while(bssInfo != NULL) { // Add a new object to the JS array that will be passed as a parameter to // the callback. // Create, populate and add a child ... JsVar *jsCurrentAccessPoint = jsvNewObject(); if (bssInfo->rssi > 0) bssInfo->rssi = 0; jsvObjectSetChildAndUnLock(jsCurrentAccessPoint, "rssi", jsvNewFromInteger(bssInfo->rssi)); jsvObjectSetChildAndUnLock(jsCurrentAccessPoint, "channel", jsvNewFromInteger(bssInfo->channel)); jsvObjectSetChildAndUnLock(jsCurrentAccessPoint, "authMode", jsvNewFromInteger(bssInfo->authmode)); jsvObjectSetChildAndUnLock(jsCurrentAccessPoint, "isHidden", jsvNewFromBool(bssInfo->is_hidden)); // The SSID may **NOT** be NULL terminated ... so handle that. char ssid[sizeof(bssInfo->ssid) + 1]; os_strncpy((char *)ssid, (char *)bssInfo->ssid, sizeof(bssInfo->ssid)); ssid[sizeof(ssid)-1] = '\0'; jsvObjectSetChildAndUnLock(jsCurrentAccessPoint, "ssid", jsvNewFromString(ssid)); char macAddrString[6*3 + 1]; os_sprintf(macAddrString, macFmt, bssInfo->bssid[0], bssInfo->bssid[1], bssInfo->bssid[2], bssInfo->bssid[3], bssInfo->bssid[4], bssInfo->bssid[5]); jsvObjectSetChildAndUnLock(jsCurrentAccessPoint, "mac", jsvNewFromString(macAddrString)); // Add the new record to the array jsvArrayPush(jsAccessPointArray, jsCurrentAccessPoint); jsvUnLock(jsCurrentAccessPoint); count++; DBGV(" - ssid: %s\n", bssInfo->ssid); bssInfo = STAILQ_NEXT(bssInfo, next); } DBG("Wifi.scan completed, found %d\n", count); // We have now completed the scan callback, so now we can invoke the JS callback. JsVar *params[1]; params[0] = jsAccessPointArray; jsiQueueEvents(NULL, g_jsScanCallback, params, 1); jsvUnLock(jsAccessPointArray); jsvUnLock(g_jsScanCallback); g_jsScanCallback = NULL; DBGV("<< Wifi.scanCB\n"); } /** * Invoke the JavaScript callback to notify the program that an ESP8266 * WiFi event has occurred. */ static void sendWifiEvent( uint32 eventType, //!< The ESP8266 WiFi event type. JsVar *jsDetails //!< The JS object to be passed as a parameter to the callback. ) { JsVar *module = getWifiModule(); if (!module) return; // out of memory? // get event name as string and compose param list JsVar *params[1]; params[0] = jsDetails; char *eventName = wifiGetEvent(eventType); DBGV("wifi.on(%s)\n", eventName); jsiQueueObjectCallbacks(module, eventName, params, 1); jsvUnLock(module); return; } static void sendWifiCompletionCB( JsVar **g_jsCallback, //!< Pointer to the global callback variable char *reason //!< NULL if successful, error string otherwise ) { if (!jsvIsFunction(*g_jsCallback)) return; // we ain't got a function pointer: nothing to do JsVar *params[1]; params[0] = reason ? jsvNewFromString(reason) : jsvNewNull(); jsiQueueEvents(NULL, *g_jsCallback, params, 1); jsvUnLock(params[0]); // unlock and delete the global callback jsvUnLock(*g_jsCallback); *g_jsCallback = NULL; } /** * ESP8266 WiFi Event handler. * This function is called by the ESP8266 * environment when significant events happen related to the WiFi environment. * The event handler is registered with a call to wifi_set_event_handler_cb() * that is provided by the ESP8266 SDK. */ static void wifiEventHandler(System_Event_t *evt) { char buf[66]; char macAddrString[6*3 + 1]; uint8_t *mac; char *reason; JsVar *jsDetails = jsvNewObject(); switch(evt->event) { // We have connected to an access point. case EVENT_STAMODE_CONNECTED: DBG("Wifi event: connected to ssid %s, ch %d\n", evt->event_info.connected.ssid, evt->event_info.connected.channel); // ssid os_strncpy(buf, (char *)evt->event_info.connected.ssid, 32); buf[evt->event_info.connected.ssid_len] = 0; jsvObjectSetChildAndUnLock(jsDetails, "ssid", jsvNewFromString(buf)); // bssid = mac address mac = evt->event_info.connected.bssid; os_sprintf(macAddrString, macFmt, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); jsvObjectSetChildAndUnLock(jsDetails, "mac", jsvNewFromString(macAddrString)); // channel jsvObjectSetChildAndUnLock(jsDetails, "channel", jsvNewFromInteger(evt->event_info.connected.channel)); sendWifiEvent(evt->event, jsDetails); break; // We have disconnected or been disconnected from an access point. case EVENT_STAMODE_DISCONNECTED: reason = wifiGetReason(evt->event_info.disconnected.reason); int8 wifiConnectStatus = wifi_station_get_connect_status(); if (wifiConnectStatus < 0) wifiConnectStatus = 0; DBG("Wifi event: disconnected from ssid %s, reason %s (%d) status=%s\n", evt->event_info.disconnected.ssid, reason, evt->event_info.disconnected.reason, wifiConn[wifiConnectStatus]); if (g_skipDisconnect) { DBGV(" Skipping disconnect\n"); g_skipDisconnect = false; break; } // if'were connecting and we get a fatal error, then make a callback if (wifiConnectStatus == STATION_WRONG_PASSWORD && jsvIsFunction(g_jsGotIpCallback)) { sendWifiCompletionCB(&g_jsGotIpCallback, "bad password"); } // if we're in the process of disconnecting we want to turn STA mode off now // at that point we may need to make a callback too if (g_disconnecting) { DBGV(" Wifi.event: turning STA mode off\n"); wifi_set_opmode(wifi_get_opmode() & SOFTAP_MODE); g_disconnecting = false; if (jsvIsFunction(g_jsDisconnectCallback)) { jsiQueueEvents(NULL, g_jsDisconnectCallback, NULL, 0); jsvUnLock(g_jsDisconnectCallback); g_jsDisconnectCallback = NULL; } } // ssid os_strncpy(buf, (char *)evt->event_info.connected.ssid, 32); buf[evt->event_info.connected.ssid_len] = 0; jsvObjectSetChildAndUnLock(jsDetails, "ssid", jsvNewFromString(buf)); // bssid = mac address mac = evt->event_info.connected.bssid; os_sprintf(macAddrString, macFmt, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); jsvObjectSetChildAndUnLock(jsDetails, "mac", jsvNewFromString(macAddrString)); jsvObjectSetChildAndUnLock(jsDetails, "reason", jsvNewFromString(reason)); sendWifiEvent(evt->event, jsDetails); break; // The authentication information at the access point has changed. case EVENT_STAMODE_AUTHMODE_CHANGE: DBG("Wifi event: auth mode %s -> %s\n", wifiAuth[evt->event_info.auth_change.old_mode], wifiAuth[evt->event_info.auth_change.new_mode]); jsvObjectSetChildAndUnLock(jsDetails, "oldMode", jsvNewFromString(wifiAuth[evt->event_info.auth_change.old_mode])); jsvObjectSetChildAndUnLock(jsDetails, "newMode", jsvNewFromString(wifiAuth[evt->event_info.auth_change.new_mode])); sendWifiEvent(evt->event, jsDetails); break; // We have been allocated an IP address. case EVENT_STAMODE_GOT_IP: DBG("Wifi event: got ip:" IPSTR ", mask:" IPSTR ", gw:" IPSTR "\n", IP2STR(&evt->event_info.got_ip.ip), IP2STR(&evt->event_info.got_ip.mask), IP2STR(&evt->event_info.got_ip.gw)); // start mDNS char *hostname = wifi_station_get_hostname(); if (hostname && hostname[0] != 0) { startMDNS(hostname); } // Make Wifi.connected() callback if (jsvIsFunction(g_jsGotIpCallback)) { sendWifiCompletionCB(&g_jsGotIpCallback, NULL); } // "on" event callback jsvObjectSetChildAndUnLock(jsDetails, "ip", networkGetAddressAsString((uint8_t *)&evt->event_info.got_ip.ip, 4, 10, '.')); jsvObjectSetChildAndUnLock(jsDetails, "mask", networkGetAddressAsString((uint8_t *)&evt->event_info.got_ip.mask, 4, 10, '.')); jsvObjectSetChildAndUnLock(jsDetails, "gw", networkGetAddressAsString((uint8_t *)&evt->event_info.got_ip.gw, 4, 10, '.')); sendWifiEvent(evt->event, jsDetails); break; case EVENT_STAMODE_DHCP_TIMEOUT: os_printf("Wifi event: DHCP timeout"); sendWifiEvent(evt->event, jsvNewNull()); break; case EVENT_SOFTAPMODE_STACONNECTED: os_printf("Wifi event: station " MACSTR " joined, AID = %d\n", MAC2STR(evt->event_info.sta_connected.mac), evt->event_info.sta_connected.aid); // "on" event callback mac = evt->event_info.sta_connected.mac; os_sprintf(macAddrString, macFmt, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); jsvObjectSetChildAndUnLock(jsDetails, "mac", jsvNewFromString(macAddrString)); sendWifiEvent(evt->event, jsDetails); break; case EVENT_SOFTAPMODE_STADISCONNECTED: os_printf("Wifi event: station " MACSTR " left, AID = %d\n", MAC2STR(evt->event_info.sta_disconnected.mac), evt->event_info.sta_disconnected.aid); // "on" event callback mac = evt->event_info.sta_disconnected.mac; os_sprintf(macAddrString, macFmt, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); jsvObjectSetChildAndUnLock(jsDetails, "mac", jsvNewFromString(macAddrString)); sendWifiEvent(evt->event, jsDetails); break; case EVENT_SOFTAPMODE_PROBEREQRECVED: os_printf("Wifi event: probe request from station " MACSTR ", rssi = %d\n", MAC2STR(evt->event_info.ap_probereqrecved.mac), evt->event_info.ap_probereqrecved.rssi); // "on" event callback int rssi = evt->event_info.ap_probereqrecved.rssi; if (rssi > 0) rssi = 0; jsvObjectSetChildAndUnLock(jsDetails, "rssi", jsvNewFromInteger(rssi)); mac = evt->event_info.ap_probereqrecved.mac; os_sprintf(macAddrString, macFmt, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); jsvObjectSetChildAndUnLock(jsDetails, "mac", jsvNewFromString(macAddrString)); sendWifiEvent(evt->event, jsDetails); break; default: os_printf("Wifi: unexpected event %d\n", evt->event); break; } jsvUnLock(jsDetails); }