Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1729 lines (1480 sloc) 58.6 KB
/*
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
*
* Copyright (C) 2015 Gordon Williams <gw@pur3.co.uk>
*
* 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
#ifdef RELEASE
#define DBG(format, ...) do { } while(0)
#define DBGV(format, ...) do { } while(0)
#else
// 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
#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 <c_types.h>
#include <user_interface.h>
#include <mem.h>
#include "osapi_release.h"
#include <ping.h>
#include <espconn.h>
#include <sntp.h>
#include <espmissingincludes.h>
#include <uart.h>
#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"
#include "jswrap_storage.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;
static uint8 savedMode = 0;
//===== 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 - <Not Used>"); // 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 - <Not Used>"); // 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
FLASH_STR(__wr202, "auth_failed"); // 202 - REASON_AUTH_FAIL
FLASH_STR(__wr203, "assoc_failed"); // 203 - REASON_ASSOC_FAIL
FLASH_STR(__wr204, "handshake_timeout"); // 204 - REASON_HANDSHAKE_TIMEOUT
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, __wr202, __wr203, __wr204
};
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 <= 204) 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
struct scan_config config = {0};
config.show_hidden = true;
wifi_station_scan(&config, 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 hidden
JsVar *jsHidden = jsvObjectGetChild(jsOptions, "hidden", 0);
if (jsvIsInt(jsHidden)) {
int hidden = jsvGetInteger(jsHidden);
if (hidden >= 0 && hidden <= 1) softApConfig.ssid_hidden = hidden;
}
jsvUnLock(jsHidden);
// 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(wifiMode[savedMode]));
// 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");
JsVar *o = jsvNewObject();
if (!o) return;
if (jsvIsString(what) && jsvIsStringEqual(what, "clear")) {
JsVar *name = jsvNewFromString(WIFI_CONFIG_STORAGE_NAME);
jswrap_storage_erase(name);
jsvUnLock(name);
DBG("Wifi.save(clear)\n");
return;
}
// station stuff
struct station_config sta_config;
wifi_station_get_config(&sta_config);
jsvObjectSetChildAndUnLock(o, "ssid", jsvNewFromString((char *)sta_config.ssid));
jsvObjectSetChildAndUnLock(o, "password", jsvNewFromString((char *)sta_config.password));
jsvObjectSetChildAndUnLock(o, "mode", jsvNewFromInteger(wifi_get_opmode()));
jsvObjectSetChildAndUnLock(o, "phyMode", jsvNewFromInteger(wifi_get_phy_mode()));
jsvObjectSetChildAndUnLock(o, "sleepType", jsvNewFromInteger(wifi_get_sleep_type()));
char *hostname = wifi_station_get_hostname();
if (hostname) jsvObjectSetChildAndUnLock(o, "hostname", jsvNewFromString((char *) hostname));
// softap stuff
struct softap_config ap_config;
wifi_softap_get_config(&ap_config);
jsvObjectSetChildAndUnLock(o, "ssidAP", jsvNewFromString((char *)ap_config.ssid));
jsvObjectSetChildAndUnLock(o, "passwordAP", jsvNewFromString((char *) ap_config.password));
jsvObjectSetChildAndUnLock(o, "authmodeAP", jsvNewFromInteger(ap_config.authmode));
jsvObjectSetChildAndUnLock(o, "hiddenAP", jsvNewFromInteger(ap_config.ssid_hidden));
jsvObjectSetChildAndUnLock(o, "channelAP", jsvNewFromInteger(ap_config.channel));
savedMode = wifi_get_opmode();
// save object
JsVar *name = jsvNewFromString(WIFI_CONFIG_STORAGE_NAME);
//JsVar *arr = jsvNewArray(&o,1);
jswrap_storage_erase(name);
jswrap_storage_write(name,o,0,0);
//jsvUnLock3(arr,name,o);
jsvUnLock2(name,o);
DBGV("< Wifi.save: write completed\n");
}
void jswrap_wifi_restore(void) {
DBG("Wifi.restore\n");
JsVar *name = jsvNewFromString(WIFI_CONFIG_STORAGE_NAME);
JsVar *o = jswrap_storage_readJSON(name);
if (!o) { // no data
jsvUnLock2(name,o);
return;
}
JsVar *v;
v = jsvObjectGetChild(o,"mode",0);
savedMode = jsvGetInteger(v);
jsvUnLock(v);
wifi_set_opmode_current(savedMode);
v = jsvObjectGetChild(o,"phyMode",0);
wifi_set_phy_mode(jsvGetInteger(v));
jsvUnLock(v);
v = jsvObjectGetChild(o,"sleepType",0);
wifi_set_sleep_type(jsvGetInteger(v));
jsvUnLock(v);
if (savedMode & SOFTAP_MODE) {
struct softap_config ap_config;
os_memset(&ap_config, 0, sizeof(ap_config));
v = jsvObjectGetChild(o,"authmodeAP",0);
ap_config.authmode =jsvGetInteger(v);
jsvUnLock(v);
v = jsvObjectGetChild(o,"hiddenAP",0);
ap_config.ssid_hidden = jsvGetInteger(v);
jsvUnLock(v);
v = jsvObjectGetChild(o,"ssidAP",0);
jsvGetString(v, (char *)ap_config.ssid, sizeof(ap_config.ssid));
ap_config.ssid_len = jsvGetStringLength(v);
jsvUnLock(v);
v = jsvObjectGetChild(o,"passwordAP",0);
jsvGetString(v, (char *)ap_config.password, sizeof(ap_config.password));
jsvUnLock(v);
v = jsvObjectGetChild(o,"channelAP",0);
ap_config.channel = jsvGetInteger(v);
jsvUnLock(v);
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 (savedMode & STATION_MODE) {
v = jsvObjectGetChild(o,"hostname",0);
if (v) {
char hostname[64];
jsvGetString(v, hostname, sizeof(hostname));
DBG("Wifi.restore: hostname=%s\n", hostname);
wifi_station_set_hostname(hostname);
}
jsvUnLock(v);
struct station_config sta_config;
os_memset(&sta_config, 0, sizeof(sta_config));
v = jsvObjectGetChild(o,"ssid",0);
jsvGetString(v, (char *)sta_config.ssid, sizeof(sta_config.ssid));
jsvUnLock(v);
v = jsvObjectGetChild(o,"password",0);
jsvGetString(v, (char *)sta_config.password, sizeof(sta_config.password));
jsvUnLock(v);
wifi_station_set_config_current(&sta_config);
DBG("Wifi.restore: STA=%s\n", sta_config.ssid);
//jsWarn("Station SSID '%s', password '%s'\n",sta_config.ssid,sta_config.password);
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[2];
params[0] = jsvNewWithFlags(JSV_NULL);
params[1] = jsIpInfo;
jsiQueueEvents(NULL, jsCallback, params, 2);
jsvUnLock(params[0]);
}
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.
JsVar *jsCallback
) {
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);
if (jsvIsFunction(jsCallback))
jsiQueueEvents(0, jsCallback, 0, 0);
}
//===== 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");
// 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_wifi_ping",
"params" : [
["ipAddr", "JsVar", "A string representation of an IP address."],
["pingCallback", "JsVar", "Optional callback function."]
]
}
**DEPRECATED** - please use `Wifi.ping` instead.
Perform a network ping request. The parameter can be either a String or a numeric IP address.
*/
void jswrap_wifi_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);
jsvUnLock(jsPingResponse);
}
}
// 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 *params[1];
params[0] = rc ? jsvNewWithFlags(JSV_NULL) : jsvNewFromString("Failure");
jsiQueueEvents(NULL, jsCallback, params, 1);
jsvUnLock(params[0]);
}
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", jsvNewFromString(wifiAuth[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(%d)\n",
evt->event_info.disconnected.ssid, reason, evt->event_info.disconnected.reason,
wifiConn[wifiConnectStatus], 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
// need two more cases
if ((wifiConnectStatus == STATION_WRONG_PASSWORD ||
wifiConnectStatus == STATION_NO_AP_FOUND ||
wifiConnectStatus == STATION_CONNECT_FAIL )
&& jsvIsFunction(g_jsGotIpCallback)) {
sendWifiCompletionCB(&g_jsGotIpCallback, wifiConn[wifiConnectStatus]);
}
// plus REASON_AUTH_EXPIRE
if (wifiConnectStatus == STATION_CONNECTING &&
evt->event_info.disconnected.reason == REASON_AUTH_EXPIRE &&
jsvIsFunction(g_jsGotIpCallback)) {
sendWifiCompletionCB(&g_jsGotIpCallback, reason);
}
// 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);
}