From b9da1bbd27e35d196593212fe9c202053cb1fea8 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 5 Oct 2015 15:25:49 -0500 Subject: [PATCH 01/10] Change for #619. No hardware reset for logical jshReset(). --- targets/esp8266/jshardware.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/targets/esp8266/jshardware.c b/targets/esp8266/jshardware.c index d2e6fb5ed..c8b7f4628 100644 --- a/targets/esp8266/jshardware.c +++ b/targets/esp8266/jshardware.c @@ -79,7 +79,7 @@ void jshInit() { * \brief Reset the hardware to a power-on state */ void jshReset() { - system_restart(); + //system_restart(); } // End of jshReset /** @@ -127,7 +127,7 @@ bool jshSleep(JsSysTime timeUntilWake) { void jshDelayMicroseconds(int microsec) { // Keep things simple and make the user responsible if they sleep for too long... if (microsec > 0) { - os_printf("Delay %ldus\n", microsec); + os_printf("Delay %d us\n", microsec); os_delay_us(microsec); } #if 0 @@ -400,7 +400,7 @@ void jshEnableWatchDog(JsVarFloat timeout) { /** - * \brief + * */ bool jshGetWatchedPinState(IOEventFlags device) { os_printf("ESP8266: jshGetWatchedPinState %d", device); @@ -479,6 +479,7 @@ bool jshIsUSBSERIALConnected() { /** * \brief * Kick a device into action (if required). + * * For instance we may need * to set up interrupts. In this ESP8266 implementation, we transmit all the * data that can be found associated with the device. @@ -495,6 +496,7 @@ void jshUSARTKick( /** * \brief Unknown * + * */ void jshSPISetup( IOEventFlags device, //!< Unknown @@ -630,7 +632,7 @@ static void saveTime() { (uint32_t)(rtcTimeStamp.timeStamp >> 32); system_rtc_mem_write(RTC_TIME_ADDR, &rtcTimeStamp, sizeof(rtcTimeStamp)); os_printf("RTC write: %lu %lu 0x%08x\n", (uint32_t)(rtcTimeStamp.timeStamp/1000000), - rtcTimeStamp.hwTimeStamp, rtcTimeStamp.cksum); + rtcTimeStamp.hwTimeStamp, (int)rtcTimeStamp.cksum); } /** @@ -703,8 +705,8 @@ static void systemTimeInit(void) { uint32_t cksum = rtcTimeStamp.cksum ^ rtcTimeStamp.hwTimeStamp ^ (uint32_t)(rtcTimeStamp.timeStamp & 0xffffffff) ^ (uint32_t)(rtcTimeStamp.timeStamp >> 32); - os_printf("RTC read: %lu %lu 0x%08x (0x%08x)\n", (uint32_t)(rtcTimeStamp.timeStamp/1000000), - rtcTimeStamp.hwTimeStamp, rtcTimeStamp.cksum, cksum); + os_printf("RTC read: %d %d 0x%08x (0x%08x)\n", (int)(rtcTimeStamp.timeStamp/1000000), + (int)rtcTimeStamp.hwTimeStamp, (unsigned int)rtcTimeStamp.cksum, (unsigned int)cksum); if (reason < 1 || reason > 4 || cksum != 0xdeadbeef) { // we lost track of time, start at zero os_printf("RTC: cannot restore time\n"); @@ -754,7 +756,7 @@ void jshUtilTimerDisable() { } void jshUtilTimerStart(JsSysTime period) { - os_printf("UStimer arm %lluus\n"); + os_printf("UStimer arm\n"); os_timer_arm_us(&utilTimer, (uint32_t)period, 0); } From 4bec81341a43052173f63ad382cea45b2077f30d Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 5 Oct 2015 20:28:26 -0500 Subject: [PATCH 02/10] Implementation of designs for issue #618. Three designs implemented with a compile time choice of which to use. --- libs/network/esp8266/jswrap_esp8266.c | 2065 +++++++++++++------------ 1 file changed, 1062 insertions(+), 1003 deletions(-) diff --git a/libs/network/esp8266/jswrap_esp8266.c b/libs/network/esp8266/jswrap_esp8266.c index ae65fb413..89e0bf6eb 100644 --- a/libs/network/esp8266/jswrap_esp8266.c +++ b/libs/network/esp8266/jswrap_esp8266.c @@ -1,1003 +1,1062 @@ -// 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 - -#define ESP8266_ON_ACCESS_POINTS "#accessPoints" - -#define _GCC_WRAP_STDINT_H -typedef long long int64_t; - -#include "jswrap_esp8266.h" -#include "jsinteractive.h" // Pull inn the jsiConsolePrint function -#include "network.h" -#include "network_esp8266.h" -#include "jswrap_net.h" - -// Forward declaration of functions. -static void scanCB(void *arg, STATUS status); -static void wifiEventHandler(System_Event_t *event); -static void ipAddrToString(struct ip_addr addr, char *string); -static char *nullTerminateString(char *target, char *source, int sourceLength); -static void setupJsNetwork(); -static void pingRecvCB(); - -static JsVar *jsScanCallback; -static JsVar *jsWiFiEventCallback; - -// A callback function to be invoked when we have an IP address. -static JsVar *jsGotIpCallback; - -static JsVar *jsPingCallback; - -// Global data structure for ping request -static struct ping_option pingOpt; - -// Reasons for which a connection failed -uint8_t wifiReason = 0; -static char *wifiReasons[] = { - "", "unspecified", "auth_expire", "auth_leave", "assoc_expire", "assoc_toomany", "not_authed", - "not_assoced", "assoc_leave", "assoc_not_authed", "disassoc_pwrcap_bad", "disassoc_supchan_bad", - "ie_invalid", "mic_failure", "4way_handshake_timeout", "group_key_update_timeout", - "ie_in_4way_differs", "group_cipher_invalid", "pairwise_cipher_invalid", "akmp_invalid", - "unsupp_rsn_ie_version", "invalid_rsn_ie_cap", "802_1x_auth_failed", "cipher_suite_rejected", - "beacon_timeout", "no_ap_found" }; - -static char *wifiGetReason(void) { - if (wifiReason <= 24) return wifiReasons[wifiReason]; - if (wifiReason >= 200 && wifiReason <= 201) return wifiReasons[wifiReason-200+24]; - return wifiReasons[1]; -} - -static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" }; -static char *wifiPhy[] = { 0, "11b", "11g", "11n" }; - -// Let's define the JavaScript class that will contain our `world()` method. We'll call it `Hello` -/*JSON{ - "type" : "class", - "class" : "ESP8266WiFi" -}*/ - - -/** - * \brief Connect the station to an access point. - * - `ssid` - The network id of the access point. - * - `password` - The password to use to connect to the access point. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "connect", - "generate" : "jswrap_ESP8266WiFi_connect", - "params" : [ - ["ssid","JsVar","The network SSID"], - ["password","JsVar","The password to the access point"], - ["gotIpCallback", "JsVar", "An optional callback invoked when we have an IP"] - ] -}*/ -void jswrap_ESP8266WiFi_connect( - JsVar *jsv_ssid, //!< The SSID of the access point to connect. - JsVar *jsv_password, //!< The password for the access point. - JsVar *gotIpCallback //!< The Callback function to be called when we are connected. - ) { - os_printf("> jswrap_ESP8266WiFi_connect\n"); - - // Check that the ssid and password values aren't obviously in error. - if (jsv_ssid == NULL || !jsvIsString(jsv_ssid)) { - jsExceptionHere(JSET_ERROR, "No SSID."); - return; - } - if (jsv_password == NULL || !jsvIsString(jsv_password)) { - jsExceptionHere(JSET_ERROR, "No password."); - return; - } - - // Check that if a callback function was supplied that we actually have a callback function. - if (gotIpCallback != NULL && !jsvIsUndefined(gotIpCallback) && !jsvIsFunction(gotIpCallback)) { - gotIpCallback = NULL; - jsExceptionHere(JSET_ERROR, "A callback function was supplied that is not a function."); - return; - } - if (jsvIsUndefined(gotIpCallback) || jsvIsNull(gotIpCallback)) { - gotIpCallback = NULL; - } - - // Set the global which is the gotIP callback - if (jsGotIpCallback != NULL) { - jsvUnLock(jsGotIpCallback); - jsGotIpCallback = NULL; - } - - // What does this do? - if (gotIpCallback != NULL) { - jsGotIpCallback = jsvLockAgainSafe(gotIpCallback); - } - os_printf("jsGotIpCallback=%p\n", jsGotIpCallback); - - // Create strings from the JsVars for the ESP8266 API calls. - char ssid[33]; - int len = jsvGetString(jsv_ssid, ssid, sizeof(ssid)-1); - ssid[len]='\0'; - char password[65]; - len = jsvGetString(jsv_password, password, sizeof(password)-1); - password[len]='\0'; - - os_printf("> - ssid=%s, password=%s\n", ssid, password); - // Set the WiFi mode of the ESP8266 - wifi_set_opmode_current(STATION_MODE); - - struct station_config stationConfig; - memset(&stationConfig, 0, sizeof(stationConfig)); - os_strncpy((char *)stationConfig.ssid, ssid, 32); - if (password != NULL) { - os_strncpy((char *)stationConfig.password, password, 64); - } else { - os_strcpy((char *)stationConfig.password, ""); - } - - // Set the WiFi configuration - wifi_station_set_config(&stationConfig); - - wifi_station_connect(); - os_printf("< jswrap_ESP8266WiFi_connect\n"); -} - - -/** - * \brief Become an access point. - * When we call this function we are instructing the ESP8266 to set itself up as an - * access point to allow other WiFi stations to connect to it. In order to be an access - * point, the ESP8266 needs to know the SSID it should use as well as the password used - * to allow clients to connect. - * - * Parameters: - * - `jsv_ssid` - The network identity that the access point will advertize itself as. - * - `jsv_password` - The password a station will need to connect to the - * access point. - * - * Notes: - * - How about if the password is not supplied, NULL or empty then we set ourselves - * up using an Open authentication mechanism? - * - Add support for hidden SSIDs. - * - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "beAccessPoint", - "generate" : "jswrap_ESP8266WiFi_beAccessPoint", - "params" : [ - ["jsv_ssid","JsVar","The network SSID"], - ["jsv_password","JsVar","The password to allow stations to connect to the access point"] - ] -}*/ -void jswrap_ESP8266WiFi_beAccessPoint( - JsVar *jsv_ssid, //!< The network identity that the access point will advertize itself as. - JsVar *jsv_password //!< The password a station will need to connect to the access point. - ) { - // Validate that the SSID and password are somewhat useful. - if (jsv_ssid == NULL || !jsvIsString(jsv_ssid)) { - jsExceptionHere(JSET_ERROR, "No SSID."); - return; - } - if (jsv_password == NULL || !jsvIsString(jsv_password)) { - jsExceptionHere(JSET_ERROR, "No password."); - return; - } - - // Create strings from the JsVars for the ESP8266 API calls. - char ssid[33]; - int len = jsvGetString(jsv_ssid, ssid, sizeof(ssid)-1); - ssid[len]='\0'; - char password[65]; - len = jsvGetString(jsv_password, password, sizeof(password)-1); - password[len]='\0'; - - // Define that we are in Soft AP mode. - os_printf("Wifi: switching to soft-AP\n"); - wifi_set_opmode_current(SOFTAP_MODE); - - // Build our SoftAP configuration details - struct softap_config softApConfig; - memset(&softApConfig, 0, sizeof(softApConfig)); - - os_strcpy((char *)softApConfig.ssid, ssid); - os_strcpy((char *)softApConfig.password, password); - softApConfig.ssid_len = 0; // Null terminated SSID - softApConfig.authmode = AUTH_WPA2_PSK; - softApConfig.ssid_hidden = 0; // Not hidden. - softApConfig.max_connection = 4; // Maximum number of connections. - - // Set the WiFi configuration. - int rc = wifi_softap_set_config_current(&softApConfig); - if (rc != 1) { - jsExceptionHere(JSET_ERROR, "Error setting ESP8266 softap config."); - } -} - - -/** - * \brief Determine the list of access points available to us. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getAccessPoints", - "generate" : "jswrap_ESP8266WiFi_getAccessPoints", - "params" : [ - ["callback","JsVar","Function to call back when access points retrieved."] - ] -}*/ -void jswrap_ESP8266WiFi_getAccessPoints( - JsVar *callback //!< Function to call back when access points retrieved. - ) { - os_printf("> ESP8266WiFi_getAccessPoints\n"); - if (callback == NULL || !jsvIsFunction(callback)) { - jsExceptionHere(JSET_ERROR, "No callback."); - return; - } - - // Save the callback for the scan in the global variable called jsScanCallback. - jsScanCallback = jsvLockAgainSafe(callback); - - // 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. - - // Ensure we are in station mode - wifi_set_opmode_current(STATION_MODE); - - // Request a scan of the network calling "scanCB" on completion - wifi_station_scan(NULL, scanCB); - - os_printf("< ESP8266WiFi_getAccessPoints\n"); -} - - -/** - * \brief Disconnect the station from the access point. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "disconnect", - "generate" : "jswrap_ESP8266WiFi_disconnect" -}*/ -void jswrap_ESP8266WiFi_disconnect() { - wifi_station_disconnect(); -} - - -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "restart", - "generate" : "jswrap_ESP8266WiFi_restart" -}*/ -void jswrap_ESP8266WiFi_restart() { - system_restart(); -} - - -/** - * \brief Register a callback function that will be invoked when a WiFi event is detected. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "onWiFiEvent", - "generate" : "jswrap_ESP8266WiFi_onWiFiEvent", - "params" : [ - ["callback","JsVar","WiFi event callback"] - ] -}*/ -void jswrap_ESP8266WiFi_onWiFiEvent( - JsVar *callback //!< WiFi event callback. - ) { - // If the callback is null - if (callback == NULL || jsvIsNull(callback)) { - if (jsWiFiEventCallback != NULL) { - jsvUnLock(jsWiFiEventCallback); - } - jsWiFiEventCallback = NULL; - return; - } - - if (!jsvIsFunction(callback)) { - jsExceptionHere(JSET_ERROR, "No callback."); - return; - } - - // We are about to save a new global WiFi even callback handler. If we have previously - // had one, we need to unlock it so that we don't leak memory. - if (jsWiFiEventCallback != NULL) { - jsvUnLock(jsWiFiEventCallback); - } - - // Save the global WiFi event callback handler. - jsWiFiEventCallback = jsvLockAgainSafe(callback); -} - - -/** - * \brief Set whether or not the ESP8266 will perform an auto connect on startup. - * A value of true means it will while a value of false means it won't. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "setAutoConnect", - "generate" : "jswrap_ESP8266WiFi_setAutoConnect", - "params" : [ - ["autoconnect","JsVar","True if we wish to auto connect."] - ] -}*/ -void jswrap_ESP8266WiFi_setAutoConnect( - JsVar *autoconnect //!< True if we wish to auto connect. - ) { - os_printf("Auto connect is: %d\n", (int)autoconnect); - // Check that we have been passed a boolean ... if not, nothing to do here. - if (!jsvIsBoolean(autoconnect)) { - return; - } - - uint8 newValue = jsvGetBool(autoconnect); - os_printf("New value: %d\n", newValue); - os_printf("jswrap_ESP8266WiFi_setAutoConnect -> Something breaks here :-(\n"); - - uart_rx_intr_disable(0); - wifi_station_set_auto_connect(newValue); - uart_rx_intr_disable(1); - os_printf("Autoconnect changed\n"); -} - - -/** - * \brief Retrieve whether or not the ESP8266 will perform an auto connect on startup. - * A value of 1 means it will while a value of zero means it won't. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getAutoConnect", - "generate" : "jswrap_ESP8266WiFi_getAutoConnect", - "return" : ["JsVar","A boolean representing our auto connect status"] -}*/ -JsVar *jswrap_ESP8266WiFi_getAutoConnect() { - uint8 result = wifi_station_get_auto_connect(); - return jsvNewFromBool(result); -} - - -/** - * \brief Retrieve the reset information that is stored when event the ESP8266 resets. - * The result will be a JS object containing the details. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getRstInfo", - "generate" : "jswrap_ESP8266WiFi_getRstInfo", - "return" : ["JsVar","A Restart Object"], - "return_object" : "Restart" -}*/ -JsVar *jswrap_ESP8266WiFi_getRstInfo() { - struct rst_info* info = system_get_rst_info(); - JsVar *restartInfo = jspNewObject(NULL, "Restart"); - jsvUnLock(jsvObjectSetChild(restartInfo, "reason", jsvNewFromInteger(info->reason))); - jsvUnLock(jsvObjectSetChild(restartInfo, "exccause", jsvNewFromInteger(info->exccause))); - jsvUnLock(jsvObjectSetChild(restartInfo, "epc1", jsvNewFromInteger(info->epc1))); - jsvUnLock(jsvObjectSetChild(restartInfo, "epc2", jsvNewFromInteger(info->epc2))); - jsvUnLock(jsvObjectSetChild(restartInfo, "epc3", jsvNewFromInteger(info->epc3))); - jsvUnLock(jsvObjectSetChild(restartInfo, "excvaddr", jsvNewFromInteger(info->excvaddr))); - jsvUnLock(jsvObjectSetChild(restartInfo, "depc", jsvNewFromInteger(info->depc))); - return restartInfo; -} - - -/** - * \brief Return an object that contains details about the state of the ESP8266. - * - `sdkVersion` - Version of the SDK. - * - `cpuFrequency` - CPU operating frequency. - * - `freeHeap` - Amount of free heap. - * - `maxCon` - Maximum number of concurrent connections. - * - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getState", - "generate" : "jswrap_ESP8266WiFi_getState", - "return" : ["JsVar","The state of the ESP8266"], - "return_object" : "ESP8266State" -}*/ -JsVar *jswrap_ESP8266WiFi_getState() { - // Create a new variable and populate it with the properties of the ESP8266 that we - // wish to return. - JsVar *esp8266State = jspNewObject(NULL, "ESP8266State"); - jsvUnLock(jsvObjectSetChild(esp8266State, "sdkVersion", jsvNewFromString(system_get_sdk_version()))); - jsvUnLock(jsvObjectSetChild(esp8266State, "cpuFrequency", jsvNewFromInteger(system_get_cpu_freq()))); - jsvUnLock(jsvObjectSetChild(esp8266State, "freeHeap", jsvNewFromInteger(system_get_free_heap_size()))); - jsvUnLock(jsvObjectSetChild(esp8266State, "maxCon", jsvNewFromInteger(espconn_tcp_get_max_con()))); - return esp8266State; -} - -/** - * \brief Return the value of an integer representation (4 bytes) of IP address - * as a string. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getAddressAsString", - "generate" : "jswrap_ESP8266WiFi_getAddressAsString", - "params" : [ - ["address","JsVar","An integer value representing an IP address."] - ], - "return" : ["JsVar","A String"] -}*/ -JsVar *jswrap_ESP8266WiFi_getAddressAsString( - JsVar *address //!< An integer value representing an IP address. - ) { - if (!jsvIsInt(address)) { - jsExceptionHere(JSET_ERROR, "No SSID."); - return NULL; - } - uint32 iAddress = (uint32)jsvGetInteger(address); - return networkGetAddressAsString((uint8 *)&iAddress, 4, 10, '.'); -} - - -/** - * \brief Retrieve the IP information about this network interface and return a JS - * object that contains its details. - * The object will have the following properties defined upon it: - * - `ip` - The IP address of the interface. - * - `netmask` - The netmask of the interface. - * - `gw` - The gateway to reach when transmitting through the interface. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getIPInfo", - "generate" : "jswrap_ESP8266WiFi_getIPInfo", - "return" : ["JsVar","A IPInfo Object"], - "return_object" : "IPInfo" -}*/ -JsVar *jswrap_ESP8266WiFi_getIPInfo() { - struct ip_info info; - wifi_get_ip_info(0, &info); - - JsVar *ipInfo = jspNewObject(NULL, "Restart"); - jsvUnLock(jsvObjectSetChild(ipInfo, "ip", jsvNewFromInteger(info.ip.addr))); - jsvUnLock(jsvObjectSetChild(ipInfo, "netmask", jsvNewFromInteger(info.netmask.addr))); - jsvUnLock(jsvObjectSetChild(ipInfo, "gw", jsvNewFromInteger(info.gw.addr))); - return ipInfo; -} - - -/** - * \brief Query the station configuration and return a JS object that represents the - * current settings. - * The object will have the following properties: - * - * - `ssid` - The network identity of the access point - * - `password` - The password to use to connect to the access point - * - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getStationConfig", - "generate" : "jswrap_ESP8266WiFi_getStationConfig", - "return" : ["JsVar","A Station Config"], - "return_object" : "StationConfig" -}*/ -JsVar *jswrap_ESP8266WiFi_getStationConfig() { - struct station_config config; - wifi_station_get_config(&config); - JsVar *jsConfig = jspNewObject(NULL, "StationConfig"); - //char ssid[33]; - //nullTerminateString(ssid, (char *)config.ssid, 32); - jsvUnLock(jsvObjectSetChild(jsConfig, "ssid", jsvNewFromString((char *)config.ssid))); - //char password[65]; - //nullTerminateString(password, (char *)config.password, 64); - jsvUnLock(jsvObjectSetChild(jsConfig, "password", jsvNewFromString((char *)config.password))); - return jsConfig; -} - - -/** - * \brief Determine the list of connected stations and return them. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getConnectedStations", - "generate" : "jswrap_ESP8266WiFi_getConnectedStations", - "return" : ["JsVar","An array of connected stations."] -}*/ -JsVar *jswrap_ESP8266WiFi_getConnectedStations() { - uint8 stationCount = wifi_softap_get_station_num(); - struct station_info *stationInfo = wifi_softap_get_station_info(); - JsVar *jsArray = jsvNewArray(NULL, 0); - if (stationInfo != NULL) { - while (stationInfo != NULL) { - os_printf("Station IP: %d.%d.%d.%d\n", IP2STR(&(stationInfo->ip))); - JsVar *jsStation = jsvNewWithFlags(JSV_OBJECT); - jsvUnLock(jsvObjectSetChild(jsStation, "ip", jsvNewFromInteger(stationInfo->ip.addr))); - jsvArrayPush(jsArray, jsStation); - stationInfo = STAILQ_NEXT(stationInfo, next); - } - wifi_softap_free_station_info(); - } - return jsArray; -} - - -/** - * \brief Get the signal strength. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getRSSI", - "generate" : "jswrap_ESP8266WiFi_getRSSI", - "return" : ["JsVar","An integer representing the signal strength."] -}*/ -JsVar *jswrap_ESP8266WiFi_getRSSI() { - int rssi = wifi_station_get_rssi(); - return jsvNewFromInteger(rssi); -} - - - -/** - * \brief Initialize the ESP8266 environment. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "init", - "generate" : "jswrap_ESP8266WiFi_init" -}*/ -void jswrap_ESP8266WiFi_init() { - os_printf("> jswrap_ESP8266WiFi_init\n"); - // register the state change handler so we get debug printout for sure - wifi_set_phy_mode(2); - wifi_set_event_handler_cb(wifiEventHandler); - os_printf("Wifi init, mode=%d\n", wifi_get_opmode()); - wifi_station_set_hostname("espruino"); - - netInit_esp8266_board(); - setupJsNetwork(); - networkState = NETWORKSTATE_ONLINE; - os_printf("< jswrap_ESP8266WiFi_init\n"); -} - - -/** - * Return the ESP8266 connection status. - * This is an integer value defined as: - * - 0 - STATION_IDLE - * - 1 - STATION_CONNECTING - * - 2 - STATION_WRONG_PASSWORD - * - 3 - STATION_NO_AP_FOUND - * - 4 - STATION_CONNECT_FAIL - * - 5 - STATION_GOT_IP - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getConnectStatus", - "generate" : "jswrap_ESP8266WiFi_getConnectStatus", - "return" : ["JsVar","A connection status"] -} - -Retrieve the connection status. The return is an object that contains: - -* status - The status code from ESP8266 -* statusMsg - The description of the code - -*/ -JsVar *jswrap_ESP8266WiFi_getConnectStatus() { - // Ask ESP8266 for the connection status - uint8 status = wifi_station_get_connect_status(); - - // Create a JS variable to return - JsVar *var = jsvNewWithFlags(JSV_OBJECT); - - // Populate the return JS variable with a property called "status" - JsVar *jsStatus = jsvNewFromInteger(status); - //jsvUnLock(jsStatus); - jsvUnLock(jsvObjectSetChild(var, "status", jsStatus)); - - // Populate the return JS variable with a property called "statusMsg" - char *statusMsg; - switch(status) { - case STATION_IDLE: - statusMsg = "STATION_IDLE"; - break; - case STATION_CONNECTING: - statusMsg = "STATION_CONNECTING"; - break; - case STATION_WRONG_PASSWORD: - statusMsg = "STATION_WRONG_PASSWORD"; - break; - case STATION_NO_AP_FOUND: - statusMsg = "STATION_NO_AP_FOUND"; - break; - case STATION_CONNECT_FAIL: - statusMsg = "STATION_CONNECT_FAIL"; - break; - case STATION_GOT_IP: - statusMsg = "STATION_GOT_IP"; - break; - default: - statusMsg = "*** Unknown ***"; - } - JsVar *jsStatusMsg = jsvNewFromString(statusMsg); - //jsvUnLock(jsStatusMsg); - jsvUnLock(jsvObjectSetChild(var, "statusMsg", jsStatusMsg)); - //jsvUnLock(var); - return var; -} - - -/** - * Test: Perform a socket connection to a partner system. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "socketConnect", - "generate" : "jswrap_ESP8266WiFi_socketConnect", - "params" : [ - ["options","JsVar","Some kind of options."], - ["callback","JsVar","Some kind of callback."] - ], - "return" : ["JsVar","A connection object"] -}*/ -JsVar *jswrap_ESP8266WiFi_socketConnect( - JsVar *options, //!< Some kind of options. - JsVar *callback //!< Some kind of callback. - ) { - os_printf("Network state = %d\n", networkState); - JsVar *ret = jswrap_net_connect(options, callback, ST_NORMAL); - return ret; -} - - -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "socketEnd", - "generate" : "jswrap_ESP8266WiFi_socketEnd", - "params" : [ - ["socket","JsVar","The socket to be closed."], - ["data","JsVar","Optional data to be sent before close."] - ] -}*/ -void jswrap_ESP8266WiFi_socketEnd( - JsVar *socket, //!< The socket to be closed. - JsVar *data //!< Optional data to be sent before close. - ) { - jswrap_net_socket_end(socket, data); -} - - -/** - * \brief Perform a network ping request. - * The parameter can be either a String or a numeric IP address. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "ping", - "generate" : "jswrap_ESP8266WiFi_ping", - "params" : [ - ["ipAddr","JsVar","A string or integer representation of an IP address."], - ["pingCallback", "JsVar", "Optional callback function."] - ] -}*/ -void jswrap_ESP8266WiFi_ping( - JsVar *ipAddr, //!< A string or integer representation of an IP address. - JsVar *pingCallback //!< Optional callback function. - ) { - // 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 (jsPingCallback != NULL) { - jsvUnLock(jsPingCallback); - } - jsPingCallback = NULL; - } else if (!jsvIsFunction(pingCallback)) { - jsExceptionHere(JSET_ERROR, "Callback is not a function."); - return; - } else { - if (jsPingCallback != NULL) { - jsvUnLock(jsPingCallback); - } - jsPingCallback = pingCallback; - jsvLockAgainSafe(jsPingCallback); - } - - // We now have an IP address to ping ... so ping. - memset(&pingOpt, 0, sizeof(pingOpt)); - pingOpt.count = 5; - pingOpt.recv_function = pingRecvCB; - ping_start(&pingOpt); -} - - -/** - * \brief Dump the data in the socket. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "dumpSocket", - "generate" : "jswrap_ESP8266WiFi_dumpSocket", - "params" : [ - ["socketId","JsVar","The socket to be dumped."] - ] -}*/ - -void jswrap_ESP8266WiFi_dumpSocket( - JsVar *socketId //!< The socket to be dumped. - ) { - esp8266_dumpSocket(jsvGetInteger(socketId)-1); -} - -/** - * \brief Null terminate a string. - */ -static char *nullTerminateString(char *target, char *source, int sourceLength) { - os_strncpy(target, source, sourceLength); - target[sourceLength-1] = '\0'; - return target; -} - -/** - * - */ -static void setupJsNetwork() { - JsNetwork net; - networkCreate(&net, JSNETWORKTYPE_ESP8266_BOARD); - networkSet(&net); -} - - -/** - * \brief 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 (jsPingCallback != NULL) { - JsVar *jsPingResponse = jspNewObject(NULL, "PingResponse"); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalCount", jsvNewFromInteger(pingResp->total_count))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalBytes", jsvNewFromInteger(pingResp->total_bytes))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalTime", jsvNewFromInteger(pingResp->total_time))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "respTime", jsvNewFromInteger(pingResp->resp_time))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "seqNo", jsvNewFromInteger(pingResp->seqno))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "timeoutCount", jsvNewFromInteger(pingResp->timeout_count))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "bytes", jsvNewFromInteger(pingResp->bytes))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "error", jsvNewFromInteger(pingResp->ping_err))); - JsVar *params[1]; - params[0] = jsPingResponse; - jsiQueueEvents(NULL, jsPingCallback, params, 1); - } -} - - -/** - * \brief 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. - */ - - os_printf(">> scanCB\n"); - // Create the Empty JS array that will be passed as a parameter to the callback. - JsVar *accessPointArray = jsvNewArray(NULL, 0); - struct bss_info *bssInfo; - - bssInfo = (struct bss_info *)arg; - while(bssInfo != NULL) { - // Add a new object to the JS array that will be passed as a parameter to - // the callback. The ESP8266 bssInfo structure contains the following: - // --- - // uint8 bssid[6] - // uint8 ssid[32] - // uint8 channel - // sint8 rssi – The received signal strength indication - // AUTH_MODE authmode - // Open = 0 - // WEP = 1 - // WPA_PSK = 2 - // WPA2_PSK = 3 - // WPA_WPA2_PSK = 4 - // uint8 is_hidden - // sint16 freq_offset - // --- - // Create, populate and add a child ... - JsVar *currentAccessPoint = jspNewObject(NULL, "AccessPoint"); - jsvUnLock(jsvObjectSetChild(currentAccessPoint, "rssi", jsvNewFromInteger(bssInfo->rssi))); - jsvUnLock(jsvObjectSetChild(currentAccessPoint, "channel", jsvNewFromInteger(bssInfo->channel))); - jsvUnLock(jsvObjectSetChild(currentAccessPoint, "authMode", jsvNewFromInteger(bssInfo->authmode))); - jsvUnLock(jsvObjectSetChild(currentAccessPoint, "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'; - jsvUnLock(jsvObjectSetChild(currentAccessPoint, "ssid", jsvNewFromString(ssid))); - - // Add the new record to the array - jsvArrayPush(accessPointArray, currentAccessPoint); - - os_printf(" - ssid: %s\n", bssInfo->ssid); - bssInfo = STAILQ_NEXT(bssInfo, next); - } - - // We have now completed the scan callback, so now we can invoke the JS callback. - JsVar *params[1]; - params[0] = accessPointArray; - jsiQueueEvents(NULL, jsScanCallback, params, 1); - jsvUnLock(jsScanCallback); - os_printf("<< scanCB\n"); -} - - -/** - * \brief Invoke the JavaScript callback to notify the program that an ESP8266 - * WiFi event has occurred. - */ -static void sendWifiEvent(uint32 eventType, JsVar *details) { - // We need to check that we actually have an event callback handler because - // it might have been disabled/removed. - if (jsWiFiEventCallback != NULL) { - // Build a callback event. - JsVar *params[2]; - params[0] = jsvNewFromInteger(eventType); - params[1] = details; - jsiQueueEvents(NULL, jsWiFiEventCallback, params, 2); - } - - if (jsGotIpCallback != NULL && eventType == EVENT_STAMODE_GOT_IP) { - JsVar *params[2]; - params[0] = jsvNewFromInteger(eventType); - params[1] = details; - jsiQueueEvents(NULL, jsGotIpCallback, params, 2); - // Once we have registered the callback, we can unlock and release - // the variable as we are only calling it once. - //jsvUnLock(jsGotIpCallback); - //jsGotIpCallback = NULL; - } -} - - -/** - * \brief 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) { - switch(evt->event) { - // We have connected to an access point. - case EVENT_STAMODE_CONNECTED: - os_printf("Wifi connected to ssid %s, ch %d\n", evt->event_info.connected.ssid, - evt->event_info.connected.channel); - sendWifiEvent(evt->event, jsvNewNull()); - break; - - // We have disconnected or been disconnected from an access point. - case EVENT_STAMODE_DISCONNECTED: - os_printf("Wifi disconnected from ssid %s, reason %s (%d)\n", - evt->event_info.disconnected.ssid, wifiGetReason(), evt->event_info.disconnected.reason); - JsVar *details = jspNewObject(NULL, "EventDetails"); - jsvObjectSetChild(details, "reason", jsvNewFromInteger(evt->event_info.disconnected.reason)); - char ssid[33]; - memcpy(ssid, evt->event_info.disconnected.ssid, evt->event_info.disconnected.ssid_len); - ssid[ evt->event_info.disconnected.ssid_len] = '\0'; - sendWifiEvent(evt->event, details); - break; - - // The authentication information at the access point has changed. - case EVENT_STAMODE_AUTHMODE_CHANGE: - os_printf("Wifi auth mode: %d -> %d\n", - evt->event_info.auth_change.old_mode, evt->event_info.auth_change.new_mode); - sendWifiEvent(evt->event, jsvNewNull()); - break; - - // We have been allocated an IP address. - case EVENT_STAMODE_GOT_IP: - os_printf("Wifi 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)); - sendWifiEvent(evt->event, jsvNewNull()); - break; - case EVENT_STAMODE_DHCP_TIMEOUT: - os_printf("Wifi DHCP timeout"); - sendWifiEvent(evt->event, jsvNewNull()); - break; - case EVENT_SOFTAPMODE_STACONNECTED: - os_printf("Wifi AP: station " MACSTR " joined, AID = %d\n", - MAC2STR(evt->event_info.sta_connected.mac), evt->event_info.sta_connected.aid); - sendWifiEvent(evt->event, jsvNewNull()); - break; - case EVENT_SOFTAPMODE_STADISCONNECTED: - os_printf("Wifi AP: station " MACSTR " left, AID = %d\n", - MAC2STR(evt->event_info.sta_disconnected.mac), evt->event_info.sta_disconnected.aid); - sendWifiEvent(evt->event, jsvNewNull()); - break; - case EVENT_SOFTAPMODE_PROBEREQRECVED: - os_printf("Wifi AP: probe request from station " MACSTR ", rssi = %d\n", - MAC2STR(evt->event_info.ap_probereqrecved.mac), evt->event_info.ap_probereqrecved.rssi); - sendWifiEvent(evt->event, jsvNewNull()); - break; - default: - os_printf("Wifi: unexpected event %d\n", evt->event); - sendWifiEvent(evt->event, jsvNewNull()); - break; - } -} - -/** - * \brief Write an IP address as a dotted decimal string. - */ -// Note: This may be a duplicate ... it appears that we may have an existing function -// in network.c which does exactly this and more!! -// -static void ipAddrToString(struct ip_addr addr, char *string) { - os_sprintf(string, "%d.%d.%d.%d", ((char *)&addr)[0], ((char *)&addr)[1], ((char *)&addr)[2], ((char *)&addr)[3]); -} +// 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 + +#define ESP8266_ON_ACCESS_POINTS "#accessPoints" + +#define _GCC_WRAP_STDINT_H +typedef long long int64_t; + +#include "jswrap_esp8266.h" +#include "jsinteractive.h" // Pull inn the jsiConsolePrint function +#include "network.h" +#include "network_esp8266.h" +#include "jswrap_net.h" + +// Forward declaration of functions. +static void scanCB(void *arg, STATUS status); +static void wifiEventHandler(System_Event_t *event); +static void ipAddrToString(struct ip_addr addr, char *string); +static char *nullTerminateString(char *target, char *source, int sourceLength); +static void setupJsNetwork(); +static void pingRecvCB(); +static char *wifiConnectStatusToString(uint8 status); + +static JsVar *jsScanCallback; +static JsVar *jsWiFiEventCallback; + +// A callback function to be invoked when we have an IP address. +static JsVar *jsGotIpCallback; + +static JsVar *jsPingCallback; + +// Global data structure for ping request +static struct ping_option pingOpt; + +// Reasons for which a connection failed +uint8_t wifiReason = 0; +static char *wifiReasons[] = { + "", "unspecified", "auth_expire", "auth_leave", "assoc_expire", "assoc_toomany", "not_authed", + "not_assoced", "assoc_leave", "assoc_not_authed", "disassoc_pwrcap_bad", "disassoc_supchan_bad", + "ie_invalid", "mic_failure", "4way_handshake_timeout", "group_key_update_timeout", + "ie_in_4way_differs", "group_cipher_invalid", "pairwise_cipher_invalid", "akmp_invalid", + "unsupp_rsn_ie_version", "invalid_rsn_ie_cap", "802_1x_auth_failed", "cipher_suite_rejected", + "beacon_timeout", "no_ap_found" }; + +static char *wifiGetReason(void) { + if (wifiReason <= 24) return wifiReasons[wifiReason]; + if (wifiReason >= 200 && wifiReason <= 201) return wifiReasons[wifiReason-200+24]; + return wifiReasons[1]; +} + +static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" }; +static char *wifiPhy[] = { 0, "11b", "11g", "11n" }; + +// Let's define the JavaScript class that will contain our `world()` method. We'll call it `Hello` +/*JSON{ + "type" : "class", + "class" : "ESP8266WiFi" +}*/ + + +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "connect", + "generate" : "jswrap_ESP8266WiFi_connect", + "params" : [ + ["ssid","JsVar","The network id of the access point."], + ["password","JsVar","The password to the access point"], + ["gotIpCallback", "JsVar", "An optional callback invoked when we have an IP"] + ] +} + * + * Connect the station to an access point. + */ +void jswrap_ESP8266WiFi_connect( + JsVar *jsv_ssid, //!< The SSID of the access point to connect. + JsVar *jsv_password, //!< The password for the access point. + JsVar *gotIpCallback //!< The Callback function to be called when we are connected. + ) { + os_printf("> jswrap_ESP8266WiFi_connect\n"); + + // Check that the ssid and password values aren't obviously in error. + if (jsv_ssid == NULL || !jsvIsString(jsv_ssid)) { + jsExceptionHere(JSET_ERROR, "No SSID."); + return; + } + if (jsv_password == NULL || !jsvIsString(jsv_password)) { + jsExceptionHere(JSET_ERROR, "No password."); + return; + } + + // Check that if a callback function was supplied that we actually have a callback function. + if (gotIpCallback != NULL && !jsvIsUndefined(gotIpCallback) && !jsvIsFunction(gotIpCallback)) { + gotIpCallback = NULL; + jsExceptionHere(JSET_ERROR, "A callback function was supplied that is not a function."); + return; + } + if (jsvIsUndefined(gotIpCallback) || jsvIsNull(gotIpCallback)) { + gotIpCallback = NULL; + } + + // Set the global which is the gotIP callback + if (jsGotIpCallback != NULL) { + jsvUnLock(jsGotIpCallback); + jsGotIpCallback = NULL; + } + + // What does this do? + if (gotIpCallback != NULL) { + jsGotIpCallback = jsvLockAgainSafe(gotIpCallback); + } + + // Debug + // os_printf("jsGotIpCallback=%p\n", jsGotIpCallback); + + // Create strings from the JsVars for the ESP8266 API calls. + char ssid[33]; + int len = jsvGetString(jsv_ssid, ssid, sizeof(ssid)-1); + ssid[len]='\0'; + char password[65]; + len = jsvGetString(jsv_password, password, sizeof(password)-1); + password[len]='\0'; + + os_printf("> - ssid=%s, password=%s\n", ssid, password); + + // Set the WiFi mode of the ESP8266 + wifi_set_opmode_current(STATION_MODE); + + struct station_config stationConfig; + memset(&stationConfig, 0, sizeof(stationConfig)); + os_strncpy((char *)stationConfig.ssid, ssid, 32); + if (password != NULL) { + os_strncpy((char *)stationConfig.password, password, 64); + } else { + os_strcpy((char *)stationConfig.password, ""); + } + + // Set the WiFi configuration + wifi_station_set_config(&stationConfig); + + uint8 wifiConnectStatus = wifi_station_get_connect_status(); + os_printf(" - Current connect status: %s\n", wifiConnectStatusToString(wifiConnectStatus)); + + if (wifiConnectStatus == STATION_GOT_IP) { + // See issue #618. There are currently three schools of thought on what should happen + // when a connect is issued and we are already connected. + // + // Option #1 - Always perform a disconnect. + // Option #2 - Perform a disconnect if the SSID or PASSWORD are different from current + // Option #3 - Fail the connect and invoke the callback telling the user that we are already connected. + // +#define ISSUE_618 1 + +#if ISSUE_618 == 1 + wifi_station_disconnect(); +#elif ISSUE_618 == 2 + struct station_config existingConfig; + wifi_station_get_config(&existingConfig); + if (os_strncpy((char *)existingConfig.ssid, (char *)stationConfig.ssid, 32) != 0 || + os_strncpy((char *)existingConfig.password, (char *)stationConfig.password, 64) != 0) { + wifi_station_disconnect(); + } +#elif ISSUE_618 == 3 + if (jsGotIpCallback != NULL) { + JsVar *params[2]; + params[0] = jsvNewFromInteger(STATION_GOT_IP); + params[1] = jsvNewNull(); + jsiQueueEvents(NULL, jsGotIpCallback, params, 2); + } +#endif + } + // Perform the network level connection + wifi_station_connect(); + os_printf("< jswrap_ESP8266WiFi_connect\n"); +} + + +/** + * \brief Become an access point. + * When we call this function we are instructing the ESP8266 to set itself up as an + * access point to allow other WiFi stations to connect to it. In order to be an access + * point, the ESP8266 needs to know the SSID it should use as well as the password used + * to allow clients to connect. + * + * Parameters: + * - `jsv_ssid` - The network identity that the access point will advertize itself as. + * - `jsv_password` - The password a station will need to connect to the + * access point. + * + * Notes: + * - How about if the password is not supplied, NULL or empty then we set ourselves + * up using an Open authentication mechanism? + * - Add support for hidden SSIDs. + * + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "beAccessPoint", + "generate" : "jswrap_ESP8266WiFi_beAccessPoint", + "params" : [ + ["jsv_ssid","JsVar","The network SSID"], + ["jsv_password","JsVar","The password to allow stations to connect to the access point"] + ] +}*/ +void jswrap_ESP8266WiFi_beAccessPoint( + JsVar *jsv_ssid, //!< The network identity that the access point will advertize itself as. + JsVar *jsv_password //!< The password a station will need to connect to the access point. + ) { + // Validate that the SSID and password are somewhat useful. + if (jsv_ssid == NULL || !jsvIsString(jsv_ssid)) { + jsExceptionHere(JSET_ERROR, "No SSID."); + return; + } + if (jsv_password == NULL || !jsvIsString(jsv_password)) { + jsExceptionHere(JSET_ERROR, "No password."); + return; + } + + // Create strings from the JsVars for the ESP8266 API calls. + char ssid[33]; + int len = jsvGetString(jsv_ssid, ssid, sizeof(ssid)-1); + ssid[len]='\0'; + char password[65]; + len = jsvGetString(jsv_password, password, sizeof(password)-1); + password[len]='\0'; + + // Define that we are in Soft AP mode. + os_printf("Wifi: switching to soft-AP\n"); + wifi_set_opmode_current(SOFTAP_MODE); + + // Build our SoftAP configuration details + struct softap_config softApConfig; + memset(&softApConfig, 0, sizeof(softApConfig)); + + os_strcpy((char *)softApConfig.ssid, ssid); + os_strcpy((char *)softApConfig.password, password); + softApConfig.ssid_len = 0; // Null terminated SSID + softApConfig.authmode = AUTH_WPA2_PSK; + softApConfig.ssid_hidden = 0; // Not hidden. + softApConfig.max_connection = 4; // Maximum number of connections. + + // Set the WiFi configuration. + int rc = wifi_softap_set_config_current(&softApConfig); + if (rc != 1) { + jsExceptionHere(JSET_ERROR, "Error setting ESP8266 softap config."); + } +} + + +/** + * \brief Determine the list of access points available to us. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getAccessPoints", + "generate" : "jswrap_ESP8266WiFi_getAccessPoints", + "params" : [ + ["callback","JsVar","Function to call back when access points retrieved."] + ] +}*/ +void jswrap_ESP8266WiFi_getAccessPoints( + JsVar *callback //!< Function to call back when access points retrieved. + ) { + os_printf("> ESP8266WiFi_getAccessPoints\n"); + if (callback == NULL || !jsvIsFunction(callback)) { + jsExceptionHere(JSET_ERROR, "No callback."); + return; + } + + // Save the callback for the scan in the global variable called jsScanCallback. + jsScanCallback = jsvLockAgainSafe(callback); + + // 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. + + // Ensure we are in station mode + wifi_set_opmode_current(STATION_MODE); + + // Request a scan of the network calling "scanCB" on completion + wifi_station_scan(NULL, scanCB); + + os_printf("< ESP8266WiFi_getAccessPoints\n"); +} + + +/** + * \brief Disconnect the station from the access point. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "disconnect", + "generate" : "jswrap_ESP8266WiFi_disconnect" +}*/ +void jswrap_ESP8266WiFi_disconnect() { + wifi_station_disconnect(); +} + + +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "restart", + "generate" : "jswrap_ESP8266WiFi_restart" +}*/ +void jswrap_ESP8266WiFi_restart() { + system_restart(); +} + + +/** + * \brief Register a callback function that will be invoked when a WiFi event is detected. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "onWiFiEvent", + "generate" : "jswrap_ESP8266WiFi_onWiFiEvent", + "params" : [ + ["callback","JsVar","WiFi event callback"] + ] +}*/ +void jswrap_ESP8266WiFi_onWiFiEvent( + JsVar *callback //!< WiFi event callback. + ) { + // If the callback is null + if (callback == NULL || jsvIsNull(callback)) { + if (jsWiFiEventCallback != NULL) { + jsvUnLock(jsWiFiEventCallback); + } + jsWiFiEventCallback = NULL; + return; + } + + if (!jsvIsFunction(callback)) { + jsExceptionHere(JSET_ERROR, "No callback."); + return; + } + + // We are about to save a new global WiFi even callback handler. If we have previously + // had one, we need to unlock it so that we don't leak memory. + if (jsWiFiEventCallback != NULL) { + jsvUnLock(jsWiFiEventCallback); + } + + // Save the global WiFi event callback handler. + jsWiFiEventCallback = jsvLockAgainSafe(callback); +} + + +/** + * \brief Set whether or not the ESP8266 will perform an auto connect on startup. + * A value of true means it will while a value of false means it won't. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "setAutoConnect", + "generate" : "jswrap_ESP8266WiFi_setAutoConnect", + "params" : [ + ["autoconnect","JsVar","True if we wish to auto connect."] + ] +}*/ +void jswrap_ESP8266WiFi_setAutoConnect( + JsVar *autoconnect //!< True if we wish to auto connect. + ) { + os_printf("Auto connect is: %d\n", (int)autoconnect); + // Check that we have been passed a boolean ... if not, nothing to do here. + if (!jsvIsBoolean(autoconnect)) { + return; + } + + uint8 newValue = jsvGetBool(autoconnect); + os_printf("New value: %d\n", newValue); + + uart_rx_intr_disable(0); + wifi_station_set_auto_connect(newValue); + uart_rx_intr_disable(1); + os_printf("Autoconnect changed\n"); +} + + +/** + * \brief Retrieve whether or not the ESP8266 will perform an auto connect on startup. + * A value of 1 means it will while a value of zero means it won't. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getAutoConnect", + "generate" : "jswrap_ESP8266WiFi_getAutoConnect", + "return" : ["JsVar","A boolean representing our auto connect status"] +}*/ +JsVar *jswrap_ESP8266WiFi_getAutoConnect() { + uint8 result = wifi_station_get_auto_connect(); + return jsvNewFromBool(result); +} + + +/** + * \brief Retrieve the reset information that is stored when event the ESP8266 resets. + * The result will be a JS object containing the details. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getRstInfo", + "generate" : "jswrap_ESP8266WiFi_getRstInfo", + "return" : ["JsVar","A Restart Object"], + "return_object" : "Restart" +}*/ +JsVar *jswrap_ESP8266WiFi_getRstInfo() { + struct rst_info* info = system_get_rst_info(); + JsVar *restartInfo = jspNewObject(NULL, "Restart"); + jsvUnLock(jsvObjectSetChild(restartInfo, "reason", jsvNewFromInteger(info->reason))); + jsvUnLock(jsvObjectSetChild(restartInfo, "exccause", jsvNewFromInteger(info->exccause))); + jsvUnLock(jsvObjectSetChild(restartInfo, "epc1", jsvNewFromInteger(info->epc1))); + jsvUnLock(jsvObjectSetChild(restartInfo, "epc2", jsvNewFromInteger(info->epc2))); + jsvUnLock(jsvObjectSetChild(restartInfo, "epc3", jsvNewFromInteger(info->epc3))); + jsvUnLock(jsvObjectSetChild(restartInfo, "excvaddr", jsvNewFromInteger(info->excvaddr))); + jsvUnLock(jsvObjectSetChild(restartInfo, "depc", jsvNewFromInteger(info->depc))); + return restartInfo; +} + + +/** + * \brief Return an object that contains details about the state of the ESP8266. + * - `sdkVersion` - Version of the SDK. + * - `cpuFrequency` - CPU operating frequency. + * - `freeHeap` - Amount of free heap. + * - `maxCon` - Maximum number of concurrent connections. + * + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getState", + "generate" : "jswrap_ESP8266WiFi_getState", + "return" : ["JsVar","The state of the ESP8266"], + "return_object" : "ESP8266State" +}*/ +JsVar *jswrap_ESP8266WiFi_getState() { + // Create a new variable and populate it with the properties of the ESP8266 that we + // wish to return. + JsVar *esp8266State = jspNewObject(NULL, "ESP8266State"); + jsvUnLock(jsvObjectSetChild(esp8266State, "sdkVersion", jsvNewFromString(system_get_sdk_version()))); + jsvUnLock(jsvObjectSetChild(esp8266State, "cpuFrequency", jsvNewFromInteger(system_get_cpu_freq()))); + jsvUnLock(jsvObjectSetChild(esp8266State, "freeHeap", jsvNewFromInteger(system_get_free_heap_size()))); + jsvUnLock(jsvObjectSetChild(esp8266State, "maxCon", jsvNewFromInteger(espconn_tcp_get_max_con()))); + return esp8266State; +} + +/** + * \brief Return the value of an integer representation (4 bytes) of IP address + * as a string. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getAddressAsString", + "generate" : "jswrap_ESP8266WiFi_getAddressAsString", + "params" : [ + ["address","JsVar","An integer value representing an IP address."] + ], + "return" : ["JsVar","A String"] +}*/ +JsVar *jswrap_ESP8266WiFi_getAddressAsString( + JsVar *address //!< An integer value representing an IP address. + ) { + if (!jsvIsInt(address)) { + jsExceptionHere(JSET_ERROR, "No SSID."); + return NULL; + } + uint32 iAddress = (uint32)jsvGetInteger(address); + return networkGetAddressAsString((uint8 *)&iAddress, 4, 10, '.'); +} + + +/** + * \brief Retrieve the IP information about this network interface and return a JS + * object that contains its details. + * The object will have the following properties defined upon it: + * - `ip` - The IP address of the interface. + * - `netmask` - The netmask of the interface. + * - `gw` - The gateway to reach when transmitting through the interface. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getIPInfo", + "generate" : "jswrap_ESP8266WiFi_getIPInfo", + "return" : ["JsVar","A IPInfo Object"], + "return_object" : "IPInfo" +}*/ +JsVar *jswrap_ESP8266WiFi_getIPInfo() { + struct ip_info info; + wifi_get_ip_info(0, &info); + + JsVar *ipInfo = jspNewObject(NULL, "Restart"); + jsvUnLock(jsvObjectSetChild(ipInfo, "ip", jsvNewFromInteger(info.ip.addr))); + jsvUnLock(jsvObjectSetChild(ipInfo, "netmask", jsvNewFromInteger(info.netmask.addr))); + jsvUnLock(jsvObjectSetChild(ipInfo, "gw", jsvNewFromInteger(info.gw.addr))); + return ipInfo; +} + + +/** + * \brief Query the station configuration and return a JS object that represents the + * current settings. + * The object will have the following properties: + * + * - `ssid` - The network identity of the access point + * - `password` - The password to use to connect to the access point + * + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getStationConfig", + "generate" : "jswrap_ESP8266WiFi_getStationConfig", + "return" : ["JsVar","A Station Config"], + "return_object" : "StationConfig" +}*/ +JsVar *jswrap_ESP8266WiFi_getStationConfig() { + struct station_config config; + wifi_station_get_config(&config); + JsVar *jsConfig = jspNewObject(NULL, "StationConfig"); + //char ssid[33]; + //nullTerminateString(ssid, (char *)config.ssid, 32); + jsvUnLock(jsvObjectSetChild(jsConfig, "ssid", jsvNewFromString((char *)config.ssid))); + //char password[65]; + //nullTerminateString(password, (char *)config.password, 64); + jsvUnLock(jsvObjectSetChild(jsConfig, "password", jsvNewFromString((char *)config.password))); + return jsConfig; +} + + +/** + * \brief Determine the list of connected stations and return them. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getConnectedStations", + "generate" : "jswrap_ESP8266WiFi_getConnectedStations", + "return" : ["JsVar","An array of connected stations."] +}*/ +JsVar *jswrap_ESP8266WiFi_getConnectedStations() { + uint8 stationCount = wifi_softap_get_station_num(); + struct station_info *stationInfo = wifi_softap_get_station_info(); + JsVar *jsArray = jsvNewArray(NULL, 0); + if (stationInfo != NULL) { + while (stationInfo != NULL) { + os_printf("Station IP: %d.%d.%d.%d\n", IP2STR(&(stationInfo->ip))); + JsVar *jsStation = jsvNewWithFlags(JSV_OBJECT); + jsvUnLock(jsvObjectSetChild(jsStation, "ip", jsvNewFromInteger(stationInfo->ip.addr))); + jsvArrayPush(jsArray, jsStation); + stationInfo = STAILQ_NEXT(stationInfo, next); + } + wifi_softap_free_station_info(); + } + return jsArray; +} + + +/** + * \brief Get the signal strength. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getRSSI", + "generate" : "jswrap_ESP8266WiFi_getRSSI", + "return" : ["JsVar","An integer representing the signal strength."] +}*/ +JsVar *jswrap_ESP8266WiFi_getRSSI() { + int rssi = wifi_station_get_rssi(); + return jsvNewFromInteger(rssi); +} + + + +/** + * \brief Initialize the ESP8266 environment. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "init", + "generate" : "jswrap_ESP8266WiFi_init" +}*/ +void jswrap_ESP8266WiFi_init() { + os_printf("> jswrap_ESP8266WiFi_init\n"); + // register the state change handler so we get debug printout for sure + wifi_set_phy_mode(2); + wifi_set_event_handler_cb(wifiEventHandler); + os_printf("Wifi init, mode=%d\n", wifi_get_opmode()); + wifi_station_set_hostname("espruino"); + + netInit_esp8266_board(); + setupJsNetwork(); + networkState = NETWORKSTATE_ONLINE; + os_printf("< jswrap_ESP8266WiFi_init\n"); +} + + +/** + * Return the ESP8266 connection status. + * This is an integer value defined as: + * - 0 - STATION_IDLE + * - 1 - STATION_CONNECTING + * - 2 - STATION_WRONG_PASSWORD + * - 3 - STATION_NO_AP_FOUND + * - 4 - STATION_CONNECT_FAIL + * - 5 - STATION_GOT_IP + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getConnectStatus", + "generate" : "jswrap_ESP8266WiFi_getConnectStatus", + "return" : ["JsVar","A connection status"] +} + +Retrieve the connection status. The return is an object that contains: + +* status - The status code from ESP8266 +* statusMsg - The description of the code + +*/ +JsVar *jswrap_ESP8266WiFi_getConnectStatus() { + // Ask ESP8266 for the connection status + uint8 status = wifi_station_get_connect_status(); + + // Create a JS variable to return + JsVar *var = jsvNewWithFlags(JSV_OBJECT); + + // Populate the return JS variable with a property called "status" + JsVar *jsStatus = jsvNewFromInteger(status); + //jsvUnLock(jsStatus); + jsvUnLock(jsvObjectSetChild(var, "status", jsStatus)); + + // Populate the return JS variable with a property called "statusMsg" + char *statusMsg; + switch(status) { + case STATION_IDLE: + statusMsg = "STATION_IDLE"; + break; + case STATION_CONNECTING: + statusMsg = "STATION_CONNECTING"; + break; + case STATION_WRONG_PASSWORD: + statusMsg = "STATION_WRONG_PASSWORD"; + break; + case STATION_NO_AP_FOUND: + statusMsg = "STATION_NO_AP_FOUND"; + break; + case STATION_CONNECT_FAIL: + statusMsg = "STATION_CONNECT_FAIL"; + break; + case STATION_GOT_IP: + statusMsg = "STATION_GOT_IP"; + break; + default: + statusMsg = "*** Unknown ***"; + } + JsVar *jsStatusMsg = jsvNewFromString(statusMsg); + //jsvUnLock(jsStatusMsg); + jsvUnLock(jsvObjectSetChild(var, "statusMsg", jsStatusMsg)); + //jsvUnLock(var); + return var; +} + + +/** + * Test: Perform a socket connection to a partner system. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "socketConnect", + "generate" : "jswrap_ESP8266WiFi_socketConnect", + "params" : [ + ["options","JsVar","Some kind of options."], + ["callback","JsVar","Some kind of callback."] + ], + "return" : ["JsVar","A connection object"] +}*/ +JsVar *jswrap_ESP8266WiFi_socketConnect( + JsVar *options, //!< Some kind of options. + JsVar *callback //!< Some kind of callback. + ) { + os_printf("Network state = %d\n", networkState); + JsVar *ret = jswrap_net_connect(options, callback, ST_NORMAL); + return ret; +} + + +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "socketEnd", + "generate" : "jswrap_ESP8266WiFi_socketEnd", + "params" : [ + ["socket","JsVar","The socket to be closed."], + ["data","JsVar","Optional data to be sent before close."] + ] +}*/ +void jswrap_ESP8266WiFi_socketEnd( + JsVar *socket, //!< The socket to be closed. + JsVar *data //!< Optional data to be sent before close. + ) { + jswrap_net_socket_end(socket, data); +} + + +/** + * \brief Perform a network ping request. + * The parameter can be either a String or a numeric IP address. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "ping", + "generate" : "jswrap_ESP8266WiFi_ping", + "params" : [ + ["ipAddr","JsVar","A string or integer representation of an IP address."], + ["pingCallback", "JsVar", "Optional callback function."] + ] +}*/ +void jswrap_ESP8266WiFi_ping( + JsVar *ipAddr, //!< A string or integer representation of an IP address. + JsVar *pingCallback //!< Optional callback function. + ) { + // 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 (jsPingCallback != NULL) { + jsvUnLock(jsPingCallback); + } + jsPingCallback = NULL; + } else if (!jsvIsFunction(pingCallback)) { + jsExceptionHere(JSET_ERROR, "Callback is not a function."); + return; + } else { + if (jsPingCallback != NULL) { + jsvUnLock(jsPingCallback); + } + jsPingCallback = pingCallback; + jsvLockAgainSafe(jsPingCallback); + } + + // We now have an IP address to ping ... so ping. + memset(&pingOpt, 0, sizeof(pingOpt)); + pingOpt.count = 5; + pingOpt.recv_function = pingRecvCB; + ping_start(&pingOpt); +} + + +/** + * \brief Dump the data in the socket. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "dumpSocket", + "generate" : "jswrap_ESP8266WiFi_dumpSocket", + "params" : [ + ["socketId","JsVar","The socket to be dumped."] + ] +}*/ + +void jswrap_ESP8266WiFi_dumpSocket( + JsVar *socketId //!< The socket to be dumped. + ) { + esp8266_dumpSocket(jsvGetInteger(socketId)-1); +} + +/** + * \brief Null terminate a string. + */ +static char *nullTerminateString(char *target, char *source, int sourceLength) { + os_strncpy(target, source, sourceLength); + target[sourceLength-1] = '\0'; + return target; +} + +/** + * + */ +static void setupJsNetwork() { + JsNetwork net; + networkCreate(&net, JSNETWORKTYPE_ESP8266_BOARD); + networkSet(&net); +} + + +/** + * \brief 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 (jsPingCallback != NULL) { + JsVar *jsPingResponse = jspNewObject(NULL, "PingResponse"); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalCount", jsvNewFromInteger(pingResp->total_count))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalBytes", jsvNewFromInteger(pingResp->total_bytes))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalTime", jsvNewFromInteger(pingResp->total_time))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "respTime", jsvNewFromInteger(pingResp->resp_time))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "seqNo", jsvNewFromInteger(pingResp->seqno))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "timeoutCount", jsvNewFromInteger(pingResp->timeout_count))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "bytes", jsvNewFromInteger(pingResp->bytes))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "error", jsvNewFromInteger(pingResp->ping_err))); + JsVar *params[1]; + params[0] = jsPingResponse; + jsiQueueEvents(NULL, jsPingCallback, params, 1); + } +} + + +/** + * \brief 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. + */ + + os_printf(">> scanCB\n"); + // Create the Empty JS array that will be passed as a parameter to the callback. + JsVar *accessPointArray = jsvNewArray(NULL, 0); + struct bss_info *bssInfo; + + bssInfo = (struct bss_info *)arg; + while(bssInfo != NULL) { + // Add a new object to the JS array that will be passed as a parameter to + // the callback. The ESP8266 bssInfo structure contains the following: + // --- + // uint8 bssid[6] + // uint8 ssid[32] + // uint8 channel + // sint8 rssi – The received signal strength indication + // AUTH_MODE authmode + // Open = 0 + // WEP = 1 + // WPA_PSK = 2 + // WPA2_PSK = 3 + // WPA_WPA2_PSK = 4 + // uint8 is_hidden + // sint16 freq_offset + // --- + // Create, populate and add a child ... + JsVar *currentAccessPoint = jspNewObject(NULL, "AccessPoint"); + jsvUnLock(jsvObjectSetChild(currentAccessPoint, "rssi", jsvNewFromInteger(bssInfo->rssi))); + jsvUnLock(jsvObjectSetChild(currentAccessPoint, "channel", jsvNewFromInteger(bssInfo->channel))); + jsvUnLock(jsvObjectSetChild(currentAccessPoint, "authMode", jsvNewFromInteger(bssInfo->authmode))); + jsvUnLock(jsvObjectSetChild(currentAccessPoint, "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'; + jsvUnLock(jsvObjectSetChild(currentAccessPoint, "ssid", jsvNewFromString(ssid))); + + // Add the new record to the array + jsvArrayPush(accessPointArray, currentAccessPoint); + + os_printf(" - ssid: %s\n", bssInfo->ssid); + bssInfo = STAILQ_NEXT(bssInfo, next); + } + + // We have now completed the scan callback, so now we can invoke the JS callback. + JsVar *params[1]; + params[0] = accessPointArray; + jsiQueueEvents(NULL, jsScanCallback, params, 1); + jsvUnLock(jsScanCallback); + os_printf("<< scanCB\n"); +} + + +/** + * \brief Invoke the JavaScript callback to notify the program that an ESP8266 + * WiFi event has occurred. + */ +static void sendWifiEvent(uint32 eventType, JsVar *details) { + // We need to check that we actually have an event callback handler because + // it might have been disabled/removed. + if (jsWiFiEventCallback != NULL) { + // Build a callback event. + JsVar *params[2]; + params[0] = jsvNewFromInteger(eventType); + params[1] = details; + jsiQueueEvents(NULL, jsWiFiEventCallback, params, 2); + } + + if (jsGotIpCallback != NULL && eventType == EVENT_STAMODE_GOT_IP) { + JsVar *params[2]; + params[0] = jsvNewFromInteger(eventType); + params[1] = details; + jsiQueueEvents(NULL, jsGotIpCallback, params, 2); + // Once we have registered the callback, we can unlock and release + // the variable as we are only calling it once. + //jsvUnLock(jsGotIpCallback); + //jsGotIpCallback = NULL; + } +} + + +/** + * \brief 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) { + switch(evt->event) { + // We have connected to an access point. + case EVENT_STAMODE_CONNECTED: + os_printf("Wifi connected to ssid %s, ch %d\n", evt->event_info.connected.ssid, + evt->event_info.connected.channel); + sendWifiEvent(evt->event, jsvNewNull()); + break; + + // We have disconnected or been disconnected from an access point. + case EVENT_STAMODE_DISCONNECTED: + os_printf("Wifi disconnected from ssid %s, reason %s (%d)\n", + evt->event_info.disconnected.ssid, wifiGetReason(), evt->event_info.disconnected.reason); + JsVar *details = jspNewObject(NULL, "EventDetails"); + jsvObjectSetChild(details, "reason", jsvNewFromInteger(evt->event_info.disconnected.reason)); + char ssid[33]; + memcpy(ssid, evt->event_info.disconnected.ssid, evt->event_info.disconnected.ssid_len); + ssid[ evt->event_info.disconnected.ssid_len] = '\0'; + sendWifiEvent(evt->event, details); + break; + + // The authentication information at the access point has changed. + case EVENT_STAMODE_AUTHMODE_CHANGE: + os_printf("Wifi auth mode: %d -> %d\n", + evt->event_info.auth_change.old_mode, evt->event_info.auth_change.new_mode); + sendWifiEvent(evt->event, jsvNewNull()); + break; + + // We have been allocated an IP address. + case EVENT_STAMODE_GOT_IP: + os_printf("Wifi 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)); + sendWifiEvent(evt->event, jsvNewNull()); + break; + case EVENT_STAMODE_DHCP_TIMEOUT: + os_printf("Wifi DHCP timeout"); + sendWifiEvent(evt->event, jsvNewNull()); + break; + case EVENT_SOFTAPMODE_STACONNECTED: + os_printf("Wifi AP: station " MACSTR " joined, AID = %d\n", + MAC2STR(evt->event_info.sta_connected.mac), evt->event_info.sta_connected.aid); + sendWifiEvent(evt->event, jsvNewNull()); + break; + case EVENT_SOFTAPMODE_STADISCONNECTED: + os_printf("Wifi AP: station " MACSTR " left, AID = %d\n", + MAC2STR(evt->event_info.sta_disconnected.mac), evt->event_info.sta_disconnected.aid); + sendWifiEvent(evt->event, jsvNewNull()); + break; + case EVENT_SOFTAPMODE_PROBEREQRECVED: + os_printf("Wifi AP: probe request from station " MACSTR ", rssi = %d\n", + MAC2STR(evt->event_info.ap_probereqrecved.mac), evt->event_info.ap_probereqrecved.rssi); + sendWifiEvent(evt->event, jsvNewNull()); + break; + default: + os_printf("Wifi: unexpected event %d\n", evt->event); + sendWifiEvent(evt->event, jsvNewNull()); + break; + } +} + +/** + * \brief Write an IP address as a dotted decimal string. + */ +// Note: This may be a duplicate ... it appears that we may have an existing function +// in network.c which does exactly this and more!! +// +static void ipAddrToString(struct ip_addr addr, char *string) { + os_sprintf(string, "%d.%d.%d.%d", ((char *)&addr)[0], ((char *)&addr)[1], ((char *)&addr)[2], ((char *)&addr)[3]); +} + +/** + * Convert an ESP8266 WiFi connect status to a string. + * + * Convert the status (as returned by `wifi_station_get_connect_status()`) to a string + * representation. + * \return A string representation of a WiFi connect status. + */ +static char *wifiConnectStatusToString(uint8 status) { + switch(status) { + case STATION_IDLE: + return "STATION_IDLE"; + case STATION_CONNECTING: + return "STATION_CONNECTING"; + case STATION_WRONG_PASSWORD: + return "STATION_WRONG_PASSWORD"; + case STATION_NO_AP_FOUND: + return "STATION_NO_AP_FOUND"; + case STATION_CONNECT_FAIL: + return "STATION_CONNECT_FAIL"; + case STATION_GOT_IP: + return "STATION_GOT_IP"; + default: + return "Unknown connect status!!"; + } +} From cf969a4136b6be5705db243c397a462be9ec9119 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 5 Oct 2015 20:31:00 -0500 Subject: [PATCH 03/10] Converted source to Unix style end of line markers. --- libs/network/esp8266/jswrap_esp8266.c | 2124 ++++++++++++------------- 1 file changed, 1062 insertions(+), 1062 deletions(-) diff --git a/libs/network/esp8266/jswrap_esp8266.c b/libs/network/esp8266/jswrap_esp8266.c index 89e0bf6eb..45764ef35 100644 --- a/libs/network/esp8266/jswrap_esp8266.c +++ b/libs/network/esp8266/jswrap_esp8266.c @@ -1,1062 +1,1062 @@ -// 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 - -#define ESP8266_ON_ACCESS_POINTS "#accessPoints" - -#define _GCC_WRAP_STDINT_H -typedef long long int64_t; - -#include "jswrap_esp8266.h" -#include "jsinteractive.h" // Pull inn the jsiConsolePrint function -#include "network.h" -#include "network_esp8266.h" -#include "jswrap_net.h" - -// Forward declaration of functions. -static void scanCB(void *arg, STATUS status); -static void wifiEventHandler(System_Event_t *event); -static void ipAddrToString(struct ip_addr addr, char *string); -static char *nullTerminateString(char *target, char *source, int sourceLength); -static void setupJsNetwork(); -static void pingRecvCB(); -static char *wifiConnectStatusToString(uint8 status); - -static JsVar *jsScanCallback; -static JsVar *jsWiFiEventCallback; - -// A callback function to be invoked when we have an IP address. -static JsVar *jsGotIpCallback; - -static JsVar *jsPingCallback; - -// Global data structure for ping request -static struct ping_option pingOpt; - -// Reasons for which a connection failed -uint8_t wifiReason = 0; -static char *wifiReasons[] = { - "", "unspecified", "auth_expire", "auth_leave", "assoc_expire", "assoc_toomany", "not_authed", - "not_assoced", "assoc_leave", "assoc_not_authed", "disassoc_pwrcap_bad", "disassoc_supchan_bad", - "ie_invalid", "mic_failure", "4way_handshake_timeout", "group_key_update_timeout", - "ie_in_4way_differs", "group_cipher_invalid", "pairwise_cipher_invalid", "akmp_invalid", - "unsupp_rsn_ie_version", "invalid_rsn_ie_cap", "802_1x_auth_failed", "cipher_suite_rejected", - "beacon_timeout", "no_ap_found" }; - -static char *wifiGetReason(void) { - if (wifiReason <= 24) return wifiReasons[wifiReason]; - if (wifiReason >= 200 && wifiReason <= 201) return wifiReasons[wifiReason-200+24]; - return wifiReasons[1]; -} - -static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" }; -static char *wifiPhy[] = { 0, "11b", "11g", "11n" }; - -// Let's define the JavaScript class that will contain our `world()` method. We'll call it `Hello` -/*JSON{ - "type" : "class", - "class" : "ESP8266WiFi" -}*/ - - -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "connect", - "generate" : "jswrap_ESP8266WiFi_connect", - "params" : [ - ["ssid","JsVar","The network id of the access point."], - ["password","JsVar","The password to the access point"], - ["gotIpCallback", "JsVar", "An optional callback invoked when we have an IP"] - ] -} - * - * Connect the station to an access point. - */ -void jswrap_ESP8266WiFi_connect( - JsVar *jsv_ssid, //!< The SSID of the access point to connect. - JsVar *jsv_password, //!< The password for the access point. - JsVar *gotIpCallback //!< The Callback function to be called when we are connected. - ) { - os_printf("> jswrap_ESP8266WiFi_connect\n"); - - // Check that the ssid and password values aren't obviously in error. - if (jsv_ssid == NULL || !jsvIsString(jsv_ssid)) { - jsExceptionHere(JSET_ERROR, "No SSID."); - return; - } - if (jsv_password == NULL || !jsvIsString(jsv_password)) { - jsExceptionHere(JSET_ERROR, "No password."); - return; - } - - // Check that if a callback function was supplied that we actually have a callback function. - if (gotIpCallback != NULL && !jsvIsUndefined(gotIpCallback) && !jsvIsFunction(gotIpCallback)) { - gotIpCallback = NULL; - jsExceptionHere(JSET_ERROR, "A callback function was supplied that is not a function."); - return; - } - if (jsvIsUndefined(gotIpCallback) || jsvIsNull(gotIpCallback)) { - gotIpCallback = NULL; - } - - // Set the global which is the gotIP callback - if (jsGotIpCallback != NULL) { - jsvUnLock(jsGotIpCallback); - jsGotIpCallback = NULL; - } - - // What does this do? - if (gotIpCallback != NULL) { - jsGotIpCallback = jsvLockAgainSafe(gotIpCallback); - } - - // Debug - // os_printf("jsGotIpCallback=%p\n", jsGotIpCallback); - - // Create strings from the JsVars for the ESP8266 API calls. - char ssid[33]; - int len = jsvGetString(jsv_ssid, ssid, sizeof(ssid)-1); - ssid[len]='\0'; - char password[65]; - len = jsvGetString(jsv_password, password, sizeof(password)-1); - password[len]='\0'; - - os_printf("> - ssid=%s, password=%s\n", ssid, password); - - // Set the WiFi mode of the ESP8266 - wifi_set_opmode_current(STATION_MODE); - - struct station_config stationConfig; - memset(&stationConfig, 0, sizeof(stationConfig)); - os_strncpy((char *)stationConfig.ssid, ssid, 32); - if (password != NULL) { - os_strncpy((char *)stationConfig.password, password, 64); - } else { - os_strcpy((char *)stationConfig.password, ""); - } - - // Set the WiFi configuration - wifi_station_set_config(&stationConfig); - - uint8 wifiConnectStatus = wifi_station_get_connect_status(); - os_printf(" - Current connect status: %s\n", wifiConnectStatusToString(wifiConnectStatus)); - - if (wifiConnectStatus == STATION_GOT_IP) { - // See issue #618. There are currently three schools of thought on what should happen - // when a connect is issued and we are already connected. - // - // Option #1 - Always perform a disconnect. - // Option #2 - Perform a disconnect if the SSID or PASSWORD are different from current - // Option #3 - Fail the connect and invoke the callback telling the user that we are already connected. - // -#define ISSUE_618 1 - -#if ISSUE_618 == 1 - wifi_station_disconnect(); -#elif ISSUE_618 == 2 - struct station_config existingConfig; - wifi_station_get_config(&existingConfig); - if (os_strncpy((char *)existingConfig.ssid, (char *)stationConfig.ssid, 32) != 0 || - os_strncpy((char *)existingConfig.password, (char *)stationConfig.password, 64) != 0) { - wifi_station_disconnect(); - } -#elif ISSUE_618 == 3 - if (jsGotIpCallback != NULL) { - JsVar *params[2]; - params[0] = jsvNewFromInteger(STATION_GOT_IP); - params[1] = jsvNewNull(); - jsiQueueEvents(NULL, jsGotIpCallback, params, 2); - } -#endif - } - // Perform the network level connection - wifi_station_connect(); - os_printf("< jswrap_ESP8266WiFi_connect\n"); -} - - -/** - * \brief Become an access point. - * When we call this function we are instructing the ESP8266 to set itself up as an - * access point to allow other WiFi stations to connect to it. In order to be an access - * point, the ESP8266 needs to know the SSID it should use as well as the password used - * to allow clients to connect. - * - * Parameters: - * - `jsv_ssid` - The network identity that the access point will advertize itself as. - * - `jsv_password` - The password a station will need to connect to the - * access point. - * - * Notes: - * - How about if the password is not supplied, NULL or empty then we set ourselves - * up using an Open authentication mechanism? - * - Add support for hidden SSIDs. - * - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "beAccessPoint", - "generate" : "jswrap_ESP8266WiFi_beAccessPoint", - "params" : [ - ["jsv_ssid","JsVar","The network SSID"], - ["jsv_password","JsVar","The password to allow stations to connect to the access point"] - ] -}*/ -void jswrap_ESP8266WiFi_beAccessPoint( - JsVar *jsv_ssid, //!< The network identity that the access point will advertize itself as. - JsVar *jsv_password //!< The password a station will need to connect to the access point. - ) { - // Validate that the SSID and password are somewhat useful. - if (jsv_ssid == NULL || !jsvIsString(jsv_ssid)) { - jsExceptionHere(JSET_ERROR, "No SSID."); - return; - } - if (jsv_password == NULL || !jsvIsString(jsv_password)) { - jsExceptionHere(JSET_ERROR, "No password."); - return; - } - - // Create strings from the JsVars for the ESP8266 API calls. - char ssid[33]; - int len = jsvGetString(jsv_ssid, ssid, sizeof(ssid)-1); - ssid[len]='\0'; - char password[65]; - len = jsvGetString(jsv_password, password, sizeof(password)-1); - password[len]='\0'; - - // Define that we are in Soft AP mode. - os_printf("Wifi: switching to soft-AP\n"); - wifi_set_opmode_current(SOFTAP_MODE); - - // Build our SoftAP configuration details - struct softap_config softApConfig; - memset(&softApConfig, 0, sizeof(softApConfig)); - - os_strcpy((char *)softApConfig.ssid, ssid); - os_strcpy((char *)softApConfig.password, password); - softApConfig.ssid_len = 0; // Null terminated SSID - softApConfig.authmode = AUTH_WPA2_PSK; - softApConfig.ssid_hidden = 0; // Not hidden. - softApConfig.max_connection = 4; // Maximum number of connections. - - // Set the WiFi configuration. - int rc = wifi_softap_set_config_current(&softApConfig); - if (rc != 1) { - jsExceptionHere(JSET_ERROR, "Error setting ESP8266 softap config."); - } -} - - -/** - * \brief Determine the list of access points available to us. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getAccessPoints", - "generate" : "jswrap_ESP8266WiFi_getAccessPoints", - "params" : [ - ["callback","JsVar","Function to call back when access points retrieved."] - ] -}*/ -void jswrap_ESP8266WiFi_getAccessPoints( - JsVar *callback //!< Function to call back when access points retrieved. - ) { - os_printf("> ESP8266WiFi_getAccessPoints\n"); - if (callback == NULL || !jsvIsFunction(callback)) { - jsExceptionHere(JSET_ERROR, "No callback."); - return; - } - - // Save the callback for the scan in the global variable called jsScanCallback. - jsScanCallback = jsvLockAgainSafe(callback); - - // 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. - - // Ensure we are in station mode - wifi_set_opmode_current(STATION_MODE); - - // Request a scan of the network calling "scanCB" on completion - wifi_station_scan(NULL, scanCB); - - os_printf("< ESP8266WiFi_getAccessPoints\n"); -} - - -/** - * \brief Disconnect the station from the access point. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "disconnect", - "generate" : "jswrap_ESP8266WiFi_disconnect" -}*/ -void jswrap_ESP8266WiFi_disconnect() { - wifi_station_disconnect(); -} - - -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "restart", - "generate" : "jswrap_ESP8266WiFi_restart" -}*/ -void jswrap_ESP8266WiFi_restart() { - system_restart(); -} - - -/** - * \brief Register a callback function that will be invoked when a WiFi event is detected. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "onWiFiEvent", - "generate" : "jswrap_ESP8266WiFi_onWiFiEvent", - "params" : [ - ["callback","JsVar","WiFi event callback"] - ] -}*/ -void jswrap_ESP8266WiFi_onWiFiEvent( - JsVar *callback //!< WiFi event callback. - ) { - // If the callback is null - if (callback == NULL || jsvIsNull(callback)) { - if (jsWiFiEventCallback != NULL) { - jsvUnLock(jsWiFiEventCallback); - } - jsWiFiEventCallback = NULL; - return; - } - - if (!jsvIsFunction(callback)) { - jsExceptionHere(JSET_ERROR, "No callback."); - return; - } - - // We are about to save a new global WiFi even callback handler. If we have previously - // had one, we need to unlock it so that we don't leak memory. - if (jsWiFiEventCallback != NULL) { - jsvUnLock(jsWiFiEventCallback); - } - - // Save the global WiFi event callback handler. - jsWiFiEventCallback = jsvLockAgainSafe(callback); -} - - -/** - * \brief Set whether or not the ESP8266 will perform an auto connect on startup. - * A value of true means it will while a value of false means it won't. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "setAutoConnect", - "generate" : "jswrap_ESP8266WiFi_setAutoConnect", - "params" : [ - ["autoconnect","JsVar","True if we wish to auto connect."] - ] -}*/ -void jswrap_ESP8266WiFi_setAutoConnect( - JsVar *autoconnect //!< True if we wish to auto connect. - ) { - os_printf("Auto connect is: %d\n", (int)autoconnect); - // Check that we have been passed a boolean ... if not, nothing to do here. - if (!jsvIsBoolean(autoconnect)) { - return; - } - - uint8 newValue = jsvGetBool(autoconnect); - os_printf("New value: %d\n", newValue); - - uart_rx_intr_disable(0); - wifi_station_set_auto_connect(newValue); - uart_rx_intr_disable(1); - os_printf("Autoconnect changed\n"); -} - - -/** - * \brief Retrieve whether or not the ESP8266 will perform an auto connect on startup. - * A value of 1 means it will while a value of zero means it won't. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getAutoConnect", - "generate" : "jswrap_ESP8266WiFi_getAutoConnect", - "return" : ["JsVar","A boolean representing our auto connect status"] -}*/ -JsVar *jswrap_ESP8266WiFi_getAutoConnect() { - uint8 result = wifi_station_get_auto_connect(); - return jsvNewFromBool(result); -} - - -/** - * \brief Retrieve the reset information that is stored when event the ESP8266 resets. - * The result will be a JS object containing the details. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getRstInfo", - "generate" : "jswrap_ESP8266WiFi_getRstInfo", - "return" : ["JsVar","A Restart Object"], - "return_object" : "Restart" -}*/ -JsVar *jswrap_ESP8266WiFi_getRstInfo() { - struct rst_info* info = system_get_rst_info(); - JsVar *restartInfo = jspNewObject(NULL, "Restart"); - jsvUnLock(jsvObjectSetChild(restartInfo, "reason", jsvNewFromInteger(info->reason))); - jsvUnLock(jsvObjectSetChild(restartInfo, "exccause", jsvNewFromInteger(info->exccause))); - jsvUnLock(jsvObjectSetChild(restartInfo, "epc1", jsvNewFromInteger(info->epc1))); - jsvUnLock(jsvObjectSetChild(restartInfo, "epc2", jsvNewFromInteger(info->epc2))); - jsvUnLock(jsvObjectSetChild(restartInfo, "epc3", jsvNewFromInteger(info->epc3))); - jsvUnLock(jsvObjectSetChild(restartInfo, "excvaddr", jsvNewFromInteger(info->excvaddr))); - jsvUnLock(jsvObjectSetChild(restartInfo, "depc", jsvNewFromInteger(info->depc))); - return restartInfo; -} - - -/** - * \brief Return an object that contains details about the state of the ESP8266. - * - `sdkVersion` - Version of the SDK. - * - `cpuFrequency` - CPU operating frequency. - * - `freeHeap` - Amount of free heap. - * - `maxCon` - Maximum number of concurrent connections. - * - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getState", - "generate" : "jswrap_ESP8266WiFi_getState", - "return" : ["JsVar","The state of the ESP8266"], - "return_object" : "ESP8266State" -}*/ -JsVar *jswrap_ESP8266WiFi_getState() { - // Create a new variable and populate it with the properties of the ESP8266 that we - // wish to return. - JsVar *esp8266State = jspNewObject(NULL, "ESP8266State"); - jsvUnLock(jsvObjectSetChild(esp8266State, "sdkVersion", jsvNewFromString(system_get_sdk_version()))); - jsvUnLock(jsvObjectSetChild(esp8266State, "cpuFrequency", jsvNewFromInteger(system_get_cpu_freq()))); - jsvUnLock(jsvObjectSetChild(esp8266State, "freeHeap", jsvNewFromInteger(system_get_free_heap_size()))); - jsvUnLock(jsvObjectSetChild(esp8266State, "maxCon", jsvNewFromInteger(espconn_tcp_get_max_con()))); - return esp8266State; -} - -/** - * \brief Return the value of an integer representation (4 bytes) of IP address - * as a string. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getAddressAsString", - "generate" : "jswrap_ESP8266WiFi_getAddressAsString", - "params" : [ - ["address","JsVar","An integer value representing an IP address."] - ], - "return" : ["JsVar","A String"] -}*/ -JsVar *jswrap_ESP8266WiFi_getAddressAsString( - JsVar *address //!< An integer value representing an IP address. - ) { - if (!jsvIsInt(address)) { - jsExceptionHere(JSET_ERROR, "No SSID."); - return NULL; - } - uint32 iAddress = (uint32)jsvGetInteger(address); - return networkGetAddressAsString((uint8 *)&iAddress, 4, 10, '.'); -} - - -/** - * \brief Retrieve the IP information about this network interface and return a JS - * object that contains its details. - * The object will have the following properties defined upon it: - * - `ip` - The IP address of the interface. - * - `netmask` - The netmask of the interface. - * - `gw` - The gateway to reach when transmitting through the interface. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getIPInfo", - "generate" : "jswrap_ESP8266WiFi_getIPInfo", - "return" : ["JsVar","A IPInfo Object"], - "return_object" : "IPInfo" -}*/ -JsVar *jswrap_ESP8266WiFi_getIPInfo() { - struct ip_info info; - wifi_get_ip_info(0, &info); - - JsVar *ipInfo = jspNewObject(NULL, "Restart"); - jsvUnLock(jsvObjectSetChild(ipInfo, "ip", jsvNewFromInteger(info.ip.addr))); - jsvUnLock(jsvObjectSetChild(ipInfo, "netmask", jsvNewFromInteger(info.netmask.addr))); - jsvUnLock(jsvObjectSetChild(ipInfo, "gw", jsvNewFromInteger(info.gw.addr))); - return ipInfo; -} - - -/** - * \brief Query the station configuration and return a JS object that represents the - * current settings. - * The object will have the following properties: - * - * - `ssid` - The network identity of the access point - * - `password` - The password to use to connect to the access point - * - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getStationConfig", - "generate" : "jswrap_ESP8266WiFi_getStationConfig", - "return" : ["JsVar","A Station Config"], - "return_object" : "StationConfig" -}*/ -JsVar *jswrap_ESP8266WiFi_getStationConfig() { - struct station_config config; - wifi_station_get_config(&config); - JsVar *jsConfig = jspNewObject(NULL, "StationConfig"); - //char ssid[33]; - //nullTerminateString(ssid, (char *)config.ssid, 32); - jsvUnLock(jsvObjectSetChild(jsConfig, "ssid", jsvNewFromString((char *)config.ssid))); - //char password[65]; - //nullTerminateString(password, (char *)config.password, 64); - jsvUnLock(jsvObjectSetChild(jsConfig, "password", jsvNewFromString((char *)config.password))); - return jsConfig; -} - - -/** - * \brief Determine the list of connected stations and return them. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getConnectedStations", - "generate" : "jswrap_ESP8266WiFi_getConnectedStations", - "return" : ["JsVar","An array of connected stations."] -}*/ -JsVar *jswrap_ESP8266WiFi_getConnectedStations() { - uint8 stationCount = wifi_softap_get_station_num(); - struct station_info *stationInfo = wifi_softap_get_station_info(); - JsVar *jsArray = jsvNewArray(NULL, 0); - if (stationInfo != NULL) { - while (stationInfo != NULL) { - os_printf("Station IP: %d.%d.%d.%d\n", IP2STR(&(stationInfo->ip))); - JsVar *jsStation = jsvNewWithFlags(JSV_OBJECT); - jsvUnLock(jsvObjectSetChild(jsStation, "ip", jsvNewFromInteger(stationInfo->ip.addr))); - jsvArrayPush(jsArray, jsStation); - stationInfo = STAILQ_NEXT(stationInfo, next); - } - wifi_softap_free_station_info(); - } - return jsArray; -} - - -/** - * \brief Get the signal strength. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getRSSI", - "generate" : "jswrap_ESP8266WiFi_getRSSI", - "return" : ["JsVar","An integer representing the signal strength."] -}*/ -JsVar *jswrap_ESP8266WiFi_getRSSI() { - int rssi = wifi_station_get_rssi(); - return jsvNewFromInteger(rssi); -} - - - -/** - * \brief Initialize the ESP8266 environment. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "init", - "generate" : "jswrap_ESP8266WiFi_init" -}*/ -void jswrap_ESP8266WiFi_init() { - os_printf("> jswrap_ESP8266WiFi_init\n"); - // register the state change handler so we get debug printout for sure - wifi_set_phy_mode(2); - wifi_set_event_handler_cb(wifiEventHandler); - os_printf("Wifi init, mode=%d\n", wifi_get_opmode()); - wifi_station_set_hostname("espruino"); - - netInit_esp8266_board(); - setupJsNetwork(); - networkState = NETWORKSTATE_ONLINE; - os_printf("< jswrap_ESP8266WiFi_init\n"); -} - - -/** - * Return the ESP8266 connection status. - * This is an integer value defined as: - * - 0 - STATION_IDLE - * - 1 - STATION_CONNECTING - * - 2 - STATION_WRONG_PASSWORD - * - 3 - STATION_NO_AP_FOUND - * - 4 - STATION_CONNECT_FAIL - * - 5 - STATION_GOT_IP - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "getConnectStatus", - "generate" : "jswrap_ESP8266WiFi_getConnectStatus", - "return" : ["JsVar","A connection status"] -} - -Retrieve the connection status. The return is an object that contains: - -* status - The status code from ESP8266 -* statusMsg - The description of the code - -*/ -JsVar *jswrap_ESP8266WiFi_getConnectStatus() { - // Ask ESP8266 for the connection status - uint8 status = wifi_station_get_connect_status(); - - // Create a JS variable to return - JsVar *var = jsvNewWithFlags(JSV_OBJECT); - - // Populate the return JS variable with a property called "status" - JsVar *jsStatus = jsvNewFromInteger(status); - //jsvUnLock(jsStatus); - jsvUnLock(jsvObjectSetChild(var, "status", jsStatus)); - - // Populate the return JS variable with a property called "statusMsg" - char *statusMsg; - switch(status) { - case STATION_IDLE: - statusMsg = "STATION_IDLE"; - break; - case STATION_CONNECTING: - statusMsg = "STATION_CONNECTING"; - break; - case STATION_WRONG_PASSWORD: - statusMsg = "STATION_WRONG_PASSWORD"; - break; - case STATION_NO_AP_FOUND: - statusMsg = "STATION_NO_AP_FOUND"; - break; - case STATION_CONNECT_FAIL: - statusMsg = "STATION_CONNECT_FAIL"; - break; - case STATION_GOT_IP: - statusMsg = "STATION_GOT_IP"; - break; - default: - statusMsg = "*** Unknown ***"; - } - JsVar *jsStatusMsg = jsvNewFromString(statusMsg); - //jsvUnLock(jsStatusMsg); - jsvUnLock(jsvObjectSetChild(var, "statusMsg", jsStatusMsg)); - //jsvUnLock(var); - return var; -} - - -/** - * Test: Perform a socket connection to a partner system. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "socketConnect", - "generate" : "jswrap_ESP8266WiFi_socketConnect", - "params" : [ - ["options","JsVar","Some kind of options."], - ["callback","JsVar","Some kind of callback."] - ], - "return" : ["JsVar","A connection object"] -}*/ -JsVar *jswrap_ESP8266WiFi_socketConnect( - JsVar *options, //!< Some kind of options. - JsVar *callback //!< Some kind of callback. - ) { - os_printf("Network state = %d\n", networkState); - JsVar *ret = jswrap_net_connect(options, callback, ST_NORMAL); - return ret; -} - - -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "socketEnd", - "generate" : "jswrap_ESP8266WiFi_socketEnd", - "params" : [ - ["socket","JsVar","The socket to be closed."], - ["data","JsVar","Optional data to be sent before close."] - ] -}*/ -void jswrap_ESP8266WiFi_socketEnd( - JsVar *socket, //!< The socket to be closed. - JsVar *data //!< Optional data to be sent before close. - ) { - jswrap_net_socket_end(socket, data); -} - - -/** - * \brief Perform a network ping request. - * The parameter can be either a String or a numeric IP address. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "ping", - "generate" : "jswrap_ESP8266WiFi_ping", - "params" : [ - ["ipAddr","JsVar","A string or integer representation of an IP address."], - ["pingCallback", "JsVar", "Optional callback function."] - ] -}*/ -void jswrap_ESP8266WiFi_ping( - JsVar *ipAddr, //!< A string or integer representation of an IP address. - JsVar *pingCallback //!< Optional callback function. - ) { - // 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 (jsPingCallback != NULL) { - jsvUnLock(jsPingCallback); - } - jsPingCallback = NULL; - } else if (!jsvIsFunction(pingCallback)) { - jsExceptionHere(JSET_ERROR, "Callback is not a function."); - return; - } else { - if (jsPingCallback != NULL) { - jsvUnLock(jsPingCallback); - } - jsPingCallback = pingCallback; - jsvLockAgainSafe(jsPingCallback); - } - - // We now have an IP address to ping ... so ping. - memset(&pingOpt, 0, sizeof(pingOpt)); - pingOpt.count = 5; - pingOpt.recv_function = pingRecvCB; - ping_start(&pingOpt); -} - - -/** - * \brief Dump the data in the socket. - */ -/*JSON{ - "type" : "staticmethod", - "class" : "ESP8266WiFi", - "name" : "dumpSocket", - "generate" : "jswrap_ESP8266WiFi_dumpSocket", - "params" : [ - ["socketId","JsVar","The socket to be dumped."] - ] -}*/ - -void jswrap_ESP8266WiFi_dumpSocket( - JsVar *socketId //!< The socket to be dumped. - ) { - esp8266_dumpSocket(jsvGetInteger(socketId)-1); -} - -/** - * \brief Null terminate a string. - */ -static char *nullTerminateString(char *target, char *source, int sourceLength) { - os_strncpy(target, source, sourceLength); - target[sourceLength-1] = '\0'; - return target; -} - -/** - * - */ -static void setupJsNetwork() { - JsNetwork net; - networkCreate(&net, JSNETWORKTYPE_ESP8266_BOARD); - networkSet(&net); -} - - -/** - * \brief 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 (jsPingCallback != NULL) { - JsVar *jsPingResponse = jspNewObject(NULL, "PingResponse"); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalCount", jsvNewFromInteger(pingResp->total_count))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalBytes", jsvNewFromInteger(pingResp->total_bytes))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalTime", jsvNewFromInteger(pingResp->total_time))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "respTime", jsvNewFromInteger(pingResp->resp_time))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "seqNo", jsvNewFromInteger(pingResp->seqno))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "timeoutCount", jsvNewFromInteger(pingResp->timeout_count))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "bytes", jsvNewFromInteger(pingResp->bytes))); - jsvUnLock(jsvObjectSetChild(jsPingResponse, "error", jsvNewFromInteger(pingResp->ping_err))); - JsVar *params[1]; - params[0] = jsPingResponse; - jsiQueueEvents(NULL, jsPingCallback, params, 1); - } -} - - -/** - * \brief 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. - */ - - os_printf(">> scanCB\n"); - // Create the Empty JS array that will be passed as a parameter to the callback. - JsVar *accessPointArray = jsvNewArray(NULL, 0); - struct bss_info *bssInfo; - - bssInfo = (struct bss_info *)arg; - while(bssInfo != NULL) { - // Add a new object to the JS array that will be passed as a parameter to - // the callback. The ESP8266 bssInfo structure contains the following: - // --- - // uint8 bssid[6] - // uint8 ssid[32] - // uint8 channel - // sint8 rssi – The received signal strength indication - // AUTH_MODE authmode - // Open = 0 - // WEP = 1 - // WPA_PSK = 2 - // WPA2_PSK = 3 - // WPA_WPA2_PSK = 4 - // uint8 is_hidden - // sint16 freq_offset - // --- - // Create, populate and add a child ... - JsVar *currentAccessPoint = jspNewObject(NULL, "AccessPoint"); - jsvUnLock(jsvObjectSetChild(currentAccessPoint, "rssi", jsvNewFromInteger(bssInfo->rssi))); - jsvUnLock(jsvObjectSetChild(currentAccessPoint, "channel", jsvNewFromInteger(bssInfo->channel))); - jsvUnLock(jsvObjectSetChild(currentAccessPoint, "authMode", jsvNewFromInteger(bssInfo->authmode))); - jsvUnLock(jsvObjectSetChild(currentAccessPoint, "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'; - jsvUnLock(jsvObjectSetChild(currentAccessPoint, "ssid", jsvNewFromString(ssid))); - - // Add the new record to the array - jsvArrayPush(accessPointArray, currentAccessPoint); - - os_printf(" - ssid: %s\n", bssInfo->ssid); - bssInfo = STAILQ_NEXT(bssInfo, next); - } - - // We have now completed the scan callback, so now we can invoke the JS callback. - JsVar *params[1]; - params[0] = accessPointArray; - jsiQueueEvents(NULL, jsScanCallback, params, 1); - jsvUnLock(jsScanCallback); - os_printf("<< scanCB\n"); -} - - -/** - * \brief Invoke the JavaScript callback to notify the program that an ESP8266 - * WiFi event has occurred. - */ -static void sendWifiEvent(uint32 eventType, JsVar *details) { - // We need to check that we actually have an event callback handler because - // it might have been disabled/removed. - if (jsWiFiEventCallback != NULL) { - // Build a callback event. - JsVar *params[2]; - params[0] = jsvNewFromInteger(eventType); - params[1] = details; - jsiQueueEvents(NULL, jsWiFiEventCallback, params, 2); - } - - if (jsGotIpCallback != NULL && eventType == EVENT_STAMODE_GOT_IP) { - JsVar *params[2]; - params[0] = jsvNewFromInteger(eventType); - params[1] = details; - jsiQueueEvents(NULL, jsGotIpCallback, params, 2); - // Once we have registered the callback, we can unlock and release - // the variable as we are only calling it once. - //jsvUnLock(jsGotIpCallback); - //jsGotIpCallback = NULL; - } -} - - -/** - * \brief 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) { - switch(evt->event) { - // We have connected to an access point. - case EVENT_STAMODE_CONNECTED: - os_printf("Wifi connected to ssid %s, ch %d\n", evt->event_info.connected.ssid, - evt->event_info.connected.channel); - sendWifiEvent(evt->event, jsvNewNull()); - break; - - // We have disconnected or been disconnected from an access point. - case EVENT_STAMODE_DISCONNECTED: - os_printf("Wifi disconnected from ssid %s, reason %s (%d)\n", - evt->event_info.disconnected.ssid, wifiGetReason(), evt->event_info.disconnected.reason); - JsVar *details = jspNewObject(NULL, "EventDetails"); - jsvObjectSetChild(details, "reason", jsvNewFromInteger(evt->event_info.disconnected.reason)); - char ssid[33]; - memcpy(ssid, evt->event_info.disconnected.ssid, evt->event_info.disconnected.ssid_len); - ssid[ evt->event_info.disconnected.ssid_len] = '\0'; - sendWifiEvent(evt->event, details); - break; - - // The authentication information at the access point has changed. - case EVENT_STAMODE_AUTHMODE_CHANGE: - os_printf("Wifi auth mode: %d -> %d\n", - evt->event_info.auth_change.old_mode, evt->event_info.auth_change.new_mode); - sendWifiEvent(evt->event, jsvNewNull()); - break; - - // We have been allocated an IP address. - case EVENT_STAMODE_GOT_IP: - os_printf("Wifi 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)); - sendWifiEvent(evt->event, jsvNewNull()); - break; - case EVENT_STAMODE_DHCP_TIMEOUT: - os_printf("Wifi DHCP timeout"); - sendWifiEvent(evt->event, jsvNewNull()); - break; - case EVENT_SOFTAPMODE_STACONNECTED: - os_printf("Wifi AP: station " MACSTR " joined, AID = %d\n", - MAC2STR(evt->event_info.sta_connected.mac), evt->event_info.sta_connected.aid); - sendWifiEvent(evt->event, jsvNewNull()); - break; - case EVENT_SOFTAPMODE_STADISCONNECTED: - os_printf("Wifi AP: station " MACSTR " left, AID = %d\n", - MAC2STR(evt->event_info.sta_disconnected.mac), evt->event_info.sta_disconnected.aid); - sendWifiEvent(evt->event, jsvNewNull()); - break; - case EVENT_SOFTAPMODE_PROBEREQRECVED: - os_printf("Wifi AP: probe request from station " MACSTR ", rssi = %d\n", - MAC2STR(evt->event_info.ap_probereqrecved.mac), evt->event_info.ap_probereqrecved.rssi); - sendWifiEvent(evt->event, jsvNewNull()); - break; - default: - os_printf("Wifi: unexpected event %d\n", evt->event); - sendWifiEvent(evt->event, jsvNewNull()); - break; - } -} - -/** - * \brief Write an IP address as a dotted decimal string. - */ -// Note: This may be a duplicate ... it appears that we may have an existing function -// in network.c which does exactly this and more!! -// -static void ipAddrToString(struct ip_addr addr, char *string) { - os_sprintf(string, "%d.%d.%d.%d", ((char *)&addr)[0], ((char *)&addr)[1], ((char *)&addr)[2], ((char *)&addr)[3]); -} - -/** - * Convert an ESP8266 WiFi connect status to a string. - * - * Convert the status (as returned by `wifi_station_get_connect_status()`) to a string - * representation. - * \return A string representation of a WiFi connect status. - */ -static char *wifiConnectStatusToString(uint8 status) { - switch(status) { - case STATION_IDLE: - return "STATION_IDLE"; - case STATION_CONNECTING: - return "STATION_CONNECTING"; - case STATION_WRONG_PASSWORD: - return "STATION_WRONG_PASSWORD"; - case STATION_NO_AP_FOUND: - return "STATION_NO_AP_FOUND"; - case STATION_CONNECT_FAIL: - return "STATION_CONNECT_FAIL"; - case STATION_GOT_IP: - return "STATION_GOT_IP"; - default: - return "Unknown connect status!!"; - } -} +// 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 + +#define ESP8266_ON_ACCESS_POINTS "#accessPoints" + +#define _GCC_WRAP_STDINT_H +typedef long long int64_t; + +#include "jswrap_esp8266.h" +#include "jsinteractive.h" // Pull inn the jsiConsolePrint function +#include "network.h" +#include "network_esp8266.h" +#include "jswrap_net.h" + +// Forward declaration of functions. +static void scanCB(void *arg, STATUS status); +static void wifiEventHandler(System_Event_t *event); +static void ipAddrToString(struct ip_addr addr, char *string); +static char *nullTerminateString(char *target, char *source, int sourceLength); +static void setupJsNetwork(); +static void pingRecvCB(); +static char *wifiConnectStatusToString(uint8 status); + +static JsVar *jsScanCallback; +static JsVar *jsWiFiEventCallback; + +// A callback function to be invoked when we have an IP address. +static JsVar *jsGotIpCallback; + +static JsVar *jsPingCallback; + +// Global data structure for ping request +static struct ping_option pingOpt; + +// Reasons for which a connection failed +uint8_t wifiReason = 0; +static char *wifiReasons[] = { + "", "unspecified", "auth_expire", "auth_leave", "assoc_expire", "assoc_toomany", "not_authed", + "not_assoced", "assoc_leave", "assoc_not_authed", "disassoc_pwrcap_bad", "disassoc_supchan_bad", + "ie_invalid", "mic_failure", "4way_handshake_timeout", "group_key_update_timeout", + "ie_in_4way_differs", "group_cipher_invalid", "pairwise_cipher_invalid", "akmp_invalid", + "unsupp_rsn_ie_version", "invalid_rsn_ie_cap", "802_1x_auth_failed", "cipher_suite_rejected", + "beacon_timeout", "no_ap_found" }; + +static char *wifiGetReason(void) { + if (wifiReason <= 24) return wifiReasons[wifiReason]; + if (wifiReason >= 200 && wifiReason <= 201) return wifiReasons[wifiReason-200+24]; + return wifiReasons[1]; +} + +static char *wifiMode[] = { 0, "STA", "AP", "AP+STA" }; +static char *wifiPhy[] = { 0, "11b", "11g", "11n" }; + +// Let's define the JavaScript class that will contain our `world()` method. We'll call it `Hello` +/*JSON{ + "type" : "class", + "class" : "ESP8266WiFi" +}*/ + + +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "connect", + "generate" : "jswrap_ESP8266WiFi_connect", + "params" : [ + ["ssid","JsVar","The network id of the access point."], + ["password","JsVar","The password to the access point"], + ["gotIpCallback", "JsVar", "An optional callback invoked when we have an IP"] + ] +} + * + * Connect the station to an access point. + */ +void jswrap_ESP8266WiFi_connect( + JsVar *jsv_ssid, //!< The SSID of the access point to connect. + JsVar *jsv_password, //!< The password for the access point. + JsVar *gotIpCallback //!< The Callback function to be called when we are connected. + ) { + os_printf("> jswrap_ESP8266WiFi_connect\n"); + + // Check that the ssid and password values aren't obviously in error. + if (jsv_ssid == NULL || !jsvIsString(jsv_ssid)) { + jsExceptionHere(JSET_ERROR, "No SSID."); + return; + } + if (jsv_password == NULL || !jsvIsString(jsv_password)) { + jsExceptionHere(JSET_ERROR, "No password."); + return; + } + + // Check that if a callback function was supplied that we actually have a callback function. + if (gotIpCallback != NULL && !jsvIsUndefined(gotIpCallback) && !jsvIsFunction(gotIpCallback)) { + gotIpCallback = NULL; + jsExceptionHere(JSET_ERROR, "A callback function was supplied that is not a function."); + return; + } + if (jsvIsUndefined(gotIpCallback) || jsvIsNull(gotIpCallback)) { + gotIpCallback = NULL; + } + + // Set the global which is the gotIP callback + if (jsGotIpCallback != NULL) { + jsvUnLock(jsGotIpCallback); + jsGotIpCallback = NULL; + } + + // What does this do? + if (gotIpCallback != NULL) { + jsGotIpCallback = jsvLockAgainSafe(gotIpCallback); + } + + // Debug + // os_printf("jsGotIpCallback=%p\n", jsGotIpCallback); + + // Create strings from the JsVars for the ESP8266 API calls. + char ssid[33]; + int len = jsvGetString(jsv_ssid, ssid, sizeof(ssid)-1); + ssid[len]='\0'; + char password[65]; + len = jsvGetString(jsv_password, password, sizeof(password)-1); + password[len]='\0'; + + os_printf("> - ssid=%s, password=%s\n", ssid, password); + + // Set the WiFi mode of the ESP8266 + wifi_set_opmode_current(STATION_MODE); + + struct station_config stationConfig; + memset(&stationConfig, 0, sizeof(stationConfig)); + os_strncpy((char *)stationConfig.ssid, ssid, 32); + if (password != NULL) { + os_strncpy((char *)stationConfig.password, password, 64); + } else { + os_strcpy((char *)stationConfig.password, ""); + } + + // Set the WiFi configuration + wifi_station_set_config(&stationConfig); + + uint8 wifiConnectStatus = wifi_station_get_connect_status(); + os_printf(" - Current connect status: %s\n", wifiConnectStatusToString(wifiConnectStatus)); + + if (wifiConnectStatus == STATION_GOT_IP) { + // See issue #618. There are currently three schools of thought on what should happen + // when a connect is issued and we are already connected. + // + // Option #1 - Always perform a disconnect. + // Option #2 - Perform a disconnect if the SSID or PASSWORD are different from current + // Option #3 - Fail the connect and invoke the callback telling the user that we are already connected. + // +#define ISSUE_618 1 + +#if ISSUE_618 == 1 + wifi_station_disconnect(); +#elif ISSUE_618 == 2 + struct station_config existingConfig; + wifi_station_get_config(&existingConfig); + if (os_strncpy((char *)existingConfig.ssid, (char *)stationConfig.ssid, 32) != 0 || + os_strncpy((char *)existingConfig.password, (char *)stationConfig.password, 64) != 0) { + wifi_station_disconnect(); + } +#elif ISSUE_618 == 3 + if (jsGotIpCallback != NULL) { + JsVar *params[2]; + params[0] = jsvNewFromInteger(STATION_GOT_IP); + params[1] = jsvNewNull(); + jsiQueueEvents(NULL, jsGotIpCallback, params, 2); + } +#endif + } + // Perform the network level connection + wifi_station_connect(); + os_printf("< jswrap_ESP8266WiFi_connect\n"); +} + + +/** + * \brief Become an access point. + * When we call this function we are instructing the ESP8266 to set itself up as an + * access point to allow other WiFi stations to connect to it. In order to be an access + * point, the ESP8266 needs to know the SSID it should use as well as the password used + * to allow clients to connect. + * + * Parameters: + * - `jsv_ssid` - The network identity that the access point will advertize itself as. + * - `jsv_password` - The password a station will need to connect to the + * access point. + * + * Notes: + * - How about if the password is not supplied, NULL or empty then we set ourselves + * up using an Open authentication mechanism? + * - Add support for hidden SSIDs. + * + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "beAccessPoint", + "generate" : "jswrap_ESP8266WiFi_beAccessPoint", + "params" : [ + ["jsv_ssid","JsVar","The network SSID"], + ["jsv_password","JsVar","The password to allow stations to connect to the access point"] + ] +}*/ +void jswrap_ESP8266WiFi_beAccessPoint( + JsVar *jsv_ssid, //!< The network identity that the access point will advertize itself as. + JsVar *jsv_password //!< The password a station will need to connect to the access point. + ) { + // Validate that the SSID and password are somewhat useful. + if (jsv_ssid == NULL || !jsvIsString(jsv_ssid)) { + jsExceptionHere(JSET_ERROR, "No SSID."); + return; + } + if (jsv_password == NULL || !jsvIsString(jsv_password)) { + jsExceptionHere(JSET_ERROR, "No password."); + return; + } + + // Create strings from the JsVars for the ESP8266 API calls. + char ssid[33]; + int len = jsvGetString(jsv_ssid, ssid, sizeof(ssid)-1); + ssid[len]='\0'; + char password[65]; + len = jsvGetString(jsv_password, password, sizeof(password)-1); + password[len]='\0'; + + // Define that we are in Soft AP mode. + os_printf("Wifi: switching to soft-AP\n"); + wifi_set_opmode_current(SOFTAP_MODE); + + // Build our SoftAP configuration details + struct softap_config softApConfig; + memset(&softApConfig, 0, sizeof(softApConfig)); + + os_strcpy((char *)softApConfig.ssid, ssid); + os_strcpy((char *)softApConfig.password, password); + softApConfig.ssid_len = 0; // Null terminated SSID + softApConfig.authmode = AUTH_WPA2_PSK; + softApConfig.ssid_hidden = 0; // Not hidden. + softApConfig.max_connection = 4; // Maximum number of connections. + + // Set the WiFi configuration. + int rc = wifi_softap_set_config_current(&softApConfig); + if (rc != 1) { + jsExceptionHere(JSET_ERROR, "Error setting ESP8266 softap config."); + } +} + + +/** + * \brief Determine the list of access points available to us. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getAccessPoints", + "generate" : "jswrap_ESP8266WiFi_getAccessPoints", + "params" : [ + ["callback","JsVar","Function to call back when access points retrieved."] + ] +}*/ +void jswrap_ESP8266WiFi_getAccessPoints( + JsVar *callback //!< Function to call back when access points retrieved. + ) { + os_printf("> ESP8266WiFi_getAccessPoints\n"); + if (callback == NULL || !jsvIsFunction(callback)) { + jsExceptionHere(JSET_ERROR, "No callback."); + return; + } + + // Save the callback for the scan in the global variable called jsScanCallback. + jsScanCallback = jsvLockAgainSafe(callback); + + // 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. + + // Ensure we are in station mode + wifi_set_opmode_current(STATION_MODE); + + // Request a scan of the network calling "scanCB" on completion + wifi_station_scan(NULL, scanCB); + + os_printf("< ESP8266WiFi_getAccessPoints\n"); +} + + +/** + * \brief Disconnect the station from the access point. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "disconnect", + "generate" : "jswrap_ESP8266WiFi_disconnect" +}*/ +void jswrap_ESP8266WiFi_disconnect() { + wifi_station_disconnect(); +} + + +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "restart", + "generate" : "jswrap_ESP8266WiFi_restart" +}*/ +void jswrap_ESP8266WiFi_restart() { + system_restart(); +} + + +/** + * \brief Register a callback function that will be invoked when a WiFi event is detected. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "onWiFiEvent", + "generate" : "jswrap_ESP8266WiFi_onWiFiEvent", + "params" : [ + ["callback","JsVar","WiFi event callback"] + ] +}*/ +void jswrap_ESP8266WiFi_onWiFiEvent( + JsVar *callback //!< WiFi event callback. + ) { + // If the callback is null + if (callback == NULL || jsvIsNull(callback)) { + if (jsWiFiEventCallback != NULL) { + jsvUnLock(jsWiFiEventCallback); + } + jsWiFiEventCallback = NULL; + return; + } + + if (!jsvIsFunction(callback)) { + jsExceptionHere(JSET_ERROR, "No callback."); + return; + } + + // We are about to save a new global WiFi even callback handler. If we have previously + // had one, we need to unlock it so that we don't leak memory. + if (jsWiFiEventCallback != NULL) { + jsvUnLock(jsWiFiEventCallback); + } + + // Save the global WiFi event callback handler. + jsWiFiEventCallback = jsvLockAgainSafe(callback); +} + + +/** + * \brief Set whether or not the ESP8266 will perform an auto connect on startup. + * A value of true means it will while a value of false means it won't. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "setAutoConnect", + "generate" : "jswrap_ESP8266WiFi_setAutoConnect", + "params" : [ + ["autoconnect","JsVar","True if we wish to auto connect."] + ] +}*/ +void jswrap_ESP8266WiFi_setAutoConnect( + JsVar *autoconnect //!< True if we wish to auto connect. + ) { + os_printf("Auto connect is: %d\n", (int)autoconnect); + // Check that we have been passed a boolean ... if not, nothing to do here. + if (!jsvIsBoolean(autoconnect)) { + return; + } + + uint8 newValue = jsvGetBool(autoconnect); + os_printf("New value: %d\n", newValue); + + uart_rx_intr_disable(0); + wifi_station_set_auto_connect(newValue); + uart_rx_intr_disable(1); + os_printf("Autoconnect changed\n"); +} + + +/** + * \brief Retrieve whether or not the ESP8266 will perform an auto connect on startup. + * A value of 1 means it will while a value of zero means it won't. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getAutoConnect", + "generate" : "jswrap_ESP8266WiFi_getAutoConnect", + "return" : ["JsVar","A boolean representing our auto connect status"] +}*/ +JsVar *jswrap_ESP8266WiFi_getAutoConnect() { + uint8 result = wifi_station_get_auto_connect(); + return jsvNewFromBool(result); +} + + +/** + * \brief Retrieve the reset information that is stored when event the ESP8266 resets. + * The result will be a JS object containing the details. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getRstInfo", + "generate" : "jswrap_ESP8266WiFi_getRstInfo", + "return" : ["JsVar","A Restart Object"], + "return_object" : "Restart" +}*/ +JsVar *jswrap_ESP8266WiFi_getRstInfo() { + struct rst_info* info = system_get_rst_info(); + JsVar *restartInfo = jspNewObject(NULL, "Restart"); + jsvUnLock(jsvObjectSetChild(restartInfo, "reason", jsvNewFromInteger(info->reason))); + jsvUnLock(jsvObjectSetChild(restartInfo, "exccause", jsvNewFromInteger(info->exccause))); + jsvUnLock(jsvObjectSetChild(restartInfo, "epc1", jsvNewFromInteger(info->epc1))); + jsvUnLock(jsvObjectSetChild(restartInfo, "epc2", jsvNewFromInteger(info->epc2))); + jsvUnLock(jsvObjectSetChild(restartInfo, "epc3", jsvNewFromInteger(info->epc3))); + jsvUnLock(jsvObjectSetChild(restartInfo, "excvaddr", jsvNewFromInteger(info->excvaddr))); + jsvUnLock(jsvObjectSetChild(restartInfo, "depc", jsvNewFromInteger(info->depc))); + return restartInfo; +} + + +/** + * \brief Return an object that contains details about the state of the ESP8266. + * - `sdkVersion` - Version of the SDK. + * - `cpuFrequency` - CPU operating frequency. + * - `freeHeap` - Amount of free heap. + * - `maxCon` - Maximum number of concurrent connections. + * + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getState", + "generate" : "jswrap_ESP8266WiFi_getState", + "return" : ["JsVar","The state of the ESP8266"], + "return_object" : "ESP8266State" +}*/ +JsVar *jswrap_ESP8266WiFi_getState() { + // Create a new variable and populate it with the properties of the ESP8266 that we + // wish to return. + JsVar *esp8266State = jspNewObject(NULL, "ESP8266State"); + jsvUnLock(jsvObjectSetChild(esp8266State, "sdkVersion", jsvNewFromString(system_get_sdk_version()))); + jsvUnLock(jsvObjectSetChild(esp8266State, "cpuFrequency", jsvNewFromInteger(system_get_cpu_freq()))); + jsvUnLock(jsvObjectSetChild(esp8266State, "freeHeap", jsvNewFromInteger(system_get_free_heap_size()))); + jsvUnLock(jsvObjectSetChild(esp8266State, "maxCon", jsvNewFromInteger(espconn_tcp_get_max_con()))); + return esp8266State; +} + +/** + * \brief Return the value of an integer representation (4 bytes) of IP address + * as a string. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getAddressAsString", + "generate" : "jswrap_ESP8266WiFi_getAddressAsString", + "params" : [ + ["address","JsVar","An integer value representing an IP address."] + ], + "return" : ["JsVar","A String"] +}*/ +JsVar *jswrap_ESP8266WiFi_getAddressAsString( + JsVar *address //!< An integer value representing an IP address. + ) { + if (!jsvIsInt(address)) { + jsExceptionHere(JSET_ERROR, "No SSID."); + return NULL; + } + uint32 iAddress = (uint32)jsvGetInteger(address); + return networkGetAddressAsString((uint8 *)&iAddress, 4, 10, '.'); +} + + +/** + * \brief Retrieve the IP information about this network interface and return a JS + * object that contains its details. + * The object will have the following properties defined upon it: + * - `ip` - The IP address of the interface. + * - `netmask` - The netmask of the interface. + * - `gw` - The gateway to reach when transmitting through the interface. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getIPInfo", + "generate" : "jswrap_ESP8266WiFi_getIPInfo", + "return" : ["JsVar","A IPInfo Object"], + "return_object" : "IPInfo" +}*/ +JsVar *jswrap_ESP8266WiFi_getIPInfo() { + struct ip_info info; + wifi_get_ip_info(0, &info); + + JsVar *ipInfo = jspNewObject(NULL, "Restart"); + jsvUnLock(jsvObjectSetChild(ipInfo, "ip", jsvNewFromInteger(info.ip.addr))); + jsvUnLock(jsvObjectSetChild(ipInfo, "netmask", jsvNewFromInteger(info.netmask.addr))); + jsvUnLock(jsvObjectSetChild(ipInfo, "gw", jsvNewFromInteger(info.gw.addr))); + return ipInfo; +} + + +/** + * \brief Query the station configuration and return a JS object that represents the + * current settings. + * The object will have the following properties: + * + * - `ssid` - The network identity of the access point + * - `password` - The password to use to connect to the access point + * + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getStationConfig", + "generate" : "jswrap_ESP8266WiFi_getStationConfig", + "return" : ["JsVar","A Station Config"], + "return_object" : "StationConfig" +}*/ +JsVar *jswrap_ESP8266WiFi_getStationConfig() { + struct station_config config; + wifi_station_get_config(&config); + JsVar *jsConfig = jspNewObject(NULL, "StationConfig"); + //char ssid[33]; + //nullTerminateString(ssid, (char *)config.ssid, 32); + jsvUnLock(jsvObjectSetChild(jsConfig, "ssid", jsvNewFromString((char *)config.ssid))); + //char password[65]; + //nullTerminateString(password, (char *)config.password, 64); + jsvUnLock(jsvObjectSetChild(jsConfig, "password", jsvNewFromString((char *)config.password))); + return jsConfig; +} + + +/** + * \brief Determine the list of connected stations and return them. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getConnectedStations", + "generate" : "jswrap_ESP8266WiFi_getConnectedStations", + "return" : ["JsVar","An array of connected stations."] +}*/ +JsVar *jswrap_ESP8266WiFi_getConnectedStations() { + uint8 stationCount = wifi_softap_get_station_num(); + struct station_info *stationInfo = wifi_softap_get_station_info(); + JsVar *jsArray = jsvNewArray(NULL, 0); + if (stationInfo != NULL) { + while (stationInfo != NULL) { + os_printf("Station IP: %d.%d.%d.%d\n", IP2STR(&(stationInfo->ip))); + JsVar *jsStation = jsvNewWithFlags(JSV_OBJECT); + jsvUnLock(jsvObjectSetChild(jsStation, "ip", jsvNewFromInteger(stationInfo->ip.addr))); + jsvArrayPush(jsArray, jsStation); + stationInfo = STAILQ_NEXT(stationInfo, next); + } + wifi_softap_free_station_info(); + } + return jsArray; +} + + +/** + * \brief Get the signal strength. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getRSSI", + "generate" : "jswrap_ESP8266WiFi_getRSSI", + "return" : ["JsVar","An integer representing the signal strength."] +}*/ +JsVar *jswrap_ESP8266WiFi_getRSSI() { + int rssi = wifi_station_get_rssi(); + return jsvNewFromInteger(rssi); +} + + + +/** + * \brief Initialize the ESP8266 environment. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "init", + "generate" : "jswrap_ESP8266WiFi_init" +}*/ +void jswrap_ESP8266WiFi_init() { + os_printf("> jswrap_ESP8266WiFi_init\n"); + // register the state change handler so we get debug printout for sure + wifi_set_phy_mode(2); + wifi_set_event_handler_cb(wifiEventHandler); + os_printf("Wifi init, mode=%d\n", wifi_get_opmode()); + wifi_station_set_hostname("espruino"); + + netInit_esp8266_board(); + setupJsNetwork(); + networkState = NETWORKSTATE_ONLINE; + os_printf("< jswrap_ESP8266WiFi_init\n"); +} + + +/** + * Return the ESP8266 connection status. + * This is an integer value defined as: + * - 0 - STATION_IDLE + * - 1 - STATION_CONNECTING + * - 2 - STATION_WRONG_PASSWORD + * - 3 - STATION_NO_AP_FOUND + * - 4 - STATION_CONNECT_FAIL + * - 5 - STATION_GOT_IP + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "getConnectStatus", + "generate" : "jswrap_ESP8266WiFi_getConnectStatus", + "return" : ["JsVar","A connection status"] +} + +Retrieve the connection status. The return is an object that contains: + +* status - The status code from ESP8266 +* statusMsg - The description of the code + +*/ +JsVar *jswrap_ESP8266WiFi_getConnectStatus() { + // Ask ESP8266 for the connection status + uint8 status = wifi_station_get_connect_status(); + + // Create a JS variable to return + JsVar *var = jsvNewWithFlags(JSV_OBJECT); + + // Populate the return JS variable with a property called "status" + JsVar *jsStatus = jsvNewFromInteger(status); + //jsvUnLock(jsStatus); + jsvUnLock(jsvObjectSetChild(var, "status", jsStatus)); + + // Populate the return JS variable with a property called "statusMsg" + char *statusMsg; + switch(status) { + case STATION_IDLE: + statusMsg = "STATION_IDLE"; + break; + case STATION_CONNECTING: + statusMsg = "STATION_CONNECTING"; + break; + case STATION_WRONG_PASSWORD: + statusMsg = "STATION_WRONG_PASSWORD"; + break; + case STATION_NO_AP_FOUND: + statusMsg = "STATION_NO_AP_FOUND"; + break; + case STATION_CONNECT_FAIL: + statusMsg = "STATION_CONNECT_FAIL"; + break; + case STATION_GOT_IP: + statusMsg = "STATION_GOT_IP"; + break; + default: + statusMsg = "*** Unknown ***"; + } + JsVar *jsStatusMsg = jsvNewFromString(statusMsg); + //jsvUnLock(jsStatusMsg); + jsvUnLock(jsvObjectSetChild(var, "statusMsg", jsStatusMsg)); + //jsvUnLock(var); + return var; +} + + +/** + * Test: Perform a socket connection to a partner system. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "socketConnect", + "generate" : "jswrap_ESP8266WiFi_socketConnect", + "params" : [ + ["options","JsVar","Some kind of options."], + ["callback","JsVar","Some kind of callback."] + ], + "return" : ["JsVar","A connection object"] +}*/ +JsVar *jswrap_ESP8266WiFi_socketConnect( + JsVar *options, //!< Some kind of options. + JsVar *callback //!< Some kind of callback. + ) { + os_printf("Network state = %d\n", networkState); + JsVar *ret = jswrap_net_connect(options, callback, ST_NORMAL); + return ret; +} + + +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "socketEnd", + "generate" : "jswrap_ESP8266WiFi_socketEnd", + "params" : [ + ["socket","JsVar","The socket to be closed."], + ["data","JsVar","Optional data to be sent before close."] + ] +}*/ +void jswrap_ESP8266WiFi_socketEnd( + JsVar *socket, //!< The socket to be closed. + JsVar *data //!< Optional data to be sent before close. + ) { + jswrap_net_socket_end(socket, data); +} + + +/** + * \brief Perform a network ping request. + * The parameter can be either a String or a numeric IP address. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "ping", + "generate" : "jswrap_ESP8266WiFi_ping", + "params" : [ + ["ipAddr","JsVar","A string or integer representation of an IP address."], + ["pingCallback", "JsVar", "Optional callback function."] + ] +}*/ +void jswrap_ESP8266WiFi_ping( + JsVar *ipAddr, //!< A string or integer representation of an IP address. + JsVar *pingCallback //!< Optional callback function. + ) { + // 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 (jsPingCallback != NULL) { + jsvUnLock(jsPingCallback); + } + jsPingCallback = NULL; + } else if (!jsvIsFunction(pingCallback)) { + jsExceptionHere(JSET_ERROR, "Callback is not a function."); + return; + } else { + if (jsPingCallback != NULL) { + jsvUnLock(jsPingCallback); + } + jsPingCallback = pingCallback; + jsvLockAgainSafe(jsPingCallback); + } + + // We now have an IP address to ping ... so ping. + memset(&pingOpt, 0, sizeof(pingOpt)); + pingOpt.count = 5; + pingOpt.recv_function = pingRecvCB; + ping_start(&pingOpt); +} + + +/** + * \brief Dump the data in the socket. + */ +/*JSON{ + "type" : "staticmethod", + "class" : "ESP8266WiFi", + "name" : "dumpSocket", + "generate" : "jswrap_ESP8266WiFi_dumpSocket", + "params" : [ + ["socketId","JsVar","The socket to be dumped."] + ] +}*/ + +void jswrap_ESP8266WiFi_dumpSocket( + JsVar *socketId //!< The socket to be dumped. + ) { + esp8266_dumpSocket(jsvGetInteger(socketId)-1); +} + +/** + * \brief Null terminate a string. + */ +static char *nullTerminateString(char *target, char *source, int sourceLength) { + os_strncpy(target, source, sourceLength); + target[sourceLength-1] = '\0'; + return target; +} + +/** + * + */ +static void setupJsNetwork() { + JsNetwork net; + networkCreate(&net, JSNETWORKTYPE_ESP8266_BOARD); + networkSet(&net); +} + + +/** + * \brief 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 (jsPingCallback != NULL) { + JsVar *jsPingResponse = jspNewObject(NULL, "PingResponse"); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalCount", jsvNewFromInteger(pingResp->total_count))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalBytes", jsvNewFromInteger(pingResp->total_bytes))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "totalTime", jsvNewFromInteger(pingResp->total_time))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "respTime", jsvNewFromInteger(pingResp->resp_time))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "seqNo", jsvNewFromInteger(pingResp->seqno))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "timeoutCount", jsvNewFromInteger(pingResp->timeout_count))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "bytes", jsvNewFromInteger(pingResp->bytes))); + jsvUnLock(jsvObjectSetChild(jsPingResponse, "error", jsvNewFromInteger(pingResp->ping_err))); + JsVar *params[1]; + params[0] = jsPingResponse; + jsiQueueEvents(NULL, jsPingCallback, params, 1); + } +} + + +/** + * \brief 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. + */ + + os_printf(">> scanCB\n"); + // Create the Empty JS array that will be passed as a parameter to the callback. + JsVar *accessPointArray = jsvNewArray(NULL, 0); + struct bss_info *bssInfo; + + bssInfo = (struct bss_info *)arg; + while(bssInfo != NULL) { + // Add a new object to the JS array that will be passed as a parameter to + // the callback. The ESP8266 bssInfo structure contains the following: + // --- + // uint8 bssid[6] + // uint8 ssid[32] + // uint8 channel + // sint8 rssi – The received signal strength indication + // AUTH_MODE authmode + // Open = 0 + // WEP = 1 + // WPA_PSK = 2 + // WPA2_PSK = 3 + // WPA_WPA2_PSK = 4 + // uint8 is_hidden + // sint16 freq_offset + // --- + // Create, populate and add a child ... + JsVar *currentAccessPoint = jspNewObject(NULL, "AccessPoint"); + jsvUnLock(jsvObjectSetChild(currentAccessPoint, "rssi", jsvNewFromInteger(bssInfo->rssi))); + jsvUnLock(jsvObjectSetChild(currentAccessPoint, "channel", jsvNewFromInteger(bssInfo->channel))); + jsvUnLock(jsvObjectSetChild(currentAccessPoint, "authMode", jsvNewFromInteger(bssInfo->authmode))); + jsvUnLock(jsvObjectSetChild(currentAccessPoint, "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'; + jsvUnLock(jsvObjectSetChild(currentAccessPoint, "ssid", jsvNewFromString(ssid))); + + // Add the new record to the array + jsvArrayPush(accessPointArray, currentAccessPoint); + + os_printf(" - ssid: %s\n", bssInfo->ssid); + bssInfo = STAILQ_NEXT(bssInfo, next); + } + + // We have now completed the scan callback, so now we can invoke the JS callback. + JsVar *params[1]; + params[0] = accessPointArray; + jsiQueueEvents(NULL, jsScanCallback, params, 1); + jsvUnLock(jsScanCallback); + os_printf("<< scanCB\n"); +} + + +/** + * \brief Invoke the JavaScript callback to notify the program that an ESP8266 + * WiFi event has occurred. + */ +static void sendWifiEvent(uint32 eventType, JsVar *details) { + // We need to check that we actually have an event callback handler because + // it might have been disabled/removed. + if (jsWiFiEventCallback != NULL) { + // Build a callback event. + JsVar *params[2]; + params[0] = jsvNewFromInteger(eventType); + params[1] = details; + jsiQueueEvents(NULL, jsWiFiEventCallback, params, 2); + } + + if (jsGotIpCallback != NULL && eventType == EVENT_STAMODE_GOT_IP) { + JsVar *params[2]; + params[0] = jsvNewFromInteger(eventType); + params[1] = details; + jsiQueueEvents(NULL, jsGotIpCallback, params, 2); + // Once we have registered the callback, we can unlock and release + // the variable as we are only calling it once. + //jsvUnLock(jsGotIpCallback); + //jsGotIpCallback = NULL; + } +} + + +/** + * \brief 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) { + switch(evt->event) { + // We have connected to an access point. + case EVENT_STAMODE_CONNECTED: + os_printf("Wifi connected to ssid %s, ch %d\n", evt->event_info.connected.ssid, + evt->event_info.connected.channel); + sendWifiEvent(evt->event, jsvNewNull()); + break; + + // We have disconnected or been disconnected from an access point. + case EVENT_STAMODE_DISCONNECTED: + os_printf("Wifi disconnected from ssid %s, reason %s (%d)\n", + evt->event_info.disconnected.ssid, wifiGetReason(), evt->event_info.disconnected.reason); + JsVar *details = jspNewObject(NULL, "EventDetails"); + jsvObjectSetChild(details, "reason", jsvNewFromInteger(evt->event_info.disconnected.reason)); + char ssid[33]; + memcpy(ssid, evt->event_info.disconnected.ssid, evt->event_info.disconnected.ssid_len); + ssid[ evt->event_info.disconnected.ssid_len] = '\0'; + sendWifiEvent(evt->event, details); + break; + + // The authentication information at the access point has changed. + case EVENT_STAMODE_AUTHMODE_CHANGE: + os_printf("Wifi auth mode: %d -> %d\n", + evt->event_info.auth_change.old_mode, evt->event_info.auth_change.new_mode); + sendWifiEvent(evt->event, jsvNewNull()); + break; + + // We have been allocated an IP address. + case EVENT_STAMODE_GOT_IP: + os_printf("Wifi 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)); + sendWifiEvent(evt->event, jsvNewNull()); + break; + case EVENT_STAMODE_DHCP_TIMEOUT: + os_printf("Wifi DHCP timeout"); + sendWifiEvent(evt->event, jsvNewNull()); + break; + case EVENT_SOFTAPMODE_STACONNECTED: + os_printf("Wifi AP: station " MACSTR " joined, AID = %d\n", + MAC2STR(evt->event_info.sta_connected.mac), evt->event_info.sta_connected.aid); + sendWifiEvent(evt->event, jsvNewNull()); + break; + case EVENT_SOFTAPMODE_STADISCONNECTED: + os_printf("Wifi AP: station " MACSTR " left, AID = %d\n", + MAC2STR(evt->event_info.sta_disconnected.mac), evt->event_info.sta_disconnected.aid); + sendWifiEvent(evt->event, jsvNewNull()); + break; + case EVENT_SOFTAPMODE_PROBEREQRECVED: + os_printf("Wifi AP: probe request from station " MACSTR ", rssi = %d\n", + MAC2STR(evt->event_info.ap_probereqrecved.mac), evt->event_info.ap_probereqrecved.rssi); + sendWifiEvent(evt->event, jsvNewNull()); + break; + default: + os_printf("Wifi: unexpected event %d\n", evt->event); + sendWifiEvent(evt->event, jsvNewNull()); + break; + } +} + +/** + * \brief Write an IP address as a dotted decimal string. + */ +// Note: This may be a duplicate ... it appears that we may have an existing function +// in network.c which does exactly this and more!! +// +static void ipAddrToString(struct ip_addr addr, char *string) { + os_sprintf(string, "%d.%d.%d.%d", ((char *)&addr)[0], ((char *)&addr)[1], ((char *)&addr)[2], ((char *)&addr)[3]); +} + +/** + * Convert an ESP8266 WiFi connect status to a string. + * + * Convert the status (as returned by `wifi_station_get_connect_status()`) to a string + * representation. + * \return A string representation of a WiFi connect status. + */ +static char *wifiConnectStatusToString(uint8 status) { + switch(status) { + case STATION_IDLE: + return "STATION_IDLE"; + case STATION_CONNECTING: + return "STATION_CONNECTING"; + case STATION_WRONG_PASSWORD: + return "STATION_WRONG_PASSWORD"; + case STATION_NO_AP_FOUND: + return "STATION_NO_AP_FOUND"; + case STATION_CONNECT_FAIL: + return "STATION_CONNECT_FAIL"; + case STATION_GOT_IP: + return "STATION_GOT_IP"; + default: + return "Unknown connect status!!"; + } +} From d8a5421d32ab4f483ed4c1f91aaf37ea75639dd8 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 5 Oct 2015 20:36:38 -0500 Subject: [PATCH 04/10] Logic fixes. --- libs/network/esp8266/jswrap_esp8266.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/libs/network/esp8266/jswrap_esp8266.c b/libs/network/esp8266/jswrap_esp8266.c index 45764ef35..9fe396a51 100644 --- a/libs/network/esp8266/jswrap_esp8266.c +++ b/libs/network/esp8266/jswrap_esp8266.c @@ -155,7 +155,7 @@ void jswrap_ESP8266WiFi_connect( // // Option #1 - Always perform a disconnect. // Option #2 - Perform a disconnect if the SSID or PASSWORD are different from current - // Option #3 - Fail the connect and invoke the callback telling the user that we are already connected. + // Option #3 - Fail the connect if we are already connected. // #define ISSUE_618 1 @@ -164,17 +164,21 @@ void jswrap_ESP8266WiFi_connect( #elif ISSUE_618 == 2 struct station_config existingConfig; wifi_station_get_config(&existingConfig); - if (os_strncpy((char *)existingConfig.ssid, (char *)stationConfig.ssid, 32) != 0 || - os_strncpy((char *)existingConfig.password, (char *)stationConfig.password, 64) != 0) { - wifi_station_disconnect(); + if (os_strncpy((char *)existingConfig.ssid, (char *)stationConfig.ssid, 32) == 0 && + os_strncpy((char *)existingConfig.password, (char *)stationConfig.password, 64) == 0) { + if (jsGotIpCallback != NULL) { + JsVar *params[2]; + params[0] = jsvNewFromInteger(STATION_GOT_IP); + params[1] = jsvNewNull(); + jsiQueueEvents(NULL, jsGotIpCallback, params, 2); + } + return; + + } else { + wifi_station_disconnect(); } #elif ISSUE_618 == 3 - if (jsGotIpCallback != NULL) { - JsVar *params[2]; - params[0] = jsvNewFromInteger(STATION_GOT_IP); - params[1] = jsvNewNull(); - jsiQueueEvents(NULL, jsGotIpCallback, params, 2); - } + // Add a return code to the function and return an already connected error. #endif } // Perform the network level connection From 8b0c2ee28256a98becd3945dac12e1bd285e5364 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 5 Oct 2015 20:47:48 -0500 Subject: [PATCH 05/10] No change commit just to force Travis to re-run. --- libs/network/esp8266/jswrap_esp8266.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/network/esp8266/jswrap_esp8266.c b/libs/network/esp8266/jswrap_esp8266.c index 9fe396a51..0292084f8 100644 --- a/libs/network/esp8266/jswrap_esp8266.c +++ b/libs/network/esp8266/jswrap_esp8266.c @@ -181,7 +181,7 @@ void jswrap_ESP8266WiFi_connect( // Add a return code to the function and return an already connected error. #endif } - // Perform the network level connection + // Perform the network level connection. wifi_station_connect(); os_printf("< jswrap_ESP8266WiFi_connect\n"); } From 52aba6230d7766d3205c2819a5bb7f28fd623d7f Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 5 Oct 2015 22:48:49 -0500 Subject: [PATCH 06/10] Updates for issue #599. --- Doxyfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doxyfile b/Doxyfile index 2ea47a253..173499465 100644 --- a/Doxyfile +++ b/Doxyfile @@ -652,7 +652,7 @@ WARN_LOGFILE = # directories like "/usr/src/myproject". Separate the files or directories # with spaces. -INPUT = src libs targets/stm32 +INPUT = src libs targets/stm32 targets/esp8266 # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is @@ -676,7 +676,7 @@ FILE_PATTERNS = # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. -RECURSIVE = NO +RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a @@ -698,7 +698,7 @@ EXCLUDE_SYMLINKS = NO # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = *.md # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -1214,7 +1214,7 @@ SERVER_BASED_SEARCH = NO # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. -GENERATE_LATEX = YES +GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be From 8149a7318a0797a0929bdee05b86f62d9069a395 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 5 Oct 2015 23:28:24 -0500 Subject: [PATCH 07/10] Removed jshFlashContainsCode function for #600. --- targets/esp8266/jshardware.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/targets/esp8266/jshardware.c b/targets/esp8266/jshardware.c index c8b7f4628..7a181b8e7 100644 --- a/targets/esp8266/jshardware.c +++ b/targets/esp8266/jshardware.c @@ -643,13 +643,6 @@ JsSysTime jshGetSystemTime() { // in us } // End of jshGetSystemTime - -bool jshFlashContainsCode() { - os_printf("ESP8266: jshFlashContainsCode\n"); - return false; -} - - /** * Set the current time in microseconds. */ From 6dcfc779f84eb4b1667ff26e594cf896eb7aba98 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 5 Oct 2015 23:41:01 -0500 Subject: [PATCH 08/10] Removed all the \brief additions. Issue #600. --- libs/network/esp8266/jswrap_esp8266.c | 44 +- libs/network/esp8266/network_esp8266.c | 2548 ++++++++++++------------ libs/network/network.c | 462 ++--- src/jsdevices.c | 24 +- src/jsinteractive.c | 18 +- src/jspin.c | 908 ++++----- src/jsspi.c | 10 +- src/jsutils.c | 8 +- src/jsvariterator.c | 1060 +++++----- src/jswrap_io.c | 1152 +++++------ src/jswrap_pin.c | 4 +- src/jswrap_spi_i2c.c | 1256 ++++++------ targets/esp8266/esp8266_board_utils.c | 4 +- targets/esp8266/jshardware.c | 53 +- targets/esp8266/user_main.c | 542 ++--- 15 files changed, 4046 insertions(+), 4047 deletions(-) diff --git a/libs/network/esp8266/jswrap_esp8266.c b/libs/network/esp8266/jswrap_esp8266.c index 0292084f8..fdc706a48 100644 --- a/libs/network/esp8266/jswrap_esp8266.c +++ b/libs/network/esp8266/jswrap_esp8266.c @@ -188,7 +188,7 @@ void jswrap_ESP8266WiFi_connect( /** - * \brief Become an access point. + * Become an access point. * When we call this function we are instructing the ESP8266 to set itself up as an * access point to allow other WiFi stations to connect to it. In order to be an access * point, the ESP8266 needs to know the SSID it should use as well as the password used @@ -261,7 +261,7 @@ void jswrap_ESP8266WiFi_beAccessPoint( /** - * \brief Determine the list of access points available to us. + * Determine the list of access points available to us. */ /*JSON{ "type" : "staticmethod", @@ -299,7 +299,7 @@ void jswrap_ESP8266WiFi_getAccessPoints( /** - * \brief Disconnect the station from the access point. + * Disconnect the station from the access point. */ /*JSON{ "type" : "staticmethod", @@ -324,7 +324,7 @@ void jswrap_ESP8266WiFi_restart() { /** - * \brief Register a callback function that will be invoked when a WiFi event is detected. + * Register a callback function that will be invoked when a WiFi event is detected. */ /*JSON{ "type" : "staticmethod", @@ -364,7 +364,7 @@ void jswrap_ESP8266WiFi_onWiFiEvent( /** - * \brief Set whether or not the ESP8266 will perform an auto connect on startup. + * Set whether or not the ESP8266 will perform an auto connect on startup. * A value of true means it will while a value of false means it won't. */ /*JSON{ @@ -396,7 +396,7 @@ void jswrap_ESP8266WiFi_setAutoConnect( /** - * \brief Retrieve whether or not the ESP8266 will perform an auto connect on startup. + * Retrieve whether or not the ESP8266 will perform an auto connect on startup. * A value of 1 means it will while a value of zero means it won't. */ /*JSON{ @@ -413,7 +413,7 @@ JsVar *jswrap_ESP8266WiFi_getAutoConnect() { /** - * \brief Retrieve the reset information that is stored when event the ESP8266 resets. + * Retrieve the reset information that is stored when event the ESP8266 resets. * The result will be a JS object containing the details. */ /*JSON{ @@ -439,7 +439,7 @@ JsVar *jswrap_ESP8266WiFi_getRstInfo() { /** - * \brief Return an object that contains details about the state of the ESP8266. + * Return an object that contains details about the state of the ESP8266. * - `sdkVersion` - Version of the SDK. * - `cpuFrequency` - CPU operating frequency. * - `freeHeap` - Amount of free heap. @@ -466,7 +466,7 @@ JsVar *jswrap_ESP8266WiFi_getState() { } /** - * \brief Return the value of an integer representation (4 bytes) of IP address + * Return the value of an integer representation (4 bytes) of IP address * as a string. */ /*JSON{ @@ -492,7 +492,7 @@ JsVar *jswrap_ESP8266WiFi_getAddressAsString( /** - * \brief Retrieve the IP information about this network interface and return a JS + * Retrieve the IP information about this network interface and return a JS * object that contains its details. * The object will have the following properties defined upon it: * - `ip` - The IP address of the interface. @@ -520,7 +520,7 @@ JsVar *jswrap_ESP8266WiFi_getIPInfo() { /** - * \brief Query the station configuration and return a JS object that represents the + * Query the station configuration and return a JS object that represents the * current settings. * The object will have the following properties: * @@ -551,7 +551,7 @@ JsVar *jswrap_ESP8266WiFi_getStationConfig() { /** - * \brief Determine the list of connected stations and return them. + * Determine the list of connected stations and return them. */ /*JSON{ "type" : "staticmethod", @@ -579,7 +579,7 @@ JsVar *jswrap_ESP8266WiFi_getConnectedStations() { /** - * \brief Get the signal strength. + * Get the signal strength. */ /*JSON{ "type" : "staticmethod", @@ -596,7 +596,7 @@ JsVar *jswrap_ESP8266WiFi_getRSSI() { /** - * \brief Initialize the ESP8266 environment. + * Initialize the ESP8266 environment. */ /*JSON{ "type" : "staticmethod", @@ -730,7 +730,7 @@ void jswrap_ESP8266WiFi_socketEnd( /** - * \brief Perform a network ping request. + * Perform a network ping request. * The parameter can be either a String or a numeric IP address. */ /*JSON{ @@ -796,7 +796,7 @@ void jswrap_ESP8266WiFi_ping( /** - * \brief Dump the data in the socket. + * Dump the data in the socket. */ /*JSON{ "type" : "staticmethod", @@ -815,7 +815,7 @@ void jswrap_ESP8266WiFi_dumpSocket( } /** - * \brief Null terminate a string. + * Null terminate a string. */ static char *nullTerminateString(char *target, char *source, int sourceLength) { os_strncpy(target, source, sourceLength); @@ -834,7 +834,7 @@ static void setupJsNetwork() { /** - * \brief Handle receiving a response from a ping reply. + * 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 @@ -867,7 +867,7 @@ static void pingRecvCB(void *pingOpt, void *pingResponse) { /** - * \brief Callback function that is invoked at the culmination of a scan. + * Callback function that is invoked at the culmination of a scan. */ static void scanCB(void *arg, STATUS status) { /** @@ -934,7 +934,7 @@ static void scanCB(void *arg, STATUS status) { /** - * \brief Invoke the JavaScript callback to notify the program that an ESP8266 + * Invoke the JavaScript callback to notify the program that an ESP8266 * WiFi event has occurred. */ static void sendWifiEvent(uint32 eventType, JsVar *details) { @@ -962,7 +962,7 @@ static void sendWifiEvent(uint32 eventType, JsVar *details) { /** - * \brief ESP8266 WiFi Event handler. + * 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() @@ -1030,7 +1030,7 @@ static void wifiEventHandler(System_Event_t *evt) { } /** - * \brief Write an IP address as a dotted decimal string. + * Write an IP address as a dotted decimal string. */ // Note: This may be a duplicate ... it appears that we may have an existing function // in network.c which does exactly this and more!! diff --git a/libs/network/esp8266/network_esp8266.c b/libs/network/esp8266/network_esp8266.c index 72ce01548..bb4d1fa35 100644 --- a/libs/network/esp8266/network_esp8266.c +++ b/libs/network/esp8266/network_esp8266.c @@ -1,1274 +1,1274 @@ -/* -5 * network_esp8266_board.c - - * - * Created on: Aug 29, 2015 - * Author: kolban - */ - -/** - * This file contains the implementation of the ESP8266_BOARD network interfaces at the TCP/IP - * level. - * - * Design notes - * ------------ - * We maintain an array of socketData structures. The number of such structures is defined in the - * MAX_SOCKETS define. The private variable that contains the array is called "socketArray". - * Each one of these array instances represents a possible socket structure that we can use. - * - * Each socket maintains state and the transitions are shown in a separate state diagram. In - * summary, the possible states are: - * - * * SOCKET_STATE_UNUSED - * * SOCKET_STATE_IDLE - * * SOCKET_STATE_CONNECTING - * * SOCKET_STATE_TRANSMITTING - * * SOCKET_STATE_CLOSING - * * SOCKET_STATE_HOST_RESOLVING - * * SOCKET_STATE_ERROR - * - * Each socket also maintains its creation purpose. There are three ways a socket - * can have been created: - * - * SOCKET_CREATED_SERVER - The socket was created as a server. - * SOCKET_CREATED_OUTBOUND - The socket was created by an out-bound. - * SOCKET_CREATED_INBOUND - The socket was created by an in-bound. - */ -// ESP8266 specific includes -#undef ESPSDK_1_3_0 -#include -#include -#include -#include -#include -#include - -#define _GCC_WRAP_STDINT_H -typedef long long int64_t; - -#include "network_esp8266.h" -#include "esp8266_board_utils.h" - -/** - * \brief The maximum number of concurrently open sockets we support. - * We should probably pair this with the ESP8266 concept of the maximum number of sockets - * that an ESP8266 instance can also support. - */ -#define MAX_SOCKETS (10) - -#define LOG os_printf - -static struct socketData *getSocketData(int s); - -/** - * \brief The next socketId to be used. - */ -static int g_nextSocketId = 0; - -static int getServerSocketByLocalPort(unsigned short port); -static void setSocketInError(int socketId, char *msg, int code); -static void dumpEspConn(struct espconn *pEspConn); -static struct socketData *allocateNewSocket(); -static void doClose(int socketId); -static void releaseSocket(int socketId); -static void resetSocketByData(struct socketData *pSocketData); -static void resetSocketById(int sckt); - -static void esp8266_callback_connectCB_inbound(void *arg); -static void esp8266_callback_connectCB_outbound(void *arg); -static void esp8266_callback_disconnectCB(void *arg); -static void esp8266_callback_sentCB(void *arg); -static void esp8266_callback_writeFinishedCB(void *arg); -static void esp8266_callback_recvCB(void *arg, char *pData, unsigned short len); -static void esp8266_callback_reconnectCB(void *arg, sint8 err); - -/** - * \brief A data structure that represents a memory buffer. - * A memory buffer is an object that represents a sized piece of memory. Given a - * memory buffer object, we know how big it is and can set or get data from it. - */ -struct memoryBuffer { - /** - * \brief The size of data associated with this buffer. - */ - size_t length; - - /** - * \brief A pointer to the memory associated with this buffer. This should be - * NULL if `length` is 0. - */ - uint8 *buf; -}; - -static uint8 *memoryBuffer_read(struct memoryBuffer *pMemoryBuffer, size_t readSize); -static uint8 *memoryBuffer_append(struct memoryBuffer *pMemoryBuffer, uint8 *pNewData, size_t length); -static void memoryBuffer_delete(struct memoryBuffer *pMemoryBuffer); -static int memoryBuffer_getSize(struct memoryBuffer *pMemoryBuffer); - -/** - * \brief The potential states for a socket. - * See the socket state diagram. - */ -enum SOCKET_STATE { - SOCKET_STATE_UNUSED, //!< The socket is unused - SOCKET_STATE_IDLE, //!< The socket is idle - SOCKET_STATE_CONNECTING, //!< The socket is connecting - SOCKET_STATE_TRANSMITTING, //!< The socket is transmitting - SOCKET_STATE_CLOSING, //!< The socket is closing - SOCKET_STATE_HOST_RESOLVING, //!< Resolving a a hostname - SOCKET_STATE_ERROR //!< The socket is in error -}; - -/** - * \brief How was the socket created. - */ -enum SOCKET_CREATION_TYPE { - SOCKET_CREATED_NONE, //!< The socket has not yet been created. - SOCKET_CREATED_SERVER, //!< The socket was created as a server. - SOCKET_CREATED_OUTBOUND, //!< The socket was created outbound. - SOCKET_CREATED_INBOUND //!< The socket was created inbound. -}; - -/** - * Here are some notes on the accepted list algorithms. - * - * tail - Where old entries are removed - * head - Where new entries are added - * - * When tail = head - the list is empty - * - * When a new entry is added: - * error: mod(head+1) == tail // List full - * *head = new entry - * head = mod(head+1) - * - * When an old entry is removed - * error: tail == head // List empty - * retrieved entry = *tail - * tail = mod(tail+1) - * - * - * 0 1 2 3 4 5 6 7 8 - * [ ][ ][ ][ ][ ][ ][ ][ ][ ] - */ - -/** - * \brief The maximum number of accepted sockets that can be remembered. - */ -#define MAX_ACCEPTED_SOCKETS (10) - -/** - * \brief The core socket structure. - * The structure is initialized by resetSocket. - */ -struct socketData { - int socketId; //!< The id of THIS socket. - enum SOCKET_STATE state; //!< What is the socket state? - enum SOCKET_CREATION_TYPE creationType; //!< How was the socket created? - bool isConnected; //!< Is this socket connected? - bool shouldClose; //!< Should this socket close when it can? - - struct espconn *pEspconn; //!< The ESPConn structure. - - struct memoryBuffer txMemoryBuffer; //!< Data to be transmitted. - uint8 *currentTx; //!< Data currently being transmitted. - uint8 *rxBuf; //!< Data received (inbound). - size_t rxBufLen; //!< The length of data in the buffer ready for consumption. - - char *errorMsg; //!< Error message. - int errorCode; //!< Error code. - - /** - * \brief A list of accepted sockets. - * This array contains the storage of a list of sockets that have been accepted by this - * server socket but have not yet been delivered to Espruino. A `head` and `tail` - * pair of indices are also associated. - */ - int acceptedSockets[MAX_ACCEPTED_SOCKETS]; - - /** - * \brief The head of the list of accepted sockets. - * The index into `acceptedSockets` where the next accepted socket will be placed. - */ - uint8 acceptedSocketsHead; - - /** - * \brief The tail of the list of accepted sockets. - * The index into `acceptedSockets` where the next accepted socket will be retrieved. - */ - uint8 acceptedSocketsTail; -}; - - -/** - * \brief An array of socket data structures. - */ -static struct socketData socketArray[MAX_SOCKETS]; - - -/** - * \brief Write the details of a socket to the debug log. - * The data associated with the socket is dumped to the debug log. - */ -void esp8266_dumpSocket( - int socketId //!< The ID of the socket data structure to be logged. - ) { - struct socketData *pSocketData = getSocketData(socketId); - LOG("Dump of socket=%d\n", socketId); - LOG(" - isConnected=%d", pSocketData->isConnected); - char *creationTypeMsg; - switch(pSocketData->creationType) { - case SOCKET_CREATED_NONE: - creationTypeMsg = "SOCKET_CREATED_NONE"; - break; - case SOCKET_CREATED_INBOUND: - creationTypeMsg = "SOCKET_CREATED_INBOUND"; - break; - case SOCKET_CREATED_OUTBOUND: - creationTypeMsg = "SOCKET_CREATED_OUTBOUND"; - break; - case SOCKET_CREATED_SERVER: - creationTypeMsg = "SOCKET_CREATED_SERVER"; - break; - } - LOG(", creationType=%s", creationTypeMsg); - LOG(", acceptedSockets=["); - int s=pSocketData->acceptedSocketsTail; - while(s != pSocketData->acceptedSocketsHead) { - LOG(" %d", pSocketData->acceptedSockets[s]); - s = (s+1)%MAX_ACCEPTED_SOCKETS; - } - LOG("]"); - LOG(", rxBufLen=%d", pSocketData->rxBufLen); - LOG(", tx length=%d", memoryBuffer_getSize(&(pSocketData->txMemoryBuffer))); - LOG(", currentTx=0x%d", (int)pSocketData->currentTx); - char *stateMsg; - switch(pSocketData->state) { - case SOCKET_STATE_CLOSING: - stateMsg = "SOCKET_STATE_CLOSING"; - break; - case SOCKET_STATE_CONNECTING: - stateMsg = "SOCKET_STATE_CONNECTING"; - break; - case SOCKET_STATE_ERROR: - stateMsg = "SOCKET_STATE_ERROR"; - break; - case SOCKET_STATE_IDLE: - stateMsg = "SOCKET_STATE_IDLE"; - break; - case SOCKET_STATE_TRANSMITTING: - stateMsg = "SOCKET_STATE_TRANSMITTING"; - break; - case SOCKET_STATE_HOST_RESOLVING: - stateMsg = "SOCKET_STATE_HOST_RESOLVING"; - break; - case SOCKET_STATE_UNUSED: - stateMsg = "SOCKET_STATE_UNUSED"; - break; - default: - stateMsg = "Unexpected state!!"; - break; - } - LOG(", state=%s", stateMsg); - LOG(", errorCode=%d", pSocketData->errorCode); - - // Print the errorMsg if it has anything to say - if (pSocketData->errorMsg != NULL && strlen(pSocketData->errorMsg) > 0) { - LOG(", errorMsg=\"%s\"", pSocketData->errorMsg); - } - - LOG("\n"); -} - - -/** - * \brief Dump a struct espconn (for debugging purposes). - */ -static void dumpEspConn( - struct espconn *pEspConn //!< - ) { - char ipString[20]; - LOG("Dump of espconn: 0x%x\n", (int)pEspConn); - if (pEspConn == NULL) { - return; - } - switch(pEspConn->type) { - case ESPCONN_TCP: - LOG(" - type = TCP\n"); - LOG(" - local address = %d.%d.%d.%d [%d]\n", - pEspConn->proto.tcp->local_ip[0], - pEspConn->proto.tcp->local_ip[1], - pEspConn->proto.tcp->local_ip[2], - pEspConn->proto.tcp->local_ip[3], - pEspConn->proto.tcp->local_port); - LOG(" - remote address = %d.%d.%d.%d [%d]\n", - pEspConn->proto.tcp->remote_ip[0], - pEspConn->proto.tcp->remote_ip[1], - pEspConn->proto.tcp->remote_ip[2], - pEspConn->proto.tcp->remote_ip[3], - pEspConn->proto.tcp->remote_port); - break; - case ESPCONN_UDP: - LOG(" - type = UDP\n"); - LOG(" - local_port = %d\n", pEspConn->proto.udp->local_port); - LOG(" - local_ip = %d.%d.%d.%d\n", - pEspConn->proto.tcp->local_ip[0], - pEspConn->proto.tcp->local_ip[1], - pEspConn->proto.tcp->local_ip[2], - pEspConn->proto.tcp->local_ip[3]); - LOG(" - remote_port = %d\n", pEspConn->proto.udp->remote_port); - LOG(" - remote_ip = %d.%d.%d.%d\n", - pEspConn->proto.tcp->remote_ip[0], - pEspConn->proto.tcp->remote_ip[1], - pEspConn->proto.tcp->remote_ip[2], - pEspConn->proto.tcp->remote_ip[3]); - break; - default: - LOG(" - type = Unknown!! 0x%x\n", pEspConn->type); - } - switch(pEspConn->state) { - case ESPCONN_NONE: - LOG(" - state=NONE"); - break; - case ESPCONN_WAIT: - LOG(" - state=WAIT"); - break; - case ESPCONN_LISTEN: - LOG(" - state=LISTEN"); - break; - case ESPCONN_CONNECT: - LOG(" - state=CONNECT"); - break; - case ESPCONN_WRITE: - LOG(" - state=WRITE"); - break; - case ESPCONN_READ: - LOG(" - state=READ"); - break; - case ESPCONN_CLOSE: - LOG(" - state=CLOSE"); - break; - default: - LOG(" - state=unknown!!"); - break; - } - LOG(", link_cnt=%d", pEspConn->link_cnt); - LOG(", reverse=0x%x\n", (unsigned int)pEspConn->reverse); -} - - -/** - * \brief Get the next new global socket id. - * \return A new socketId that is assured to be unique. - */ -static int getNextGlobalSocketId() { - int ret = g_nextSocketId; - g_nextSocketId++; - return ret; -} - - -/** - * \brief Allocate a new socket - * Look for the first free socket in the array of sockets and return the first one - * that is available. The socketId property is set to a unique and new socketId value - * that will not previously have been seen. - * \return The socketData structure for the returned socket. - */ -static struct socketData *allocateNewSocket() { - // Walk through each of the sockets in the array of possible sockets and stop - // at the first one that is flagged as not in use. For that socket, set its - // socketId to the next global socketId value. - int i; - for (i=0; isocketId == socketId) { - return pSocketData; - } - pSocketData++; - } - return NULL; -} - - -/** - * \brief Find the server socket that is bound to the given local port. - * \return The socket id of the socket listening on the given port or -1 if there is no - * server socket that matches. - */ -static int getServerSocketByLocalPort( - unsigned short port //!< The port number on which a server socket is listening. - ) { - // Loop through each of the sockets in the socket array looking for a socket - // that is inuse, a server and has a local_port of the passed in port number. - int socketArrayIndex; - struct socketData *pSocketData = socketArray; - for (socketArrayIndex=0; socketArrayIndexstate != SOCKET_STATE_UNUSED && - pSocketData->creationType == SOCKET_CREATED_SERVER && - pSocketData->pEspconn->proto.tcp->local_port == port) - { - return pSocketData->socketId; - } - pSocketData++; - } // End of for each socket - return -1; -} - - -/** - * \brief Reset the socket to its clean and unused state. - * The socket is found by its socket id. - */ -static void resetSocketById( - int sckt //!< The socket id to be reset. - ) { - struct socketData *pSocketData = getSocketData(sckt); - resetSocketByData(pSocketData); -} - -/** - * \brief Reset the socket to its clean and unused state. - * The socket is specified by its socket data pointer. - */ -static void resetSocketByData( - struct socketData *pSocketData //!< The data pointer to the socket. - ) { - assert(pSocketData != NULL); - - memoryBuffer_delete(&pSocketData->txMemoryBuffer); - - pSocketData->pEspconn = NULL; - pSocketData->state = SOCKET_STATE_UNUSED; - pSocketData->rxBuf = NULL; - pSocketData->rxBufLen = 0; - pSocketData->creationType = SOCKET_CREATED_NONE; - pSocketData->isConnected = false; - pSocketData->shouldClose = false; - pSocketData->errorMsg = ""; - pSocketData->errorCode = 0; - pSocketData->socketId = -1; - - pSocketData->acceptedSocketsHead = 0; // Set the head to 0 - pSocketData->acceptedSocketsTail = 0; // Set the tail to 9. - pSocketData->pEspconn = NULL; -} - - -/** - * \brief Release the socket and return it to the free pool. - */ -static void releaseSocket( - int socketId //!< The socket id of the socket to be released. - ) { - os_printf("> releaseSocket: %d\n", socketId); - esp8266_dumpSocket(socketId); - - struct socketData *pSocketData = getSocketData(socketId); - assert(pSocketData->state != SOCKET_STATE_UNUSED); - - if (memoryBuffer_getSize(&pSocketData->txMemoryBuffer) > 0) { - os_printf(" - Oh oh ... attempt to close socket while the TX memoryBuffer is not empty!\n"); - } - if (pSocketData->rxBuf != NULL || pSocketData->rxBufLen != 0) { - os_printf(" - Oh oh ... attempt to close socket while the rxBuffer is not empty!\n"); - } - - // If this socket is not an incoming socket that means that the espconn structure was created - // by us and we should release the storage we allocated. - if (pSocketData->creationType != SOCKET_CREATED_INBOUND) { - os_free(pSocketData->pEspconn->proto.tcp); - pSocketData->pEspconn->proto.tcp = NULL; - os_free(pSocketData->pEspconn); - pSocketData->pEspconn = NULL; - } - resetSocketByData(pSocketData); - os_printf("< releaseSocket\n"); -} - - -/** - * \brief Initialize the ESP8266_BOARD environment. - * Walk through each of the sockets and initialize each one. - */ -void netInit_esp8266_board() { - int socketArrayIndex; - struct socketData *pSocketData = socketArray; - for (socketArrayIndex=0; socketArrayIndexstate != SOCKET_STATE_CLOSING) { - int rc = espconn_disconnect(pSocketData->pEspconn); - pSocketData->state = SOCKET_STATE_CLOSING; - - if (rc != 0) { - os_printf("espconn_disconnect: rc=%d\n", rc); - setSocketInError(socketId, "espconn_disconnect", rc); - } - } - // Our existing state on entry was SOCKET_STATE_CLOSING which means that we got here - // because we were previously flagged as closing. - else { - releaseSocket(socketId); - } -} - - -/** - * \brief Set the given socket as being in error supplying a message and a code. - * The socket state is placed in `SOCKET_STATE_ERROR`. - */ -static void setSocketInError( - int socketId, //!< The socket id that is being flagged as in error. - char *msg, //!< A message to associate with the error. - int code //!< A low level error code. - ) { - struct socketData *pSocketData = getSocketData(socketId); - pSocketData->state = SOCKET_STATE_ERROR; - pSocketData->errorMsg = msg; - pSocketData->errorCode = code; -} - -/** - * \brief Callback function registered to the ESP8266 environment that is - * invoked when a new inbound connection has been formed. - * A new connection - * can occur when the ESP8266 makes a call out to a partner (in that - * case the ESP8266 is acting as a client) or a new connection can - * occur when a partner calls into a listening ESP8266. In that case - * the ESP8266 is acting as a server. - */ -static void esp8266_callback_connectCB_inbound( - void *arg //!< - ) { - os_printf(">> connectCB_inbound\n"); - struct espconn *pEspconn = (struct espconn *)arg; - assert(pEspconn != NULL); - - espconn_regist_disconcb(pEspconn, esp8266_callback_disconnectCB); - espconn_regist_reconcb(pEspconn, esp8266_callback_reconnectCB); - espconn_regist_sentcb(pEspconn, esp8266_callback_sentCB); - espconn_regist_recvcb(pEspconn, esp8266_callback_recvCB); - espconn_regist_write_finish(pEspconn, esp8266_callback_writeFinishedCB); - - dumpEspConn(pEspconn); - - int inboundSocket = getServerSocketByLocalPort(pEspconn->proto.tcp->local_port); - assert(inboundSocket != -1); - struct socketData *pSocketData = getSocketData(inboundSocket); - assert(pSocketData != NULL); - - esp8266_dumpSocket(pSocketData->socketId); - - os_printf("** new client has connected to us **\n"); - - if ((pSocketData->acceptedSocketsHead + 1) % MAX_ACCEPTED_SOCKETS == pSocketData->acceptedSocketsTail) { - os_printf("WARNING!! - Discarding inbound client because we have too many accepted clients.\n"); - os_printf("<< connectCB_inbound\n"); - return; - } - - struct socketData *pClientSocketData = allocateNewSocket(); - if (pClientSocketData == NULL) { - os_printf("!!! Ran out of sockets !!!\n"); - return; - } - assert(pClientSocketData != NULL); - pClientSocketData->pEspconn = pEspconn; - pClientSocketData->pEspconn->reverse = pClientSocketData; - pClientSocketData->creationType = SOCKET_CREATED_INBOUND; - pClientSocketData->isConnected = true; - pClientSocketData->state = SOCKET_STATE_IDLE; - - pSocketData->acceptedSockets[pSocketData->acceptedSocketsHead] = pClientSocketData->socketId; - pSocketData->acceptedSocketsHead = (pSocketData->acceptedSocketsHead + 1) % MAX_ACCEPTED_SOCKETS; - - os_printf("<< connectCB_inbound\n"); -} - -/** - * \brief Callback function registered to the ESP8266 environment that is - * invoked when a new outbound connection has been formed. - */ -static void esp8266_callback_connectCB_outbound( - void *arg //!< A pointer to a `struct espconn`. - ) { - os_printf(">> connectCB_outbound\n"); - struct espconn *pEspconn = (struct espconn *)arg; - assert(pEspconn != NULL); - - dumpEspConn(pEspconn); - - struct socketData *pSocketData = (struct socketData *)pEspconn->reverse; - assert(pSocketData != NULL); - - esp8266_dumpSocket(pSocketData->socketId); - - // Flag the socket as connected to a partner. - pSocketData->isConnected = true; - - assert(pSocketData->state == SOCKET_STATE_CONNECTING); - if (pSocketData->shouldClose) { - doClose(pSocketData->socketId); - } else { - pSocketData->state = SOCKET_STATE_IDLE; - } - os_printf("<< connectCB_outbound\n"); -} - - -/** - * \brief Callback function registered to the ESP8266 environment that is - * Invoked when a previous connection has been disconnected. - */ -static void esp8266_callback_disconnectCB( - void *arg //!< A pointer to a `struct espconn`. - ) { - struct espconn *pEspconn = (struct espconn *)arg; - struct socketData *pSocketData = (struct socketData *)pEspconn->reverse; - assert(pSocketData != NULL); - assert(pSocketData->state != SOCKET_STATE_UNUSED); - - os_printf(">> disconnectCB\n"); - dumpEspConn(pEspconn); - esp8266_dumpSocket(pSocketData->socketId); - - // If the socket state is SOCKET_STATE_CLOSING then that means we can release the socket. The reason - // for this is that the last thing the user did was request an explicit socket close. - if (pSocketData->state == SOCKET_STATE_CLOSING) { - releaseSocket(pSocketData->socketId); - } else { - pSocketData->state = SOCKET_STATE_CLOSING; - pSocketData->isConnected = false; - } - os_printf("<< disconnectCB\n"); -} - - -/** - * - */ -static void esp8266_callback_writeFinishedCB( - void *arg //!< A pointer to a `struct espconn`. - ) { - os_printf(">> writeFinishedCB\n"); - struct espconn *pEspconn = (struct espconn *)arg; - struct socketData *pSocketData = (struct socketData *)pEspconn->reverse; - if (pSocketData->currentTx != NULL) { - os_free(pSocketData->currentTx); - pSocketData->currentTx = NULL; - } - os_printf("<< writeFinishedCB\n"); -} - - -/** - * \brief Error handler callback. - * Although this is called reconnect by Espressif, this is really an error handler - * routine. It will be called when an error is detected. - */ -static void esp8266_callback_reconnectCB( - void *arg, //!< A pointer to a `struct espconn`. - sint8 err //!< The error code. - ) { - os_printf(">> reconnectCB: Error code is: %d - %s\n", err, esp8266_errorToString(err)); - os_printf("<< reconnectCB"); -} - - -/** - * \brief Callback function registered to the ESP8266 environment that is - * invoked when a send operation has been completed. - */ -static void esp8266_callback_sentCB( - void *arg //!< A pointer to a `struct espconn`. - ) { - os_printf(">> sendCB\n"); - struct espconn *pEspconn = (struct espconn *)arg; - struct socketData *pSocketData = (struct socketData *)pEspconn->reverse; - - assert(pSocketData != NULL); - assert(pSocketData->state == SOCKET_STATE_TRANSMITTING); - - // We have transmitted the data ... which means that the data that was in the transmission - // buffer can be released. - if (pSocketData->currentTx != NULL) { - os_free(pSocketData->currentTx); - pSocketData->currentTx = NULL; - } - - if (pSocketData->shouldClose) { - doClose(pSocketData->socketId); - } else { - pSocketData->state = SOCKET_STATE_IDLE; - } - os_printf("<< sendCB\n"); -} - - -/** - * \brief ESP8266 callback function that is invoked when new data has arrived over - * the TCP/IP connection. - */ -static void esp8266_callback_recvCB( - void *arg, //!< A pointer to a `struct espconn`. - char *pData, //!< A pointer to data received over the socket. - unsigned short len //!< The length of the data. - ) { - struct espconn *pEspconn = (struct espconn *)arg; - struct socketData *pSocketData = (struct socketData *)pEspconn->reverse; - - assert(pSocketData != NULL); - assert(pSocketData->state != SOCKET_STATE_UNUSED); - - os_printf(">> recvCB for socket=%d, length=%d\n", pSocketData->socketId, len); - - // If we don't have any existing unconsumed data then malloc some storage and - // copy the received data into that storage. - if (pSocketData->rxBufLen == 0) { - pSocketData->rxBuf = (void *)os_malloc(len); - memcpy(pSocketData->rxBuf, pData, len); - pSocketData->rxBufLen = len; - } else { -// Allocate a new buffer big enough for the original data and the new data -// Copy the original data to the start of the new buffer ... -// Copy the new new data to the offset into the new buffer just after -// the original data. -// Release the original data. -// Update the socket data. - uint8 *pNewBuf = (uint8 *)os_malloc(len + pSocketData->rxBufLen); - memcpy(pNewBuf, pSocketData->rxBuf, pSocketData->rxBufLen); - memcpy(pNewBuf + pSocketData->rxBufLen, pData, len); - os_free(pSocketData->rxBuf); - pSocketData->rxBuf = pNewBuf; - pSocketData->rxBufLen += len; - } // End of new data allocated. - dumpEspConn(pEspconn); - os_printf("<< recvCB\n"); - -} - - -// ------------------------------------------------- - -/** - * \brief Define the implementation functions for the logical network functions. - */ -void netSetCallbacks_esp8266_board( - JsNetwork *net //!< The Network we are going to use. - ) { - net->idle = net_ESP8266_BOARD_idle; - net->checkError = net_ESP8266_BOARD_checkError; - net->createsocket = net_ESP8266_BOARD_createSocket; - net->closesocket = net_ESP8266_BOARD_closeSocket; - net->accept = net_ESP8266_BOARD_accept; - net->gethostbyname = net_ESP8266_BOARD_gethostbyname; - net->recv = net_ESP8266_BOARD_recv; - net->send = net_ESP8266_BOARD_send; -} - - -/** - * \brief Determine if there is a new client connection on the server socket. - * This function is called to poll to see if the serverSckt has a new - * accepted connection (socket) and, if it does, return it else return -1 to indicate - * that there was no new accepted socket. - */ -int net_ESP8266_BOARD_accept( - JsNetwork *net, //!< The Network we are going to use to create the socket. - int serverSckt //!< The socket that we are checking to see if there is a new client connection. - ) { - //os_printf("> net_ESP8266_BOARD_accept\n"); - struct socketData *pSocketData = getSocketData(serverSckt); - assert(pSocketData->state != SOCKET_STATE_UNUSED); - assert(pSocketData->creationType == SOCKET_CREATED_SERVER); - - // If the list is empty, return. - if (pSocketData->acceptedSocketsHead == pSocketData->acceptedSocketsTail) { - // Return -1 if there is no new client socket for this server. - return -1; - } - - // Return the 1st socket id that is in the list of accepted sockets. We also update the - // list to indicate that it has been read. - int acceptedSocketId = pSocketData->acceptedSockets[pSocketData->acceptedSocketsTail]; - pSocketData->acceptedSocketsTail = (pSocketData->acceptedSocketsTail + 1) % MAX_ACCEPTED_SOCKETS; - - os_printf("> net_ESP8266_BOARD_accept: Accepted a new socket, socketId=%d\n", acceptedSocketId); - return acceptedSocketId; -} - - -/** - * \brief Receive data from the network device. - * Returns the number of bytes received which may be 0 and -1 if there was an error. - */ -int net_ESP8266_BOARD_recv( - JsNetwork *net, //!< The Network we are going to use to create the socket. - int sckt, //!< The socket from which we are to receive data. - void *buf, //!< The storage buffer into which we will receive data. - size_t len //!< The length of the buffer. - ) { - struct socketData *pSocketData = getSocketData(sckt); - assert(pSocketData->state != SOCKET_STATE_UNUSED); - - // If there is no data in the receive buffer, then all we need do is return - // 0 bytes as the length of data moved. - if (pSocketData->rxBufLen == 0) { - if (pSocketData->state == SOCKET_STATE_CLOSING) { - return -1; - } - return 0; - } - - // If the receive buffer contains data and is it is able to fit in the buffer - // passed into us then we can copy all the data and the receive buffer will be clear. - if (pSocketData->rxBufLen <= len) { - memcpy(buf, pSocketData->rxBuf, pSocketData->rxBufLen); - int retLen = pSocketData->rxBufLen; - os_free(pSocketData->rxBuf); - pSocketData->rxBufLen = 0; - pSocketData->rxBuf = NULL; - return retLen; - } - - // If we are here, then we have more data in the receive buffer than is available - // to be returned in this request for data. So we have to copy the amount of data - // that is allowed to be returned and then strip that from the beginning of the - // receive buffer. - - // First we copy the data we are going to return. - memcpy(buf, pSocketData->rxBuf, len); - - // Next we allocate a new buffer and copy in the data we are not returning. - uint8 *pNewBuf = (uint8 *)os_malloc(pSocketData->rxBufLen-len); - memcpy(pNewBuf, pSocketData->rxBuf + len, pSocketData->rxBufLen-len); - - // Now we juggle pointers and release the original RX buffer now that we have a new - // one. It is likely that this algorithm can be significantly improved since there - // is a period of time where we might actuall have TWO copies of the data. - uint8 *pTemp = pSocketData->rxBuf; - pSocketData->rxBuf = pNewBuf; - pSocketData->rxBufLen = pSocketData->rxBufLen-len; - os_free(pTemp); - - return len; -} - - -/** - * \brief Send data to the partner. - * The return is the number of bytes actually transmitted which may also be - * 0 to indicate no bytes sent or -1 to indicate an error. For the ESP8266 implementation we - * will return 0 if the socket is not connected or we are in the `SOCKET_STATE_TRANSMITTING` - * state. - */ -int net_ESP8266_BOARD_send( - JsNetwork *net, //!< The Network we are going to use to create the socket. - int sckt, //!< The socket over which we will send data. - const void *buf, //!< The buffer containing the data to be sent. - size_t len //!< The length of data in the buffer to send. - ) { - os_printf("> net_ESP8266_BOARD_send: Request to send data to socket %d of size %d: ", sckt, len); - - struct socketData *pSocketData = getSocketData(sckt); - assert(pSocketData->state != SOCKET_STATE_UNUSED); - - // If we are not connected, then we can't currently send data. - if (pSocketData->isConnected == false) { - os_printf(" - Not connected\n"); - return 0; - } - - // If we are currently sending data, we can't send more. - if (pSocketData->state == SOCKET_STATE_TRANSMITTING) { - os_printf(" - Currently transmitting\n"); - return 0; - } - - // Log the content of the data we are sening. - esp8266_board_writeString(buf, len); - - os_printf("\n"); - - assert(pSocketData->state == SOCKET_STATE_IDLE); - - pSocketData->state = SOCKET_STATE_TRANSMITTING; - - // Copy the data that was passed to us to a private area. We do this because we must not - // assume that the data passed in will be available after this function returns. It may have - // been passed in on the stack. - assert(pSocketData->currentTx == NULL); - pSocketData->currentTx = (uint8_t *)os_malloc(len); - memcpy(pSocketData->currentTx, buf, len); - - // Send the data over the ESP8266 SDK. - int rc = espconn_send(pSocketData->pEspconn, pSocketData->currentTx, len); - if (rc < 0) { - setSocketInError(sckt, "espconn_send", rc); - os_free(pSocketData->currentTx); - pSocketData->currentTx = NULL; - return -1; - } - - esp8266_dumpSocket(sckt); - os_printf("< net_ESP8266_BOARD_send\n"); - return len; -} - - -/** - * \brief Perform idle processing. - * There is the possibility that we may wish to perform logic when we are idle. For the - * ESP8266 there is no specific idle network processing needed. - */ -void net_ESP8266_BOARD_idle( - JsNetwork *net //!< The Network we are part of. - ) { - // Don't echo here because it is called continuously - //os_printf("> net_ESP8266_BOARD_idle\n"); -} - - -/** - * \brief Check for errors. - * Returns true if there are NO errors. - */ -bool net_ESP8266_BOARD_checkError( - JsNetwork *net //!< The Network we are going to use to create the socket. - ) { - //os_printf("> net_ESP8266_BOARD_checkError\n"); - return true; -} - - -/** - * \brief Create a new socket. - * if `ipAddress == 0`, creates a server otherwise creates a client (and automatically connects). Returns >=0 on success. - */ -int net_ESP8266_BOARD_createSocket( - JsNetwork *net, //!< The Network we are going to use to create the socket. - uint32_t ipAddress, //!< The address of the partner of the socket or 0 if we are to be a server. - unsigned short port //!< The port number that the partner is listening upon. - ) { - os_printf("> net_ESP8266_BOARD_createSocket: host: %d.%d.%d.%d, port:%d \n", ((char *)(&ipAddress))[0], ((char *)(&ipAddress))[1], ((char *)(&ipAddress))[2], ((char *)(&ipAddress))[3], port); - - bool isServer = (ipAddress == 0); - - struct socketData *pSocketData = allocateNewSocket(); - if (pSocketData == NULL) { // No free socket - os_printf("< net_ESP8266_BOARD_createSocket: No free sockets\n"); - return -1; - } - - int newSocket = pSocketData->socketId; - pSocketData->pEspconn = (struct espconn *)os_malloc(sizeof(struct espconn)); - assert(pSocketData->pEspconn); - - struct espconn *pEspconn = pSocketData->pEspconn; - - pEspconn->type = ESPCONN_TCP; - pEspconn->state = ESPCONN_NONE; - pEspconn->proto.tcp = (esp_tcp *)os_malloc(sizeof(esp_tcp)); - pEspconn->reverse = pSocketData; - assert(pEspconn->proto.tcp != NULL); - os_memset(pEspconn->proto.tcp, 0, sizeof(esp_tcp)); - - // NOTE: We must not call these functions until AFTER we have allocated storage - // for the 'esp_tcp' structure. - espconn_regist_disconcb(pEspconn, esp8266_callback_disconnectCB); - espconn_regist_reconcb(pEspconn, esp8266_callback_reconnectCB); - espconn_regist_sentcb(pEspconn, esp8266_callback_sentCB); - espconn_regist_recvcb(pEspconn, esp8266_callback_recvCB); - espconn_regist_write_finish(pEspconn, esp8266_callback_writeFinishedCB); - - struct ip_info ipconfig; - wifi_get_ip_info(STATION_IF, &ipconfig); // Get the local IP address - os_memcpy(pEspconn->proto.tcp->local_ip, &ipconfig.ip, 4); - - // If we are not a server ... - if (isServer == false) { - pSocketData->state = SOCKET_STATE_CONNECTING; - pSocketData->creationType = SOCKET_CREATED_OUTBOUND; - pEspconn->proto.tcp->remote_port = port; - pEspconn->proto.tcp->local_port = espconn_port(); - - *(uint32 *)(pEspconn->proto.tcp->remote_ip) = ipAddress; - - // Ensure that we have flagged this socket as NOT connected - pSocketData->isConnected = false; - - espconn_regist_connectcb(pEspconn, esp8266_callback_connectCB_outbound); - - // Make a call to espconn_connect. - int rc = espconn_connect(pEspconn); - if (rc != 0) { - os_printf("Err: net_ESP8266_BOARD_createSocket -> espconn_connect returned: %d. Using local port: %d\n", rc, pEspconn->proto.tcp->local_port); - setSocketInError(newSocket, "espconn_connect", rc); - } - } - // If the ipAddress IS 0 ... then we are a server. - else - { - // We are going to set ourselves up as a server - pSocketData->state = SOCKET_STATE_IDLE; - pSocketData->creationType = SOCKET_CREATED_SERVER; - pEspconn->proto.tcp->local_port = port; - - espconn_regist_connectcb(pEspconn, esp8266_callback_connectCB_inbound); - - // Make a call to espconn_accept - int rc = espconn_accept(pEspconn); - if (rc != 0) { - os_printf("Err: net_ESP8266_BOARD_createSocket -> espconn_accept returned: %d. Using local port: %d\n", rc, pEspconn->proto.tcp->local_port); - setSocketInError(newSocket, "espconn_accept", rc); - } - } - - dumpEspConn(pEspconn); - os_printf("< net_ESP8266_BOARD_createSocket, socket=%d\n", newSocket); - return newSocket; -} - - -/** - * \brief Close a socket. - */ -void net_ESP8266_BOARD_closeSocket( - JsNetwork *net, //!< The Network we are going to use to create the socket. - int socketId //!< The socket to be closed. - ) { - os_printf("> net_ESP8266_BOARD_closeSocket, socket=%d\n", socketId); - - struct socketData *pSocketData = getSocketData(socketId); - - assert(pSocketData != NULL); - assert(pSocketData->state != SOCKET_STATE_UNUSED); // Shouldn't be closing an unused socket. - - dumpEspConn(pSocketData->pEspconn); - esp8266_dumpSocket(socketId); - - // How we close the socket is a function of what kind of socket it is. - if (pSocketData->creationType == SOCKET_CREATED_SERVER) { - int rc = espconn_delete(pSocketData->pEspconn); - if (rc != 0) { - os_printf("espconn_delete: rc=%d\n", rc); - } - } // End this is a server socket - else - { - if (pSocketData->state == SOCKET_STATE_IDLE || pSocketData->state == SOCKET_STATE_CLOSING) { - doClose(socketId); - } else { - pSocketData->shouldClose = true; - } - } // End this is a client socket -} - - -/** - * \brief Callback handler for espconn_gethostbyname. - * This is a function that will be called back by the ESP8266 when the resolution of - * a hostname has been completed. - */ -static void dnsFoundCallback(const char *hostName, ip_addr_t *ipAddr, void *arg) { - assert(arg != NULL); - - uint32_t *returnIp = (uint32_t *)arg; - - // ipAddr will be NULL if the IP address can not be resolved. - if (ipAddr == NULL) { - * returnIp = 0; - } else { - *returnIp = ipAddr->addr; - } -} - - -/** - * \brief Get an IP address from a name. - * Sets 'outIp' to 0 on failure and 0xFFFFFFFF on unknown. At some time later, the - * IP address will be properly updated. - */ -void net_ESP8266_BOARD_gethostbyname( - JsNetwork *net, //!< The Network we are going to use to create the socket. - char *hostName, //!< The string representing the hostname we wish to lookup. - uint32_t *outIp //!< The address into which the resolved IP address will be stored. - ) { - assert(hostName != NULL); - assert(outIp != NULL); - os_printf("> net_ESP8266_BOARD_gethostbyname: Resolving: %s\n", hostName); - int rc = espconn_gethostbyname((struct espconn *)outIp, hostName, (ip_addr_t *)outIp, dnsFoundCallback); - // A rc of ESPCONN_OK means that we have an IP and it was stored in outIp. - // A rc of ESPCONN_INPROGRESS means that we will get the IP on a callback. - if (rc == ESPCONN_INPROGRESS) { - *outIp = 0xFFFFFFFF; - } -} - - -// ---------------------------------------------------------------- - -/** - * The following section is all about a logical concept called a memoryBuffer. This is an - * abstract data type that contains data in memory. The operations we can perform upon it - * are: - - * o memoryBuffer_append - Append data to the end of the memory buffer. - * o memoryBuffer_read - Read a fixed number of bytes from the memory buffer. - * o memoryBuffer_delete - Delete the memory buffer. No further operations should be performed - * against it. - * o memoryBuffer_getSize - Get the size of data contained in the memory buffer. - */ - -/** - * \brief Delete all content of the memory buffer. - */ -static void memoryBuffer_delete( - struct memoryBuffer *pMemoryBuffer //!< - ) { - if (pMemoryBuffer->length > 0) { - os_free(pMemoryBuffer->buf); - pMemoryBuffer->buf = NULL; - pMemoryBuffer->length = 0; - } -} - - -/** - * \brief Append new data to the end of the existing memory buffer. - */ -static uint8 *memoryBuffer_append( - struct memoryBuffer *pMemoryBuffer, //!< - uint8 *pNewData, //!< - size_t length //!< - ) { - assert(pMemoryBuffer != NULL); - - if (length == 0) { - return pMemoryBuffer->buf; - } - - assert(pNewData != NULL); - - // Handle the memory buffer being empty. - if (pMemoryBuffer->length == 0) { - pMemoryBuffer->buf = (uint8 *)os_malloc(length); - if (pMemoryBuffer->buf == NULL) { // Out of memory - jsError("malloc failed at memoryBuffer_append trying to allocate %d", length); - } else { - memcpy(pMemoryBuffer->buf, pNewData, length); - pMemoryBuffer->length = length; - } - } else { - // The memory buffer was not empty, so we append data. - int newSize = pMemoryBuffer->length + length; - uint8 *resizedStorage = (uint8 *)os_realloc(pMemoryBuffer->buf, newSize); - if (resizedStorage != NULL) { - pMemoryBuffer->buf = resizedStorage; - memcpy(pMemoryBuffer->buf + length, pNewData, length); - pMemoryBuffer->length = newSize; - } - } - return pMemoryBuffer->buf; -} - - -/** - * \brief Return how much data is stored in the memory buffer. - */ -static int memoryBuffer_getSize( - struct memoryBuffer *pMemoryBuffer //!< - ) { - assert(pMemoryBuffer != NULL); - return pMemoryBuffer->length; -} - - -/** - * \brief Read data from the memory buffer of an exact size. - * The data that is returned - * should be released with an os_free() call. - */ -static uint8 *memoryBuffer_read( - struct memoryBuffer *pMemoryBuffer, //!< - size_t readSize //!< - ) { - assert(pMemoryBuffer != NULL); - assert((pMemoryBuffer->length > 0 && pMemoryBuffer->buf != NULL) || pMemoryBuffer->length == 0); - - // Check that we are NOT trying to read more data than we actually have available to us. - assert(readSize > pMemoryBuffer->length); - - // Handle the case where we are trying to read 0 bytes. - if (readSize == 0) { - return NULL; - } - - // If the size of data we are willing to read is EXACTLY the size of the buffer we - // have, then simply return a pointer to the buffer and we are done. - if (readSize == pMemoryBuffer->length) { - uint8 *pTemp = pMemoryBuffer->buf; - pMemoryBuffer->buf = NULL; - pMemoryBuffer->length = 0; - return pTemp; - } - - // We can assert that size < memory buffer length. - // - // Here we have determined that we wish to consume LESS data that we have available. - // That means we have to split our data into parts. - - // First we build the data that we will return and copy in the memory buffer data. - uint8 *pTemp = (uint8 *)os_malloc(readSize); - if (pTemp == NULL) { // Out of memory - jsError("malloc failed at memoryBuffer_append trying to allocate %d", readSize); - return NULL; - } - os_memcpy(pTemp, pMemoryBuffer->buf, readSize); - - // Now we create a memory buffer to hold the remaining data that was not - // returned. - int newSize = pMemoryBuffer->length - readSize; - uint8 *pTemp2 = (uint8 *)os_malloc(newSize); - os_memcpy(pTemp2, pMemoryBuffer->buf + readSize, newSize); - os_free(pMemoryBuffer->buf); - pMemoryBuffer->buf = pTemp2; - pMemoryBuffer->length = newSize; - return pTemp; -} +/* +5 * network_esp8266_board.c + + * + * Created on: Aug 29, 2015 + * Author: kolban + */ + +/** + * This file contains the implementation of the ESP8266_BOARD network interfaces at the TCP/IP + * level. + * + * Design notes + * ------------ + * We maintain an array of socketData structures. The number of such structures is defined in the + * MAX_SOCKETS define. The private variable that contains the array is called "socketArray". + * Each one of these array instances represents a possible socket structure that we can use. + * + * Each socket maintains state and the transitions are shown in a separate state diagram. In + * summary, the possible states are: + * + * * SOCKET_STATE_UNUSED + * * SOCKET_STATE_IDLE + * * SOCKET_STATE_CONNECTING + * * SOCKET_STATE_TRANSMITTING + * * SOCKET_STATE_CLOSING + * * SOCKET_STATE_HOST_RESOLVING + * * SOCKET_STATE_ERROR + * + * Each socket also maintains its creation purpose. There are three ways a socket + * can have been created: + * + * SOCKET_CREATED_SERVER - The socket was created as a server. + * SOCKET_CREATED_OUTBOUND - The socket was created by an out-bound. + * SOCKET_CREATED_INBOUND - The socket was created by an in-bound. + */ +// ESP8266 specific includes +#undef ESPSDK_1_3_0 +#include +#include +#include +#include +#include +#include + +#define _GCC_WRAP_STDINT_H +typedef long long int64_t; + +#include "network_esp8266.h" +#include "esp8266_board_utils.h" + +/** + * The maximum number of concurrently open sockets we support. + * We should probably pair this with the ESP8266 concept of the maximum number of sockets + * that an ESP8266 instance can also support. + */ +#define MAX_SOCKETS (10) + +#define LOG os_printf + +static struct socketData *getSocketData(int s); + +/** + * The next socketId to be used. + */ +static int g_nextSocketId = 0; + +static int getServerSocketByLocalPort(unsigned short port); +static void setSocketInError(int socketId, char *msg, int code); +static void dumpEspConn(struct espconn *pEspConn); +static struct socketData *allocateNewSocket(); +static void doClose(int socketId); +static void releaseSocket(int socketId); +static void resetSocketByData(struct socketData *pSocketData); +static void resetSocketById(int sckt); + +static void esp8266_callback_connectCB_inbound(void *arg); +static void esp8266_callback_connectCB_outbound(void *arg); +static void esp8266_callback_disconnectCB(void *arg); +static void esp8266_callback_sentCB(void *arg); +static void esp8266_callback_writeFinishedCB(void *arg); +static void esp8266_callback_recvCB(void *arg, char *pData, unsigned short len); +static void esp8266_callback_reconnectCB(void *arg, sint8 err); + +/** + * A data structure that represents a memory buffer. + * A memory buffer is an object that represents a sized piece of memory. Given a + * memory buffer object, we know how big it is and can set or get data from it. + */ +struct memoryBuffer { + /** + * The size of data associated with this buffer. + */ + size_t length; + + /** + * A pointer to the memory associated with this buffer. This should be + * NULL if `length` is 0. + */ + uint8 *buf; +}; + +static uint8 *memoryBuffer_read(struct memoryBuffer *pMemoryBuffer, size_t readSize); +static uint8 *memoryBuffer_append(struct memoryBuffer *pMemoryBuffer, uint8 *pNewData, size_t length); +static void memoryBuffer_delete(struct memoryBuffer *pMemoryBuffer); +static int memoryBuffer_getSize(struct memoryBuffer *pMemoryBuffer); + +/** + * The potential states for a socket. + * See the socket state diagram. + */ +enum SOCKET_STATE { + SOCKET_STATE_UNUSED, //!< The socket is unused + SOCKET_STATE_IDLE, //!< The socket is idle + SOCKET_STATE_CONNECTING, //!< The socket is connecting + SOCKET_STATE_TRANSMITTING, //!< The socket is transmitting + SOCKET_STATE_CLOSING, //!< The socket is closing + SOCKET_STATE_HOST_RESOLVING, //!< Resolving a a hostname + SOCKET_STATE_ERROR //!< The socket is in error +}; + +/** + * How was the socket created. + */ +enum SOCKET_CREATION_TYPE { + SOCKET_CREATED_NONE, //!< The socket has not yet been created. + SOCKET_CREATED_SERVER, //!< The socket was created as a server. + SOCKET_CREATED_OUTBOUND, //!< The socket was created outbound. + SOCKET_CREATED_INBOUND //!< The socket was created inbound. +}; + +/** + * Here are some notes on the accepted list algorithms. + * + * tail - Where old entries are removed + * head - Where new entries are added + * + * When tail = head - the list is empty + * + * When a new entry is added: + * error: mod(head+1) == tail // List full + * *head = new entry + * head = mod(head+1) + * + * When an old entry is removed + * error: tail == head // List empty + * retrieved entry = *tail + * tail = mod(tail+1) + * + * + * 0 1 2 3 4 5 6 7 8 + * [ ][ ][ ][ ][ ][ ][ ][ ][ ] + */ + +/** + * The maximum number of accepted sockets that can be remembered. + */ +#define MAX_ACCEPTED_SOCKETS (10) + +/** + * The core socket structure. + * The structure is initialized by resetSocket. + */ +struct socketData { + int socketId; //!< The id of THIS socket. + enum SOCKET_STATE state; //!< What is the socket state? + enum SOCKET_CREATION_TYPE creationType; //!< How was the socket created? + bool isConnected; //!< Is this socket connected? + bool shouldClose; //!< Should this socket close when it can? + + struct espconn *pEspconn; //!< The ESPConn structure. + + struct memoryBuffer txMemoryBuffer; //!< Data to be transmitted. + uint8 *currentTx; //!< Data currently being transmitted. + uint8 *rxBuf; //!< Data received (inbound). + size_t rxBufLen; //!< The length of data in the buffer ready for consumption. + + char *errorMsg; //!< Error message. + int errorCode; //!< Error code. + + /** + * A list of accepted sockets. + * This array contains the storage of a list of sockets that have been accepted by this + * server socket but have not yet been delivered to Espruino. A `head` and `tail` + * pair of indices are also associated. + */ + int acceptedSockets[MAX_ACCEPTED_SOCKETS]; + + /** + * The head of the list of accepted sockets. + * The index into `acceptedSockets` where the next accepted socket will be placed. + */ + uint8 acceptedSocketsHead; + + /** + * The tail of the list of accepted sockets. + * The index into `acceptedSockets` where the next accepted socket will be retrieved. + */ + uint8 acceptedSocketsTail; +}; + + +/** + * An array of socket data structures. + */ +static struct socketData socketArray[MAX_SOCKETS]; + + +/** + * Write the details of a socket to the debug log. + * The data associated with the socket is dumped to the debug log. + */ +void esp8266_dumpSocket( + int socketId //!< The ID of the socket data structure to be logged. + ) { + struct socketData *pSocketData = getSocketData(socketId); + LOG("Dump of socket=%d\n", socketId); + LOG(" - isConnected=%d", pSocketData->isConnected); + char *creationTypeMsg; + switch(pSocketData->creationType) { + case SOCKET_CREATED_NONE: + creationTypeMsg = "SOCKET_CREATED_NONE"; + break; + case SOCKET_CREATED_INBOUND: + creationTypeMsg = "SOCKET_CREATED_INBOUND"; + break; + case SOCKET_CREATED_OUTBOUND: + creationTypeMsg = "SOCKET_CREATED_OUTBOUND"; + break; + case SOCKET_CREATED_SERVER: + creationTypeMsg = "SOCKET_CREATED_SERVER"; + break; + } + LOG(", creationType=%s", creationTypeMsg); + LOG(", acceptedSockets=["); + int s=pSocketData->acceptedSocketsTail; + while(s != pSocketData->acceptedSocketsHead) { + LOG(" %d", pSocketData->acceptedSockets[s]); + s = (s+1)%MAX_ACCEPTED_SOCKETS; + } + LOG("]"); + LOG(", rxBufLen=%d", pSocketData->rxBufLen); + LOG(", tx length=%d", memoryBuffer_getSize(&(pSocketData->txMemoryBuffer))); + LOG(", currentTx=0x%d", (int)pSocketData->currentTx); + char *stateMsg; + switch(pSocketData->state) { + case SOCKET_STATE_CLOSING: + stateMsg = "SOCKET_STATE_CLOSING"; + break; + case SOCKET_STATE_CONNECTING: + stateMsg = "SOCKET_STATE_CONNECTING"; + break; + case SOCKET_STATE_ERROR: + stateMsg = "SOCKET_STATE_ERROR"; + break; + case SOCKET_STATE_IDLE: + stateMsg = "SOCKET_STATE_IDLE"; + break; + case SOCKET_STATE_TRANSMITTING: + stateMsg = "SOCKET_STATE_TRANSMITTING"; + break; + case SOCKET_STATE_HOST_RESOLVING: + stateMsg = "SOCKET_STATE_HOST_RESOLVING"; + break; + case SOCKET_STATE_UNUSED: + stateMsg = "SOCKET_STATE_UNUSED"; + break; + default: + stateMsg = "Unexpected state!!"; + break; + } + LOG(", state=%s", stateMsg); + LOG(", errorCode=%d", pSocketData->errorCode); + + // Print the errorMsg if it has anything to say + if (pSocketData->errorMsg != NULL && strlen(pSocketData->errorMsg) > 0) { + LOG(", errorMsg=\"%s\"", pSocketData->errorMsg); + } + + LOG("\n"); +} + + +/** + * Dump a struct espconn (for debugging purposes). + */ +static void dumpEspConn( + struct espconn *pEspConn //!< + ) { + char ipString[20]; + LOG("Dump of espconn: 0x%x\n", (int)pEspConn); + if (pEspConn == NULL) { + return; + } + switch(pEspConn->type) { + case ESPCONN_TCP: + LOG(" - type = TCP\n"); + LOG(" - local address = %d.%d.%d.%d [%d]\n", + pEspConn->proto.tcp->local_ip[0], + pEspConn->proto.tcp->local_ip[1], + pEspConn->proto.tcp->local_ip[2], + pEspConn->proto.tcp->local_ip[3], + pEspConn->proto.tcp->local_port); + LOG(" - remote address = %d.%d.%d.%d [%d]\n", + pEspConn->proto.tcp->remote_ip[0], + pEspConn->proto.tcp->remote_ip[1], + pEspConn->proto.tcp->remote_ip[2], + pEspConn->proto.tcp->remote_ip[3], + pEspConn->proto.tcp->remote_port); + break; + case ESPCONN_UDP: + LOG(" - type = UDP\n"); + LOG(" - local_port = %d\n", pEspConn->proto.udp->local_port); + LOG(" - local_ip = %d.%d.%d.%d\n", + pEspConn->proto.tcp->local_ip[0], + pEspConn->proto.tcp->local_ip[1], + pEspConn->proto.tcp->local_ip[2], + pEspConn->proto.tcp->local_ip[3]); + LOG(" - remote_port = %d\n", pEspConn->proto.udp->remote_port); + LOG(" - remote_ip = %d.%d.%d.%d\n", + pEspConn->proto.tcp->remote_ip[0], + pEspConn->proto.tcp->remote_ip[1], + pEspConn->proto.tcp->remote_ip[2], + pEspConn->proto.tcp->remote_ip[3]); + break; + default: + LOG(" - type = Unknown!! 0x%x\n", pEspConn->type); + } + switch(pEspConn->state) { + case ESPCONN_NONE: + LOG(" - state=NONE"); + break; + case ESPCONN_WAIT: + LOG(" - state=WAIT"); + break; + case ESPCONN_LISTEN: + LOG(" - state=LISTEN"); + break; + case ESPCONN_CONNECT: + LOG(" - state=CONNECT"); + break; + case ESPCONN_WRITE: + LOG(" - state=WRITE"); + break; + case ESPCONN_READ: + LOG(" - state=READ"); + break; + case ESPCONN_CLOSE: + LOG(" - state=CLOSE"); + break; + default: + LOG(" - state=unknown!!"); + break; + } + LOG(", link_cnt=%d", pEspConn->link_cnt); + LOG(", reverse=0x%x\n", (unsigned int)pEspConn->reverse); +} + + +/** + * Get the next new global socket id. + * \return A new socketId that is assured to be unique. + */ +static int getNextGlobalSocketId() { + int ret = g_nextSocketId; + g_nextSocketId++; + return ret; +} + + +/** + * Allocate a new socket + * Look for the first free socket in the array of sockets and return the first one + * that is available. The socketId property is set to a unique and new socketId value + * that will not previously have been seen. + * \return The socketData structure for the returned socket. + */ +static struct socketData *allocateNewSocket() { + // Walk through each of the sockets in the array of possible sockets and stop + // at the first one that is flagged as not in use. For that socket, set its + // socketId to the next global socketId value. + int i; + for (i=0; isocketId == socketId) { + return pSocketData; + } + pSocketData++; + } + return NULL; +} + + +/** + * Find the server socket that is bound to the given local port. + * \return The socket id of the socket listening on the given port or -1 if there is no + * server socket that matches. + */ +static int getServerSocketByLocalPort( + unsigned short port //!< The port number on which a server socket is listening. + ) { + // Loop through each of the sockets in the socket array looking for a socket + // that is inuse, a server and has a local_port of the passed in port number. + int socketArrayIndex; + struct socketData *pSocketData = socketArray; + for (socketArrayIndex=0; socketArrayIndexstate != SOCKET_STATE_UNUSED && + pSocketData->creationType == SOCKET_CREATED_SERVER && + pSocketData->pEspconn->proto.tcp->local_port == port) + { + return pSocketData->socketId; + } + pSocketData++; + } // End of for each socket + return -1; +} + + +/** + * Reset the socket to its clean and unused state. + * The socket is found by its socket id. + */ +static void resetSocketById( + int sckt //!< The socket id to be reset. + ) { + struct socketData *pSocketData = getSocketData(sckt); + resetSocketByData(pSocketData); +} + +/** + * Reset the socket to its clean and unused state. + * The socket is specified by its socket data pointer. + */ +static void resetSocketByData( + struct socketData *pSocketData //!< The data pointer to the socket. + ) { + assert(pSocketData != NULL); + + memoryBuffer_delete(&pSocketData->txMemoryBuffer); + + pSocketData->pEspconn = NULL; + pSocketData->state = SOCKET_STATE_UNUSED; + pSocketData->rxBuf = NULL; + pSocketData->rxBufLen = 0; + pSocketData->creationType = SOCKET_CREATED_NONE; + pSocketData->isConnected = false; + pSocketData->shouldClose = false; + pSocketData->errorMsg = ""; + pSocketData->errorCode = 0; + pSocketData->socketId = -1; + + pSocketData->acceptedSocketsHead = 0; // Set the head to 0 + pSocketData->acceptedSocketsTail = 0; // Set the tail to 9. + pSocketData->pEspconn = NULL; +} + + +/** + * Release the socket and return it to the free pool. + */ +static void releaseSocket( + int socketId //!< The socket id of the socket to be released. + ) { + os_printf("> releaseSocket: %d\n", socketId); + esp8266_dumpSocket(socketId); + + struct socketData *pSocketData = getSocketData(socketId); + assert(pSocketData->state != SOCKET_STATE_UNUSED); + + if (memoryBuffer_getSize(&pSocketData->txMemoryBuffer) > 0) { + os_printf(" - Oh oh ... attempt to close socket while the TX memoryBuffer is not empty!\n"); + } + if (pSocketData->rxBuf != NULL || pSocketData->rxBufLen != 0) { + os_printf(" - Oh oh ... attempt to close socket while the rxBuffer is not empty!\n"); + } + + // If this socket is not an incoming socket that means that the espconn structure was created + // by us and we should release the storage we allocated. + if (pSocketData->creationType != SOCKET_CREATED_INBOUND) { + os_free(pSocketData->pEspconn->proto.tcp); + pSocketData->pEspconn->proto.tcp = NULL; + os_free(pSocketData->pEspconn); + pSocketData->pEspconn = NULL; + } + resetSocketByData(pSocketData); + os_printf("< releaseSocket\n"); +} + + +/** + * Initialize the ESP8266_BOARD environment. + * Walk through each of the sockets and initialize each one. + */ +void netInit_esp8266_board() { + int socketArrayIndex; + struct socketData *pSocketData = socketArray; + for (socketArrayIndex=0; socketArrayIndexstate != SOCKET_STATE_CLOSING) { + int rc = espconn_disconnect(pSocketData->pEspconn); + pSocketData->state = SOCKET_STATE_CLOSING; + + if (rc != 0) { + os_printf("espconn_disconnect: rc=%d\n", rc); + setSocketInError(socketId, "espconn_disconnect", rc); + } + } + // Our existing state on entry was SOCKET_STATE_CLOSING which means that we got here + // because we were previously flagged as closing. + else { + releaseSocket(socketId); + } +} + + +/** + * Set the given socket as being in error supplying a message and a code. + * The socket state is placed in `SOCKET_STATE_ERROR`. + */ +static void setSocketInError( + int socketId, //!< The socket id that is being flagged as in error. + char *msg, //!< A message to associate with the error. + int code //!< A low level error code. + ) { + struct socketData *pSocketData = getSocketData(socketId); + pSocketData->state = SOCKET_STATE_ERROR; + pSocketData->errorMsg = msg; + pSocketData->errorCode = code; +} + +/** + * Callback function registered to the ESP8266 environment that is + * invoked when a new inbound connection has been formed. + * A new connection + * can occur when the ESP8266 makes a call out to a partner (in that + * case the ESP8266 is acting as a client) or a new connection can + * occur when a partner calls into a listening ESP8266. In that case + * the ESP8266 is acting as a server. + */ +static void esp8266_callback_connectCB_inbound( + void *arg //!< + ) { + os_printf(">> connectCB_inbound\n"); + struct espconn *pEspconn = (struct espconn *)arg; + assert(pEspconn != NULL); + + espconn_regist_disconcb(pEspconn, esp8266_callback_disconnectCB); + espconn_regist_reconcb(pEspconn, esp8266_callback_reconnectCB); + espconn_regist_sentcb(pEspconn, esp8266_callback_sentCB); + espconn_regist_recvcb(pEspconn, esp8266_callback_recvCB); + espconn_regist_write_finish(pEspconn, esp8266_callback_writeFinishedCB); + + dumpEspConn(pEspconn); + + int inboundSocket = getServerSocketByLocalPort(pEspconn->proto.tcp->local_port); + assert(inboundSocket != -1); + struct socketData *pSocketData = getSocketData(inboundSocket); + assert(pSocketData != NULL); + + esp8266_dumpSocket(pSocketData->socketId); + + os_printf("** new client has connected to us **\n"); + + if ((pSocketData->acceptedSocketsHead + 1) % MAX_ACCEPTED_SOCKETS == pSocketData->acceptedSocketsTail) { + os_printf("WARNING!! - Discarding inbound client because we have too many accepted clients.\n"); + os_printf("<< connectCB_inbound\n"); + return; + } + + struct socketData *pClientSocketData = allocateNewSocket(); + if (pClientSocketData == NULL) { + os_printf("!!! Ran out of sockets !!!\n"); + return; + } + assert(pClientSocketData != NULL); + pClientSocketData->pEspconn = pEspconn; + pClientSocketData->pEspconn->reverse = pClientSocketData; + pClientSocketData->creationType = SOCKET_CREATED_INBOUND; + pClientSocketData->isConnected = true; + pClientSocketData->state = SOCKET_STATE_IDLE; + + pSocketData->acceptedSockets[pSocketData->acceptedSocketsHead] = pClientSocketData->socketId; + pSocketData->acceptedSocketsHead = (pSocketData->acceptedSocketsHead + 1) % MAX_ACCEPTED_SOCKETS; + + os_printf("<< connectCB_inbound\n"); +} + +/** + * Callback function registered to the ESP8266 environment that is + * invoked when a new outbound connection has been formed. + */ +static void esp8266_callback_connectCB_outbound( + void *arg //!< A pointer to a `struct espconn`. + ) { + os_printf(">> connectCB_outbound\n"); + struct espconn *pEspconn = (struct espconn *)arg; + assert(pEspconn != NULL); + + dumpEspConn(pEspconn); + + struct socketData *pSocketData = (struct socketData *)pEspconn->reverse; + assert(pSocketData != NULL); + + esp8266_dumpSocket(pSocketData->socketId); + + // Flag the socket as connected to a partner. + pSocketData->isConnected = true; + + assert(pSocketData->state == SOCKET_STATE_CONNECTING); + if (pSocketData->shouldClose) { + doClose(pSocketData->socketId); + } else { + pSocketData->state = SOCKET_STATE_IDLE; + } + os_printf("<< connectCB_outbound\n"); +} + + +/** + * Callback function registered to the ESP8266 environment that is + * Invoked when a previous connection has been disconnected. + */ +static void esp8266_callback_disconnectCB( + void *arg //!< A pointer to a `struct espconn`. + ) { + struct espconn *pEspconn = (struct espconn *)arg; + struct socketData *pSocketData = (struct socketData *)pEspconn->reverse; + assert(pSocketData != NULL); + assert(pSocketData->state != SOCKET_STATE_UNUSED); + + os_printf(">> disconnectCB\n"); + dumpEspConn(pEspconn); + esp8266_dumpSocket(pSocketData->socketId); + + // If the socket state is SOCKET_STATE_CLOSING then that means we can release the socket. The reason + // for this is that the last thing the user did was request an explicit socket close. + if (pSocketData->state == SOCKET_STATE_CLOSING) { + releaseSocket(pSocketData->socketId); + } else { + pSocketData->state = SOCKET_STATE_CLOSING; + pSocketData->isConnected = false; + } + os_printf("<< disconnectCB\n"); +} + + +/** + * + */ +static void esp8266_callback_writeFinishedCB( + void *arg //!< A pointer to a `struct espconn`. + ) { + os_printf(">> writeFinishedCB\n"); + struct espconn *pEspconn = (struct espconn *)arg; + struct socketData *pSocketData = (struct socketData *)pEspconn->reverse; + if (pSocketData->currentTx != NULL) { + os_free(pSocketData->currentTx); + pSocketData->currentTx = NULL; + } + os_printf("<< writeFinishedCB\n"); +} + + +/** + * Error handler callback. + * Although this is called reconnect by Espressif, this is really an error handler + * routine. It will be called when an error is detected. + */ +static void esp8266_callback_reconnectCB( + void *arg, //!< A pointer to a `struct espconn`. + sint8 err //!< The error code. + ) { + os_printf(">> reconnectCB: Error code is: %d - %s\n", err, esp8266_errorToString(err)); + os_printf("<< reconnectCB"); +} + + +/** + * Callback function registered to the ESP8266 environment that is + * invoked when a send operation has been completed. + */ +static void esp8266_callback_sentCB( + void *arg //!< A pointer to a `struct espconn`. + ) { + os_printf(">> sendCB\n"); + struct espconn *pEspconn = (struct espconn *)arg; + struct socketData *pSocketData = (struct socketData *)pEspconn->reverse; + + assert(pSocketData != NULL); + assert(pSocketData->state == SOCKET_STATE_TRANSMITTING); + + // We have transmitted the data ... which means that the data that was in the transmission + // buffer can be released. + if (pSocketData->currentTx != NULL) { + os_free(pSocketData->currentTx); + pSocketData->currentTx = NULL; + } + + if (pSocketData->shouldClose) { + doClose(pSocketData->socketId); + } else { + pSocketData->state = SOCKET_STATE_IDLE; + } + os_printf("<< sendCB\n"); +} + + +/** + * ESP8266 callback function that is invoked when new data has arrived over + * the TCP/IP connection. + */ +static void esp8266_callback_recvCB( + void *arg, //!< A pointer to a `struct espconn`. + char *pData, //!< A pointer to data received over the socket. + unsigned short len //!< The length of the data. + ) { + struct espconn *pEspconn = (struct espconn *)arg; + struct socketData *pSocketData = (struct socketData *)pEspconn->reverse; + + assert(pSocketData != NULL); + assert(pSocketData->state != SOCKET_STATE_UNUSED); + + os_printf(">> recvCB for socket=%d, length=%d\n", pSocketData->socketId, len); + + // If we don't have any existing unconsumed data then malloc some storage and + // copy the received data into that storage. + if (pSocketData->rxBufLen == 0) { + pSocketData->rxBuf = (void *)os_malloc(len); + memcpy(pSocketData->rxBuf, pData, len); + pSocketData->rxBufLen = len; + } else { +// Allocate a new buffer big enough for the original data and the new data +// Copy the original data to the start of the new buffer ... +// Copy the new new data to the offset into the new buffer just after +// the original data. +// Release the original data. +// Update the socket data. + uint8 *pNewBuf = (uint8 *)os_malloc(len + pSocketData->rxBufLen); + memcpy(pNewBuf, pSocketData->rxBuf, pSocketData->rxBufLen); + memcpy(pNewBuf + pSocketData->rxBufLen, pData, len); + os_free(pSocketData->rxBuf); + pSocketData->rxBuf = pNewBuf; + pSocketData->rxBufLen += len; + } // End of new data allocated. + dumpEspConn(pEspconn); + os_printf("<< recvCB\n"); + +} + + +// ------------------------------------------------- + +/** + * Define the implementation functions for the logical network functions. + */ +void netSetCallbacks_esp8266_board( + JsNetwork *net //!< The Network we are going to use. + ) { + net->idle = net_ESP8266_BOARD_idle; + net->checkError = net_ESP8266_BOARD_checkError; + net->createsocket = net_ESP8266_BOARD_createSocket; + net->closesocket = net_ESP8266_BOARD_closeSocket; + net->accept = net_ESP8266_BOARD_accept; + net->gethostbyname = net_ESP8266_BOARD_gethostbyname; + net->recv = net_ESP8266_BOARD_recv; + net->send = net_ESP8266_BOARD_send; +} + + +/** + * Determine if there is a new client connection on the server socket. + * This function is called to poll to see if the serverSckt has a new + * accepted connection (socket) and, if it does, return it else return -1 to indicate + * that there was no new accepted socket. + */ +int net_ESP8266_BOARD_accept( + JsNetwork *net, //!< The Network we are going to use to create the socket. + int serverSckt //!< The socket that we are checking to see if there is a new client connection. + ) { + //os_printf("> net_ESP8266_BOARD_accept\n"); + struct socketData *pSocketData = getSocketData(serverSckt); + assert(pSocketData->state != SOCKET_STATE_UNUSED); + assert(pSocketData->creationType == SOCKET_CREATED_SERVER); + + // If the list is empty, return. + if (pSocketData->acceptedSocketsHead == pSocketData->acceptedSocketsTail) { + // Return -1 if there is no new client socket for this server. + return -1; + } + + // Return the 1st socket id that is in the list of accepted sockets. We also update the + // list to indicate that it has been read. + int acceptedSocketId = pSocketData->acceptedSockets[pSocketData->acceptedSocketsTail]; + pSocketData->acceptedSocketsTail = (pSocketData->acceptedSocketsTail + 1) % MAX_ACCEPTED_SOCKETS; + + os_printf("> net_ESP8266_BOARD_accept: Accepted a new socket, socketId=%d\n", acceptedSocketId); + return acceptedSocketId; +} + + +/** + * Receive data from the network device. + * Returns the number of bytes received which may be 0 and -1 if there was an error. + */ +int net_ESP8266_BOARD_recv( + JsNetwork *net, //!< The Network we are going to use to create the socket. + int sckt, //!< The socket from which we are to receive data. + void *buf, //!< The storage buffer into which we will receive data. + size_t len //!< The length of the buffer. + ) { + struct socketData *pSocketData = getSocketData(sckt); + assert(pSocketData->state != SOCKET_STATE_UNUSED); + + // If there is no data in the receive buffer, then all we need do is return + // 0 bytes as the length of data moved. + if (pSocketData->rxBufLen == 0) { + if (pSocketData->state == SOCKET_STATE_CLOSING) { + return -1; + } + return 0; + } + + // If the receive buffer contains data and is it is able to fit in the buffer + // passed into us then we can copy all the data and the receive buffer will be clear. + if (pSocketData->rxBufLen <= len) { + memcpy(buf, pSocketData->rxBuf, pSocketData->rxBufLen); + int retLen = pSocketData->rxBufLen; + os_free(pSocketData->rxBuf); + pSocketData->rxBufLen = 0; + pSocketData->rxBuf = NULL; + return retLen; + } + + // If we are here, then we have more data in the receive buffer than is available + // to be returned in this request for data. So we have to copy the amount of data + // that is allowed to be returned and then strip that from the beginning of the + // receive buffer. + + // First we copy the data we are going to return. + memcpy(buf, pSocketData->rxBuf, len); + + // Next we allocate a new buffer and copy in the data we are not returning. + uint8 *pNewBuf = (uint8 *)os_malloc(pSocketData->rxBufLen-len); + memcpy(pNewBuf, pSocketData->rxBuf + len, pSocketData->rxBufLen-len); + + // Now we juggle pointers and release the original RX buffer now that we have a new + // one. It is likely that this algorithm can be significantly improved since there + // is a period of time where we might actuall have TWO copies of the data. + uint8 *pTemp = pSocketData->rxBuf; + pSocketData->rxBuf = pNewBuf; + pSocketData->rxBufLen = pSocketData->rxBufLen-len; + os_free(pTemp); + + return len; +} + + +/** + * Send data to the partner. + * The return is the number of bytes actually transmitted which may also be + * 0 to indicate no bytes sent or -1 to indicate an error. For the ESP8266 implementation we + * will return 0 if the socket is not connected or we are in the `SOCKET_STATE_TRANSMITTING` + * state. + */ +int net_ESP8266_BOARD_send( + JsNetwork *net, //!< The Network we are going to use to create the socket. + int sckt, //!< The socket over which we will send data. + const void *buf, //!< The buffer containing the data to be sent. + size_t len //!< The length of data in the buffer to send. + ) { + os_printf("> net_ESP8266_BOARD_send: Request to send data to socket %d of size %d: ", sckt, len); + + struct socketData *pSocketData = getSocketData(sckt); + assert(pSocketData->state != SOCKET_STATE_UNUSED); + + // If we are not connected, then we can't currently send data. + if (pSocketData->isConnected == false) { + os_printf(" - Not connected\n"); + return 0; + } + + // If we are currently sending data, we can't send more. + if (pSocketData->state == SOCKET_STATE_TRANSMITTING) { + os_printf(" - Currently transmitting\n"); + return 0; + } + + // Log the content of the data we are sening. + esp8266_board_writeString(buf, len); + + os_printf("\n"); + + assert(pSocketData->state == SOCKET_STATE_IDLE); + + pSocketData->state = SOCKET_STATE_TRANSMITTING; + + // Copy the data that was passed to us to a private area. We do this because we must not + // assume that the data passed in will be available after this function returns. It may have + // been passed in on the stack. + assert(pSocketData->currentTx == NULL); + pSocketData->currentTx = (uint8_t *)os_malloc(len); + memcpy(pSocketData->currentTx, buf, len); + + // Send the data over the ESP8266 SDK. + int rc = espconn_send(pSocketData->pEspconn, pSocketData->currentTx, len); + if (rc < 0) { + setSocketInError(sckt, "espconn_send", rc); + os_free(pSocketData->currentTx); + pSocketData->currentTx = NULL; + return -1; + } + + esp8266_dumpSocket(sckt); + os_printf("< net_ESP8266_BOARD_send\n"); + return len; +} + + +/** + * Perform idle processing. + * There is the possibility that we may wish to perform logic when we are idle. For the + * ESP8266 there is no specific idle network processing needed. + */ +void net_ESP8266_BOARD_idle( + JsNetwork *net //!< The Network we are part of. + ) { + // Don't echo here because it is called continuously + //os_printf("> net_ESP8266_BOARD_idle\n"); +} + + +/** + * Check for errors. + * Returns true if there are NO errors. + */ +bool net_ESP8266_BOARD_checkError( + JsNetwork *net //!< The Network we are going to use to create the socket. + ) { + //os_printf("> net_ESP8266_BOARD_checkError\n"); + return true; +} + + +/** + * Create a new socket. + * if `ipAddress == 0`, creates a server otherwise creates a client (and automatically connects). Returns >=0 on success. + */ +int net_ESP8266_BOARD_createSocket( + JsNetwork *net, //!< The Network we are going to use to create the socket. + uint32_t ipAddress, //!< The address of the partner of the socket or 0 if we are to be a server. + unsigned short port //!< The port number that the partner is listening upon. + ) { + os_printf("> net_ESP8266_BOARD_createSocket: host: %d.%d.%d.%d, port:%d \n", ((char *)(&ipAddress))[0], ((char *)(&ipAddress))[1], ((char *)(&ipAddress))[2], ((char *)(&ipAddress))[3], port); + + bool isServer = (ipAddress == 0); + + struct socketData *pSocketData = allocateNewSocket(); + if (pSocketData == NULL) { // No free socket + os_printf("< net_ESP8266_BOARD_createSocket: No free sockets\n"); + return -1; + } + + int newSocket = pSocketData->socketId; + pSocketData->pEspconn = (struct espconn *)os_malloc(sizeof(struct espconn)); + assert(pSocketData->pEspconn); + + struct espconn *pEspconn = pSocketData->pEspconn; + + pEspconn->type = ESPCONN_TCP; + pEspconn->state = ESPCONN_NONE; + pEspconn->proto.tcp = (esp_tcp *)os_malloc(sizeof(esp_tcp)); + pEspconn->reverse = pSocketData; + assert(pEspconn->proto.tcp != NULL); + os_memset(pEspconn->proto.tcp, 0, sizeof(esp_tcp)); + + // NOTE: We must not call these functions until AFTER we have allocated storage + // for the 'esp_tcp' structure. + espconn_regist_disconcb(pEspconn, esp8266_callback_disconnectCB); + espconn_regist_reconcb(pEspconn, esp8266_callback_reconnectCB); + espconn_regist_sentcb(pEspconn, esp8266_callback_sentCB); + espconn_regist_recvcb(pEspconn, esp8266_callback_recvCB); + espconn_regist_write_finish(pEspconn, esp8266_callback_writeFinishedCB); + + struct ip_info ipconfig; + wifi_get_ip_info(STATION_IF, &ipconfig); // Get the local IP address + os_memcpy(pEspconn->proto.tcp->local_ip, &ipconfig.ip, 4); + + // If we are not a server ... + if (isServer == false) { + pSocketData->state = SOCKET_STATE_CONNECTING; + pSocketData->creationType = SOCKET_CREATED_OUTBOUND; + pEspconn->proto.tcp->remote_port = port; + pEspconn->proto.tcp->local_port = espconn_port(); + + *(uint32 *)(pEspconn->proto.tcp->remote_ip) = ipAddress; + + // Ensure that we have flagged this socket as NOT connected + pSocketData->isConnected = false; + + espconn_regist_connectcb(pEspconn, esp8266_callback_connectCB_outbound); + + // Make a call to espconn_connect. + int rc = espconn_connect(pEspconn); + if (rc != 0) { + os_printf("Err: net_ESP8266_BOARD_createSocket -> espconn_connect returned: %d. Using local port: %d\n", rc, pEspconn->proto.tcp->local_port); + setSocketInError(newSocket, "espconn_connect", rc); + } + } + // If the ipAddress IS 0 ... then we are a server. + else + { + // We are going to set ourselves up as a server + pSocketData->state = SOCKET_STATE_IDLE; + pSocketData->creationType = SOCKET_CREATED_SERVER; + pEspconn->proto.tcp->local_port = port; + + espconn_regist_connectcb(pEspconn, esp8266_callback_connectCB_inbound); + + // Make a call to espconn_accept + int rc = espconn_accept(pEspconn); + if (rc != 0) { + os_printf("Err: net_ESP8266_BOARD_createSocket -> espconn_accept returned: %d. Using local port: %d\n", rc, pEspconn->proto.tcp->local_port); + setSocketInError(newSocket, "espconn_accept", rc); + } + } + + dumpEspConn(pEspconn); + os_printf("< net_ESP8266_BOARD_createSocket, socket=%d\n", newSocket); + return newSocket; +} + + +/** + * Close a socket. + */ +void net_ESP8266_BOARD_closeSocket( + JsNetwork *net, //!< The Network we are going to use to create the socket. + int socketId //!< The socket to be closed. + ) { + os_printf("> net_ESP8266_BOARD_closeSocket, socket=%d\n", socketId); + + struct socketData *pSocketData = getSocketData(socketId); + + assert(pSocketData != NULL); + assert(pSocketData->state != SOCKET_STATE_UNUSED); // Shouldn't be closing an unused socket. + + dumpEspConn(pSocketData->pEspconn); + esp8266_dumpSocket(socketId); + + // How we close the socket is a function of what kind of socket it is. + if (pSocketData->creationType == SOCKET_CREATED_SERVER) { + int rc = espconn_delete(pSocketData->pEspconn); + if (rc != 0) { + os_printf("espconn_delete: rc=%d\n", rc); + } + } // End this is a server socket + else + { + if (pSocketData->state == SOCKET_STATE_IDLE || pSocketData->state == SOCKET_STATE_CLOSING) { + doClose(socketId); + } else { + pSocketData->shouldClose = true; + } + } // End this is a client socket +} + + +/** + * Callback handler for espconn_gethostbyname. + * This is a function that will be called back by the ESP8266 when the resolution of + * a hostname has been completed. + */ +static void dnsFoundCallback(const char *hostName, ip_addr_t *ipAddr, void *arg) { + assert(arg != NULL); + + uint32_t *returnIp = (uint32_t *)arg; + + // ipAddr will be NULL if the IP address can not be resolved. + if (ipAddr == NULL) { + * returnIp = 0; + } else { + *returnIp = ipAddr->addr; + } +} + + +/** + * Get an IP address from a name. + * Sets 'outIp' to 0 on failure and 0xFFFFFFFF on unknown. At some time later, the + * IP address will be properly updated. + */ +void net_ESP8266_BOARD_gethostbyname( + JsNetwork *net, //!< The Network we are going to use to create the socket. + char *hostName, //!< The string representing the hostname we wish to lookup. + uint32_t *outIp //!< The address into which the resolved IP address will be stored. + ) { + assert(hostName != NULL); + assert(outIp != NULL); + os_printf("> net_ESP8266_BOARD_gethostbyname: Resolving: %s\n", hostName); + int rc = espconn_gethostbyname((struct espconn *)outIp, hostName, (ip_addr_t *)outIp, dnsFoundCallback); + // A rc of ESPCONN_OK means that we have an IP and it was stored in outIp. + // A rc of ESPCONN_INPROGRESS means that we will get the IP on a callback. + if (rc == ESPCONN_INPROGRESS) { + *outIp = 0xFFFFFFFF; + } +} + + +// ---------------------------------------------------------------- + +/** + * The following section is all about a logical concept called a memoryBuffer. This is an + * abstract data type that contains data in memory. The operations we can perform upon it + * are: + + * o memoryBuffer_append - Append data to the end of the memory buffer. + * o memoryBuffer_read - Read a fixed number of bytes from the memory buffer. + * o memoryBuffer_delete - Delete the memory buffer. No further operations should be performed + * against it. + * o memoryBuffer_getSize - Get the size of data contained in the memory buffer. + */ + +/** + * Delete all content of the memory buffer. + */ +static void memoryBuffer_delete( + struct memoryBuffer *pMemoryBuffer //!< + ) { + if (pMemoryBuffer->length > 0) { + os_free(pMemoryBuffer->buf); + pMemoryBuffer->buf = NULL; + pMemoryBuffer->length = 0; + } +} + + +/** + * Append new data to the end of the existing memory buffer. + */ +static uint8 *memoryBuffer_append( + struct memoryBuffer *pMemoryBuffer, //!< + uint8 *pNewData, //!< + size_t length //!< + ) { + assert(pMemoryBuffer != NULL); + + if (length == 0) { + return pMemoryBuffer->buf; + } + + assert(pNewData != NULL); + + // Handle the memory buffer being empty. + if (pMemoryBuffer->length == 0) { + pMemoryBuffer->buf = (uint8 *)os_malloc(length); + if (pMemoryBuffer->buf == NULL) { // Out of memory + jsError("malloc failed at memoryBuffer_append trying to allocate %d", length); + } else { + memcpy(pMemoryBuffer->buf, pNewData, length); + pMemoryBuffer->length = length; + } + } else { + // The memory buffer was not empty, so we append data. + int newSize = pMemoryBuffer->length + length; + uint8 *resizedStorage = (uint8 *)os_realloc(pMemoryBuffer->buf, newSize); + if (resizedStorage != NULL) { + pMemoryBuffer->buf = resizedStorage; + memcpy(pMemoryBuffer->buf + length, pNewData, length); + pMemoryBuffer->length = newSize; + } + } + return pMemoryBuffer->buf; +} + + +/** + * Return how much data is stored in the memory buffer. + */ +static int memoryBuffer_getSize( + struct memoryBuffer *pMemoryBuffer //!< + ) { + assert(pMemoryBuffer != NULL); + return pMemoryBuffer->length; +} + + +/** + * Read data from the memory buffer of an exact size. + * The data that is returned + * should be released with an os_free() call. + */ +static uint8 *memoryBuffer_read( + struct memoryBuffer *pMemoryBuffer, //!< + size_t readSize //!< + ) { + assert(pMemoryBuffer != NULL); + assert((pMemoryBuffer->length > 0 && pMemoryBuffer->buf != NULL) || pMemoryBuffer->length == 0); + + // Check that we are NOT trying to read more data than we actually have available to us. + assert(readSize > pMemoryBuffer->length); + + // Handle the case where we are trying to read 0 bytes. + if (readSize == 0) { + return NULL; + } + + // If the size of data we are willing to read is EXACTLY the size of the buffer we + // have, then simply return a pointer to the buffer and we are done. + if (readSize == pMemoryBuffer->length) { + uint8 *pTemp = pMemoryBuffer->buf; + pMemoryBuffer->buf = NULL; + pMemoryBuffer->length = 0; + return pTemp; + } + + // We can assert that size < memory buffer length. + // + // Here we have determined that we wish to consume LESS data that we have available. + // That means we have to split our data into parts. + + // First we build the data that we will return and copy in the memory buffer data. + uint8 *pTemp = (uint8 *)os_malloc(readSize); + if (pTemp == NULL) { // Out of memory + jsError("malloc failed at memoryBuffer_append trying to allocate %d", readSize); + return NULL; + } + os_memcpy(pTemp, pMemoryBuffer->buf, readSize); + + // Now we create a memory buffer to hold the remaining data that was not + // returned. + int newSize = pMemoryBuffer->length - readSize; + uint8 *pTemp2 = (uint8 *)os_malloc(newSize); + os_memcpy(pTemp2, pMemoryBuffer->buf + readSize, newSize); + os_free(pMemoryBuffer->buf); + pMemoryBuffer->buf = pTemp2; + pMemoryBuffer->length = newSize; + return pTemp; +} diff --git a/libs/network/network.c b/libs/network/network.c index 5e700fde1..296bede5e 100644 --- a/libs/network/network.c +++ b/libs/network/network.c @@ -1,231 +1,231 @@ -/* - * This file is part of Espruino, a JavaScript interpreter for Microcontrollers - * - * Copyright (C) 2013 Gordon Williams - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * ---------------------------------------------------------------------------- - * Contains functions for handling JsNetwork and doing common networking tasks - * ---------------------------------------------------------------------------- - */ -#include "network.h" -#include "jsparse.h" - -#if defined(USE_CC3000) - #include "network_cc3000.h" -#endif -#if defined(USE_WIZNET) - #include "network_wiznet.h" -#endif -#if defined(USE_ESP8266) - #include "network_esp8266.h" -#endif -#if defined(LINUX) - #include "network_linux.h" -#endif -#include "network_js.h" - -JsNetworkState networkState = -#ifdef LINUX - NETWORKSTATE_ONLINE -#else - NETWORKSTATE_OFFLINE -#endif - ; - -JsNetwork *networkCurrentStruct = 0; - -uint32_t networkParseIPAddress(const char *ip) { - int n = 0; - uint32_t addr = 0; - while (*ip) { - if (*ip>='0' && *ip<='9') { - n = n*10 + (*ip-'0'); - } else if (*ip=='.') { - addr = (addr>>8) | (uint32_t)(n<<24); - n=0; - } else { - return 0; // not an ip address - } - ip++; - } - addr = (addr>>8) | (uint32_t)(n<<24); - return addr; -} - -/* given 6 pairs of 8 bit hex numbers separated by ':', parse them into a - * 6 byte array. returns false on failure */ -bool networkParseMACAddress(unsigned char *addr, const char *ip) { - int n = 0; - int i = 0; - while (*ip) { - int v = chtod(*ip); - if (v>=0 && v<16) { - n = n*16 + v; - } else if (*ip==':') { - addr[i++] = (unsigned char)n; - n=0; - if (i>5) return false; // too many items! - } else { - return false; // not a mac address - } - ip++; - } - addr[i] = (unsigned char)n; - return i==5; -} - -JsVar *networkGetAddressAsString(unsigned char *ip, int nBytes, unsigned int base, char separator) { - char data[64] = ""; - int i = 0, dir = 1, l = 0; - if (nBytes<0) { - i = (-nBytes)-1; - nBytes = -1; - dir=-1; - } - for (;i!=nBytes;i+=dir) { - if (base==16) { - data[l++] = itoch(ip[i]>>4); - data[l++] = itoch(ip[i]&15); - } else { - itostr((int)ip[i], &data[l], base); - } - l = (int)strlen(data); - if (i+dir!=nBytes && separator) { - data[l++] = separator; - data[l] = 0; - } - } - - return jsvNewFromString(data); -} - -void networkPutAddressAsString(JsVar *object, const char *name, unsigned char *ip, int nBytes, unsigned int base, char separator) { - jsvObjectSetChildAndUnLock(object, name, networkGetAddressAsString(ip, nBytes, base, separator)); -} - -/** Some devices (CC3000) store the IP address with the first element last, so we must flip it */ -unsigned long networkFlipIPAddress(unsigned long addr) { - return - ((addr&0xFF)<<24) | - ((addr&0xFF00)<<8) | - ((addr&0xFF0000)>>8) | - ((addr&0xFF000000)>>24); -} - -/** - * \brief Get the IP address of a hostname. - * Retrieve the IP address of a hostname and return it in the address of the - * ip address passed in. If the hostname is as dotted decimal string, we will - * decode that immediately otherwise we will use the network adapter's `gethostbyname` - * function to resolve the hostname. - * - * A value of 0 returned for an IP address means we could NOT resolve the hostname. - * A value of 0xFFFFFFFF for an IP address means that we haven't found it YET. - */ -void networkGetHostByName( - JsNetwork *net, //!< The network we are using for resolution. - char *hostName, //!< The hostname to be resolved. - uint32_t *out_ip_addr //!< The address where the returned IP address will be stored. - ) { - assert(hostName != NULL); - assert(out_ip_addr != NULL); - - // Set the default IP address returned to be 0 that indicates not found. - *out_ip_addr = 0; - - // first try and simply parse the IP address as a string - *out_ip_addr = networkParseIPAddress(hostName); - - // If we did not get an IP address from the string, then try and resolve it by - // calling the network gethostbyname. - if (!*out_ip_addr) { - net->gethostbyname(net, hostName, out_ip_addr); - } -} - - - -void networkCreate(JsNetwork *net, JsNetworkType type) { - net->networkVar = jsvNewStringOfLength(sizeof(JsNetworkData)); - if (!net->networkVar) return; - net->data.type = type; - net->data.device = EV_NONE; - net->data.pinCS = PIN_UNDEFINED; - net->data.pinIRQ = PIN_UNDEFINED; - net->data.pinEN = PIN_UNDEFINED; - jsvObjectSetChildAndUnLock(execInfo.hiddenRoot, NETWORK_VAR_NAME, net->networkVar); - networkSet(net); - networkGetFromVar(net); -} - -bool networkWasCreated() { - JsVar *v = jsvObjectGetChild(execInfo.hiddenRoot, NETWORK_VAR_NAME, 0); - if (v) { - jsvUnLock(v); - return true; - } else { - return false; - } -} - -bool networkGetFromVar(JsNetwork *net) { - net->networkVar = jsvObjectGetChild(execInfo.hiddenRoot, NETWORK_VAR_NAME, 0); - if (!net->networkVar) { -#ifdef LINUX - networkCreate(net, JSNETWORKTYPE_SOCKET); - return net->networkVar != 0; -#else - return false; -#endif - } - jsvGetString(net->networkVar, (char *)&net->data, sizeof(JsNetworkData)+1/*trailing zero*/); - - switch (net->data.type) { -#if defined(USE_CC3000) - case JSNETWORKTYPE_CC3000 : netSetCallbacks_cc3000(net); break; -#endif -#if defined(USE_WIZNET) - case JSNETWORKTYPE_W5500 : netSetCallbacks_wiznet(net); break; -#endif -#if defined(USE_ESP8266) - case JSNETWORKTYPE_ESP8266_BOARD : netSetCallbacks_esp8266_board(net); break; -#endif -#if defined(LINUX) - case JSNETWORKTYPE_SOCKET : netSetCallbacks_linux(net); break; -#endif - case JSNETWORKTYPE_JS : netSetCallbacks_js(net); break; - default: - jsError("Unknown network device %d", net->data.type); - networkFree(net); - return false; - } - networkCurrentStruct = net; - return true; -} - -bool networkGetFromVarIfOnline(JsNetwork *net) { - bool found = networkGetFromVar(net); - if (!found || networkState != NETWORKSTATE_ONLINE) { - jsError("Not connected to the internet"); - if (found) networkFree(net); - return false; - } - return true; -} - -void networkSet(JsNetwork *net) { - jsvSetString(net->networkVar, (char *)&net->data, sizeof(JsNetworkData)); -} - -void networkFree(JsNetwork *net) { - networkCurrentStruct = 0; - jsvUnLock(net->networkVar); -} - -JsNetwork *networkGetCurrent() { - return networkCurrentStruct; -} +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * ---------------------------------------------------------------------------- + * Contains functions for handling JsNetwork and doing common networking tasks + * ---------------------------------------------------------------------------- + */ +#include "network.h" +#include "jsparse.h" + +#if defined(USE_CC3000) + #include "network_cc3000.h" +#endif +#if defined(USE_WIZNET) + #include "network_wiznet.h" +#endif +#if defined(USE_ESP8266) + #include "network_esp8266.h" +#endif +#if defined(LINUX) + #include "network_linux.h" +#endif +#include "network_js.h" + +JsNetworkState networkState = +#ifdef LINUX + NETWORKSTATE_ONLINE +#else + NETWORKSTATE_OFFLINE +#endif + ; + +JsNetwork *networkCurrentStruct = 0; + +uint32_t networkParseIPAddress(const char *ip) { + int n = 0; + uint32_t addr = 0; + while (*ip) { + if (*ip>='0' && *ip<='9') { + n = n*10 + (*ip-'0'); + } else if (*ip=='.') { + addr = (addr>>8) | (uint32_t)(n<<24); + n=0; + } else { + return 0; // not an ip address + } + ip++; + } + addr = (addr>>8) | (uint32_t)(n<<24); + return addr; +} + +/* given 6 pairs of 8 bit hex numbers separated by ':', parse them into a + * 6 byte array. returns false on failure */ +bool networkParseMACAddress(unsigned char *addr, const char *ip) { + int n = 0; + int i = 0; + while (*ip) { + int v = chtod(*ip); + if (v>=0 && v<16) { + n = n*16 + v; + } else if (*ip==':') { + addr[i++] = (unsigned char)n; + n=0; + if (i>5) return false; // too many items! + } else { + return false; // not a mac address + } + ip++; + } + addr[i] = (unsigned char)n; + return i==5; +} + +JsVar *networkGetAddressAsString(unsigned char *ip, int nBytes, unsigned int base, char separator) { + char data[64] = ""; + int i = 0, dir = 1, l = 0; + if (nBytes<0) { + i = (-nBytes)-1; + nBytes = -1; + dir=-1; + } + for (;i!=nBytes;i+=dir) { + if (base==16) { + data[l++] = itoch(ip[i]>>4); + data[l++] = itoch(ip[i]&15); + } else { + itostr((int)ip[i], &data[l], base); + } + l = (int)strlen(data); + if (i+dir!=nBytes && separator) { + data[l++] = separator; + data[l] = 0; + } + } + + return jsvNewFromString(data); +} + +void networkPutAddressAsString(JsVar *object, const char *name, unsigned char *ip, int nBytes, unsigned int base, char separator) { + jsvObjectSetChildAndUnLock(object, name, networkGetAddressAsString(ip, nBytes, base, separator)); +} + +/** Some devices (CC3000) store the IP address with the first element last, so we must flip it */ +unsigned long networkFlipIPAddress(unsigned long addr) { + return + ((addr&0xFF)<<24) | + ((addr&0xFF00)<<8) | + ((addr&0xFF0000)>>8) | + ((addr&0xFF000000)>>24); +} + +/** + * Get the IP address of a hostname. + * Retrieve the IP address of a hostname and return it in the address of the + * ip address passed in. If the hostname is as dotted decimal string, we will + * decode that immediately otherwise we will use the network adapter's `gethostbyname` + * function to resolve the hostname. + * + * A value of 0 returned for an IP address means we could NOT resolve the hostname. + * A value of 0xFFFFFFFF for an IP address means that we haven't found it YET. + */ +void networkGetHostByName( + JsNetwork *net, //!< The network we are using for resolution. + char *hostName, //!< The hostname to be resolved. + uint32_t *out_ip_addr //!< The address where the returned IP address will be stored. + ) { + assert(hostName != NULL); + assert(out_ip_addr != NULL); + + // Set the default IP address returned to be 0 that indicates not found. + *out_ip_addr = 0; + + // first try and simply parse the IP address as a string + *out_ip_addr = networkParseIPAddress(hostName); + + // If we did not get an IP address from the string, then try and resolve it by + // calling the network gethostbyname. + if (!*out_ip_addr) { + net->gethostbyname(net, hostName, out_ip_addr); + } +} + + + +void networkCreate(JsNetwork *net, JsNetworkType type) { + net->networkVar = jsvNewStringOfLength(sizeof(JsNetworkData)); + if (!net->networkVar) return; + net->data.type = type; + net->data.device = EV_NONE; + net->data.pinCS = PIN_UNDEFINED; + net->data.pinIRQ = PIN_UNDEFINED; + net->data.pinEN = PIN_UNDEFINED; + jsvObjectSetChildAndUnLock(execInfo.hiddenRoot, NETWORK_VAR_NAME, net->networkVar); + networkSet(net); + networkGetFromVar(net); +} + +bool networkWasCreated() { + JsVar *v = jsvObjectGetChild(execInfo.hiddenRoot, NETWORK_VAR_NAME, 0); + if (v) { + jsvUnLock(v); + return true; + } else { + return false; + } +} + +bool networkGetFromVar(JsNetwork *net) { + net->networkVar = jsvObjectGetChild(execInfo.hiddenRoot, NETWORK_VAR_NAME, 0); + if (!net->networkVar) { +#ifdef LINUX + networkCreate(net, JSNETWORKTYPE_SOCKET); + return net->networkVar != 0; +#else + return false; +#endif + } + jsvGetString(net->networkVar, (char *)&net->data, sizeof(JsNetworkData)+1/*trailing zero*/); + + switch (net->data.type) { +#if defined(USE_CC3000) + case JSNETWORKTYPE_CC3000 : netSetCallbacks_cc3000(net); break; +#endif +#if defined(USE_WIZNET) + case JSNETWORKTYPE_W5500 : netSetCallbacks_wiznet(net); break; +#endif +#if defined(USE_ESP8266) + case JSNETWORKTYPE_ESP8266_BOARD : netSetCallbacks_esp8266_board(net); break; +#endif +#if defined(LINUX) + case JSNETWORKTYPE_SOCKET : netSetCallbacks_linux(net); break; +#endif + case JSNETWORKTYPE_JS : netSetCallbacks_js(net); break; + default: + jsError("Unknown network device %d", net->data.type); + networkFree(net); + return false; + } + networkCurrentStruct = net; + return true; +} + +bool networkGetFromVarIfOnline(JsNetwork *net) { + bool found = networkGetFromVar(net); + if (!found || networkState != NETWORKSTATE_ONLINE) { + jsError("Not connected to the internet"); + if (found) networkFree(net); + return false; + } + return true; +} + +void networkSet(JsNetwork *net) { + jsvSetString(net->networkVar, (char *)&net->data, sizeof(JsNetworkData)); +} + +void networkFree(JsNetwork *net) { + networkCurrentStruct = 0; + jsvUnLock(net->networkVar); +} + +JsNetwork *networkGetCurrent() { + return networkCurrentStruct; +} diff --git a/src/jsdevices.c b/src/jsdevices.c index 7a24c6ebf..d71c59777 100644 --- a/src/jsdevices.c +++ b/src/jsdevices.c @@ -32,7 +32,7 @@ JshEventCallbackCallback jshEventCallbacks[EV_EXTI_MAX+1-EV_EXTI0]; // DATA TRANSMIT BUFFER /** - * \brief A single character to be transmitted. + * A single character to be transmitted. */ typedef struct { IOEventFlags flags; //!< Where this data should be transmitted @@ -40,7 +40,7 @@ typedef struct { } PACKED_FLAGS TxBufferItem; /** - * \brief An array of items to transmit. + * An array of items to transmit. */ volatile TxBufferItem txBuffer[TXBUFFERMASK+1]; @@ -67,7 +67,7 @@ volatile unsigned char ioHead=0, ioTail=0; /** - * \brief Initialize all the devices. + * Initialize all the devices. */ void jshInitDevices() { // called from jshInit int i; @@ -83,7 +83,7 @@ void jshInitDevices() { // called from jshInit // ---------------------------------------------------------------------------- /** - * \brief Queue a character for transmission. + * Queue a character for transmission. */ void jshTransmit( IOEventFlags device, //!< The device to be used for transmission. @@ -140,7 +140,7 @@ IOEventFlags jshGetDeviceToTransmit() { } /** - * \brief Try and get a character for transmission. + * Try and get a character for transmission. * \return The next byte to transmit or -1 if there is none. */ int jshGetCharToTransmit( @@ -187,7 +187,7 @@ void jshTransmitFlush() { } /** - * \brief Discard all the data waiting for transmission. + * Discard all the data waiting for transmission. */ void jshTransmitClearDevice( IOEventFlags device //!< The device to be cleared. @@ -210,7 +210,7 @@ void jshTransmitMove(IOEventFlags from, IOEventFlags to) { } /** - * \brief Determine if we have data to be transmitted. + * Determine if we have data to be transmitted. * \return True if we have data to transmit and false otherwise. */ bool jshHasTransmitData() { @@ -218,7 +218,7 @@ bool jshHasTransmitData() { } /** - * \brief flag that the buffer has overflowed. + * flag that the buffer has overflowed. */ void jshIOEventOverflowed() { // Error here - just set flag so we don't dump a load of data out @@ -227,7 +227,7 @@ void jshIOEventOverflowed() { /** - * \brief Send a character to the specified device. + * Send a character to the specified device. */ void jshPushIOCharEvent( IOEventFlags channel, // !< The device to target for output. @@ -340,7 +340,7 @@ bool jshPopIOEventOfType(IOEventFlags eventType, IOEvent *result) { } /** - * \brief Determine if we have I/O events to process. + * Determine if we have I/O events to process. * \return True if there are I/O events to be processed. */ bool jshHasEvents() { @@ -369,7 +369,7 @@ bool jshHasEventSpaceForChars(int n) { // DEVICES /** - * \brief Get a string representation of a device. + * Get a string representation of a device. * \return A string representation of a device. */ const char *jshGetDeviceString( @@ -417,7 +417,7 @@ const char *jshGetDeviceString( } /** - * \brief Get a device identity from a string. + * Get a device identity from a string. * \return A device identity. */ IOEventFlags jshFromDeviceString( diff --git a/src/jsinteractive.c b/src/jsinteractive.c index ad7eb9b0f..5e1e290ae 100644 --- a/src/jsinteractive.c +++ b/src/jsinteractive.c @@ -71,7 +71,7 @@ void jsiDebuggerLine(JsVar *line); // ---------------------------------------------------------------------------- /** - * \brief Get the device from the class variable. + * Get the device from the class variable. */ IOEventFlags jsiGetDeviceFromClass(JsVar *class) { // Devices have their Object data set up to something special @@ -124,7 +124,7 @@ static NO_INLINE void jsiAppendToInputLine(const char *str) { } /** - * \brief Change the console to a new location. + * Change the console to a new location. */ void jsiSetConsoleDevice( IOEventFlags device //!< The device to use as a console. @@ -157,7 +157,7 @@ void jsiSetConsoleDevice( } /** - * \brief Retrieve the device being used as the console. + * Retrieve the device being used as the console. */ IOEventFlags jsiGetConsoleDevice() { // The `consoleDevice` is the global used to hold the current console. This function @@ -166,7 +166,7 @@ IOEventFlags jsiGetConsoleDevice() { } /** - * \brief Send a character to the console. + * Send a character to the console. */ NO_INLINE void jsiConsolePrintChar(char data) { jshTransmit(consoleDevice, (unsigned char)data); @@ -183,7 +183,7 @@ NO_INLINE void jsiConsolePrint(const char *str) { } /** - * \brief Perform a printf to the console. + * Perform a printf to the console. * Execute a printf command to the current JS console. */ void jsiConsolePrintf(const char *fmt, ...) { @@ -228,7 +228,7 @@ void jsiConsolePrintStringVarWithNewLineChar(JsVar *v, size_t fromCharacter, cha } /** - * \brief Print the contents of a string var - directly. + * Print the contents of a string var - directly. */ void jsiConsolePrintStringVar(JsVar *v) { jsiConsolePrintStringVarWithNewLineChar(v,0,0); @@ -357,7 +357,7 @@ void jsiConsolePrintPosition(struct JsLex *lex, size_t tokenPos) { } /** - * \brief Clear the input line of data. + * Clear the input line of data. */ void jsiClearInputLine() { jsiConsoleRemoveInputLine(); @@ -368,7 +368,7 @@ void jsiClearInputLine() { } /** - * \brief ??? What does this do ???. + * ??? What does this do ???. */ void jsiSetBusy( JsiBusyDevice device, //!< ??? @@ -386,7 +386,7 @@ void jsiSetBusy( } /** - * \brief Set the status of a pin as a function of whether we are asleep. + * Set the status of a pin as a function of whether we are asleep. * When called, if a pin is set for a sleep indicator, we set the pin to be true * if the sleep type is awake and false otherwise. */ diff --git a/src/jspin.c b/src/jspin.c index d874862bf..e2ef2a5b2 100644 --- a/src/jspin.c +++ b/src/jspin.c @@ -1,454 +1,454 @@ -/* - * This file is part of Espruino, a JavaScript interpreter for Microcontrollers - * - * Copyright (C) 2013 Gordon Williams - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * ---------------------------------------------------------------------------- - * Utilities and definitions for handling Pins - * ---------------------------------------------------------------------------- - */ - -#include "jspin.h" -#include "jspininfo.h" // auto-generated -#include "jsinteractive.h" -#include "jshardware.h" - -// jshGetDeviceObjectFor -#include "jswrapper.h" - -#if defined(PICO) || defined(NUCLEOF401RE) || defined(NUCLEOF411RE) -#define PIN_NAMES_DIRECT // work out pin names directly from port + pin in pinInfo -#endif - -/** - * \brief Validate that the pin is a good pin. - * \return True if the pin is valid. - */ -bool jshIsPinValid(Pin pin) { - // Note, PIN_UNDEFINED is always > JSH_PIN_COUNT - return pin < JSH_PIN_COUNT && pinInfo[pin].port != JSH_PORT_NONE; -} - -/** - * \brief Get a pin value from an encoded strin. - * \return A pin value. - */ -Pin jshGetPinFromString(const char *s) { - // !!!FIX!!! This function needs an algorithm description. - - // built in constants - - if (s[0]=='B' && s[1]=='T' && s[2]=='N') { -#ifdef BTN1_PININDEX - if (!s[3]) return BTN1_PININDEX; - if (s[3]=='1' && !s[4]) return BTN1_PININDEX; -#endif -#ifdef BTN2_PININDEX - if (s[3]=='2' && !s[4]) return BTN2_PININDEX; -#endif -#ifdef BTN3_PININDEX - if (s[3]=='3' && !s[4]) return BTN3_PININDEX; -#endif -#ifdef BTN4_PININDEX - if (s[3]=='4' && !s[4]) return BTN4_PININDEX; -#endif - } - if (s[0]=='L' && s[1]=='E' && s[2]=='D') { -#ifdef LED1_PININDEX - if (!s[3]) return LED1_PININDEX; - if (s[3]=='1' && !s[4]) return LED1_PININDEX; -#endif -#ifdef LED2_PININDEX - if (s[3]=='2' && !s[4]) return LED2_PININDEX; -#endif -#ifdef LED3_PININDEX - if (s[3]=='3' && !s[4]) return LED3_PININDEX; -#endif -#ifdef LED4_PININDEX - if (s[3]=='4' && !s[4]) return LED4_PININDEX; -#endif -#ifdef LED5_PININDEX - if (s[3]=='5' && !s[4]) return LED5_PININDEX; -#endif -#ifdef LED6_PININDEX - if (s[3]=='6' && !s[4]) return LED6_PININDEX; -#endif -#ifdef LED7_PININDEX - if (s[3]=='7' && !s[4]) return LED7_PININDEX; -#endif -#ifdef LED8_PININDEX - if (s[3]=='8' && !s[4]) return LED8_PININDEX; -#endif - } - - if ((s[0]>='A' && s[0]<='H') && s[1]) { - int port = JSH_PORTA+s[0]-'A'; - int pin = -1; - if (s[1]>='0' && s[1]<='9') { - if (!s[2]) { // D0-D9 - pin = (s[1]-'0'); - } else if (s[2]>='0' && s[2]<='9') { - if (!s[3]) { - pin = ((s[1]-'0')*10 + (s[2]-'0')); - } else if (!s[4] && s[3]>='0' && s[3]<='9') { - pin = ((s[1]-'0')*100 + (s[2]-'0')*10 + (s[3]-'0')); - } - } - } - if (pin>=0) { -#ifdef PIN_NAMES_DIRECT - int i; - for (i=0;i=JSH_PORTA_OFFSET && -#endif - pin=JSH_PORTB_OFFSET && pin=JSH_PORTC_OFFSET && pin=JSH_PORTD_OFFSET && pin=JSH_PORTE_OFFSET && pin=JSH_PORTF_OFFSET && pin=JSH_PORTG_OFFSET && pin=JSH_PORTH_OFFSET && pinvarData.str[5]==0/*should never be more than 4 chars!*/) { - return jshGetPinFromString(&pinv->varData.str[0]); - } else if (jsvIsInt(pinv) /* This also tests for the Pin datatype */) { - return (Pin)jsvGetInteger(pinv); - } else return PIN_UNDEFINED; -} - -Pin jshGetPinFromVarAndUnLock(JsVar *pinv) { - Pin pin = jshGetPinFromVar(pinv); - jsvUnLock(pinv); - return pin; -} - - // ---------------------------------------------------------------------------- - - // Whether a pin's state has been set manually or not -BITFIELD_DECL(jshPinStateIsManual, JSH_PIN_COUNT); // TODO: This should be set to all 0 - -bool jshGetPinStateIsManual(Pin pin) { - return BITFIELD_GET(jshPinStateIsManual, pin); -} - -void jshSetPinStateIsManual(Pin pin, bool manual) { - BITFIELD_SET(jshPinStateIsManual, pin, manual); -} - - // ---------------------------------------------------------------------------- - -/** - * \brief Get the value of a pin. - * \return The value of the pin. - */ -bool jshPinInput( - Pin pin //!< The pin to have the value retrieved. - ) { - bool value = false; - if (jshIsPinValid(pin)) { - if (!jshGetPinStateIsManual(pin)) - jshPinSetState(pin, JSHPINSTATE_GPIO_IN); - - value = jshPinGetValue(pin); - } - // Handle pin being invalid. - else jsExceptionHere(JSET_ERROR, "Invalid pin!"); - return value; -} - - -/** - * \brief Set the value of a pin. - */ -void jshPinOutput( - Pin pin, //!< The pin to set. - bool value //!< The new value to set on the pin. - ) { - if (jshIsPinValid(pin)) { - if (!jshGetPinStateIsManual(pin)) - jshPinSetState(pin, JSHPINSTATE_GPIO_OUT); - jshPinSetValue(pin, value); - } - // Handle pin being invalid. - else jsExceptionHere(JSET_ERROR, "Invalid pin!"); -} - - -// ---------------------------------------------------------------------------- - -// Convert an event type flag into a jshPinFunction for an actual hardware device -JshPinFunction jshGetPinFunctionFromDevice(IOEventFlags device) { - switch (device) { - case EV_SERIAL1 : return JSH_USART1; - case EV_SERIAL2 : return JSH_USART2; - case EV_SERIAL3 : return JSH_USART3; - case EV_SERIAL4 : return JSH_USART4; - case EV_SERIAL5 : return JSH_USART5; - case EV_SERIAL6 : return JSH_USART6; - - case EV_SPI1 : return JSH_SPI1; - case EV_SPI2 : return JSH_SPI2; - case EV_SPI3 : return JSH_SPI3; - - case EV_I2C1 : return JSH_I2C1; - case EV_I2C2 : return JSH_I2C2; - case EV_I2C3 : return JSH_I2C3; - default: return 0; - } -} - -/** Try and find a specific type of function for the given pin. Can be given an invalid pin and will return 0. */ -JshPinFunction NO_INLINE jshGetPinFunctionForPin(Pin pin, JshPinFunction functionType) { - if (!jshIsPinValid(pin)) return 0; - int i; - for (i=0;i>JSH_SHIFT_INFO)); - if (info & JSH_TIMER_NEGATED) { - infoStrBuf[3]='N'; - infoStrBuf[4] = 0; - } else { - infoStrBuf[3] = 0; - } - } - int devIdx = 1 + ((((pinFunc&JSH_MASK_TYPE) - firstDevice) >> JSH_SHIFT_TYPE)); - - if (!devStr) { - jsiConsolePrintf("Couldn't convert pin function %d\n", pinFunc); - return; - } - if (flags & JSPFTS_DEVICE) strncat(buf, devStr, bufSize); - if (flags & JSPFTS_DEVICE_NUMBER) itostr(devIdx, &buf[strlen(buf)], 10); - if (flags & JSPFTS_SPACE) strncat(buf, " ", bufSize); - if (infoStr && (flags & JSPFTS_TYPE)) strncat(buf, infoStr, bufSize); -} - -/** Prints a list of capable pins, eg: - jshPrintCapablePins(..., "PWM", JSH_TIMER1, JSH_TIMERMAX, 0,0, false) - jshPrintCapablePins(..., "SPI", JSH_SPI1, JSH_SPIMAX, JSH_MASK_INFO,JSH_SPI_SCK, false) - jshPrintCapablePins(..., "Analog Input", 0,0,0,0, true) - for analogs */ -void NO_INLINE jshPrintCapablePins(Pin existingPin, const char *functionName, JshPinFunction typeMin, JshPinFunction typeMax, JshPinFunction pMask, JshPinFunction pData, bool printAnalogs) { - if (functionName) { - jsError("Pin %p is not capable of %s\nSuitable pins are:", existingPin, functionName); - } - - Pin pin; - int i,n=0; - for (pin=0;pin=typeMin && type<=typeMax && ((pinInfo[pin].functions[i]&pMask)==pData)) { - has = true; -#ifdef STM32F1 - af = pinInfo[pin].functions[i] & JSH_MASK_AF; -#endif - } - } - } - if (has) { - jsiConsolePrintf("%p",pin); -#ifdef STM32F1 - if (af!=JSH_AF0) jsiConsolePrint("(AF)"); -#endif - jsiConsolePrint(" "); - if (n++==8) { n=0; jsiConsolePrint("\n"); } - } - } - jsiConsolePrint("\n"); -} - -/** Find a device of the given type that works on the given pin. For instance: - * `jshGetDeviceFor(JSH_SPI1, JSH_SPIMAX, pin); - */ -JshPinFunction jshGetDeviceFor(JshPinFunction deviceMin, JshPinFunction deviceMax, Pin pin) { - if (!jshIsPinValid(pin)) return JSH_NOTHING; - int i; - for (i=0;i= deviceMin && - (f&JSH_MASK_TYPE) <= deviceMax) - return f; - } - return JSH_NOTHING; -} - -/** Like jshGetDeviceFor, but returns an actual Object (eg. SPI) if one can be found. */ -JsVar *jshGetDeviceObjectFor(JshPinFunction deviceMin, JshPinFunction deviceMax, Pin pin) { - JshPinFunction dev = jshGetDeviceFor(deviceMin, deviceMax, pin); - if (dev==JSH_NOTHING) return 0; - char devName[16]; - jshPinFunctionToString(dev, JSPFTS_DEVICE|JSPFTS_DEVICE_NUMBER, devName, sizeof(devName)); - JsVar *devVar = jsvObjectGetChild(execInfo.root, devName, 0); - if (devVar) return devVar; - return jswFindBuiltInFunction(0, devName); -} +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * ---------------------------------------------------------------------------- + * Utilities and definitions for handling Pins + * ---------------------------------------------------------------------------- + */ + +#include "jspin.h" +#include "jspininfo.h" // auto-generated +#include "jsinteractive.h" +#include "jshardware.h" + +// jshGetDeviceObjectFor +#include "jswrapper.h" + +#if defined(PICO) || defined(NUCLEOF401RE) || defined(NUCLEOF411RE) +#define PIN_NAMES_DIRECT // work out pin names directly from port + pin in pinInfo +#endif + +/** + * Validate that the pin is a good pin. + * \return True if the pin is valid. + */ +bool jshIsPinValid(Pin pin) { + // Note, PIN_UNDEFINED is always > JSH_PIN_COUNT + return pin < JSH_PIN_COUNT && pinInfo[pin].port != JSH_PORT_NONE; +} + +/** + * Get a pin value from an encoded strin. + * \return A pin value. + */ +Pin jshGetPinFromString(const char *s) { + // !!!FIX!!! This function needs an algorithm description. + + // built in constants + + if (s[0]=='B' && s[1]=='T' && s[2]=='N') { +#ifdef BTN1_PININDEX + if (!s[3]) return BTN1_PININDEX; + if (s[3]=='1' && !s[4]) return BTN1_PININDEX; +#endif +#ifdef BTN2_PININDEX + if (s[3]=='2' && !s[4]) return BTN2_PININDEX; +#endif +#ifdef BTN3_PININDEX + if (s[3]=='3' && !s[4]) return BTN3_PININDEX; +#endif +#ifdef BTN4_PININDEX + if (s[3]=='4' && !s[4]) return BTN4_PININDEX; +#endif + } + if (s[0]=='L' && s[1]=='E' && s[2]=='D') { +#ifdef LED1_PININDEX + if (!s[3]) return LED1_PININDEX; + if (s[3]=='1' && !s[4]) return LED1_PININDEX; +#endif +#ifdef LED2_PININDEX + if (s[3]=='2' && !s[4]) return LED2_PININDEX; +#endif +#ifdef LED3_PININDEX + if (s[3]=='3' && !s[4]) return LED3_PININDEX; +#endif +#ifdef LED4_PININDEX + if (s[3]=='4' && !s[4]) return LED4_PININDEX; +#endif +#ifdef LED5_PININDEX + if (s[3]=='5' && !s[4]) return LED5_PININDEX; +#endif +#ifdef LED6_PININDEX + if (s[3]=='6' && !s[4]) return LED6_PININDEX; +#endif +#ifdef LED7_PININDEX + if (s[3]=='7' && !s[4]) return LED7_PININDEX; +#endif +#ifdef LED8_PININDEX + if (s[3]=='8' && !s[4]) return LED8_PININDEX; +#endif + } + + if ((s[0]>='A' && s[0]<='H') && s[1]) { + int port = JSH_PORTA+s[0]-'A'; + int pin = -1; + if (s[1]>='0' && s[1]<='9') { + if (!s[2]) { // D0-D9 + pin = (s[1]-'0'); + } else if (s[2]>='0' && s[2]<='9') { + if (!s[3]) { + pin = ((s[1]-'0')*10 + (s[2]-'0')); + } else if (!s[4] && s[3]>='0' && s[3]<='9') { + pin = ((s[1]-'0')*100 + (s[2]-'0')*10 + (s[3]-'0')); + } + } + } + if (pin>=0) { +#ifdef PIN_NAMES_DIRECT + int i; + for (i=0;i=JSH_PORTA_OFFSET && +#endif + pin=JSH_PORTB_OFFSET && pin=JSH_PORTC_OFFSET && pin=JSH_PORTD_OFFSET && pin=JSH_PORTE_OFFSET && pin=JSH_PORTF_OFFSET && pin=JSH_PORTG_OFFSET && pin=JSH_PORTH_OFFSET && pinvarData.str[5]==0/*should never be more than 4 chars!*/) { + return jshGetPinFromString(&pinv->varData.str[0]); + } else if (jsvIsInt(pinv) /* This also tests for the Pin datatype */) { + return (Pin)jsvGetInteger(pinv); + } else return PIN_UNDEFINED; +} + +Pin jshGetPinFromVarAndUnLock(JsVar *pinv) { + Pin pin = jshGetPinFromVar(pinv); + jsvUnLock(pinv); + return pin; +} + + // ---------------------------------------------------------------------------- + + // Whether a pin's state has been set manually or not +BITFIELD_DECL(jshPinStateIsManual, JSH_PIN_COUNT); // TODO: This should be set to all 0 + +bool jshGetPinStateIsManual(Pin pin) { + return BITFIELD_GET(jshPinStateIsManual, pin); +} + +void jshSetPinStateIsManual(Pin pin, bool manual) { + BITFIELD_SET(jshPinStateIsManual, pin, manual); +} + + // ---------------------------------------------------------------------------- + +/** + * Get the value of a pin. + * \return The value of the pin. + */ +bool jshPinInput( + Pin pin //!< The pin to have the value retrieved. + ) { + bool value = false; + if (jshIsPinValid(pin)) { + if (!jshGetPinStateIsManual(pin)) + jshPinSetState(pin, JSHPINSTATE_GPIO_IN); + + value = jshPinGetValue(pin); + } + // Handle pin being invalid. + else jsExceptionHere(JSET_ERROR, "Invalid pin!"); + return value; +} + + +/** + * Set the value of a pin. + */ +void jshPinOutput( + Pin pin, //!< The pin to set. + bool value //!< The new value to set on the pin. + ) { + if (jshIsPinValid(pin)) { + if (!jshGetPinStateIsManual(pin)) + jshPinSetState(pin, JSHPINSTATE_GPIO_OUT); + jshPinSetValue(pin, value); + } + // Handle pin being invalid. + else jsExceptionHere(JSET_ERROR, "Invalid pin!"); +} + + +// ---------------------------------------------------------------------------- + +// Convert an event type flag into a jshPinFunction for an actual hardware device +JshPinFunction jshGetPinFunctionFromDevice(IOEventFlags device) { + switch (device) { + case EV_SERIAL1 : return JSH_USART1; + case EV_SERIAL2 : return JSH_USART2; + case EV_SERIAL3 : return JSH_USART3; + case EV_SERIAL4 : return JSH_USART4; + case EV_SERIAL5 : return JSH_USART5; + case EV_SERIAL6 : return JSH_USART6; + + case EV_SPI1 : return JSH_SPI1; + case EV_SPI2 : return JSH_SPI2; + case EV_SPI3 : return JSH_SPI3; + + case EV_I2C1 : return JSH_I2C1; + case EV_I2C2 : return JSH_I2C2; + case EV_I2C3 : return JSH_I2C3; + default: return 0; + } +} + +/** Try and find a specific type of function for the given pin. Can be given an invalid pin and will return 0. */ +JshPinFunction NO_INLINE jshGetPinFunctionForPin(Pin pin, JshPinFunction functionType) { + if (!jshIsPinValid(pin)) return 0; + int i; + for (i=0;i>JSH_SHIFT_INFO)); + if (info & JSH_TIMER_NEGATED) { + infoStrBuf[3]='N'; + infoStrBuf[4] = 0; + } else { + infoStrBuf[3] = 0; + } + } + int devIdx = 1 + ((((pinFunc&JSH_MASK_TYPE) - firstDevice) >> JSH_SHIFT_TYPE)); + + if (!devStr) { + jsiConsolePrintf("Couldn't convert pin function %d\n", pinFunc); + return; + } + if (flags & JSPFTS_DEVICE) strncat(buf, devStr, bufSize); + if (flags & JSPFTS_DEVICE_NUMBER) itostr(devIdx, &buf[strlen(buf)], 10); + if (flags & JSPFTS_SPACE) strncat(buf, " ", bufSize); + if (infoStr && (flags & JSPFTS_TYPE)) strncat(buf, infoStr, bufSize); +} + +/** Prints a list of capable pins, eg: + jshPrintCapablePins(..., "PWM", JSH_TIMER1, JSH_TIMERMAX, 0,0, false) + jshPrintCapablePins(..., "SPI", JSH_SPI1, JSH_SPIMAX, JSH_MASK_INFO,JSH_SPI_SCK, false) + jshPrintCapablePins(..., "Analog Input", 0,0,0,0, true) - for analogs */ +void NO_INLINE jshPrintCapablePins(Pin existingPin, const char *functionName, JshPinFunction typeMin, JshPinFunction typeMax, JshPinFunction pMask, JshPinFunction pData, bool printAnalogs) { + if (functionName) { + jsError("Pin %p is not capable of %s\nSuitable pins are:", existingPin, functionName); + } + + Pin pin; + int i,n=0; + for (pin=0;pin=typeMin && type<=typeMax && ((pinInfo[pin].functions[i]&pMask)==pData)) { + has = true; +#ifdef STM32F1 + af = pinInfo[pin].functions[i] & JSH_MASK_AF; +#endif + } + } + } + if (has) { + jsiConsolePrintf("%p",pin); +#ifdef STM32F1 + if (af!=JSH_AF0) jsiConsolePrint("(AF)"); +#endif + jsiConsolePrint(" "); + if (n++==8) { n=0; jsiConsolePrint("\n"); } + } + } + jsiConsolePrint("\n"); +} + +/** Find a device of the given type that works on the given pin. For instance: + * `jshGetDeviceFor(JSH_SPI1, JSH_SPIMAX, pin); + */ +JshPinFunction jshGetDeviceFor(JshPinFunction deviceMin, JshPinFunction deviceMax, Pin pin) { + if (!jshIsPinValid(pin)) return JSH_NOTHING; + int i; + for (i=0;i= deviceMin && + (f&JSH_MASK_TYPE) <= deviceMax) + return f; + } + return JSH_NOTHING; +} + +/** Like jshGetDeviceFor, but returns an actual Object (eg. SPI) if one can be found. */ +JsVar *jshGetDeviceObjectFor(JshPinFunction deviceMin, JshPinFunction deviceMax, Pin pin) { + JshPinFunction dev = jshGetDeviceFor(deviceMin, deviceMax, pin); + if (dev==JSH_NOTHING) return 0; + char devName[16]; + jshPinFunctionToString(dev, JSPFTS_DEVICE|JSPFTS_DEVICE_NUMBER, devName, sizeof(devName)); + JsVar *devVar = jsvObjectGetChild(execInfo.root, devName, 0); + if (devVar) return devVar; + return jswFindBuiltInFunction(0, devName); +} diff --git a/src/jsspi.c b/src/jsspi.c index 80692e10b..2b4e87a98 100644 --- a/src/jsspi.c +++ b/src/jsspi.c @@ -15,7 +15,7 @@ #include "jsinteractive.h" /** - * \brief Dump the internal SPI Info data structure to the console. + * Dump the internal SPI Info data structure to the console. * This is an internal debugging function. */ void jsspiDumpSPIInfo(JshSPIInfo *inf) { @@ -31,7 +31,7 @@ int jsspiHardwareFunc(int data, spi_sender_data *info) { /** - * \brief Send a single byte through SPI. + * Send a single byte through SPI. * \return The received byte. */ int jsspiFastSoftwareFunc( @@ -56,7 +56,7 @@ int jsspiFastSoftwareFunc( /** - * \brief Send a single byte through SPI. + * Send a single byte through SPI. * \return The received byte. */ int jsspiSoftwareFunc( @@ -105,7 +105,7 @@ int jsspiSoftwareFunc( /** - * \brief Populate a JshSPIInfo structure from a JS Object. + * Populate a JshSPIInfo structure from a JS Object. * The object properties that are examined are: * * `sck` - The pin to use for the clock. * * `miso` - The pin to use for Master In/Slave Out. @@ -149,7 +149,7 @@ void jsspiPopulateSPIInfo( } /** - * \brief Select the SPI send function. + * Select the SPI send function. * Get the correct SPI send function (and the data to send to it). We do this * by examining the device and determining if it is hardware, software fast * or software regular. diff --git a/src/jsutils.c b/src/jsutils.c index d6cadaf30..b21de1a9f 100644 --- a/src/jsutils.c +++ b/src/jsutils.c @@ -156,7 +156,7 @@ long long stringToIntWithRadix(const char *s, int forceRadix, bool *hasError) { } /** - * \brief Convert hex, binary, octal or decimal string into an int. + * Convert hex, binary, octal or decimal string into an int. */ long long stringToInt(const char *s) { return stringToIntWithRadix(s,0,0); @@ -327,7 +327,7 @@ void srand(unsigned int seed) { /** - * \brief Convert a string to a JS float variable where the string is of a specific radix. + * Convert a string to a JS float variable where the string is of a specific radix. * \return A JS float variable. */ JsVarFloat stringToFloatWithRadix( @@ -413,7 +413,7 @@ JsVarFloat stringToFloatWithRadix( /** - * \brief convert a string to a floating point JS variable. + * convert a string to a floating point JS variable. * \return a JS float variable. */ JsVarFloat stringToFloat( @@ -512,7 +512,7 @@ JsVarFloat wrapAround(JsVarFloat val, JsVarFloat size) { } /** - * \brief Espruino-special printf with a callback. + * Espruino-special printf with a callback. * * The supported format specifiers are: * * `%d` = int diff --git a/src/jsvariterator.c b/src/jsvariterator.c index 2b59daf0b..d7fe41513 100644 --- a/src/jsvariterator.c +++ b/src/jsvariterator.c @@ -1,530 +1,530 @@ -/* - * This file is part of Espruino, a JavaScript interpreter for Microcontrollers - * - * Copyright (C) 2013 Gordon Williams - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * ---------------------------------------------------------------------------- - * Iterators for Variables - * ---------------------------------------------------------------------------- - */ -#include "jsvariterator.h" - -/** - * \brief Iterate over the contents of the content of a variable, calling callback for each. - * Contents may be: - * * numeric -> output - * * a string -> output each character - * * array/arraybuffer -> call itself on each element - * object -> call itself object.count times, on object.data - */ -bool jsvIterateCallback( - JsVar *data, // The data to iterate over. - void (*callback)(int item, void *callbackData), // The callback function invoke. - void *callbackData // Data to be passed to the callback function - ) { - bool ok = true; - // Handle the data being a single numeric. - if (jsvIsNumeric(data)) { - callback((int)jsvGetInteger(data), callbackData); - } - // Handle the data being an object. - else if (jsvIsObject(data)) { - JsVar *countVar = jsvObjectGetChild(data, "count", 0); - JsVar *dataVar = jsvObjectGetChild(data, "data", 0); - if (countVar && dataVar && jsvIsNumeric(countVar)) { - int n = (int)jsvGetInteger(countVar); - while (ok && n-- > 0) { - ok = jsvIterateCallback(dataVar, callback, callbackData); - } - } else { - jsWarn("If specifying an object, it must be of the form {data : ..., count : N}"); - } - jsvUnLock2(countVar, dataVar); - } - // Handle the data being a string - else if (jsvIsString(data)) { - JsvStringIterator it; - jsvStringIteratorNew(&it, data, 0); - while (jsvStringIteratorHasChar(&it) && ok) { - char ch = jsvStringIteratorGetChar(&it); - callback(ch, callbackData); - jsvStringIteratorNext(&it); - } - jsvStringIteratorFree(&it); - } - // Handle the data being an array buffer - else if (jsvIsArrayBuffer(data)) { - JsvArrayBufferIterator it; - jsvArrayBufferIteratorNew(&it, data, 0); - if (JSV_ARRAYBUFFER_GET_SIZE(it.type) == 1 && !JSV_ARRAYBUFFER_IS_SIGNED(it.type)) { - // faster for single byte arrays. - while (jsvArrayBufferIteratorHasElement(&it)) { - callback((int)(unsigned char)jsvStringIteratorGetChar(&it.it), callbackData); - jsvArrayBufferIteratorNext(&it); - } - } else { - while (jsvArrayBufferIteratorHasElement(&it)) { - callback((int)jsvArrayBufferIteratorGetIntegerValue(&it), callbackData); - jsvArrayBufferIteratorNext(&it); - } - } - jsvArrayBufferIteratorFree(&it); - } - // Handle the data being iterable - else if (jsvIsIterable(data)) { - JsvIterator it; - jsvIteratorNew(&it, data); - while (jsvIteratorHasElement(&it) && ok) { - JsVar *el = jsvIteratorGetValue(&it); - ok = jsvIterateCallback(el, callback, callbackData); - jsvUnLock(el); - jsvIteratorNext(&it); - } - jsvIteratorFree(&it); - } else { - jsWarn("Expecting a number or something iterable, got %t", data); - ok = false; - } - return ok; -} - - -/** - * \brief An iterable callback that counts how many times it was called. - * This is a function that can be supplied to `jsvIterateCallback`. - */ -static void jsvIterateCallbackCountCb( - int n, //!< The current item being iterated. Not used. - void *data //!< A pointer to an int that counts how many times we were called. - ) { - NOT_USED(n); - int *count = (int*)data; - (*count)++; -} - - -/** - * \brief Determine how many items are in this variable that will be iterated over. - * \return The number of iterations we will call for this variable. - */ -int jsvIterateCallbackCount(JsVar *var) { - // Actually iterate over the variable where the callback function merely increments a counter - // that is initially zero. The result will be the number of times the callback for iteration - // was invoked and hence the iteration count of the variable. - int count = 0; - jsvIterateCallback(var, jsvIterateCallbackCountCb, (void *)&count); - return count; -} - - -typedef struct { unsigned char *buf; unsigned int idx, length; } JsvIterateCallbackToBytesData; -static void jsvIterateCallbackToBytesCb(int data, void *userData) { - JsvIterateCallbackToBytesData *cbData = (JsvIterateCallbackToBytesData*)userData; - if (cbData->idx < cbData->length) - cbData->buf[cbData->idx] = (unsigned char)data; - cbData->idx++; -} -/** Write all data in array to the data pointer (of size dataSize bytes) */ -unsigned int jsvIterateCallbackToBytes(JsVar *var, unsigned char *data, unsigned int dataSize) { - JsvIterateCallbackToBytesData cbData; - cbData.buf = (unsigned char *)data; - cbData.idx = 0; - cbData.length = dataSize; - jsvIterateCallback(var, jsvIterateCallbackToBytesCb, (void*)&cbData); - return cbData.idx; -} - -// -------------------------------------------------------------------------------------------- - -void jsvStringIteratorNew(JsvStringIterator *it, JsVar *str, size_t startIdx) { - assert(jsvHasCharacterData(str)); - it->var = jsvLockAgain(str); - it->charsInVar = jsvGetCharactersInVar(str); - if (jsvIsFlatString(str)) { - /* Flat strings use the first var to store the size, and subsequent vars - to store the actual data, so we tweak charIdx to handle this */ - it->varIndex = -sizeof(JsVar); - it->charsInVar += sizeof(JsVar); - it->charIdx = sizeof(JsVar)+startIdx; - } else { - it->varIndex = 0; - it->charIdx = startIdx; - } - while (it->charIdx>0 && it->charIdx >= it->charsInVar) { - it->charIdx -= it->charsInVar; - it->varIndex += it->charsInVar; - if (it->var) { - if (jsvGetLastChild(it->var)) { - JsVar *next = jsvLock(jsvGetLastChild(it->var)); - jsvUnLock(it->var); - it->var = next; - it->charsInVar = jsvGetCharactersInVar(it->var); - } else { - jsvUnLock(it->var); - it->var = 0; - it->charsInVar = 0; - it->varIndex = startIdx - it->charIdx; - return; // at end of string - get out of loop - } - } - } -} - -void jsvStringIteratorNext(JsvStringIterator *it) { - jsvStringIteratorNextInline(it); -} - -void jsvStringIteratorGotoEnd(JsvStringIterator *it) { - assert(it->var); - while (jsvGetLastChild(it->var)) { - JsVar *next = jsvLock(jsvGetLastChild(it->var)); - jsvUnLock(it->var); - it->var = next; - it->varIndex += it->charsInVar; - it->charsInVar = jsvGetCharactersInVar(it->var); - } - if (it->charsInVar) it->charIdx = it->charsInVar-1; - else it->charIdx = 0; -} - -void jsvStringIteratorAppend(JsvStringIterator *it, char ch) { - if (!it->var) return; - if (it->charsInVar>0) { - assert(it->charIdx+1 == it->charsInVar /* check at end */); - it->charIdx++; - } else - assert(it->charIdx == 0); - /* Note: jsvGetMaxCharactersInVar will return the wrong length when - * applied to flat strings, but we don't care because the length will - * be smaller than charIdx, which will force a new string to be - * appended onto the end */ - if (it->charIdx >= jsvGetMaxCharactersInVar(it->var)) { - assert(!jsvGetLastChild(it->var)); - JsVar *next = jsvNewWithFlags(JSV_STRING_EXT_0); - if (!next) { - jsvUnLock(it->var); - it->var = 0; - it->charIdx = 0; - return; // out of memory - } - // we don't ref, because StringExts are never reffed as they only have one owner (and ALWAYS have an owner) - jsvSetLastChild(it->var, jsvGetRef(next)); - jsvUnLock(it->var); - it->var = next; - it->varIndex += it->charIdx; - it->charIdx = 0; // it's new, so empty - } - it->var->varData.str[it->charIdx] = ch; - it->charsInVar = it->charIdx+1; - jsvSetCharactersInVar(it->var, it->charsInVar); -} - - -// -------------------------------------------------------------------------------------------- -void jsvArrayBufferIteratorNew(JsvArrayBufferIterator *it, JsVar *arrayBuffer, size_t index) { - assert(jsvIsArrayBuffer(arrayBuffer)); - it->index = index; - it->type = arrayBuffer->varData.arraybuffer.type; - it->byteLength = arrayBuffer->varData.arraybuffer.length * JSV_ARRAYBUFFER_GET_SIZE(it->type); - it->byteOffset = arrayBuffer->varData.arraybuffer.byteOffset; - JsVar *arrayBufferData = jsvGetArrayBufferBackingString(arrayBuffer); - - it->byteLength += it->byteOffset; // because we'll check if we have more bytes using this - it->byteOffset = it->byteOffset + index*JSV_ARRAYBUFFER_GET_SIZE(it->type); - if (it->byteOffset>=(it->byteLength+1-JSV_ARRAYBUFFER_GET_SIZE(it->type))) { - jsvUnLock(arrayBufferData); - it->type = ARRAYBUFFERVIEW_UNDEFINED; - return; - } - jsvStringIteratorNew(&it->it, arrayBufferData, (size_t)it->byteOffset); - jsvUnLock(arrayBufferData); - it->hasAccessedElement = false; -} - -static void jsvArrayBufferIteratorGetValueData(JsvArrayBufferIterator *it, char *data) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; - assert(!it->hasAccessedElement); // we just haven't implemented this case yet - unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - for (i=0;iit); - if (dataLen!=1) jsvStringIteratorNext(&it->it); - } - if (dataLen!=1) it->hasAccessedElement = true; -} - -static JsVarInt jsvArrayBufferIteratorDataToInt(JsvArrayBufferIterator *it, char *data) { - unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - JsVarInt v = 0; - if (dataLen==1) v = *(int8_t*)data; - else if (dataLen==2) v = *(short*)data; - else if (dataLen==4) v = *(int*)data; - else assert(0); - if ((!JSV_ARRAYBUFFER_IS_SIGNED(it->type))) - v = v & (JsVarInt)((1UL << (8*dataLen))-1); - return v; -} - -static JsVarFloat jsvArrayBufferIteratorDataToFloat(JsvArrayBufferIterator *it, char *data) { - unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - JsVarFloat v = 0; - if (dataLen==4) v = *(float*)data; - else if (dataLen==8) v = *(double*)data; - else assert(0); - return v; -} - -JsVar *jsvArrayBufferIteratorGetValue(JsvArrayBufferIterator *it) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; - char data[8]; - jsvArrayBufferIteratorGetValueData(it, data); - if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { - return jsvNewFromFloat(jsvArrayBufferIteratorDataToFloat(it, data)); - } else { - JsVarInt i = jsvArrayBufferIteratorDataToInt(it, data); - if (it->type == ARRAYBUFFERVIEW_UINT32) - return jsvNewFromLongInteger((long long)(uint32_t)i); - return jsvNewFromInteger(i); - } -} - -JsVar *jsvArrayBufferIteratorGetValueAndRewind(JsvArrayBufferIterator *it) { - JsvStringIterator oldIt = jsvStringIteratorClone(&it->it); - JsVar *v = jsvArrayBufferIteratorGetValue(it); - jsvStringIteratorFree(&it->it); - it->it = oldIt; - it->hasAccessedElement = false; - return v; -} - -JsVarInt jsvArrayBufferIteratorGetIntegerValue(JsvArrayBufferIterator *it) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; - char data[8]; - jsvArrayBufferIteratorGetValueData(it, data); - if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { - return (JsVarInt)jsvArrayBufferIteratorDataToFloat(it, data); - } else { - return jsvArrayBufferIteratorDataToInt(it, data); - } -} - -JsVarFloat jsvArrayBufferIteratorGetFloatValue(JsvArrayBufferIterator *it) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; - char data[8]; - jsvArrayBufferIteratorGetValueData(it, data); - if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { - return jsvArrayBufferIteratorDataToFloat(it, data); - } else { - return (JsVarFloat)jsvArrayBufferIteratorDataToInt(it, data); - } -} - -static void jsvArrayBufferIteratorIntToData(char *data, unsigned int dataLen, int type, JsVarInt v) { - if (JSV_ARRAYBUFFER_IS_CLAMPED(type)) { - assert(dataLen==1 && !JSV_ARRAYBUFFER_IS_SIGNED(type)); // all we support right now - if (v<0) v=0; - if (v>255) v=255; - } - // we don't care about sign when writing - as it gets truncated - if (dataLen==1) { data[0] = (char)v; } - else if (dataLen==2) { *(short*)data = (short)v; } - else if (dataLen==4) { *(int*)data = (int)v; } - else if (dataLen==8) { *(long long*)data = (long long)v; } - else assert(0); -} - -static void jsvArrayBufferIteratorFloatToData(char *data, unsigned int dataLen, int type, JsVarFloat v) { - NOT_USED(type); - if (dataLen==4) { *(float*)data = (float)v; } - else if (dataLen==8) { *(double*)data = (double)v; } - else assert(0); -} - -void jsvArrayBufferIteratorSetIntegerValue(JsvArrayBufferIterator *it, JsVarInt v) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; - assert(!it->hasAccessedElement); // we just haven't implemented this case yet - char data[8]; - unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - - if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { - jsvArrayBufferIteratorFloatToData(data, dataLen, it->type, (JsVarFloat)v); - } else { - jsvArrayBufferIteratorIntToData(data, dataLen, it->type, v); - } - - for (i=0;iit, data[i]); - if (dataLen!=1) jsvStringIteratorNext(&it->it); - } - if (dataLen!=1) it->hasAccessedElement = true; -} - -void jsvArrayBufferIteratorSetValue(JsvArrayBufferIterator *it, JsVar *value) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; - assert(!it->hasAccessedElement); // we just haven't implemented this case yet - char data[8]; - unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - - if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { - jsvArrayBufferIteratorFloatToData(data, dataLen, it->type, jsvGetFloat(value)); - } else { - jsvArrayBufferIteratorIntToData(data, dataLen, it->type, jsvGetInteger(value)); - } - - for (i=0;iit, data[i]); - if (dataLen!=1) jsvStringIteratorNext(&it->it); - } - if (dataLen!=1) it->hasAccessedElement = true; -} - -void jsvArrayBufferIteratorSetByteValue(JsvArrayBufferIterator *it, char c) { - if (JSV_ARRAYBUFFER_GET_SIZE(it->type)!=1) { - assert(0); - return; - } - jsvStringIteratorSetChar(&it->it, c); -} - -void jsvArrayBufferIteratorSetValueAndRewind(JsvArrayBufferIterator *it, JsVar *value) { - JsvStringIterator oldIt = jsvStringIteratorClone(&it->it); - jsvArrayBufferIteratorSetValue(it, value); - jsvStringIteratorFree(&it->it); - it->it = oldIt; - it->hasAccessedElement = false; -} - - -JsVar* jsvArrayBufferIteratorGetIndex(JsvArrayBufferIterator *it) { - return jsvNewFromInteger((JsVarInt)it->index); -} - -bool jsvArrayBufferIteratorHasElement(JsvArrayBufferIterator *it) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return false; - if (it->hasAccessedElement) return true; - return it->byteOffset <= (it->byteLength-JSV_ARRAYBUFFER_GET_SIZE(it->type)); -} - -void jsvArrayBufferIteratorNext(JsvArrayBufferIterator *it) { - it->index++; - it->byteOffset += JSV_ARRAYBUFFER_GET_SIZE(it->type); - if (!it->hasAccessedElement) { - unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - while (dataLen--) - jsvStringIteratorNext(&it->it); - } else - it->hasAccessedElement = false; -} - -void jsvArrayBufferIteratorFree(JsvArrayBufferIterator *it) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; - jsvStringIteratorFree(&it->it); -} -// -------------------------------------------------------------------------------------------- -/* General Purpose iterator, for Strings, Arrays, Objects, Typed Arrays */ - -void jsvIteratorNew(JsvIterator *it, JsVar *obj) { - if (jsvIsArray(obj) || jsvIsObject(obj) || jsvIsFunction(obj)) { - it->type = JSVI_OBJECT; - jsvObjectIteratorNew(&it->it.obj, obj); - } else if (jsvIsArrayBuffer(obj)) { - it->type = JSVI_ARRAYBUFFER; - jsvArrayBufferIteratorNew(&it->it.buf, obj, 0); - } else if (jsvHasCharacterData(obj)) { - it->type = JSVI_STRING; - jsvStringIteratorNew(&it->it.str, obj, 0); - } else assert(0); -} - -JsVar *jsvIteratorGetKey(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : return jsvObjectIteratorGetKey(&it->it.obj); - case JSVI_STRING : return jsvMakeIntoVariableName(jsvNewFromInteger((JsVarInt)jsvStringIteratorGetIndex(&it->it.str)), 0); // some things expect a veriable name - case JSVI_ARRAYBUFFER : return jsvMakeIntoVariableName(jsvArrayBufferIteratorGetIndex(&it->it.buf), 0); // some things expect a veriable name - default: assert(0); return 0; - } -} - -JsVar *jsvIteratorGetValue(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : return jsvObjectIteratorGetValue(&it->it.obj); - case JSVI_STRING : { char buf[2] = {jsvStringIteratorGetChar(&it->it.str),0}; return jsvNewFromString(buf); } - case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetValueAndRewind(&it->it.buf); - default: assert(0); return 0; - } -} - -JsVarInt jsvIteratorGetIntegerValue(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : { - // fast path for arrays of ints - if (jsvIsNameInt(it->it.obj.var)) return (JsVarInt)jsvGetFirstChildSigned(it->it.obj.var); - return jsvGetIntegerAndUnLock(jsvObjectIteratorGetValue(&it->it.obj)); - } - case JSVI_STRING : return (JsVarInt)jsvStringIteratorGetChar(&it->it.str); - case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetIntegerValue(&it->it.buf); - default: assert(0); return 0; - } -} - -JsVarFloat jsvIteratorGetFloatValue(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : return jsvGetFloatAndUnLock(jsvObjectIteratorGetValue(&it->it.obj)); - case JSVI_STRING : return (JsVarFloat)jsvStringIteratorGetChar(&it->it.str); - case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetFloatValue(&it->it.buf); - default: assert(0); return 0; - } -} - -JsVar *jsvIteratorSetValue(JsvIterator *it, JsVar *value) { - switch (it->type) { - case JSVI_OBJECT : jsvObjectIteratorSetValue(&it->it.obj, value); break; - case JSVI_STRING : jsvStringIteratorSetChar(&it->it.str, (char)(jsvIsString(value) ? value->varData.str[0] : (char)jsvGetInteger(value))); break; - case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorSetValueAndRewind(&it->it.buf, value); break; - default: assert(0); break; - } - return value; -} - -bool jsvIteratorHasElement(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : return jsvObjectIteratorHasValue(&it->it.obj); - case JSVI_STRING : return jsvStringIteratorHasChar(&it->it.str); - case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorHasElement(&it->it.buf); - default: assert(0); return 0; - } -} - -void jsvIteratorNext(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : jsvObjectIteratorNext(&it->it.obj); break; - case JSVI_STRING : jsvStringIteratorNext(&it->it.str); break; - case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorNext(&it->it.buf); break; - default: assert(0); break; - } -} - -void jsvIteratorFree(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : jsvObjectIteratorFree(&it->it.obj); break; - case JSVI_STRING : jsvStringIteratorFree(&it->it.str); break; - case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorFree(&it->it.buf); break; - default: assert(0); break; - } -} - -JsvIterator jsvIteratorClone(JsvIterator *it) { - JsvIterator newit; - newit.type = it->type; - switch (it->type) { - case JSVI_OBJECT : newit.it.obj = jsvObjectIteratorClone(&it->it.obj); break; - case JSVI_STRING : newit.it.str = jsvStringIteratorClone(&it->it.str); break; - case JSVI_ARRAYBUFFER : newit.it.buf = jsvArrayBufferIteratorClone(&it->it.buf); break; - default: assert(0); break; - } - return newit; -} - +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * ---------------------------------------------------------------------------- + * Iterators for Variables + * ---------------------------------------------------------------------------- + */ +#include "jsvariterator.h" + +/** + * Iterate over the contents of the content of a variable, calling callback for each. + * Contents may be: + * * numeric -> output + * * a string -> output each character + * * array/arraybuffer -> call itself on each element + * object -> call itself object.count times, on object.data + */ +bool jsvIterateCallback( + JsVar *data, // The data to iterate over. + void (*callback)(int item, void *callbackData), // The callback function invoke. + void *callbackData // Data to be passed to the callback function + ) { + bool ok = true; + // Handle the data being a single numeric. + if (jsvIsNumeric(data)) { + callback((int)jsvGetInteger(data), callbackData); + } + // Handle the data being an object. + else if (jsvIsObject(data)) { + JsVar *countVar = jsvObjectGetChild(data, "count", 0); + JsVar *dataVar = jsvObjectGetChild(data, "data", 0); + if (countVar && dataVar && jsvIsNumeric(countVar)) { + int n = (int)jsvGetInteger(countVar); + while (ok && n-- > 0) { + ok = jsvIterateCallback(dataVar, callback, callbackData); + } + } else { + jsWarn("If specifying an object, it must be of the form {data : ..., count : N}"); + } + jsvUnLock2(countVar, dataVar); + } + // Handle the data being a string + else if (jsvIsString(data)) { + JsvStringIterator it; + jsvStringIteratorNew(&it, data, 0); + while (jsvStringIteratorHasChar(&it) && ok) { + char ch = jsvStringIteratorGetChar(&it); + callback(ch, callbackData); + jsvStringIteratorNext(&it); + } + jsvStringIteratorFree(&it); + } + // Handle the data being an array buffer + else if (jsvIsArrayBuffer(data)) { + JsvArrayBufferIterator it; + jsvArrayBufferIteratorNew(&it, data, 0); + if (JSV_ARRAYBUFFER_GET_SIZE(it.type) == 1 && !JSV_ARRAYBUFFER_IS_SIGNED(it.type)) { + // faster for single byte arrays. + while (jsvArrayBufferIteratorHasElement(&it)) { + callback((int)(unsigned char)jsvStringIteratorGetChar(&it.it), callbackData); + jsvArrayBufferIteratorNext(&it); + } + } else { + while (jsvArrayBufferIteratorHasElement(&it)) { + callback((int)jsvArrayBufferIteratorGetIntegerValue(&it), callbackData); + jsvArrayBufferIteratorNext(&it); + } + } + jsvArrayBufferIteratorFree(&it); + } + // Handle the data being iterable + else if (jsvIsIterable(data)) { + JsvIterator it; + jsvIteratorNew(&it, data); + while (jsvIteratorHasElement(&it) && ok) { + JsVar *el = jsvIteratorGetValue(&it); + ok = jsvIterateCallback(el, callback, callbackData); + jsvUnLock(el); + jsvIteratorNext(&it); + } + jsvIteratorFree(&it); + } else { + jsWarn("Expecting a number or something iterable, got %t", data); + ok = false; + } + return ok; +} + + +/** + * An iterable callback that counts how many times it was called. + * This is a function that can be supplied to `jsvIterateCallback`. + */ +static void jsvIterateCallbackCountCb( + int n, //!< The current item being iterated. Not used. + void *data //!< A pointer to an int that counts how many times we were called. + ) { + NOT_USED(n); + int *count = (int*)data; + (*count)++; +} + + +/** + * Determine how many items are in this variable that will be iterated over. + * \return The number of iterations we will call for this variable. + */ +int jsvIterateCallbackCount(JsVar *var) { + // Actually iterate over the variable where the callback function merely increments a counter + // that is initially zero. The result will be the number of times the callback for iteration + // was invoked and hence the iteration count of the variable. + int count = 0; + jsvIterateCallback(var, jsvIterateCallbackCountCb, (void *)&count); + return count; +} + + +typedef struct { unsigned char *buf; unsigned int idx, length; } JsvIterateCallbackToBytesData; +static void jsvIterateCallbackToBytesCb(int data, void *userData) { + JsvIterateCallbackToBytesData *cbData = (JsvIterateCallbackToBytesData*)userData; + if (cbData->idx < cbData->length) + cbData->buf[cbData->idx] = (unsigned char)data; + cbData->idx++; +} +/** Write all data in array to the data pointer (of size dataSize bytes) */ +unsigned int jsvIterateCallbackToBytes(JsVar *var, unsigned char *data, unsigned int dataSize) { + JsvIterateCallbackToBytesData cbData; + cbData.buf = (unsigned char *)data; + cbData.idx = 0; + cbData.length = dataSize; + jsvIterateCallback(var, jsvIterateCallbackToBytesCb, (void*)&cbData); + return cbData.idx; +} + +// -------------------------------------------------------------------------------------------- + +void jsvStringIteratorNew(JsvStringIterator *it, JsVar *str, size_t startIdx) { + assert(jsvHasCharacterData(str)); + it->var = jsvLockAgain(str); + it->charsInVar = jsvGetCharactersInVar(str); + if (jsvIsFlatString(str)) { + /* Flat strings use the first var to store the size, and subsequent vars + to store the actual data, so we tweak charIdx to handle this */ + it->varIndex = -sizeof(JsVar); + it->charsInVar += sizeof(JsVar); + it->charIdx = sizeof(JsVar)+startIdx; + } else { + it->varIndex = 0; + it->charIdx = startIdx; + } + while (it->charIdx>0 && it->charIdx >= it->charsInVar) { + it->charIdx -= it->charsInVar; + it->varIndex += it->charsInVar; + if (it->var) { + if (jsvGetLastChild(it->var)) { + JsVar *next = jsvLock(jsvGetLastChild(it->var)); + jsvUnLock(it->var); + it->var = next; + it->charsInVar = jsvGetCharactersInVar(it->var); + } else { + jsvUnLock(it->var); + it->var = 0; + it->charsInVar = 0; + it->varIndex = startIdx - it->charIdx; + return; // at end of string - get out of loop + } + } + } +} + +void jsvStringIteratorNext(JsvStringIterator *it) { + jsvStringIteratorNextInline(it); +} + +void jsvStringIteratorGotoEnd(JsvStringIterator *it) { + assert(it->var); + while (jsvGetLastChild(it->var)) { + JsVar *next = jsvLock(jsvGetLastChild(it->var)); + jsvUnLock(it->var); + it->var = next; + it->varIndex += it->charsInVar; + it->charsInVar = jsvGetCharactersInVar(it->var); + } + if (it->charsInVar) it->charIdx = it->charsInVar-1; + else it->charIdx = 0; +} + +void jsvStringIteratorAppend(JsvStringIterator *it, char ch) { + if (!it->var) return; + if (it->charsInVar>0) { + assert(it->charIdx+1 == it->charsInVar /* check at end */); + it->charIdx++; + } else + assert(it->charIdx == 0); + /* Note: jsvGetMaxCharactersInVar will return the wrong length when + * applied to flat strings, but we don't care because the length will + * be smaller than charIdx, which will force a new string to be + * appended onto the end */ + if (it->charIdx >= jsvGetMaxCharactersInVar(it->var)) { + assert(!jsvGetLastChild(it->var)); + JsVar *next = jsvNewWithFlags(JSV_STRING_EXT_0); + if (!next) { + jsvUnLock(it->var); + it->var = 0; + it->charIdx = 0; + return; // out of memory + } + // we don't ref, because StringExts are never reffed as they only have one owner (and ALWAYS have an owner) + jsvSetLastChild(it->var, jsvGetRef(next)); + jsvUnLock(it->var); + it->var = next; + it->varIndex += it->charIdx; + it->charIdx = 0; // it's new, so empty + } + it->var->varData.str[it->charIdx] = ch; + it->charsInVar = it->charIdx+1; + jsvSetCharactersInVar(it->var, it->charsInVar); +} + + +// -------------------------------------------------------------------------------------------- +void jsvArrayBufferIteratorNew(JsvArrayBufferIterator *it, JsVar *arrayBuffer, size_t index) { + assert(jsvIsArrayBuffer(arrayBuffer)); + it->index = index; + it->type = arrayBuffer->varData.arraybuffer.type; + it->byteLength = arrayBuffer->varData.arraybuffer.length * JSV_ARRAYBUFFER_GET_SIZE(it->type); + it->byteOffset = arrayBuffer->varData.arraybuffer.byteOffset; + JsVar *arrayBufferData = jsvGetArrayBufferBackingString(arrayBuffer); + + it->byteLength += it->byteOffset; // because we'll check if we have more bytes using this + it->byteOffset = it->byteOffset + index*JSV_ARRAYBUFFER_GET_SIZE(it->type); + if (it->byteOffset>=(it->byteLength+1-JSV_ARRAYBUFFER_GET_SIZE(it->type))) { + jsvUnLock(arrayBufferData); + it->type = ARRAYBUFFERVIEW_UNDEFINED; + return; + } + jsvStringIteratorNew(&it->it, arrayBufferData, (size_t)it->byteOffset); + jsvUnLock(arrayBufferData); + it->hasAccessedElement = false; +} + +static void jsvArrayBufferIteratorGetValueData(JsvArrayBufferIterator *it, char *data) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; + assert(!it->hasAccessedElement); // we just haven't implemented this case yet + unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + for (i=0;iit); + if (dataLen!=1) jsvStringIteratorNext(&it->it); + } + if (dataLen!=1) it->hasAccessedElement = true; +} + +static JsVarInt jsvArrayBufferIteratorDataToInt(JsvArrayBufferIterator *it, char *data) { + unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + JsVarInt v = 0; + if (dataLen==1) v = *(int8_t*)data; + else if (dataLen==2) v = *(short*)data; + else if (dataLen==4) v = *(int*)data; + else assert(0); + if ((!JSV_ARRAYBUFFER_IS_SIGNED(it->type))) + v = v & (JsVarInt)((1UL << (8*dataLen))-1); + return v; +} + +static JsVarFloat jsvArrayBufferIteratorDataToFloat(JsvArrayBufferIterator *it, char *data) { + unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + JsVarFloat v = 0; + if (dataLen==4) v = *(float*)data; + else if (dataLen==8) v = *(double*)data; + else assert(0); + return v; +} + +JsVar *jsvArrayBufferIteratorGetValue(JsvArrayBufferIterator *it) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; + char data[8]; + jsvArrayBufferIteratorGetValueData(it, data); + if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { + return jsvNewFromFloat(jsvArrayBufferIteratorDataToFloat(it, data)); + } else { + JsVarInt i = jsvArrayBufferIteratorDataToInt(it, data); + if (it->type == ARRAYBUFFERVIEW_UINT32) + return jsvNewFromLongInteger((long long)(uint32_t)i); + return jsvNewFromInteger(i); + } +} + +JsVar *jsvArrayBufferIteratorGetValueAndRewind(JsvArrayBufferIterator *it) { + JsvStringIterator oldIt = jsvStringIteratorClone(&it->it); + JsVar *v = jsvArrayBufferIteratorGetValue(it); + jsvStringIteratorFree(&it->it); + it->it = oldIt; + it->hasAccessedElement = false; + return v; +} + +JsVarInt jsvArrayBufferIteratorGetIntegerValue(JsvArrayBufferIterator *it) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; + char data[8]; + jsvArrayBufferIteratorGetValueData(it, data); + if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { + return (JsVarInt)jsvArrayBufferIteratorDataToFloat(it, data); + } else { + return jsvArrayBufferIteratorDataToInt(it, data); + } +} + +JsVarFloat jsvArrayBufferIteratorGetFloatValue(JsvArrayBufferIterator *it) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; + char data[8]; + jsvArrayBufferIteratorGetValueData(it, data); + if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { + return jsvArrayBufferIteratorDataToFloat(it, data); + } else { + return (JsVarFloat)jsvArrayBufferIteratorDataToInt(it, data); + } +} + +static void jsvArrayBufferIteratorIntToData(char *data, unsigned int dataLen, int type, JsVarInt v) { + if (JSV_ARRAYBUFFER_IS_CLAMPED(type)) { + assert(dataLen==1 && !JSV_ARRAYBUFFER_IS_SIGNED(type)); // all we support right now + if (v<0) v=0; + if (v>255) v=255; + } + // we don't care about sign when writing - as it gets truncated + if (dataLen==1) { data[0] = (char)v; } + else if (dataLen==2) { *(short*)data = (short)v; } + else if (dataLen==4) { *(int*)data = (int)v; } + else if (dataLen==8) { *(long long*)data = (long long)v; } + else assert(0); +} + +static void jsvArrayBufferIteratorFloatToData(char *data, unsigned int dataLen, int type, JsVarFloat v) { + NOT_USED(type); + if (dataLen==4) { *(float*)data = (float)v; } + else if (dataLen==8) { *(double*)data = (double)v; } + else assert(0); +} + +void jsvArrayBufferIteratorSetIntegerValue(JsvArrayBufferIterator *it, JsVarInt v) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; + assert(!it->hasAccessedElement); // we just haven't implemented this case yet + char data[8]; + unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + + if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { + jsvArrayBufferIteratorFloatToData(data, dataLen, it->type, (JsVarFloat)v); + } else { + jsvArrayBufferIteratorIntToData(data, dataLen, it->type, v); + } + + for (i=0;iit, data[i]); + if (dataLen!=1) jsvStringIteratorNext(&it->it); + } + if (dataLen!=1) it->hasAccessedElement = true; +} + +void jsvArrayBufferIteratorSetValue(JsvArrayBufferIterator *it, JsVar *value) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; + assert(!it->hasAccessedElement); // we just haven't implemented this case yet + char data[8]; + unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + + if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { + jsvArrayBufferIteratorFloatToData(data, dataLen, it->type, jsvGetFloat(value)); + } else { + jsvArrayBufferIteratorIntToData(data, dataLen, it->type, jsvGetInteger(value)); + } + + for (i=0;iit, data[i]); + if (dataLen!=1) jsvStringIteratorNext(&it->it); + } + if (dataLen!=1) it->hasAccessedElement = true; +} + +void jsvArrayBufferIteratorSetByteValue(JsvArrayBufferIterator *it, char c) { + if (JSV_ARRAYBUFFER_GET_SIZE(it->type)!=1) { + assert(0); + return; + } + jsvStringIteratorSetChar(&it->it, c); +} + +void jsvArrayBufferIteratorSetValueAndRewind(JsvArrayBufferIterator *it, JsVar *value) { + JsvStringIterator oldIt = jsvStringIteratorClone(&it->it); + jsvArrayBufferIteratorSetValue(it, value); + jsvStringIteratorFree(&it->it); + it->it = oldIt; + it->hasAccessedElement = false; +} + + +JsVar* jsvArrayBufferIteratorGetIndex(JsvArrayBufferIterator *it) { + return jsvNewFromInteger((JsVarInt)it->index); +} + +bool jsvArrayBufferIteratorHasElement(JsvArrayBufferIterator *it) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return false; + if (it->hasAccessedElement) return true; + return it->byteOffset <= (it->byteLength-JSV_ARRAYBUFFER_GET_SIZE(it->type)); +} + +void jsvArrayBufferIteratorNext(JsvArrayBufferIterator *it) { + it->index++; + it->byteOffset += JSV_ARRAYBUFFER_GET_SIZE(it->type); + if (!it->hasAccessedElement) { + unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + while (dataLen--) + jsvStringIteratorNext(&it->it); + } else + it->hasAccessedElement = false; +} + +void jsvArrayBufferIteratorFree(JsvArrayBufferIterator *it) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; + jsvStringIteratorFree(&it->it); +} +// -------------------------------------------------------------------------------------------- +/* General Purpose iterator, for Strings, Arrays, Objects, Typed Arrays */ + +void jsvIteratorNew(JsvIterator *it, JsVar *obj) { + if (jsvIsArray(obj) || jsvIsObject(obj) || jsvIsFunction(obj)) { + it->type = JSVI_OBJECT; + jsvObjectIteratorNew(&it->it.obj, obj); + } else if (jsvIsArrayBuffer(obj)) { + it->type = JSVI_ARRAYBUFFER; + jsvArrayBufferIteratorNew(&it->it.buf, obj, 0); + } else if (jsvHasCharacterData(obj)) { + it->type = JSVI_STRING; + jsvStringIteratorNew(&it->it.str, obj, 0); + } else assert(0); +} + +JsVar *jsvIteratorGetKey(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : return jsvObjectIteratorGetKey(&it->it.obj); + case JSVI_STRING : return jsvMakeIntoVariableName(jsvNewFromInteger((JsVarInt)jsvStringIteratorGetIndex(&it->it.str)), 0); // some things expect a veriable name + case JSVI_ARRAYBUFFER : return jsvMakeIntoVariableName(jsvArrayBufferIteratorGetIndex(&it->it.buf), 0); // some things expect a veriable name + default: assert(0); return 0; + } +} + +JsVar *jsvIteratorGetValue(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : return jsvObjectIteratorGetValue(&it->it.obj); + case JSVI_STRING : { char buf[2] = {jsvStringIteratorGetChar(&it->it.str),0}; return jsvNewFromString(buf); } + case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetValueAndRewind(&it->it.buf); + default: assert(0); return 0; + } +} + +JsVarInt jsvIteratorGetIntegerValue(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : { + // fast path for arrays of ints + if (jsvIsNameInt(it->it.obj.var)) return (JsVarInt)jsvGetFirstChildSigned(it->it.obj.var); + return jsvGetIntegerAndUnLock(jsvObjectIteratorGetValue(&it->it.obj)); + } + case JSVI_STRING : return (JsVarInt)jsvStringIteratorGetChar(&it->it.str); + case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetIntegerValue(&it->it.buf); + default: assert(0); return 0; + } +} + +JsVarFloat jsvIteratorGetFloatValue(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : return jsvGetFloatAndUnLock(jsvObjectIteratorGetValue(&it->it.obj)); + case JSVI_STRING : return (JsVarFloat)jsvStringIteratorGetChar(&it->it.str); + case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetFloatValue(&it->it.buf); + default: assert(0); return 0; + } +} + +JsVar *jsvIteratorSetValue(JsvIterator *it, JsVar *value) { + switch (it->type) { + case JSVI_OBJECT : jsvObjectIteratorSetValue(&it->it.obj, value); break; + case JSVI_STRING : jsvStringIteratorSetChar(&it->it.str, (char)(jsvIsString(value) ? value->varData.str[0] : (char)jsvGetInteger(value))); break; + case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorSetValueAndRewind(&it->it.buf, value); break; + default: assert(0); break; + } + return value; +} + +bool jsvIteratorHasElement(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : return jsvObjectIteratorHasValue(&it->it.obj); + case JSVI_STRING : return jsvStringIteratorHasChar(&it->it.str); + case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorHasElement(&it->it.buf); + default: assert(0); return 0; + } +} + +void jsvIteratorNext(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : jsvObjectIteratorNext(&it->it.obj); break; + case JSVI_STRING : jsvStringIteratorNext(&it->it.str); break; + case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorNext(&it->it.buf); break; + default: assert(0); break; + } +} + +void jsvIteratorFree(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : jsvObjectIteratorFree(&it->it.obj); break; + case JSVI_STRING : jsvStringIteratorFree(&it->it.str); break; + case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorFree(&it->it.buf); break; + default: assert(0); break; + } +} + +JsvIterator jsvIteratorClone(JsvIterator *it) { + JsvIterator newit; + newit.type = it->type; + switch (it->type) { + case JSVI_OBJECT : newit.it.obj = jsvObjectIteratorClone(&it->it.obj); break; + case JSVI_STRING : newit.it.str = jsvStringIteratorClone(&it->it.str); break; + case JSVI_ARRAYBUFFER : newit.it.buf = jsvArrayBufferIteratorClone(&it->it.buf); break; + default: assert(0); break; + } + return newit; +} + diff --git a/src/jswrap_io.c b/src/jswrap_io.c index 0033621a3..479a8cb05 100644 --- a/src/jswrap_io.c +++ b/src/jswrap_io.c @@ -1,576 +1,576 @@ -/* - * This file is part of Espruino, a JavaScript interpreter for Microcontrollers - * - * Copyright (C) 2013 Gordon Williams - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * ---------------------------------------------------------------------------- - * This file is designed to be parsed during the build process - * - * JavaScript Hardware IO Functions - * ---------------------------------------------------------------------------- - */ -#include "jswrap_io.h" -#include "jsvar.h" -#include "jswrap_arraybuffer.h" // for jswrap_io_peek - -/*JSON{ - "type" : "function", - "name" : "peek8", - "generate_full" : "jswrap_io_peek(addr,count,1)", - "params" : [ - ["addr", "int", "The address in memory to read"], - ["count", "int", "(optional) the number of items to read. If >1 a Uint8Array will be returned."] - ], - "return" : ["JsVar","The value of memory at the given location"] -} -Read 8 bits of memory at the given location - DANGEROUS! - */ -/*JSON{ - "type" : "function", - "name" : "poke8", - "generate_full" : "jswrap_io_poke(addr,value,1)", - "params" : [ - ["addr","int","The address in memory to write"], - ["value","JsVar","The value to write, or an array of values"] - ] -} -Write 8 bits of memory at the given location - VERY DANGEROUS! - */ -/*JSON{ - "type" : "function", - "name" : "peek16", - "generate_full" : "jswrap_io_peek(addr,count,2)", - "params" : [ - ["addr","int","The address in memory to read"], - ["count","int","(optional) the number of items to read. If >1 a Uint16Array will be returned."] - ], - "return" : ["JsVar","The value of memory at the given location"] -} -Read 16 bits of memory at the given location - DANGEROUS! - */ -/*JSON{ - "type" : "function", - "name" : "poke16", - "generate_full" : "jswrap_io_poke(addr,value,2)", - "params" : [ - ["addr","int","The address in memory to write"], - ["value","JsVar","The value to write, or an array of values"] - ] -} -Write 16 bits of memory at the given location - VERY DANGEROUS! - */ -/*JSON{ - "type" : "function", - "name" : "peek32", - "generate_full" : "jswrap_io_peek(addr,count,4)", - "params" : [ - ["addr","int","The address in memory to read"], - ["count","int","(optional) the number of items to read. If >1 a Uint32Array will be returned."] - ], - "return" : ["JsVar","The value of memory at the given location"] -} -Read 32 bits of memory at the given location - DANGEROUS! - */ -/*JSON{ - "type" : "function", - "name" : "poke32", - "generate_full" : "jswrap_io_poke(addr,value,4)", - "params" : [ - ["addr","int","The address in memory to write"], - ["value","JsVar","The value to write, or an array of values"] - ] -} -Write 32 bits of memory at the given location - VERY DANGEROUS! - */ - -uint32_t _jswrap_io_peek(JsVarInt addr, int wordSize) { - if (wordSize==1) return (uint32_t)*(unsigned char*)(size_t)addr; - if (wordSize==2) return (uint32_t)*(unsigned short*)(size_t)addr; - if (wordSize==4) return (uint32_t)*(unsigned int*)(size_t)addr; - return 0; -} - -JsVar *jswrap_io_peek(JsVarInt addr, JsVarInt count, int wordSize) { - if (count<=1) { - return jsvNewFromLongInteger((long long)_jswrap_io_peek(addr, wordSize)); - } else { - JsVarDataArrayBufferViewType aType; - if (wordSize==1) aType=ARRAYBUFFERVIEW_UINT8; - if (wordSize==2) aType=ARRAYBUFFERVIEW_UINT16; - if (wordSize==4) aType=ARRAYBUFFERVIEW_UINT32; - JsVar *arr = jsvNewTypedArray(aType, count); - if (!arr) return 0; - JsvArrayBufferIterator it; - jsvArrayBufferIteratorNew(&it, arr, 0); - while (jsvArrayBufferIteratorHasElement(&it)) { - jsvArrayBufferIteratorSetIntegerValue(&it, (JsVarInt)_jswrap_io_peek(addr, wordSize)); - addr += wordSize; - jsvArrayBufferIteratorNext(&it); - } - jsvArrayBufferIteratorFree(&it); - return arr; - } -} - -void _jswrap_io_poke(JsVarInt addr, uint32_t data, int wordSize) { - if (wordSize==1) (*(unsigned char*)(size_t)addr) = (unsigned char)data; - else if (wordSize==2) (*(unsigned short*)(size_t)addr) = (unsigned short)data; - else if (wordSize==4) (*(unsigned int*)(size_t)addr) = (unsigned int)data; -} - -void jswrap_io_poke(JsVarInt addr, JsVar *data, int wordSize) { - if (jsvIsNumeric(data)) { - _jswrap_io_poke(addr, (uint32_t)jsvGetInteger(data), wordSize); - } else if (jsvIsIterable(data)) { - JsvIterator it; - jsvIteratorNew(&it, data); - while (jsvIteratorHasElement(&it)) { - _jswrap_io_poke(addr, (uint32_t)jsvIteratorGetIntegerValue(&it), wordSize); - addr += wordSize; - jsvIteratorNext(&it); - } - jsvIteratorFree(&it); - } -} - - -/*JSON{ - "type" : "function", - "name" : "analogRead", - "generate" : "jshPinAnalog", - "params" : [ - ["pin","pin",["The pin to use","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `ADC` markers."]] - ], - "return" : ["float","The analog Value of the Pin between 0 and 1"] -} -Get the analog value of the given pin - -This is different to Arduino which only returns an integer between 0 and 1023 - -However only pins connected to an ADC will work (see the datasheet) - - **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"analog"` - */ -/*JSON{ - "type" : "function", - "name" : "analogWrite", - "generate" : "jswrap_io_analogWrite", - "params" : [ - ["pin","pin",["The pin to use","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `PWM` or `DAC` markers."]], - ["value","float","A value between 0 and 1"], - ["options","JsVar",["An object containing options for analog output - see below"]] - ] -} -Set the analog Value of a pin. It will be output using PWM. - -Objects can contain: - -* `freq` - pulse frequency in Hz, eg. ```analogWrite(A0,0.5,{ freq : 10 });``` - specifying a frequency will force PWM output, even if the pin has a DAC -* `soft` - boolean, If true software PWM is used if available. -* `forceSoft` - boolean, If true software PWM is used even - - **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` - */ -void jswrap_io_analogWrite(Pin pin, JsVarFloat value, JsVar *options) { - JsVarFloat freq = 0; - JshAnalogOutputFlags flags = JSAOF_NONE; - if (jsvIsObject(options)) { - freq = jsvGetFloatAndUnLock(jsvObjectGetChild(options, "freq", 0)); - if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "forceSoft", 0))) - flags |= JSAOF_FORCE_SOFTWARE; - else if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "soft", 0))) - flags |= JSAOF_ALLOW_SOFTWARE; - } - - jshPinAnalogOutput(pin, value, freq, flags); -} - -/*JSON{ - "type" : "function", - "name" : "digitalPulse", - "generate" : "jswrap_io_digitalPulse", - "params" : [ - ["pin","pin","The pin to use"], - ["value","bool","Whether to pulse high (true) or low (false)"], - ["time","JsVar","A time in milliseconds, or an array of times (in which case a square wave will be output starting with a pulse of 'value')"] - ] -} -Pulse the pin with the value for the given time in milliseconds. It uses a hardware timer to produce accurate pulses, and returns immediately (before the pulse has finished). Use `digitalPulse(A0,1,0)` to wait until a previous pulse has finished. - -eg. `digitalPulse(A0,1,5);` pulses A0 high for 5ms. `digitalPulse(A0,1,[5,2,4]);` pulses A0 high for 5ms, low for 2ms, and high for 4ms - - **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` - -digitalPulse is for SHORT pulses that need to be very accurate. If you're doing anything over a few milliseconds, use setTimeout instead. - */ -void jswrap_io_digitalPulse(Pin pin, bool value, JsVar *times) { - if (jsvIsNumeric(times)) { - JsVarFloat time = jsvGetFloat(times); - if (time<0 || isnan(time)) { - jsExceptionHere(JSET_ERROR, "Pulse Time given for digitalPulse is less than 0, or not a number"); - } else { - jshPinPulse(pin, value, time); - } - } else if (jsvIsIterable(times)) { - // iterable, so output a square wave - JsvIterator it; - jsvIteratorNew(&it, times); - while (jsvIteratorHasElement(&it)) { - JsVarFloat time = jsvIteratorGetFloatValue(&it); - if (time>=0 && !isnan(time)) - jshPinPulse(pin, value, time); - value = !value; - jsvIteratorNext(&it); - } - jsvIteratorFree(&it); - } else { - jsExceptionHere(JSET_ERROR, "Expecting a number or array, got %t", times); - } -} - -/*JSON{ - "type" : "function", - "name" : "digitalWrite", - "generate" : "jswrap_io_digitalWrite", - "params" : [ - ["pin", "JsVar","The pin to use"], - ["value", "int","Whether to pulse high (true) or low (false)"] - ] -} -Set the digital value of the given pin. - - **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` - -If pin argument is an array of pins (eg. `[A2,A1,A0]`) the value argument will be treated -as an array of bits where the last array element is the least significant bit. - -In this case, pin values are set last significant bit first (from the right-hand side -of the array of pins). This means you can use the same pin multiple times, for -example `digitalWrite([A1,A1,A0,A0],0b0101)` would pulse A0 followed by A1. -*/ - -/** - * \brief Set the output of a GPIO. - */ -void jswrap_io_digitalWrite( - JsVar *pinVar, //!< A pin or pins. - JsVarInt value //!< The value of the output. - ) { - // Handle the case where it is an array of pins. - if (jsvIsArray(pinVar)) { - JsVarRef pinName = jsvGetLastChild(pinVar); // NOTE: start at end and work back! - while (pinName) { - JsVar *pinNamePtr = jsvLock(pinName); - JsVar *pinPtr = jsvSkipName(pinNamePtr); - jshPinOutput(jshGetPinFromVar(pinPtr), value&1); - jsvUnLock(pinPtr); - pinName = jsvGetPrevSibling(pinNamePtr); - jsvUnLock(pinNamePtr); - value = value>>1; // next bit down - } - } - // Handle the case where it is a single pin. - else { - Pin pin = jshGetPinFromVar(pinVar); - jshPinOutput(pin, value != 0); - } -} - - -/*JSON{ - "type" : "function", - "name" : "digitalRead", - "generate" : "jswrap_io_digitalRead", - "params" : [ - ["pin","JsVar","The pin to use"] - ], - "return" : ["int","The digital Value of the Pin"] -} -Get the digital value of the given pin. - - **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"input"` - -If the pin argument is an array of pins (eg. `[A2,A1,A0]`) the value returned will be an number where -the last array element is the least significant bit, for example if `A0=A1=1` and `A2=0`, `digitalRead([A2,A1,A0]) == 0b011` -*/ - -/** - * \brief Read the value of a GPIO pin. - */ -JsVarInt jswrap_io_digitalRead(JsVar *pinVar) { - // Hadnle the case where it is an array of pins. - if (jsvIsArray(pinVar)) { - int pins = 0; - JsVarInt value = 0; - JsvObjectIterator it; - jsvObjectIteratorNew(&it, pinVar); - while (jsvObjectIteratorHasValue(&it)) { - JsVar *pinPtr = jsvObjectIteratorGetValue(&it); - value = (value<<1) | (JsVarInt)jshPinInput(jshGetPinFromVar(pinPtr)); - jsvUnLock(pinPtr); - jsvObjectIteratorNext(&it); - pins++; - } - jsvObjectIteratorFree(&it); - if (pins==0) return 0; // return undefined if array empty - return value; - } - // Handle the case where it is a single pin. - else { - Pin pin = jshGetPinFromVar(pinVar); - return jshPinInput(pin); - } -} - -/*JSON{ - "type" : "function", - "name" : "pinMode", - "generate" : "jswrap_io_pinMode", - "params" : [ - ["pin","pin","The pin to set pin mode for"], - ["mode","JsVar","The mode - a string that is either 'analog', 'input', 'input_pullup', 'input_pulldown', 'output', 'opendrain', 'af_output' or 'af_opendrain'. Do not include this argument if you want to revert to automatic pin mode setting."] - ] -} -Set the mode of the given pin. - - * `analog` - Analog input - * `input` - Digital input - * `input_pullup` - Digital input with internal ~40k pull-up resistor - * `input_pulldown` - Digital input with internal ~40k pull-down resistor - * `output` - Digital output - * `opendrain` - Digital output that only ever pulls down to 0v. Sending a logical `1` leaves the pin open circuit - * `af_output` - Digital output from built-in peripheral - * `af_opendrain` - Digital output from built-in peripheral that only ever pulls down to 0v. - * Sending a logical `1` leaves the pin open circuit - - **Note:** `digitalRead`/`digitalWrite`/etc set the pin mode automatically *unless* `pinMode` has been called first. If you want `digitalRead`/etc to set the pin mode automatically after you have called `pinMode`, simply call it again with no mode argument: `pinMode(pin)` -*/ - -/** - * \brief Set the mode of a pin. - */ -void jswrap_io_pinMode( - Pin pin, //!< The pin to set. - JsVar *mode //!< The new mode of the pin. - ) { - if (!jshIsPinValid(pin)) { - jsExceptionHere(JSET_ERROR, "Invalid pin"); - return; - } - JshPinState m = JSHPINSTATE_UNDEFINED; - if (jsvIsString(mode)) { - if (jsvIsStringEqual(mode, "analog")) m = JSHPINSTATE_ADC_IN; - else if (jsvIsStringEqual(mode, "input")) m = JSHPINSTATE_GPIO_IN; - else if (jsvIsStringEqual(mode, "input_pullup")) m = JSHPINSTATE_GPIO_IN_PULLUP; - else if (jsvIsStringEqual(mode, "input_pulldown")) m = JSHPINSTATE_GPIO_IN_PULLDOWN; - else if (jsvIsStringEqual(mode, "output")) m = JSHPINSTATE_GPIO_OUT; - else if (jsvIsStringEqual(mode, "opendrain")) m = JSHPINSTATE_GPIO_OUT_OPENDRAIN; - else if (jsvIsStringEqual(mode, "af_output")) m = JSHPINSTATE_AF_OUT; - else if (jsvIsStringEqual(mode, "af_opendrain")) m = JSHPINSTATE_AF_OUT_OPENDRAIN; - } - if (m != JSHPINSTATE_UNDEFINED) { - jshSetPinStateIsManual(pin, true); - jshPinSetState(pin, m); - } else { - jshSetPinStateIsManual(pin, false); - if (!jsvIsUndefined(mode)) { - jsExceptionHere(JSET_ERROR, "Unknown pin mode"); - } - } -} - -/*JSON{ - "type" : "function", - "name" : "getPinMode", - "generate" : "jswrap_io_getPinMode", - "params" : [ - ["pin","pin","The pin to check"] - ], - "return" : ["JsVar","The pin mode, as a string"] -} -Return the current mode of the given pin. See `pinMode` for more information. - */ -JsVar *jswrap_io_getPinMode(Pin pin) { - if (!jshIsPinValid(pin)) { - jsExceptionHere(JSET_ERROR, "Invalid pin"); - return 0; - } - JshPinState m = jshPinGetState(pin)&JSHPINSTATE_MASK; - const char *text = 0; - switch (m) { - case JSHPINSTATE_ADC_IN : text = "analog"; break; - case JSHPINSTATE_GPIO_IN : text = "input"; break; - case JSHPINSTATE_GPIO_IN_PULLUP : text = "input_pullup"; break; - case JSHPINSTATE_GPIO_IN_PULLDOWN : text = "input_pulldown"; break; - case JSHPINSTATE_GPIO_OUT : text = "output"; break; - case JSHPINSTATE_GPIO_OUT_OPENDRAIN : text = "opendrain"; break; - case JSHPINSTATE_AF_OUT : text = "af_output"; break; - case JSHPINSTATE_AF_OUT_OPENDRAIN : text = "af_opendrain"; break; - default: break; - } - if (text) return jsvNewFromString(text); - return 0; -} - -/*JSON{ - "type" : "function", - "name" : "setWatch", - "generate" : "jswrap_interface_setWatch", - "params" : [ - ["function","JsVar","A Function or String to be executed"], - ["pin","pin","The pin to watch"], - ["options","JsVar",["If this is a boolean or integer, it determines whether to call this once (false = default) or every time a change occurs (true)","If this is an object, it can contain the following information: ```{ repeat: true/false(default), edge:'rising'/'falling'/'both'(default), debounce:10}```. `debounce` is the time in ms to wait for bounces to subside, or 0."]] - ], - "return" : ["JsVar","An ID that can be passed to clearWatch"] -} -Call the function specified when the pin changes. Watches set with `setWatch` can be removed using `clearWatch`. - -The function may also take an argument, which is an object of type `{state:bool, time:float, lastTime:float}`. - - * `state` is whether the pin is currently a `1` or a `0` - * `time` is the time in seconds at which the pin changed state - * `lastTime` is the time in seconds at which the **pin last changed state**. When using `edge:'rising'` or `edge:'falling'`, this is not the same as when the function was last called. - -For instance, if you want to measure the length of a positive pulse you could use `setWatch(function(e) { console.log(e.time-e.lastTime); }, BTN, { repeat:true, edge:'falling' });`. -This will only be called on the falling edge of the pulse, but will be able to measure the width of the pulse because `e.lastTime` is the time of the rising edge. - -Internally, an interrupt writes the time of the pin's state change into a queue, and the function -supplied to `setWatch` is executed only from the main message loop. However, if the callback is a -native function `void (bool state)` then you can add `irq:true` to options, which will cause the -function to be called from within the IRQ. When doing this, interrupts will happen on both edges -and there will be no debouncing. - -**Note:** The STM32 chip (used in the [Espruino Board](/EspruinoBoard) and [Pico](/Pico)) cannot -watch two pins with the same number - eg `A0` and `B0`. - - */ -JsVar *jswrap_interface_setWatch(JsVar *func, Pin pin, JsVar *repeatOrObject) { - if (!jshIsPinValid(pin)) { - jsError("Invalid pin"); - return 0; - } - - if (!jsiIsWatchingPin(pin) && !jshCanWatch(pin)) { - jsWarn("Unable to set watch. You may already have a watch on a pin with the same number (eg. A0 and B0)"); - return 0; - } - - bool repeat = false; - JsVarFloat debounce = 0; - int edge = 0; - bool isIRQ = false; - if (jsvIsObject(repeatOrObject)) { - JsVar *v; - repeat = jsvGetBoolAndUnLock(jsvObjectGetChild(repeatOrObject, "repeat", 0)); - debounce = jsvGetFloatAndUnLock(jsvObjectGetChild(repeatOrObject, "debounce", 0)); - if (isnan(debounce) || debounce<0) debounce=0; - v = jsvObjectGetChild(repeatOrObject, "edge", 0); - if (jsvIsString(v)) { - if (jsvIsStringEqual(v, "rising")) edge=1; - else if (jsvIsStringEqual(v, "falling")) edge=-1; - else if (jsvIsStringEqual(v, "both")) edge=0; - else jsWarn("'edge' in setWatch should be a string - either 'rising', 'falling' or 'both'"); - } else if (!jsvIsUndefined(v)) - jsWarn("'edge' in setWatch should be a string - either 'rising', 'falling' or 'both'"); - jsvUnLock(v); - isIRQ = jsvGetBoolAndUnLock(jsvObjectGetChild(repeatOrObject, "irq", 0)); - } else - repeat = jsvGetBool(repeatOrObject); - - JsVarInt itemIndex = -1; - if (!jsvIsFunction(func) && !jsvIsString(func)) { - jsExceptionHere(JSET_ERROR, "Function or String not supplied!"); - } else { - // Create a new watch - JsVar *watchPtr = jsvNewWithFlags(JSV_OBJECT); - if (watchPtr) { - jsvObjectSetChildAndUnLock(watchPtr, "pin", jsvNewFromPin(pin)); - if (repeat) jsvObjectSetChildAndUnLock(watchPtr, "recur", jsvNewFromBool(repeat)); - if (debounce>0) jsvObjectSetChildAndUnLock(watchPtr, "debounce", jsvNewFromInteger((JsVarInt)jshGetTimeFromMilliseconds(debounce))); - if (edge) jsvObjectSetChildAndUnLock(watchPtr, "edge", jsvNewFromInteger(edge)); - jsvObjectSetChild(watchPtr, "callback", func); // no unlock intentionally - } - - // If nothing already watching the pin, set up a watch - IOEventFlags exti = EV_NONE; - if (!jsiIsWatchingPin(pin)) - exti = jshPinWatch(pin, true); - // disable event callbacks by default - if (exti) { - jshSetEventCallback(exti, 0); - if (isIRQ) { - if (jsvIsNativeFunction(func)) { - jshSetEventCallback(exti, (JshEventCallbackCallback)jsvGetNativeFunctionPtr(func)); - } else { - jsExceptionHere(JSET_ERROR, "irq=true set, but function is not a native function"); - } - } - } else { - if (isIRQ) - jsExceptionHere(JSET_ERROR, "irq=true set, but watch is already used"); - } - - - JsVar *watchArrayPtr = jsvLock(watchArray); - itemIndex = jsvArrayAddToEnd(watchArrayPtr, watchPtr, 1) - 1; - jsvUnLock2(watchArrayPtr, watchPtr); - - - } - return (itemIndex>=0) ? jsvNewFromInteger(itemIndex) : 0/*undefined*/; -} - -/*JSON{ - "type" : "function", - "name" : "clearWatch", - "generate" : "jswrap_interface_clearWatch", - "params" : [ - ["id","JsVar","The id returned by a previous call to setWatch"] - ] -} -Clear the Watch that was created with setWatch. If no parameter is supplied, all watches will be removed. - */ -void jswrap_interface_clearWatch(JsVar *idVar) { - - if (jsvIsUndefined(idVar)) { - JsVar *watchArrayPtr = jsvLock(watchArray); - JsvObjectIterator it; - jsvObjectIteratorNew(&it, watchArrayPtr); - while (jsvObjectIteratorHasValue(&it)) { - JsVar *watchPtr = jsvObjectIteratorGetValue(&it); - JsVar *watchPin = jsvObjectGetChild(watchPtr, "pin", 0); - jshPinWatch(jshGetPinFromVar(watchPin), false); - jsvUnLock2(watchPin, watchPtr); - jsvObjectIteratorNext(&it); - } - jsvObjectIteratorFree(&it); - // remove all items - jsvRemoveAllChildren(watchArrayPtr); - jsvUnLock(watchArrayPtr); - } else { - JsVar *watchArrayPtr = jsvLock(watchArray); - JsVar *watchNamePtr = jsvFindChildFromVar(watchArrayPtr, idVar, false); - jsvUnLock(watchArrayPtr); - if (watchNamePtr) { // child is a 'name' - JsVar *watchPtr = jsvSkipName(watchNamePtr); - Pin pin = jshGetPinFromVarAndUnLock(jsvObjectGetChild(watchPtr, "pin", 0)); - jsvUnLock(watchPtr); - - JsVar *watchArrayPtr = jsvLock(watchArray); - jsvRemoveChild(watchArrayPtr, watchNamePtr); - jsvUnLock2(watchNamePtr, watchArrayPtr); - - // Now check if this pin is still being watched - if (!jsiIsWatchingPin(pin)) - jshPinWatch(pin, false); // 'unwatch' pin - } else { - jsExceptionHere(JSET_ERROR, "Unknown Watch"); - } - } -} - - +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * ---------------------------------------------------------------------------- + * This file is designed to be parsed during the build process + * + * JavaScript Hardware IO Functions + * ---------------------------------------------------------------------------- + */ +#include "jswrap_io.h" +#include "jsvar.h" +#include "jswrap_arraybuffer.h" // for jswrap_io_peek + +/*JSON{ + "type" : "function", + "name" : "peek8", + "generate_full" : "jswrap_io_peek(addr,count,1)", + "params" : [ + ["addr", "int", "The address in memory to read"], + ["count", "int", "(optional) the number of items to read. If >1 a Uint8Array will be returned."] + ], + "return" : ["JsVar","The value of memory at the given location"] +} +Read 8 bits of memory at the given location - DANGEROUS! + */ +/*JSON{ + "type" : "function", + "name" : "poke8", + "generate_full" : "jswrap_io_poke(addr,value,1)", + "params" : [ + ["addr","int","The address in memory to write"], + ["value","JsVar","The value to write, or an array of values"] + ] +} +Write 8 bits of memory at the given location - VERY DANGEROUS! + */ +/*JSON{ + "type" : "function", + "name" : "peek16", + "generate_full" : "jswrap_io_peek(addr,count,2)", + "params" : [ + ["addr","int","The address in memory to read"], + ["count","int","(optional) the number of items to read. If >1 a Uint16Array will be returned."] + ], + "return" : ["JsVar","The value of memory at the given location"] +} +Read 16 bits of memory at the given location - DANGEROUS! + */ +/*JSON{ + "type" : "function", + "name" : "poke16", + "generate_full" : "jswrap_io_poke(addr,value,2)", + "params" : [ + ["addr","int","The address in memory to write"], + ["value","JsVar","The value to write, or an array of values"] + ] +} +Write 16 bits of memory at the given location - VERY DANGEROUS! + */ +/*JSON{ + "type" : "function", + "name" : "peek32", + "generate_full" : "jswrap_io_peek(addr,count,4)", + "params" : [ + ["addr","int","The address in memory to read"], + ["count","int","(optional) the number of items to read. If >1 a Uint32Array will be returned."] + ], + "return" : ["JsVar","The value of memory at the given location"] +} +Read 32 bits of memory at the given location - DANGEROUS! + */ +/*JSON{ + "type" : "function", + "name" : "poke32", + "generate_full" : "jswrap_io_poke(addr,value,4)", + "params" : [ + ["addr","int","The address in memory to write"], + ["value","JsVar","The value to write, or an array of values"] + ] +} +Write 32 bits of memory at the given location - VERY DANGEROUS! + */ + +uint32_t _jswrap_io_peek(JsVarInt addr, int wordSize) { + if (wordSize==1) return (uint32_t)*(unsigned char*)(size_t)addr; + if (wordSize==2) return (uint32_t)*(unsigned short*)(size_t)addr; + if (wordSize==4) return (uint32_t)*(unsigned int*)(size_t)addr; + return 0; +} + +JsVar *jswrap_io_peek(JsVarInt addr, JsVarInt count, int wordSize) { + if (count<=1) { + return jsvNewFromLongInteger((long long)_jswrap_io_peek(addr, wordSize)); + } else { + JsVarDataArrayBufferViewType aType; + if (wordSize==1) aType=ARRAYBUFFERVIEW_UINT8; + if (wordSize==2) aType=ARRAYBUFFERVIEW_UINT16; + if (wordSize==4) aType=ARRAYBUFFERVIEW_UINT32; + JsVar *arr = jsvNewTypedArray(aType, count); + if (!arr) return 0; + JsvArrayBufferIterator it; + jsvArrayBufferIteratorNew(&it, arr, 0); + while (jsvArrayBufferIteratorHasElement(&it)) { + jsvArrayBufferIteratorSetIntegerValue(&it, (JsVarInt)_jswrap_io_peek(addr, wordSize)); + addr += wordSize; + jsvArrayBufferIteratorNext(&it); + } + jsvArrayBufferIteratorFree(&it); + return arr; + } +} + +void _jswrap_io_poke(JsVarInt addr, uint32_t data, int wordSize) { + if (wordSize==1) (*(unsigned char*)(size_t)addr) = (unsigned char)data; + else if (wordSize==2) (*(unsigned short*)(size_t)addr) = (unsigned short)data; + else if (wordSize==4) (*(unsigned int*)(size_t)addr) = (unsigned int)data; +} + +void jswrap_io_poke(JsVarInt addr, JsVar *data, int wordSize) { + if (jsvIsNumeric(data)) { + _jswrap_io_poke(addr, (uint32_t)jsvGetInteger(data), wordSize); + } else if (jsvIsIterable(data)) { + JsvIterator it; + jsvIteratorNew(&it, data); + while (jsvIteratorHasElement(&it)) { + _jswrap_io_poke(addr, (uint32_t)jsvIteratorGetIntegerValue(&it), wordSize); + addr += wordSize; + jsvIteratorNext(&it); + } + jsvIteratorFree(&it); + } +} + + +/*JSON{ + "type" : "function", + "name" : "analogRead", + "generate" : "jshPinAnalog", + "params" : [ + ["pin","pin",["The pin to use","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `ADC` markers."]] + ], + "return" : ["float","The analog Value of the Pin between 0 and 1"] +} +Get the analog value of the given pin + +This is different to Arduino which only returns an integer between 0 and 1023 + +However only pins connected to an ADC will work (see the datasheet) + + **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"analog"` + */ +/*JSON{ + "type" : "function", + "name" : "analogWrite", + "generate" : "jswrap_io_analogWrite", + "params" : [ + ["pin","pin",["The pin to use","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `PWM` or `DAC` markers."]], + ["value","float","A value between 0 and 1"], + ["options","JsVar",["An object containing options for analog output - see below"]] + ] +} +Set the analog Value of a pin. It will be output using PWM. + +Objects can contain: + +* `freq` - pulse frequency in Hz, eg. ```analogWrite(A0,0.5,{ freq : 10 });``` - specifying a frequency will force PWM output, even if the pin has a DAC +* `soft` - boolean, If true software PWM is used if available. +* `forceSoft` - boolean, If true software PWM is used even + + **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` + */ +void jswrap_io_analogWrite(Pin pin, JsVarFloat value, JsVar *options) { + JsVarFloat freq = 0; + JshAnalogOutputFlags flags = JSAOF_NONE; + if (jsvIsObject(options)) { + freq = jsvGetFloatAndUnLock(jsvObjectGetChild(options, "freq", 0)); + if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "forceSoft", 0))) + flags |= JSAOF_FORCE_SOFTWARE; + else if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "soft", 0))) + flags |= JSAOF_ALLOW_SOFTWARE; + } + + jshPinAnalogOutput(pin, value, freq, flags); +} + +/*JSON{ + "type" : "function", + "name" : "digitalPulse", + "generate" : "jswrap_io_digitalPulse", + "params" : [ + ["pin","pin","The pin to use"], + ["value","bool","Whether to pulse high (true) or low (false)"], + ["time","JsVar","A time in milliseconds, or an array of times (in which case a square wave will be output starting with a pulse of 'value')"] + ] +} +Pulse the pin with the value for the given time in milliseconds. It uses a hardware timer to produce accurate pulses, and returns immediately (before the pulse has finished). Use `digitalPulse(A0,1,0)` to wait until a previous pulse has finished. + +eg. `digitalPulse(A0,1,5);` pulses A0 high for 5ms. `digitalPulse(A0,1,[5,2,4]);` pulses A0 high for 5ms, low for 2ms, and high for 4ms + + **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` + +digitalPulse is for SHORT pulses that need to be very accurate. If you're doing anything over a few milliseconds, use setTimeout instead. + */ +void jswrap_io_digitalPulse(Pin pin, bool value, JsVar *times) { + if (jsvIsNumeric(times)) { + JsVarFloat time = jsvGetFloat(times); + if (time<0 || isnan(time)) { + jsExceptionHere(JSET_ERROR, "Pulse Time given for digitalPulse is less than 0, or not a number"); + } else { + jshPinPulse(pin, value, time); + } + } else if (jsvIsIterable(times)) { + // iterable, so output a square wave + JsvIterator it; + jsvIteratorNew(&it, times); + while (jsvIteratorHasElement(&it)) { + JsVarFloat time = jsvIteratorGetFloatValue(&it); + if (time>=0 && !isnan(time)) + jshPinPulse(pin, value, time); + value = !value; + jsvIteratorNext(&it); + } + jsvIteratorFree(&it); + } else { + jsExceptionHere(JSET_ERROR, "Expecting a number or array, got %t", times); + } +} + +/*JSON{ + "type" : "function", + "name" : "digitalWrite", + "generate" : "jswrap_io_digitalWrite", + "params" : [ + ["pin", "JsVar","The pin to use"], + ["value", "int","Whether to pulse high (true) or low (false)"] + ] +} +Set the digital value of the given pin. + + **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` + +If pin argument is an array of pins (eg. `[A2,A1,A0]`) the value argument will be treated +as an array of bits where the last array element is the least significant bit. + +In this case, pin values are set last significant bit first (from the right-hand side +of the array of pins). This means you can use the same pin multiple times, for +example `digitalWrite([A1,A1,A0,A0],0b0101)` would pulse A0 followed by A1. +*/ + +/** + * Set the output of a GPIO. + */ +void jswrap_io_digitalWrite( + JsVar *pinVar, //!< A pin or pins. + JsVarInt value //!< The value of the output. + ) { + // Handle the case where it is an array of pins. + if (jsvIsArray(pinVar)) { + JsVarRef pinName = jsvGetLastChild(pinVar); // NOTE: start at end and work back! + while (pinName) { + JsVar *pinNamePtr = jsvLock(pinName); + JsVar *pinPtr = jsvSkipName(pinNamePtr); + jshPinOutput(jshGetPinFromVar(pinPtr), value&1); + jsvUnLock(pinPtr); + pinName = jsvGetPrevSibling(pinNamePtr); + jsvUnLock(pinNamePtr); + value = value>>1; // next bit down + } + } + // Handle the case where it is a single pin. + else { + Pin pin = jshGetPinFromVar(pinVar); + jshPinOutput(pin, value != 0); + } +} + + +/*JSON{ + "type" : "function", + "name" : "digitalRead", + "generate" : "jswrap_io_digitalRead", + "params" : [ + ["pin","JsVar","The pin to use"] + ], + "return" : ["int","The digital Value of the Pin"] +} +Get the digital value of the given pin. + + **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"input"` + +If the pin argument is an array of pins (eg. `[A2,A1,A0]`) the value returned will be an number where +the last array element is the least significant bit, for example if `A0=A1=1` and `A2=0`, `digitalRead([A2,A1,A0]) == 0b011` +*/ + +/** + * Read the value of a GPIO pin. + */ +JsVarInt jswrap_io_digitalRead(JsVar *pinVar) { + // Hadnle the case where it is an array of pins. + if (jsvIsArray(pinVar)) { + int pins = 0; + JsVarInt value = 0; + JsvObjectIterator it; + jsvObjectIteratorNew(&it, pinVar); + while (jsvObjectIteratorHasValue(&it)) { + JsVar *pinPtr = jsvObjectIteratorGetValue(&it); + value = (value<<1) | (JsVarInt)jshPinInput(jshGetPinFromVar(pinPtr)); + jsvUnLock(pinPtr); + jsvObjectIteratorNext(&it); + pins++; + } + jsvObjectIteratorFree(&it); + if (pins==0) return 0; // return undefined if array empty + return value; + } + // Handle the case where it is a single pin. + else { + Pin pin = jshGetPinFromVar(pinVar); + return jshPinInput(pin); + } +} + +/*JSON{ + "type" : "function", + "name" : "pinMode", + "generate" : "jswrap_io_pinMode", + "params" : [ + ["pin","pin","The pin to set pin mode for"], + ["mode","JsVar","The mode - a string that is either 'analog', 'input', 'input_pullup', 'input_pulldown', 'output', 'opendrain', 'af_output' or 'af_opendrain'. Do not include this argument if you want to revert to automatic pin mode setting."] + ] +} +Set the mode of the given pin. + + * `analog` - Analog input + * `input` - Digital input + * `input_pullup` - Digital input with internal ~40k pull-up resistor + * `input_pulldown` - Digital input with internal ~40k pull-down resistor + * `output` - Digital output + * `opendrain` - Digital output that only ever pulls down to 0v. Sending a logical `1` leaves the pin open circuit + * `af_output` - Digital output from built-in peripheral + * `af_opendrain` - Digital output from built-in peripheral that only ever pulls down to 0v. + * Sending a logical `1` leaves the pin open circuit + + **Note:** `digitalRead`/`digitalWrite`/etc set the pin mode automatically *unless* `pinMode` has been called first. If you want `digitalRead`/etc to set the pin mode automatically after you have called `pinMode`, simply call it again with no mode argument: `pinMode(pin)` +*/ + +/** + * Set the mode of a pin. + */ +void jswrap_io_pinMode( + Pin pin, //!< The pin to set. + JsVar *mode //!< The new mode of the pin. + ) { + if (!jshIsPinValid(pin)) { + jsExceptionHere(JSET_ERROR, "Invalid pin"); + return; + } + JshPinState m = JSHPINSTATE_UNDEFINED; + if (jsvIsString(mode)) { + if (jsvIsStringEqual(mode, "analog")) m = JSHPINSTATE_ADC_IN; + else if (jsvIsStringEqual(mode, "input")) m = JSHPINSTATE_GPIO_IN; + else if (jsvIsStringEqual(mode, "input_pullup")) m = JSHPINSTATE_GPIO_IN_PULLUP; + else if (jsvIsStringEqual(mode, "input_pulldown")) m = JSHPINSTATE_GPIO_IN_PULLDOWN; + else if (jsvIsStringEqual(mode, "output")) m = JSHPINSTATE_GPIO_OUT; + else if (jsvIsStringEqual(mode, "opendrain")) m = JSHPINSTATE_GPIO_OUT_OPENDRAIN; + else if (jsvIsStringEqual(mode, "af_output")) m = JSHPINSTATE_AF_OUT; + else if (jsvIsStringEqual(mode, "af_opendrain")) m = JSHPINSTATE_AF_OUT_OPENDRAIN; + } + if (m != JSHPINSTATE_UNDEFINED) { + jshSetPinStateIsManual(pin, true); + jshPinSetState(pin, m); + } else { + jshSetPinStateIsManual(pin, false); + if (!jsvIsUndefined(mode)) { + jsExceptionHere(JSET_ERROR, "Unknown pin mode"); + } + } +} + +/*JSON{ + "type" : "function", + "name" : "getPinMode", + "generate" : "jswrap_io_getPinMode", + "params" : [ + ["pin","pin","The pin to check"] + ], + "return" : ["JsVar","The pin mode, as a string"] +} +Return the current mode of the given pin. See `pinMode` for more information. + */ +JsVar *jswrap_io_getPinMode(Pin pin) { + if (!jshIsPinValid(pin)) { + jsExceptionHere(JSET_ERROR, "Invalid pin"); + return 0; + } + JshPinState m = jshPinGetState(pin)&JSHPINSTATE_MASK; + const char *text = 0; + switch (m) { + case JSHPINSTATE_ADC_IN : text = "analog"; break; + case JSHPINSTATE_GPIO_IN : text = "input"; break; + case JSHPINSTATE_GPIO_IN_PULLUP : text = "input_pullup"; break; + case JSHPINSTATE_GPIO_IN_PULLDOWN : text = "input_pulldown"; break; + case JSHPINSTATE_GPIO_OUT : text = "output"; break; + case JSHPINSTATE_GPIO_OUT_OPENDRAIN : text = "opendrain"; break; + case JSHPINSTATE_AF_OUT : text = "af_output"; break; + case JSHPINSTATE_AF_OUT_OPENDRAIN : text = "af_opendrain"; break; + default: break; + } + if (text) return jsvNewFromString(text); + return 0; +} + +/*JSON{ + "type" : "function", + "name" : "setWatch", + "generate" : "jswrap_interface_setWatch", + "params" : [ + ["function","JsVar","A Function or String to be executed"], + ["pin","pin","The pin to watch"], + ["options","JsVar",["If this is a boolean or integer, it determines whether to call this once (false = default) or every time a change occurs (true)","If this is an object, it can contain the following information: ```{ repeat: true/false(default), edge:'rising'/'falling'/'both'(default), debounce:10}```. `debounce` is the time in ms to wait for bounces to subside, or 0."]] + ], + "return" : ["JsVar","An ID that can be passed to clearWatch"] +} +Call the function specified when the pin changes. Watches set with `setWatch` can be removed using `clearWatch`. + +The function may also take an argument, which is an object of type `{state:bool, time:float, lastTime:float}`. + + * `state` is whether the pin is currently a `1` or a `0` + * `time` is the time in seconds at which the pin changed state + * `lastTime` is the time in seconds at which the **pin last changed state**. When using `edge:'rising'` or `edge:'falling'`, this is not the same as when the function was last called. + +For instance, if you want to measure the length of a positive pulse you could use `setWatch(function(e) { console.log(e.time-e.lastTime); }, BTN, { repeat:true, edge:'falling' });`. +This will only be called on the falling edge of the pulse, but will be able to measure the width of the pulse because `e.lastTime` is the time of the rising edge. + +Internally, an interrupt writes the time of the pin's state change into a queue, and the function +supplied to `setWatch` is executed only from the main message loop. However, if the callback is a +native function `void (bool state)` then you can add `irq:true` to options, which will cause the +function to be called from within the IRQ. When doing this, interrupts will happen on both edges +and there will be no debouncing. + +**Note:** The STM32 chip (used in the [Espruino Board](/EspruinoBoard) and [Pico](/Pico)) cannot +watch two pins with the same number - eg `A0` and `B0`. + + */ +JsVar *jswrap_interface_setWatch(JsVar *func, Pin pin, JsVar *repeatOrObject) { + if (!jshIsPinValid(pin)) { + jsError("Invalid pin"); + return 0; + } + + if (!jsiIsWatchingPin(pin) && !jshCanWatch(pin)) { + jsWarn("Unable to set watch. You may already have a watch on a pin with the same number (eg. A0 and B0)"); + return 0; + } + + bool repeat = false; + JsVarFloat debounce = 0; + int edge = 0; + bool isIRQ = false; + if (jsvIsObject(repeatOrObject)) { + JsVar *v; + repeat = jsvGetBoolAndUnLock(jsvObjectGetChild(repeatOrObject, "repeat", 0)); + debounce = jsvGetFloatAndUnLock(jsvObjectGetChild(repeatOrObject, "debounce", 0)); + if (isnan(debounce) || debounce<0) debounce=0; + v = jsvObjectGetChild(repeatOrObject, "edge", 0); + if (jsvIsString(v)) { + if (jsvIsStringEqual(v, "rising")) edge=1; + else if (jsvIsStringEqual(v, "falling")) edge=-1; + else if (jsvIsStringEqual(v, "both")) edge=0; + else jsWarn("'edge' in setWatch should be a string - either 'rising', 'falling' or 'both'"); + } else if (!jsvIsUndefined(v)) + jsWarn("'edge' in setWatch should be a string - either 'rising', 'falling' or 'both'"); + jsvUnLock(v); + isIRQ = jsvGetBoolAndUnLock(jsvObjectGetChild(repeatOrObject, "irq", 0)); + } else + repeat = jsvGetBool(repeatOrObject); + + JsVarInt itemIndex = -1; + if (!jsvIsFunction(func) && !jsvIsString(func)) { + jsExceptionHere(JSET_ERROR, "Function or String not supplied!"); + } else { + // Create a new watch + JsVar *watchPtr = jsvNewWithFlags(JSV_OBJECT); + if (watchPtr) { + jsvObjectSetChildAndUnLock(watchPtr, "pin", jsvNewFromPin(pin)); + if (repeat) jsvObjectSetChildAndUnLock(watchPtr, "recur", jsvNewFromBool(repeat)); + if (debounce>0) jsvObjectSetChildAndUnLock(watchPtr, "debounce", jsvNewFromInteger((JsVarInt)jshGetTimeFromMilliseconds(debounce))); + if (edge) jsvObjectSetChildAndUnLock(watchPtr, "edge", jsvNewFromInteger(edge)); + jsvObjectSetChild(watchPtr, "callback", func); // no unlock intentionally + } + + // If nothing already watching the pin, set up a watch + IOEventFlags exti = EV_NONE; + if (!jsiIsWatchingPin(pin)) + exti = jshPinWatch(pin, true); + // disable event callbacks by default + if (exti) { + jshSetEventCallback(exti, 0); + if (isIRQ) { + if (jsvIsNativeFunction(func)) { + jshSetEventCallback(exti, (JshEventCallbackCallback)jsvGetNativeFunctionPtr(func)); + } else { + jsExceptionHere(JSET_ERROR, "irq=true set, but function is not a native function"); + } + } + } else { + if (isIRQ) + jsExceptionHere(JSET_ERROR, "irq=true set, but watch is already used"); + } + + + JsVar *watchArrayPtr = jsvLock(watchArray); + itemIndex = jsvArrayAddToEnd(watchArrayPtr, watchPtr, 1) - 1; + jsvUnLock2(watchArrayPtr, watchPtr); + + + } + return (itemIndex>=0) ? jsvNewFromInteger(itemIndex) : 0/*undefined*/; +} + +/*JSON{ + "type" : "function", + "name" : "clearWatch", + "generate" : "jswrap_interface_clearWatch", + "params" : [ + ["id","JsVar","The id returned by a previous call to setWatch"] + ] +} +Clear the Watch that was created with setWatch. If no parameter is supplied, all watches will be removed. + */ +void jswrap_interface_clearWatch(JsVar *idVar) { + + if (jsvIsUndefined(idVar)) { + JsVar *watchArrayPtr = jsvLock(watchArray); + JsvObjectIterator it; + jsvObjectIteratorNew(&it, watchArrayPtr); + while (jsvObjectIteratorHasValue(&it)) { + JsVar *watchPtr = jsvObjectIteratorGetValue(&it); + JsVar *watchPin = jsvObjectGetChild(watchPtr, "pin", 0); + jshPinWatch(jshGetPinFromVar(watchPin), false); + jsvUnLock2(watchPin, watchPtr); + jsvObjectIteratorNext(&it); + } + jsvObjectIteratorFree(&it); + // remove all items + jsvRemoveAllChildren(watchArrayPtr); + jsvUnLock(watchArrayPtr); + } else { + JsVar *watchArrayPtr = jsvLock(watchArray); + JsVar *watchNamePtr = jsvFindChildFromVar(watchArrayPtr, idVar, false); + jsvUnLock(watchArrayPtr); + if (watchNamePtr) { // child is a 'name' + JsVar *watchPtr = jsvSkipName(watchNamePtr); + Pin pin = jshGetPinFromVarAndUnLock(jsvObjectGetChild(watchPtr, "pin", 0)); + jsvUnLock(watchPtr); + + JsVar *watchArrayPtr = jsvLock(watchArray); + jsvRemoveChild(watchArrayPtr, watchNamePtr); + jsvUnLock2(watchNamePtr, watchArrayPtr); + + // Now check if this pin is still being watched + if (!jsiIsWatchingPin(pin)) + jshPinWatch(pin, false); // 'unwatch' pin + } else { + jsExceptionHere(JSET_ERROR, "Unknown Watch"); + } + } +} + + diff --git a/src/jswrap_pin.c b/src/jswrap_pin.c index 177173417..dc8458995 100644 --- a/src/jswrap_pin.c +++ b/src/jswrap_pin.c @@ -40,7 +40,7 @@ You can call the methods on Pin, or you can use Wiring-style functions such as d Creates a pin from the given argument (or returns undefined if no argument) */ /** - * \brief Create an instance of a Pin class. + * Create an instance of a Pin class. */ JsVar *jswrap_pin_constructor(JsVar *val) { Pin pin = jshGetPinFromVar(val); @@ -189,7 +189,7 @@ Get information about this pin and its capabilities. Of the form: Will return undefined if pin is not valid. */ /** - * \brief + * */ JsVar *jswrap_pin_getInfo( JsVar *parent //!< The class instance representing the pin. diff --git a/src/jswrap_spi_i2c.c b/src/jswrap_spi_i2c.c index 0894334e0..98beb2003 100644 --- a/src/jswrap_spi_i2c.c +++ b/src/jswrap_spi_i2c.c @@ -1,628 +1,628 @@ -/* - * This file is part of Espruino, a JavaScript interpreter for Microcontrollers - * - * Copyright (C) 2013 Gordon Williams - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * ---------------------------------------------------------------------------- - * This file is designed to be parsed during the build process - * - * JavaScript SPI and I2C Functions - * ---------------------------------------------------------------------------- - */ -#include "jsspi.h" -#include "jswrap_spi_i2c.h" -#include "jsdevices.h" -#include "jsinteractive.h" -#include "jswrap_arraybuffer.h" - -/*JSON{ - "type" : "class", - "class" : "SPI" -} -This class allows use of the built-in SPI ports. Currently it is SPI master only. - */ - -/*JSON{ - "type" : "object", - "name" : "SPI1", - "instanceof" : "SPI", - "#if" : "SPI_COUNT>=1" -} -The first SPI port - */ -/*JSON{ - "type" : "object", - "name" : "SPI2", - "instanceof" : "SPI", - "#if" : "SPI_COUNT>=2" -} -The second SPI port - */ -/*JSON{ - "type" : "object", - "name" : "SPI3", - "instanceof" : "SPI", - "#if" : "SPI_COUNT>=3" -} -The third SPI port - */ - -/*JSON{ - "type" : "constructor", - "class" : "SPI", - "name" : "SPI", - "generate" : "jswrap_spi_constructor" -} -Create a software SPI port. This has limited functionality (no baud rate), but it can work on any pins. - -Use `SPI.setup` to configure this port. - */ -JsVar *jswrap_spi_constructor() { - return jsvNewWithFlags(JSV_OBJECT); -} - -/*JSON{ - "type" : "staticmethod", - "class" : "SPI", - "name" : "find", - "generate_full" : "jshGetDeviceObjectFor(JSH_SPI1, JSH_SPIMAX, pin)", - "params" : [ - ["pin","pin","A pin to search with"] - ], - "return" : ["JsVar","An object of type `SPI`, or `undefined` if one couldn't be found."] -} -Try and find an SPI hardware device that will work on this pin (eg. `SPI1`) - -May return undefined if no device can be found. -*/ - -/*JSON{ - "type" : "method", - "class" : "SPI", - "name" : "setup", - "generate" : "jswrap_spi_setup", - "params" : [ - ["options","JsVar",["An optional structure containing extra information on initialising the SPI port","Please note that baud rate is set to the nearest that can be managed - which may be -+ 50%","```{sck:pin, miso:pin, mosi:pin, baud:integer=100000, mode:integer=0, order:'msb'/'lsb'='msb' }```","If sck,miso and mosi are left out, they will automatically be chosen. However if one or more is specified then the unspecified pins will not be set up.","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `SPI` marker.","The SPI ```mode``` is between 0 and 3 - see http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Clock_polarity_and_phase","On STM32F1-based parts, you cannot mix AF and non-AF pins (SPI pins are usually grouped on the chip - and you can't mix pins from two groups). Espruino will not warn you about this."]] - ] -} -Set up this SPI port as an SPI Master. - */ -void jswrap_spi_setup( - JsVar *parent, //!< The variable that is the class instance of this function. - JsVar *options //!< The options controlling SPI. - ) { - // - // Design: The options variable is a JS Object which contains a series of settings. These - // settings are parsed by `jsspiPopulateSPIInfo` to populate a C structure of type - // `JshSPIInfo`. - // - // The options are also hung off the class instance variable in a property symbolically called - // DEVICE_OPTIONS_NAME ("_options"). - // - IOEventFlags device = jsiGetDeviceFromClass(parent); - JshSPIInfo inf; - - // Debug - // jsiConsolePrintf("jswrap_spi_setup called parent=%v, options=%v\n", parent, options); - - jsspiPopulateSPIInfo(&inf, options); - - if (DEVICE_IS_SPI(device)) { - jshSPISetup(device, &inf); -#ifdef LINUX - if (jsvIsObject(options)) { - jsvObjectSetChildAndUnLock(parent, "path", jsvObjectGetChild(options, "path", 0)); - } -#endif - } else if (device == EV_NONE) { - // software mode - at least configure pins properly - if (inf.pinSCK != PIN_UNDEFINED) - jshPinSetState(inf.pinSCK, JSHPINSTATE_GPIO_OUT); - if (inf.pinMISO != PIN_UNDEFINED) - jshPinSetState(inf.pinMISO, JSHPINSTATE_GPIO_IN); - if (inf.pinMOSI != PIN_UNDEFINED) - jshPinSetState(inf.pinMOSI, JSHPINSTATE_GPIO_OUT); - } else return; - // Set up options, so we can initialise it on startup - if (options) - jsvUnLock(jsvSetNamedChild(parent, options, DEVICE_OPTIONS_NAME)); - else - jsvRemoveNamedChild(parent, DEVICE_OPTIONS_NAME); -} - - -/*JSON{ - "type" : "method", - "class" : "SPI", - "name" : "send", - "generate" : "jswrap_spi_send", - "params" : [ - ["data","JsVar","The data to send - either an Integer, Array, String, or Object of the form `{data: ..., count:#}`"], - ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised."] - ], - "return" : ["JsVar","The data that was returned"] -} -Send data down SPI, and return the result. Sending an integer will return an integer, a String will return a String, and anything else will return a Uint8Array. - -Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. - -For maximum speeds, please pass either Strings or Typed Arrays as arguments. Note that you can even pass arrays of arrays, like `[1,[2,3,4],5]` - - */ -typedef struct { - spi_sender spiSend; //!< A function to be called to send SPI data. - spi_sender_data spiSendData; //!< Control information on the nature of the SPI interface. - int rxAmt; //!< - int txAmt; //!< - JsvArrayBufferIterator it; //!< A buffer to hold the response data from MISO -} jswrap_spi_send_data; - - -/** - * \brief Send a single byte to the SPI device, used ad callback. - */ -void jswrap_spi_send_cb( - int c, //!< The byte to send through SPI. - jswrap_spi_send_data *data //!< Control information on how to send to SPI. - ) { - // Invoke the SPI send function to transmit the single byte. - int result = data->spiSend(c, &data->spiSendData); - if (c>=0) data->txAmt++; - if (result>=0) { - jsvArrayBufferIteratorSetByteValue(&data->it, (char)result); - jsvArrayBufferIteratorNext(&data->it); - data->rxAmt++; - } -} - - -/** - * \brief Send data through SPI. - * The data can be in a variety of formats including: - * * `numeric` - A single byte is transmitted. - * * `string` - Each character in the string is transmitted. - * * `iterable` - An iterable object is transmitted. - * \return the Received bytes (MISO). This is byte array. - */ -JsVar *jswrap_spi_send( - JsVar *parent, //!< A description of the SPI device to send data through. - JsVar *srcdata, //!< The data to send through SPI. - Pin nss_pin //!< The pin to toggle low then high (CS) - ) { - // Debug - // jsiConsolePrintf("jswrap_spi_send called: parent=%j, srcdata=%j, nss_pin=%p\n", parent, srcdata, nss_pin); - NOT_USED(parent); - IOEventFlags device = jsiGetDeviceFromClass(parent); - - jswrap_spi_send_data data; - if (!jsspiGetSendFunction(parent, &data.spiSend, &data.spiSendData)) - return 0; - - JsVar *dst = 0; - - // we're sending and receiving - if (DEVICE_IS_SPI(device)) jshSPISetReceive(device, true); - - // assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); - - // Now that we are setup, we can send the data. - - // Handle the data being a single byte value - if (jsvIsNumeric(srcdata)) { - int r = data.spiSend((unsigned char)jsvGetInteger(srcdata), &data.spiSendData); - if (r<0) r = data.spiSend(-1, &data.spiSendData); - dst = jsvNewFromInteger(r); // retrieve the byte (no send!) - } - // Handle the data being a string - else if (jsvIsString(srcdata)) { - dst = jsvNewFromEmptyString(); - JsvStringIterator it; - jsvStringIteratorNew(&it, srcdata, 0); - int incount = 0, outcount = 0; - while (jsvStringIteratorHasChar(&it) && !jspIsInterrupted()) { - unsigned char in = (unsigned char)jsvStringIteratorGetChar(&it); - incount++; - int out = data.spiSend(in, &data.spiSendData); - if (out>=0) { - outcount++; - char outc = (char)out; - jsvAppendStringBuf(dst, (char*)&outc, 1); - } - jsvStringIteratorNext(&it); - } - jsvStringIteratorFree(&it); - // finally add the remaining bytes (no send!) - while (outcount < incount && !jspIsInterrupted()) { - outcount++; - unsigned char out = (unsigned char)data.spiSend(-1, &data.spiSendData); - jsvAppendStringBuf(dst, (char*)&out, 1); - } - } - // Handle the data being an iterable. - else { - int nBytes = jsvIterateCallbackCount(srcdata); - dst = jsvNewTypedArray(ARRAYBUFFERVIEW_UINT8, nBytes); - if (dst) { - data.rxAmt = data.txAmt = 0; - jsvArrayBufferIteratorNew(&data.it, dst, 0); - // Write data - jsvIterateCallback(srcdata, (void (*)(int, void *))jswrap_spi_send_cb, &data); - // Wait until SPI send is finished, and flush data - while (data.rxAmt < data.txAmt && !jspIsInterrupted()) - jswrap_spi_send_cb(-1, &data); - jsvArrayBufferIteratorFree(&data.it); - } - } - - // de-assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); - return dst; -} - - -/*JSON{ - "type" : "method", - "class" : "SPI", - "name" : "write", - "generate" : "jswrap_spi_write", - "params" : [ - ["data","JsVarArray",["One or more items to write. May be ints, strings, arrays, or objects of the form `{data: ..., count:#}`.","If the last argument is a pin, it is taken to be the NSS pin"]] - ] -} -Write a character or array of characters to SPI - without reading the result back. - -For maximum speeds, please pass either Strings or Typed Arrays as arguments. - */ -void jswrap_spi_write( - JsVar *parent, //!< - JsVar *args //!< - ) { - NOT_USED(parent); - IOEventFlags device = jsiGetDeviceFromClass(parent); - - spi_sender spiSend; - spi_sender_data spiSendData; - if (!jsspiGetSendFunction(parent, &spiSend, &spiSendData)) - return; - - Pin nss_pin = PIN_UNDEFINED; - // If the last value is a pin, use it as the NSS pin - JsVarInt len = jsvGetArrayLength(args); - if (len > 0) { - JsVar *last = jsvGetArrayItem(args, len-1); // look at the last value - if (jsvIsPin(last)) { - nss_pin = jshGetPinFromVar(last); - jsvUnLock(jsvArrayPop(args)); - } - jsvUnLock(last); - } - - // we're only sending (no receive) - if (DEVICE_IS_SPI(device)) jshSPISetReceive(device, false); - - // assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); - // Write data - jsvIterateCallback(args, (void (*)(int, void *))spiSend, &spiSendData); - // Wait until SPI send is finished, and flush data - if (DEVICE_IS_SPI(device)) - jshSPIWait(device); - // de-assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); -} - -/*JSON{ - "type" : "method", - "class" : "SPI", - "name" : "send4bit", - "generate" : "jswrap_spi_send4bit", - "params" : [ - ["data","JsVar","The data to send - either an integer, array, or string"], - ["bit0","int32","The 4 bits to send for a 0 (MSB first)"], - ["bit1","int32","The 4 bits to send for a 1 (MSB first)"], - ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised."] - ] -} -Send data down SPI, using 4 bits for each 'real' bit (MSB first). This can be useful for faking one-wire style protocols - -Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. - */ -void jswrap_spi_send4bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin) { - NOT_USED(parent); - IOEventFlags device = jsiGetDeviceFromClass(parent); - if (!DEVICE_IS_SPI(device)) { - jsExceptionHere(JSET_ERROR, "SPI.send4bit only works on hardware SPI"); - return; - } - - jshSPISet16(device, true); // 16 bit output - - if (bit0==0 && bit1==0) { - bit0 = 0x01; - bit1 = 0x03; - } - bit0 = bit0 & 0x0F; - bit1 = bit1 & 0x0F; - - if (!jshIsDeviceInitialised(device)) { - JshSPIInfo inf; - jshSPIInitInfo(&inf); - jshSPISetup(device, &inf); - } - - // we're just sending (no receive) - jshSPISetReceive(device, false); - // assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); - - // send data - if (jsvIsNumeric(srcdata)) { - jsspiSend4bit(device, (unsigned char)jsvGetInteger(srcdata), bit0, bit1); - } else if (jsvIsIterable(srcdata)) { - jshInterruptOff(); - JsvIterator it; - jsvIteratorNew(&it, srcdata); - while (jsvIteratorHasElement(&it)) { - unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it); - jsspiSend4bit(device, in, bit0, bit1); - jsvIteratorNext(&it); - } - jsvIteratorFree(&it); - jshInterruptOn(); - } else { - jsExceptionHere(JSET_ERROR, "Variable type %t not suited to transmit operation", srcdata); - } - - jshSPIWait(device); // wait until SPI send finished and clear the RX buffer - - // de-assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); - jshSPISet16(device, false); // back to 8 bit -} - -/*JSON{ - "type" : "method", - "class" : "SPI", - "name" : "send8bit", - "ifndef" : "SAVE_ON_FLASH", - "generate" : "jswrap_spi_send8bit", - "params" : [ - ["data","JsVar","The data to send - either an integer, array, or string"], - ["bit0","int32","The 8 bits to send for a 0 (MSB first)"], - ["bit1","int32","The 8 bits to send for a 1 (MSB first)"], - ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised"] - ] -} -Send data down SPI, using 8 bits for each 'real' bit (MSB first). This can be useful for faking one-wire style protocols - -Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. - */ -void jswrap_spi_send8bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin) { - NOT_USED(parent); - IOEventFlags device = jsiGetDeviceFromClass(parent); - if (!DEVICE_IS_SPI(device)) { - jsExceptionHere(JSET_ERROR, "SPI.send8bit only works on hardware SPI"); - return; - } - jshSPISet16(device, true); // 16 bit output - - if (bit0==0 && bit1==0) { - bit0 = 0x03; - bit1 = 0x0F; - } - bit0 = bit0 & 0xFF; - bit1 = bit1 & 0xFF; - - if (!jshIsDeviceInitialised(device)) { - JshSPIInfo inf; - jshSPIInitInfo(&inf); - jshSPISetup(device, &inf); - } - - // we're just sending (no receive) - jshSPISetReceive(device, false); - // assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); - - // send data - if (jsvIsNumeric(srcdata)) { - jsspiSend8bit(device, (unsigned char)jsvGetInteger(srcdata), bit0, bit1); - } else if (jsvIsIterable(srcdata)) { - jshInterruptOff(); - JsvIterator it; - jsvIteratorNew(&it, srcdata); - while (jsvIteratorHasElement(&it)) { - unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it); - jsspiSend8bit(device, in, bit0, bit1); - jsvIteratorNext(&it); - } - jsvIteratorFree(&it); - jshInterruptOn(); - } else { - jsExceptionHere(JSET_ERROR, "Variable type %t not suited to transmit operation", srcdata); - } - - jshSPIWait(device); // wait until SPI send finished and clear the RX buffer - - // de-assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); - jshSPISet16(device, false); // back to 8 bit -} - -/*JSON{ - "type" : "class", - "class" : "I2C" -} -This class allows use of the built-in I2C ports. Currently it allows I2C Master mode only. - -All addresses are in 7 bit format. If you have an 8 bit address then you need to shift it one bit to the right. - */ - -/*JSON{ - "type" : "staticmethod", - "class" : "I2C", - "name" : "find", - "generate_full" : "jshGetDeviceObjectFor(JSH_I2C1, JSH_I2CMAX, pin)", - "params" : [ - ["pin","pin","A pin to search with"] - ], - "return" : ["JsVar","An object of type `I2C`, or `undefined` if one couldn't be found."] -} -Try and find an I2C hardware device that will work on this pin (eg. `I2C1`) - -May return undefined if no device can be found. -*/ - -/*JSON{ - "type" : "object", - "name" : "I2C1", - "instanceof" : "I2C", - "#if" : "I2C_COUNT>=1" -} -The first I2C port - */ -/*JSON{ - "type" : "object", - "name" : "I2C2", - "instanceof" : "I2C", - "#if" : "I2C_COUNT>=2" -} -The second I2C port - */ -/*JSON{ - "type" : "object", - "name" : "I2C3", - "instanceof" : "I2C", - "#if" : "I2C_COUNT>=3" -} -The third I2C port - */ - - - -/*JSON{ - "type" : "method", - "class" : "I2C", - "name" : "setup", - "generate" : "jswrap_i2c_setup", - "params" : [ - ["options","JsVar",["An optional structure containing extra information on initialising the I2C port","```{scl:pin, sda:pin, bitrate:100000}```","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `I2C` marker. Note that 400000kHz is the maximum bitrate for most parts."]] - ] -} -Set up this I2C port - -If not specified in options, the default pins are used (usually the lowest numbered pins on the lowest port that supports this peripheral) - */ -void jswrap_i2c_setup(JsVar *parent, JsVar *options) { - IOEventFlags device = jsiGetDeviceFromClass(parent); - if (!DEVICE_IS_I2C(device)) return; - JshI2CInfo inf; - jshI2CInitInfo(&inf); - if (jsvIsObject(options)) { - inf.pinSCL = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "scl", 0)); - inf.pinSDA = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "sda", 0)); - JsVar *v = jsvObjectGetChild(options, "bitrate", 0); - if (v) - inf.bitrate = jsvGetIntegerAndUnLock(v); - } - jshI2CSetup(device, &inf); - // Set up options, so we can initialise it on startup - if (options) - jsvUnLock(jsvSetNamedChild(parent, options, DEVICE_OPTIONS_NAME)); - else - jsvRemoveNamedChild(parent, DEVICE_OPTIONS_NAME); -} - - -static NO_INLINE int i2c_get_address(JsVar *address, bool *sendStop) { - *sendStop = true; - if (jsvIsObject(address)) { - JsVar *stopVar = jsvObjectGetChild(address, "stop", 0); - if (stopVar) *sendStop = jsvGetBoolAndUnLock(stopVar); - return jsvGetIntegerAndUnLock(jsvObjectGetChild(address, "address", 0)); - } else - return jsvGetInteger(address); -} - - -/*JSON{ - "type" : "method", - "class" : "I2C", - "name" : "writeTo", - "generate" : "jswrap_i2c_writeTo", - "params" : [ - ["address","JsVar","The 7 bit address of the device to transmit to, or an object of the form `{address:12, stop:false}` to send this data without a STOP signal."], - ["data","JsVarArray","One or more items to write. May be ints, strings, arrays, or objects of the form `{data: ..., count:#}`."] - ] -} -Transmit to the slave device with the given address. This is like Arduino's beginTransmission, write, and endTransmission rolled up into one. - */ - -void jswrap_i2c_writeTo(JsVar *parent, JsVar *addressVar, JsVar *args) { - IOEventFlags device = jsiGetDeviceFromClass(parent); - if (!DEVICE_IS_I2C(device)) return; - - bool sendStop = true; - int address = i2c_get_address(addressVar, &sendStop); - - size_t l = (size_t)jsvIterateCallbackCount(args); - if (l+256 > jsuGetFreeStack()) { - jsExceptionHere(JSET_ERROR, "Not enough free stack to send this amount of data"); - return; - } - - unsigned char *data = (unsigned char *)alloca(l); - jsvIterateCallbackToBytes(args, data, l); - - jshI2CWrite(device, (unsigned char)address, l, data, sendStop); -} - -/*JSON{ - "type" : "method", - "class" : "I2C", - "name" : "readFrom", - "generate" : "jswrap_i2c_readFrom", - "params" : [ - ["address","JsVar","The 7 bit address of the device to request bytes from, or an object of the form `{address:12, stop:false}` to send this data without a STOP signal."], - ["quantity","int32","The number of bytes to request"] - ], - "return" : ["JsVar","The data that was returned - as a Uint8Array"], - "return_object" : "Uint8Array" -} -Request bytes from the given slave device, and return them as a Uint8Array (packed array of bytes). This is like using Arduino Wire's requestFrom, available and read functions. Sends a STOP - */ -JsVar *jswrap_i2c_readFrom(JsVar *parent, JsVar *addressVar, int nBytes) { - IOEventFlags device = jsiGetDeviceFromClass(parent); - if (!DEVICE_IS_I2C(device)) return 0; - - bool sendStop = true; - int address = i2c_get_address(addressVar, &sendStop); - - if (nBytes<=0) - return 0; - if ((unsigned int)nBytes+256 > jsuGetFreeStack()) { - jsExceptionHere(JSET_ERROR, "Not enough free stack to receive this amount of data"); - return 0; - } - unsigned char *buf = (unsigned char *)alloca((size_t)nBytes); - - jshI2CRead(device, (unsigned char)address, nBytes, buf, sendStop); - - JsVar *array = jsvNewTypedArray(ARRAYBUFFERVIEW_UINT8, nBytes); - if (array) { - JsvArrayBufferIterator it; - jsvArrayBufferIteratorNew(&it, array, 0); - unsigned int i; - for (i=0;i<(unsigned)nBytes;i++) { - jsvArrayBufferIteratorSetByteValue(&it, (char)buf[i]); - jsvArrayBufferIteratorNext(&it); - } - jsvArrayBufferIteratorFree(&it); - } - return array; -} +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * ---------------------------------------------------------------------------- + * This file is designed to be parsed during the build process + * + * JavaScript SPI and I2C Functions + * ---------------------------------------------------------------------------- + */ +#include "jsspi.h" +#include "jswrap_spi_i2c.h" +#include "jsdevices.h" +#include "jsinteractive.h" +#include "jswrap_arraybuffer.h" + +/*JSON{ + "type" : "class", + "class" : "SPI" +} +This class allows use of the built-in SPI ports. Currently it is SPI master only. + */ + +/*JSON{ + "type" : "object", + "name" : "SPI1", + "instanceof" : "SPI", + "#if" : "SPI_COUNT>=1" +} +The first SPI port + */ +/*JSON{ + "type" : "object", + "name" : "SPI2", + "instanceof" : "SPI", + "#if" : "SPI_COUNT>=2" +} +The second SPI port + */ +/*JSON{ + "type" : "object", + "name" : "SPI3", + "instanceof" : "SPI", + "#if" : "SPI_COUNT>=3" +} +The third SPI port + */ + +/*JSON{ + "type" : "constructor", + "class" : "SPI", + "name" : "SPI", + "generate" : "jswrap_spi_constructor" +} +Create a software SPI port. This has limited functionality (no baud rate), but it can work on any pins. + +Use `SPI.setup` to configure this port. + */ +JsVar *jswrap_spi_constructor() { + return jsvNewWithFlags(JSV_OBJECT); +} + +/*JSON{ + "type" : "staticmethod", + "class" : "SPI", + "name" : "find", + "generate_full" : "jshGetDeviceObjectFor(JSH_SPI1, JSH_SPIMAX, pin)", + "params" : [ + ["pin","pin","A pin to search with"] + ], + "return" : ["JsVar","An object of type `SPI`, or `undefined` if one couldn't be found."] +} +Try and find an SPI hardware device that will work on this pin (eg. `SPI1`) + +May return undefined if no device can be found. +*/ + +/*JSON{ + "type" : "method", + "class" : "SPI", + "name" : "setup", + "generate" : "jswrap_spi_setup", + "params" : [ + ["options","JsVar",["An optional structure containing extra information on initialising the SPI port","Please note that baud rate is set to the nearest that can be managed - which may be -+ 50%","```{sck:pin, miso:pin, mosi:pin, baud:integer=100000, mode:integer=0, order:'msb'/'lsb'='msb' }```","If sck,miso and mosi are left out, they will automatically be chosen. However if one or more is specified then the unspecified pins will not be set up.","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `SPI` marker.","The SPI ```mode``` is between 0 and 3 - see http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Clock_polarity_and_phase","On STM32F1-based parts, you cannot mix AF and non-AF pins (SPI pins are usually grouped on the chip - and you can't mix pins from two groups). Espruino will not warn you about this."]] + ] +} +Set up this SPI port as an SPI Master. + */ +void jswrap_spi_setup( + JsVar *parent, //!< The variable that is the class instance of this function. + JsVar *options //!< The options controlling SPI. + ) { + // + // Design: The options variable is a JS Object which contains a series of settings. These + // settings are parsed by `jsspiPopulateSPIInfo` to populate a C structure of type + // `JshSPIInfo`. + // + // The options are also hung off the class instance variable in a property symbolically called + // DEVICE_OPTIONS_NAME ("_options"). + // + IOEventFlags device = jsiGetDeviceFromClass(parent); + JshSPIInfo inf; + + // Debug + // jsiConsolePrintf("jswrap_spi_setup called parent=%v, options=%v\n", parent, options); + + jsspiPopulateSPIInfo(&inf, options); + + if (DEVICE_IS_SPI(device)) { + jshSPISetup(device, &inf); +#ifdef LINUX + if (jsvIsObject(options)) { + jsvObjectSetChildAndUnLock(parent, "path", jsvObjectGetChild(options, "path", 0)); + } +#endif + } else if (device == EV_NONE) { + // software mode - at least configure pins properly + if (inf.pinSCK != PIN_UNDEFINED) + jshPinSetState(inf.pinSCK, JSHPINSTATE_GPIO_OUT); + if (inf.pinMISO != PIN_UNDEFINED) + jshPinSetState(inf.pinMISO, JSHPINSTATE_GPIO_IN); + if (inf.pinMOSI != PIN_UNDEFINED) + jshPinSetState(inf.pinMOSI, JSHPINSTATE_GPIO_OUT); + } else return; + // Set up options, so we can initialise it on startup + if (options) + jsvUnLock(jsvSetNamedChild(parent, options, DEVICE_OPTIONS_NAME)); + else + jsvRemoveNamedChild(parent, DEVICE_OPTIONS_NAME); +} + + +/*JSON{ + "type" : "method", + "class" : "SPI", + "name" : "send", + "generate" : "jswrap_spi_send", + "params" : [ + ["data","JsVar","The data to send - either an Integer, Array, String, or Object of the form `{data: ..., count:#}`"], + ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised."] + ], + "return" : ["JsVar","The data that was returned"] +} +Send data down SPI, and return the result. Sending an integer will return an integer, a String will return a String, and anything else will return a Uint8Array. + +Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. + +For maximum speeds, please pass either Strings or Typed Arrays as arguments. Note that you can even pass arrays of arrays, like `[1,[2,3,4],5]` + + */ +typedef struct { + spi_sender spiSend; //!< A function to be called to send SPI data. + spi_sender_data spiSendData; //!< Control information on the nature of the SPI interface. + int rxAmt; //!< + int txAmt; //!< + JsvArrayBufferIterator it; //!< A buffer to hold the response data from MISO +} jswrap_spi_send_data; + + +/** + * Send a single byte to the SPI device, used ad callback. + */ +void jswrap_spi_send_cb( + int c, //!< The byte to send through SPI. + jswrap_spi_send_data *data //!< Control information on how to send to SPI. + ) { + // Invoke the SPI send function to transmit the single byte. + int result = data->spiSend(c, &data->spiSendData); + if (c>=0) data->txAmt++; + if (result>=0) { + jsvArrayBufferIteratorSetByteValue(&data->it, (char)result); + jsvArrayBufferIteratorNext(&data->it); + data->rxAmt++; + } +} + + +/** + * Send data through SPI. + * The data can be in a variety of formats including: + * * `numeric` - A single byte is transmitted. + * * `string` - Each character in the string is transmitted. + * * `iterable` - An iterable object is transmitted. + * \return the Received bytes (MISO). This is byte array. + */ +JsVar *jswrap_spi_send( + JsVar *parent, //!< A description of the SPI device to send data through. + JsVar *srcdata, //!< The data to send through SPI. + Pin nss_pin //!< The pin to toggle low then high (CS) + ) { + // Debug + // jsiConsolePrintf("jswrap_spi_send called: parent=%j, srcdata=%j, nss_pin=%p\n", parent, srcdata, nss_pin); + NOT_USED(parent); + IOEventFlags device = jsiGetDeviceFromClass(parent); + + jswrap_spi_send_data data; + if (!jsspiGetSendFunction(parent, &data.spiSend, &data.spiSendData)) + return 0; + + JsVar *dst = 0; + + // we're sending and receiving + if (DEVICE_IS_SPI(device)) jshSPISetReceive(device, true); + + // assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); + + // Now that we are setup, we can send the data. + + // Handle the data being a single byte value + if (jsvIsNumeric(srcdata)) { + int r = data.spiSend((unsigned char)jsvGetInteger(srcdata), &data.spiSendData); + if (r<0) r = data.spiSend(-1, &data.spiSendData); + dst = jsvNewFromInteger(r); // retrieve the byte (no send!) + } + // Handle the data being a string + else if (jsvIsString(srcdata)) { + dst = jsvNewFromEmptyString(); + JsvStringIterator it; + jsvStringIteratorNew(&it, srcdata, 0); + int incount = 0, outcount = 0; + while (jsvStringIteratorHasChar(&it) && !jspIsInterrupted()) { + unsigned char in = (unsigned char)jsvStringIteratorGetChar(&it); + incount++; + int out = data.spiSend(in, &data.spiSendData); + if (out>=0) { + outcount++; + char outc = (char)out; + jsvAppendStringBuf(dst, (char*)&outc, 1); + } + jsvStringIteratorNext(&it); + } + jsvStringIteratorFree(&it); + // finally add the remaining bytes (no send!) + while (outcount < incount && !jspIsInterrupted()) { + outcount++; + unsigned char out = (unsigned char)data.spiSend(-1, &data.spiSendData); + jsvAppendStringBuf(dst, (char*)&out, 1); + } + } + // Handle the data being an iterable. + else { + int nBytes = jsvIterateCallbackCount(srcdata); + dst = jsvNewTypedArray(ARRAYBUFFERVIEW_UINT8, nBytes); + if (dst) { + data.rxAmt = data.txAmt = 0; + jsvArrayBufferIteratorNew(&data.it, dst, 0); + // Write data + jsvIterateCallback(srcdata, (void (*)(int, void *))jswrap_spi_send_cb, &data); + // Wait until SPI send is finished, and flush data + while (data.rxAmt < data.txAmt && !jspIsInterrupted()) + jswrap_spi_send_cb(-1, &data); + jsvArrayBufferIteratorFree(&data.it); + } + } + + // de-assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); + return dst; +} + + +/*JSON{ + "type" : "method", + "class" : "SPI", + "name" : "write", + "generate" : "jswrap_spi_write", + "params" : [ + ["data","JsVarArray",["One or more items to write. May be ints, strings, arrays, or objects of the form `{data: ..., count:#}`.","If the last argument is a pin, it is taken to be the NSS pin"]] + ] +} +Write a character or array of characters to SPI - without reading the result back. + +For maximum speeds, please pass either Strings or Typed Arrays as arguments. + */ +void jswrap_spi_write( + JsVar *parent, //!< + JsVar *args //!< + ) { + NOT_USED(parent); + IOEventFlags device = jsiGetDeviceFromClass(parent); + + spi_sender spiSend; + spi_sender_data spiSendData; + if (!jsspiGetSendFunction(parent, &spiSend, &spiSendData)) + return; + + Pin nss_pin = PIN_UNDEFINED; + // If the last value is a pin, use it as the NSS pin + JsVarInt len = jsvGetArrayLength(args); + if (len > 0) { + JsVar *last = jsvGetArrayItem(args, len-1); // look at the last value + if (jsvIsPin(last)) { + nss_pin = jshGetPinFromVar(last); + jsvUnLock(jsvArrayPop(args)); + } + jsvUnLock(last); + } + + // we're only sending (no receive) + if (DEVICE_IS_SPI(device)) jshSPISetReceive(device, false); + + // assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); + // Write data + jsvIterateCallback(args, (void (*)(int, void *))spiSend, &spiSendData); + // Wait until SPI send is finished, and flush data + if (DEVICE_IS_SPI(device)) + jshSPIWait(device); + // de-assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); +} + +/*JSON{ + "type" : "method", + "class" : "SPI", + "name" : "send4bit", + "generate" : "jswrap_spi_send4bit", + "params" : [ + ["data","JsVar","The data to send - either an integer, array, or string"], + ["bit0","int32","The 4 bits to send for a 0 (MSB first)"], + ["bit1","int32","The 4 bits to send for a 1 (MSB first)"], + ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised."] + ] +} +Send data down SPI, using 4 bits for each 'real' bit (MSB first). This can be useful for faking one-wire style protocols + +Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. + */ +void jswrap_spi_send4bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin) { + NOT_USED(parent); + IOEventFlags device = jsiGetDeviceFromClass(parent); + if (!DEVICE_IS_SPI(device)) { + jsExceptionHere(JSET_ERROR, "SPI.send4bit only works on hardware SPI"); + return; + } + + jshSPISet16(device, true); // 16 bit output + + if (bit0==0 && bit1==0) { + bit0 = 0x01; + bit1 = 0x03; + } + bit0 = bit0 & 0x0F; + bit1 = bit1 & 0x0F; + + if (!jshIsDeviceInitialised(device)) { + JshSPIInfo inf; + jshSPIInitInfo(&inf); + jshSPISetup(device, &inf); + } + + // we're just sending (no receive) + jshSPISetReceive(device, false); + // assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); + + // send data + if (jsvIsNumeric(srcdata)) { + jsspiSend4bit(device, (unsigned char)jsvGetInteger(srcdata), bit0, bit1); + } else if (jsvIsIterable(srcdata)) { + jshInterruptOff(); + JsvIterator it; + jsvIteratorNew(&it, srcdata); + while (jsvIteratorHasElement(&it)) { + unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it); + jsspiSend4bit(device, in, bit0, bit1); + jsvIteratorNext(&it); + } + jsvIteratorFree(&it); + jshInterruptOn(); + } else { + jsExceptionHere(JSET_ERROR, "Variable type %t not suited to transmit operation", srcdata); + } + + jshSPIWait(device); // wait until SPI send finished and clear the RX buffer + + // de-assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); + jshSPISet16(device, false); // back to 8 bit +} + +/*JSON{ + "type" : "method", + "class" : "SPI", + "name" : "send8bit", + "ifndef" : "SAVE_ON_FLASH", + "generate" : "jswrap_spi_send8bit", + "params" : [ + ["data","JsVar","The data to send - either an integer, array, or string"], + ["bit0","int32","The 8 bits to send for a 0 (MSB first)"], + ["bit1","int32","The 8 bits to send for a 1 (MSB first)"], + ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised"] + ] +} +Send data down SPI, using 8 bits for each 'real' bit (MSB first). This can be useful for faking one-wire style protocols + +Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. + */ +void jswrap_spi_send8bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin) { + NOT_USED(parent); + IOEventFlags device = jsiGetDeviceFromClass(parent); + if (!DEVICE_IS_SPI(device)) { + jsExceptionHere(JSET_ERROR, "SPI.send8bit only works on hardware SPI"); + return; + } + jshSPISet16(device, true); // 16 bit output + + if (bit0==0 && bit1==0) { + bit0 = 0x03; + bit1 = 0x0F; + } + bit0 = bit0 & 0xFF; + bit1 = bit1 & 0xFF; + + if (!jshIsDeviceInitialised(device)) { + JshSPIInfo inf; + jshSPIInitInfo(&inf); + jshSPISetup(device, &inf); + } + + // we're just sending (no receive) + jshSPISetReceive(device, false); + // assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); + + // send data + if (jsvIsNumeric(srcdata)) { + jsspiSend8bit(device, (unsigned char)jsvGetInteger(srcdata), bit0, bit1); + } else if (jsvIsIterable(srcdata)) { + jshInterruptOff(); + JsvIterator it; + jsvIteratorNew(&it, srcdata); + while (jsvIteratorHasElement(&it)) { + unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it); + jsspiSend8bit(device, in, bit0, bit1); + jsvIteratorNext(&it); + } + jsvIteratorFree(&it); + jshInterruptOn(); + } else { + jsExceptionHere(JSET_ERROR, "Variable type %t not suited to transmit operation", srcdata); + } + + jshSPIWait(device); // wait until SPI send finished and clear the RX buffer + + // de-assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); + jshSPISet16(device, false); // back to 8 bit +} + +/*JSON{ + "type" : "class", + "class" : "I2C" +} +This class allows use of the built-in I2C ports. Currently it allows I2C Master mode only. + +All addresses are in 7 bit format. If you have an 8 bit address then you need to shift it one bit to the right. + */ + +/*JSON{ + "type" : "staticmethod", + "class" : "I2C", + "name" : "find", + "generate_full" : "jshGetDeviceObjectFor(JSH_I2C1, JSH_I2CMAX, pin)", + "params" : [ + ["pin","pin","A pin to search with"] + ], + "return" : ["JsVar","An object of type `I2C`, or `undefined` if one couldn't be found."] +} +Try and find an I2C hardware device that will work on this pin (eg. `I2C1`) + +May return undefined if no device can be found. +*/ + +/*JSON{ + "type" : "object", + "name" : "I2C1", + "instanceof" : "I2C", + "#if" : "I2C_COUNT>=1" +} +The first I2C port + */ +/*JSON{ + "type" : "object", + "name" : "I2C2", + "instanceof" : "I2C", + "#if" : "I2C_COUNT>=2" +} +The second I2C port + */ +/*JSON{ + "type" : "object", + "name" : "I2C3", + "instanceof" : "I2C", + "#if" : "I2C_COUNT>=3" +} +The third I2C port + */ + + + +/*JSON{ + "type" : "method", + "class" : "I2C", + "name" : "setup", + "generate" : "jswrap_i2c_setup", + "params" : [ + ["options","JsVar",["An optional structure containing extra information on initialising the I2C port","```{scl:pin, sda:pin, bitrate:100000}```","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `I2C` marker. Note that 400000kHz is the maximum bitrate for most parts."]] + ] +} +Set up this I2C port + +If not specified in options, the default pins are used (usually the lowest numbered pins on the lowest port that supports this peripheral) + */ +void jswrap_i2c_setup(JsVar *parent, JsVar *options) { + IOEventFlags device = jsiGetDeviceFromClass(parent); + if (!DEVICE_IS_I2C(device)) return; + JshI2CInfo inf; + jshI2CInitInfo(&inf); + if (jsvIsObject(options)) { + inf.pinSCL = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "scl", 0)); + inf.pinSDA = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "sda", 0)); + JsVar *v = jsvObjectGetChild(options, "bitrate", 0); + if (v) + inf.bitrate = jsvGetIntegerAndUnLock(v); + } + jshI2CSetup(device, &inf); + // Set up options, so we can initialise it on startup + if (options) + jsvUnLock(jsvSetNamedChild(parent, options, DEVICE_OPTIONS_NAME)); + else + jsvRemoveNamedChild(parent, DEVICE_OPTIONS_NAME); +} + + +static NO_INLINE int i2c_get_address(JsVar *address, bool *sendStop) { + *sendStop = true; + if (jsvIsObject(address)) { + JsVar *stopVar = jsvObjectGetChild(address, "stop", 0); + if (stopVar) *sendStop = jsvGetBoolAndUnLock(stopVar); + return jsvGetIntegerAndUnLock(jsvObjectGetChild(address, "address", 0)); + } else + return jsvGetInteger(address); +} + + +/*JSON{ + "type" : "method", + "class" : "I2C", + "name" : "writeTo", + "generate" : "jswrap_i2c_writeTo", + "params" : [ + ["address","JsVar","The 7 bit address of the device to transmit to, or an object of the form `{address:12, stop:false}` to send this data without a STOP signal."], + ["data","JsVarArray","One or more items to write. May be ints, strings, arrays, or objects of the form `{data: ..., count:#}`."] + ] +} +Transmit to the slave device with the given address. This is like Arduino's beginTransmission, write, and endTransmission rolled up into one. + */ + +void jswrap_i2c_writeTo(JsVar *parent, JsVar *addressVar, JsVar *args) { + IOEventFlags device = jsiGetDeviceFromClass(parent); + if (!DEVICE_IS_I2C(device)) return; + + bool sendStop = true; + int address = i2c_get_address(addressVar, &sendStop); + + size_t l = (size_t)jsvIterateCallbackCount(args); + if (l+256 > jsuGetFreeStack()) { + jsExceptionHere(JSET_ERROR, "Not enough free stack to send this amount of data"); + return; + } + + unsigned char *data = (unsigned char *)alloca(l); + jsvIterateCallbackToBytes(args, data, l); + + jshI2CWrite(device, (unsigned char)address, l, data, sendStop); +} + +/*JSON{ + "type" : "method", + "class" : "I2C", + "name" : "readFrom", + "generate" : "jswrap_i2c_readFrom", + "params" : [ + ["address","JsVar","The 7 bit address of the device to request bytes from, or an object of the form `{address:12, stop:false}` to send this data without a STOP signal."], + ["quantity","int32","The number of bytes to request"] + ], + "return" : ["JsVar","The data that was returned - as a Uint8Array"], + "return_object" : "Uint8Array" +} +Request bytes from the given slave device, and return them as a Uint8Array (packed array of bytes). This is like using Arduino Wire's requestFrom, available and read functions. Sends a STOP + */ +JsVar *jswrap_i2c_readFrom(JsVar *parent, JsVar *addressVar, int nBytes) { + IOEventFlags device = jsiGetDeviceFromClass(parent); + if (!DEVICE_IS_I2C(device)) return 0; + + bool sendStop = true; + int address = i2c_get_address(addressVar, &sendStop); + + if (nBytes<=0) + return 0; + if ((unsigned int)nBytes+256 > jsuGetFreeStack()) { + jsExceptionHere(JSET_ERROR, "Not enough free stack to receive this amount of data"); + return 0; + } + unsigned char *buf = (unsigned char *)alloca((size_t)nBytes); + + jshI2CRead(device, (unsigned char)address, nBytes, buf, sendStop); + + JsVar *array = jsvNewTypedArray(ARRAYBUFFERVIEW_UINT8, nBytes); + if (array) { + JsvArrayBufferIterator it; + jsvArrayBufferIteratorNew(&it, array, 0); + unsigned int i; + for (i=0;i<(unsigned)nBytes;i++) { + jsvArrayBufferIteratorSetByteValue(&it, (char)buf[i]); + jsvArrayBufferIteratorNext(&it); + } + jsvArrayBufferIteratorFree(&it); + } + return array; +} diff --git a/targets/esp8266/esp8266_board_utils.c b/targets/esp8266/esp8266_board_utils.c index 06720046a..abe42051c 100644 --- a/targets/esp8266/esp8266_board_utils.c +++ b/targets/esp8266/esp8266_board_utils.c @@ -13,7 +13,7 @@ typedef long long int64_t; #include "jsutils.h" /** - * \brief Convert an ESP8266 error code to a string. + * Convert an ESP8266 error code to a string. * Given an ESP8266 network error code, return a string representation * of the meaning of that code. * \return A string representation of an error code. @@ -51,7 +51,7 @@ const char *esp8266_errorToString( /** - * \brief Write a buffer of data to the console. + * Write a buffer of data to the console. * The buffer is pointed to by the buffer * parameter and will be written for the length parameter. This is useful because * unlike a string, the data does not have to be NULL terminated. diff --git a/targets/esp8266/jshardware.c b/targets/esp8266/jshardware.c index 7a181b8e7..19b94d92e 100644 --- a/targets/esp8266/jshardware.c +++ b/targets/esp8266/jshardware.c @@ -76,14 +76,14 @@ void jshInit() { } // End of jshInit /** - * \brief Reset the hardware to a power-on state + * Reset the hardware to a power-on state. */ void jshReset() { //system_restart(); } // End of jshReset /** - * \brief Handle whatever needs to be done in the idle loop when there's nothing to do + * Handle whatever needs to be done in the idle loop when there's nothing to do. * * Nothing is needed on the esp8266. The watchdog timer is taken care of by the SDK. */ @@ -215,7 +215,7 @@ static uint8_t pinFunction(JshPinState state) { /** - * \brief Convert a pin state to a string representation. + * Convert a pin state to a string representation. */ static char *pinStateToString(JshPinState state) { switch(state) { @@ -251,7 +251,7 @@ static char *pinStateToString(JshPinState state) { } /** - * \brief Set the state of the specific pin. + * Set the state of the specific pin. * * The possible states are: * @@ -321,7 +321,7 @@ void jshPinSetState(Pin pin, //!< The pin to have its state changed. /** - * \brief Return the current state of the selected pin. + * Return the current state of the selected pin. * \return The current state of the selected pin. */ JshPinState jshPinGetState(Pin pin) { @@ -332,7 +332,7 @@ JshPinState jshPinGetState(Pin pin) { //===== GPIO and PIN stuff ===== /** - * \brief Set the value of the corresponding pin. + * Set the value of the corresponding pin. */ void jshPinSetValue(Pin pin, //!< The pin to have its value changed. bool value //!< The new value of the pin. @@ -344,7 +344,7 @@ void jshPinSetValue(Pin pin, //!< The pin to have its value changed. /** - * \brief Get the value of the corresponding pin. + * Get the value of the corresponding pin. * \return The current value of the pin. */ bool jshPinGetValue(Pin pin //!< The pin to have its value read. @@ -356,7 +356,7 @@ bool jshPinGetValue(Pin pin //!< The pin to have its value read. /** - * \brief + * */ JsVarFloat jshPinAnalog(Pin pin) { os_printf("> ESP8266: jshPinAnalog: %d\n", pin); @@ -365,7 +365,7 @@ JsVarFloat jshPinAnalog(Pin pin) { /** - * \brief + * */ int jshPinAnalogFast(Pin pin) { os_printf("> ESP8266: jshPinAnalogFast: %d\n", pin); @@ -374,7 +374,7 @@ int jshPinAnalogFast(Pin pin) { /** - * \brief + * */ JshPinFunction jshPinAnalogOutput(Pin pin, JsVarFloat value, JsVarFloat freq, JshAnalogOutputFlags flags) { // if freq<=0, the default is used os_printf("ESP8266: jshPinAnalogOutput: %d, %d, %d\n", pin, (int)value, (int)freq); @@ -384,7 +384,7 @@ JshPinFunction jshPinAnalogOutput(Pin pin, JsVarFloat value, JsVarFloat freq, Js /** - * \brief + * */ void jshSetOutputValue(JshPinFunction func, int value) { os_printf("ESP8266: jshSetOutputValue %d %d\n", func, value); @@ -392,7 +392,7 @@ void jshSetOutputValue(JshPinFunction func, int value) { /** - * \brief + * */ void jshEnableWatchDog(JsVarFloat timeout) { os_printf("ESP8266: jshEnableWatchDog %0.3f\n", timeout); @@ -409,7 +409,7 @@ bool jshGetWatchedPinState(IOEventFlags device) { /** - * \brief Set the value of the pin to be the value supplied and then wait for + * Set the value of the pin to be the value supplied and then wait for * a given period and set the pin value again to be the opposite. */ void jshPinPulse(Pin pin, //!< The pin to be pulsed. @@ -427,7 +427,7 @@ void jshPinPulse(Pin pin, //!< The pin to be pulsed. /** - * \brief + * */ bool jshCanWatch(Pin pin) { return false; @@ -435,7 +435,7 @@ bool jshCanWatch(Pin pin) { /** - * \brief + * */ IOEventFlags jshPinWatch( Pin pin, //!< Unknown @@ -449,7 +449,7 @@ IOEventFlags jshPinWatch( /** - * \brief + * */ JshPinFunction jshGetCurrentPinFunction(Pin pin) { //os_printf("jshGetCurrentPinFunction %d\n", pin); @@ -457,7 +457,7 @@ JshPinFunction jshGetCurrentPinFunction(Pin pin) { } /** - * \brief + * */ bool jshIsEventForPin(IOEvent *event, Pin pin) { return IOEVENTFLAGS_GETTYPE(event->flags) == pinToEVEXTI(pin); @@ -466,7 +466,7 @@ bool jshIsEventForPin(IOEvent *event, Pin pin) { //===== USART and Serial ===== /** - * \brief + * */ void jshUSARTSetup(IOEventFlags device, JshUSARTInfo *inf) { } @@ -477,7 +477,6 @@ bool jshIsUSBSERIALConnected() { } // End of jshIsUSBSERIALConnected /** - * \brief * Kick a device into action (if required). * * For instance we may need @@ -494,7 +493,7 @@ void jshUSARTKick( //===== SPI ===== /** - * \brief Unknown + * Unknown * * */ @@ -518,7 +517,7 @@ int jshSPISend( /** - \brief * Send 16 bit data through the given SPI device. + * Send 16 bit data through the given SPI device. */ void jshSPISend16( IOEventFlags device, //!< Unknown @@ -531,7 +530,7 @@ void jshSPISend16( /** - * \brief Set whether to send 16 bits or 8 over SPI. + * Set whether to send 16 bits or 8 over SPI. */ void jshSPISet16( IOEventFlags device, //!< Unknown @@ -542,7 +541,7 @@ void jshSPISet16( /** - * \brief Wait until SPI send is finished. + * Wait until SPI send is finished. */ void jshSPIWait( IOEventFlags device //!< Unknown @@ -821,7 +820,7 @@ unsigned int jshGetRandomNumber() { //===== Read-write flash ===== /** - * \brief Read data from flash memory into the buffer. + * Read data from flash memory into the buffer. * * This reads from flash using memory-mapped reads. Only works for the first 1MB and * requires 4-byte aligned reads. @@ -851,7 +850,7 @@ void jshFlashRead( /** - * \brief Write data to flash memory from the buffer. + * Write data to flash memory from the buffer. * * This is called from jswrap_flash_write and ... which guarantee that addr is 4-byte aligned * and len is a multiple of 4. @@ -878,7 +877,7 @@ void jshFlashWrite( /** - * \brief Return start address and size of the flash page the given address resides in. + * Return start address and size of the flash page the given address resides in. * Returns false if no page. */ bool jshFlashGetPage( @@ -896,7 +895,7 @@ bool jshFlashGetPage( /** - * \brief Erase the flash page containing the address. + * Erase the flash page containing the address. */ void jshFlashErasePage( uint32_t addr //!< diff --git a/targets/esp8266/user_main.c b/targets/esp8266/user_main.c index 84b775c79..d1b736daf 100644 --- a/targets/esp8266/user_main.c +++ b/targets/esp8266/user_main.c @@ -1,271 +1,271 @@ -/* - * This file is part of Espruino/ESP8266, a JavaScript interpreter for ESP8266 - * - * - * 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/. - */ - -#include -#include -#include -#include -#include - -//#define FAKE_STDLIB -#define _GCC_WRAP_STDINT_H -typedef long long int64_t; - -#include -#include -#include -#include "ESP8266_board.h" - -// --- Constants -// The size of the task queue -#define TASK_QUEUE_LENGTH 10 - -// Should we introduce a ticker to say we are still alive? -#define EPS8266_BOARD_HEARTBEAT - -// --- Forward definitions -static void mainLoop(); - -// --- File local variables - -// The task queue for the app -static os_event_t taskAppQueue[TASK_QUEUE_LENGTH]; - -// Flag indicating whether or not main loop processing is suspended. -static bool suspendMainLoopFlag = false; - -// Time structure for main loop time suspension. -static os_timer_t mainLoopSuspendTimer; - -// --- Functions - -#if 0 -/** - * \brief A callback function to be invoked when a line has been entered on the telnet client. - * Here we want to pass that line to the JS parser for processing. - */ -static void telnetLineCB(char *line) { - jsiConsolePrintf("LineCB: %s", line); - // Pass the line to the interactive module ... - - jshPushIOCharEvents(jsiGetConsoleDevice(), line, strlen(line)); - //jspEvaluate(line, true); - //jsiDumpState(); - telnet_send("JS> "); -} // End of lineCB - - -/** - * When we have been allocated a TCP/IP address, this function is called back. Obviously - * there is little we can do at the network level until we have an IP. - */ -static void gotIpCallback() { - telnet_startListening(telnetLineCB); -} // End of gotIpCallback -#endif - -static char *rst_codes[] = { - "power on", "wdt reset", "exception", "soft wdt", "restart", "deep sleep", "reset pin", -}; -static char *flash_maps[] = { - "512KB:256/256", "256KB", "1MB:512/512", "2MB:512/512", "4MB:512/512", - "2MB:1024/1024", "4MB:1024/1024" -}; - -/** - * \brief Dump the ESP8266 restart information. - * This is purely for debugging. - * When an ESP8266 crashes, before it ends, it records its exception information. - * This function retrieves that data and logs it. - */ -static void dumpRestart() { - struct rst_info *rstInfo = system_get_rst_info(); - os_printf("Restart info:\n"); - os_printf(" reason: %d=%s\n", rstInfo->reason, rst_codes[rstInfo->reason]); - os_printf(" exccause: %x\n", rstInfo->exccause); - os_printf(" epc1: %x\n", rstInfo->epc1); - os_printf(" epc2: %x\n", rstInfo->epc2); - os_printf(" epc3: %x\n", rstInfo->epc3); - os_printf(" excvaddr: %x\n", rstInfo->excvaddr); - os_printf(" depc: %x\n", rstInfo->depc); - - uint32_t fid = spi_flash_get_id(); - os_printf("Flash map %s, manuf 0x%02lX chip 0x%04lX\n", flash_maps[system_get_flash_size_map()], - fid & 0xff, (fid&0xff00)|((fid>>16)&0xff)); -} - - -/** - * \brief Queue a task for the main loop. - */ -static void queueTaskMainLoop() { - system_os_post(TASK_APP_QUEUE, TASK_APP_MAINLOOP, 0); -} - - -/** - * \brief Suspend processing the main loop for a period of time. - */ -void suspendMainLoop( - uint32 interval //!< suspension interval in milliseconds - ) { - suspendMainLoopFlag = true; - os_timer_arm(&mainLoopSuspendTimer, interval, 0 /* No repeat */); -} - - -/** - * \brief Enable main loop processing. - */ -static void enableMainLoop() { - suspendMainLoopFlag = false; - queueTaskMainLoop(); -} - -/** - * \brief Idle callback from the SDK, triggers an idle loop iteration - */ -static void idle(void) { - // The idle callback comes form the SDK's ets_run function just before it puts the - // processor to sleep waiting for an interrupt to occur. I.e. it's really a - // "I am about to idle the processor" interrupt not a persistent "I am idle" - // callback that comes over and over. - // We thus have to use this callback to trigger something so it doesn't in fact go - // idle. - system_os_post(TASK_APP_QUEUE, TASK_APP_MAINLOOP, 0); -} - - -/** - * \brief The event handler for ESP8266 tasks as created by system_os_post() on the TASK_APP_QUEUE. - */ -static void eventHandler( - os_event_t *pEvent //!< - ) { - - switch (pEvent->sig) { - // Handle the main loop event. - case TASK_APP_MAINLOOP: - mainLoop(); - break; - // Handle the event to process received data. - case TASK_APP_RX_DATA: - { - // Get the data from the UART RX buffer. If the size of the returned data is - // not zero, then push it onto the Espruino processing queue for characters. - char pBuffer[100]; - int size = getRXBuffer(pBuffer, sizeof(pBuffer)); - if (size > 0) { - jshPushIOCharEvents(jsiGetConsoleDevice(), pBuffer, size); - } - } - break; - // Handle the unknown event type. - default: - os_printf("user_main: eventHandler: Unknown task type: %d", - pEvent->sig); - break; - } -} - - -static uint32 lastTime = 0; - -/** - * \brief Perform the main loop processing. - * This is where work is performed - * as often as possible. - */ -static void mainLoop() { - if (suspendMainLoopFlag == true) { - return; - } - jsiLoop(); - -#ifdef EPS8266_BOARD_HEARTBEAT - if (system_get_time() - lastTime > 1000 * 1000 * 5) { - lastTime = system_get_time(); - os_printf("tick: %ld, heap: %ld\n", - (uint32)(jshGetSystemTime()), system_get_free_heap_size()); - } -#endif - - // Setup for another callback - //queueTaskMainLoop(); - suspendMainLoop(0); // HACK to get around SDK 1.4 bug -} - - -/** - * The ESP8266 provides a mechanism to register a callback that is invoked when initialization - * of the ESP8266 is complete. This is the implementation of that callback. At this point - * we can assume that the ESP8266 is fully ready to do work for us. - */ -static void initDone() { - os_printf("initDone invoked\n"); - - // Discard any junk data in the input as this is a boot. - //uart_rx_discard(); - - jshInit(); // Initialize the hardware - jsvInit(); // Initialize the variables - jsiInit(true); // Initialize the interactive subsystem - - // Register the event handlers. - system_os_task(eventHandler, TASK_APP_QUEUE, taskAppQueue, TASK_QUEUE_LENGTH); - - // At this point, our JavaScript environment should be up and running. - - // Initialize the networking subsystem. - jswrap_ESP8266WiFi_init(); - - // Register the idle callback handler to run the main loop - //ets_set_idle_cb(idle_cb, NULL); // - queueTaskMainLoop(); // get things going without idle callback - - return; -} - - -/** - * This is a required function needed for ESP8266 SDK. It allows RF parameters, in particular - * whether to calibrate the RF, to be set before the SDK does the calibration, which happens - * before user_init() is called. - */ -void user_rf_pre_init() { - os_printf("Time sys=%lu rtc=%lu\n", system_get_time(), system_get_rtc_time()); -} - - -/** - * \brief The main entry point in an ESP8266 application. - * It is where the logic of ESP8266 starts. - */ -void user_init() { - system_timer_reinit(); // use microsecond os_timer_* - // Initialize the UART devices - uart_init(BIT_RATE_115200, BIT_RATE_115200); - os_delay_us(10000); // give the uart a break - UART_SetPrintPort(1); - system_set_os_print(1); - - // Dump the restart exception information. - dumpRestart(); - os_printf("Heap: %d\n", system_get_free_heap_size()); - os_printf("Variables: %d @%dea = %ldbytes\n", JSVAR_CACHE_SIZE, sizeof(JsVar), - JSVAR_CACHE_SIZE * sizeof(JsVar)); - os_printf("Time sys=%lu rtc=%lu\n", system_get_time(), system_get_rtc_time()); - - // Register the ESP8266 initialization callback. - system_init_done_cb(initDone); - - // Do NOT attempt to auto connect to an access point. - //wifi_station_set_auto_connect(0); - os_timer_setfn(&mainLoopSuspendTimer, enableMainLoop, NULL); -} +/* + * This file is part of Espruino/ESP8266, a JavaScript interpreter for ESP8266 + * + * + * 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/. + */ + +#include +#include +#include +#include +#include + +//#define FAKE_STDLIB +#define _GCC_WRAP_STDINT_H +typedef long long int64_t; + +#include +#include +#include +#include "ESP8266_board.h" + +// --- Constants +// The size of the task queue +#define TASK_QUEUE_LENGTH 10 + +// Should we introduce a ticker to say we are still alive? +#define EPS8266_BOARD_HEARTBEAT + +// --- Forward definitions +static void mainLoop(); + +// --- File local variables + +// The task queue for the app +static os_event_t taskAppQueue[TASK_QUEUE_LENGTH]; + +// Flag indicating whether or not main loop processing is suspended. +static bool suspendMainLoopFlag = false; + +// Time structure for main loop time suspension. +static os_timer_t mainLoopSuspendTimer; + +// --- Functions + +#if 0 +/** + * A callback function to be invoked when a line has been entered on the telnet client. + * Here we want to pass that line to the JS parser for processing. + */ +static void telnetLineCB(char *line) { + jsiConsolePrintf("LineCB: %s", line); + // Pass the line to the interactive module ... + + jshPushIOCharEvents(jsiGetConsoleDevice(), line, strlen(line)); + //jspEvaluate(line, true); + //jsiDumpState(); + telnet_send("JS> "); +} // End of lineCB + + +/** + * When we have been allocated a TCP/IP address, this function is called back. Obviously + * there is little we can do at the network level until we have an IP. + */ +static void gotIpCallback() { + telnet_startListening(telnetLineCB); +} // End of gotIpCallback +#endif + +static char *rst_codes[] = { + "power on", "wdt reset", "exception", "soft wdt", "restart", "deep sleep", "reset pin", +}; +static char *flash_maps[] = { + "512KB:256/256", "256KB", "1MB:512/512", "2MB:512/512", "4MB:512/512", + "2MB:1024/1024", "4MB:1024/1024" +}; + +/** + * Dump the ESP8266 restart information. + * This is purely for debugging. + * When an ESP8266 crashes, before it ends, it records its exception information. + * This function retrieves that data and logs it. + */ +static void dumpRestart() { + struct rst_info *rstInfo = system_get_rst_info(); + os_printf("Restart info:\n"); + os_printf(" reason: %d=%s\n", rstInfo->reason, rst_codes[rstInfo->reason]); + os_printf(" exccause: %x\n", rstInfo->exccause); + os_printf(" epc1: %x\n", rstInfo->epc1); + os_printf(" epc2: %x\n", rstInfo->epc2); + os_printf(" epc3: %x\n", rstInfo->epc3); + os_printf(" excvaddr: %x\n", rstInfo->excvaddr); + os_printf(" depc: %x\n", rstInfo->depc); + + uint32_t fid = spi_flash_get_id(); + os_printf("Flash map %s, manuf 0x%02lX chip 0x%04lX\n", flash_maps[system_get_flash_size_map()], + fid & 0xff, (fid&0xff00)|((fid>>16)&0xff)); +} + + +/** + * Queue a task for the main loop. + */ +static void queueTaskMainLoop() { + system_os_post(TASK_APP_QUEUE, TASK_APP_MAINLOOP, 0); +} + + +/** + * Suspend processing the main loop for a period of time. + */ +void suspendMainLoop( + uint32 interval //!< suspension interval in milliseconds + ) { + suspendMainLoopFlag = true; + os_timer_arm(&mainLoopSuspendTimer, interval, 0 /* No repeat */); +} + + +/** + * Enable main loop processing. + */ +static void enableMainLoop() { + suspendMainLoopFlag = false; + queueTaskMainLoop(); +} + +/** + * Idle callback from the SDK, triggers an idle loop iteration + */ +static void idle(void) { + // The idle callback comes form the SDK's ets_run function just before it puts the + // processor to sleep waiting for an interrupt to occur. I.e. it's really a + // "I am about to idle the processor" interrupt not a persistent "I am idle" + // callback that comes over and over. + // We thus have to use this callback to trigger something so it doesn't in fact go + // idle. + system_os_post(TASK_APP_QUEUE, TASK_APP_MAINLOOP, 0); +} + + +/** + * The event handler for ESP8266 tasks as created by system_os_post() on the TASK_APP_QUEUE. + */ +static void eventHandler( + os_event_t *pEvent //!< + ) { + + switch (pEvent->sig) { + // Handle the main loop event. + case TASK_APP_MAINLOOP: + mainLoop(); + break; + // Handle the event to process received data. + case TASK_APP_RX_DATA: + { + // Get the data from the UART RX buffer. If the size of the returned data is + // not zero, then push it onto the Espruino processing queue for characters. + char pBuffer[100]; + int size = getRXBuffer(pBuffer, sizeof(pBuffer)); + if (size > 0) { + jshPushIOCharEvents(jsiGetConsoleDevice(), pBuffer, size); + } + } + break; + // Handle the unknown event type. + default: + os_printf("user_main: eventHandler: Unknown task type: %d", + pEvent->sig); + break; + } +} + + +static uint32 lastTime = 0; + +/** + * Perform the main loop processing. + * This is where work is performed + * as often as possible. + */ +static void mainLoop() { + if (suspendMainLoopFlag == true) { + return; + } + jsiLoop(); + +#ifdef EPS8266_BOARD_HEARTBEAT + if (system_get_time() - lastTime > 1000 * 1000 * 5) { + lastTime = system_get_time(); + os_printf("tick: %ld, heap: %ld\n", + (uint32)(jshGetSystemTime()), system_get_free_heap_size()); + } +#endif + + // Setup for another callback + //queueTaskMainLoop(); + suspendMainLoop(0); // HACK to get around SDK 1.4 bug +} + + +/** + * The ESP8266 provides a mechanism to register a callback that is invoked when initialization + * of the ESP8266 is complete. This is the implementation of that callback. At this point + * we can assume that the ESP8266 is fully ready to do work for us. + */ +static void initDone() { + os_printf("initDone invoked\n"); + + // Discard any junk data in the input as this is a boot. + //uart_rx_discard(); + + jshInit(); // Initialize the hardware + jsvInit(); // Initialize the variables + jsiInit(true); // Initialize the interactive subsystem + + // Register the event handlers. + system_os_task(eventHandler, TASK_APP_QUEUE, taskAppQueue, TASK_QUEUE_LENGTH); + + // At this point, our JavaScript environment should be up and running. + + // Initialize the networking subsystem. + jswrap_ESP8266WiFi_init(); + + // Register the idle callback handler to run the main loop + //ets_set_idle_cb(idle_cb, NULL); // + queueTaskMainLoop(); // get things going without idle callback + + return; +} + + +/** + * This is a required function needed for ESP8266 SDK. It allows RF parameters, in particular + * whether to calibrate the RF, to be set before the SDK does the calibration, which happens + * before user_init() is called. + */ +void user_rf_pre_init() { + os_printf("Time sys=%lu rtc=%lu\n", system_get_time(), system_get_rtc_time()); +} + + +/** + * The main entry point in an ESP8266 application. + * It is where the logic of ESP8266 starts. + */ +void user_init() { + system_timer_reinit(); // use microsecond os_timer_* + // Initialize the UART devices + uart_init(BIT_RATE_115200, BIT_RATE_115200); + os_delay_us(10000); // give the uart a break + UART_SetPrintPort(1); + system_set_os_print(1); + + // Dump the restart exception information. + dumpRestart(); + os_printf("Heap: %d\n", system_get_free_heap_size()); + os_printf("Variables: %d @%dea = %ldbytes\n", JSVAR_CACHE_SIZE, sizeof(JsVar), + JSVAR_CACHE_SIZE * sizeof(JsVar)); + os_printf("Time sys=%lu rtc=%lu\n", system_get_time(), system_get_rtc_time()); + + // Register the ESP8266 initialization callback. + system_init_done_cb(initDone); + + // Do NOT attempt to auto connect to an access point. + //wifi_station_set_auto_connect(0); + os_timer_setfn(&mainLoopSuspendTimer, enableMainLoop, NULL); +} From 9774f7f544b706e0e418fd4b8e93e8c90968b911 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 5 Oct 2015 23:49:31 -0500 Subject: [PATCH 09/10] Conversion to Unix EOL markers. Issue #600. --- src/jspin.c | 908 ++++++++++++------------- src/jsvariterator.c | 1060 ++++++++++++++--------------- src/jswrap_io.c | 1152 ++++++++++++++++---------------- src/jswrap_spi_i2c.c | 1256 +++++++++++++++++------------------ targets/esp8266/user_main.c | 542 +++++++-------- 5 files changed, 2459 insertions(+), 2459 deletions(-) diff --git a/src/jspin.c b/src/jspin.c index e2ef2a5b2..d0a6f0f01 100644 --- a/src/jspin.c +++ b/src/jspin.c @@ -1,454 +1,454 @@ -/* - * This file is part of Espruino, a JavaScript interpreter for Microcontrollers - * - * Copyright (C) 2013 Gordon Williams - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * ---------------------------------------------------------------------------- - * Utilities and definitions for handling Pins - * ---------------------------------------------------------------------------- - */ - -#include "jspin.h" -#include "jspininfo.h" // auto-generated -#include "jsinteractive.h" -#include "jshardware.h" - -// jshGetDeviceObjectFor -#include "jswrapper.h" - -#if defined(PICO) || defined(NUCLEOF401RE) || defined(NUCLEOF411RE) -#define PIN_NAMES_DIRECT // work out pin names directly from port + pin in pinInfo -#endif - -/** - * Validate that the pin is a good pin. - * \return True if the pin is valid. - */ -bool jshIsPinValid(Pin pin) { - // Note, PIN_UNDEFINED is always > JSH_PIN_COUNT - return pin < JSH_PIN_COUNT && pinInfo[pin].port != JSH_PORT_NONE; -} - -/** - * Get a pin value from an encoded strin. - * \return A pin value. - */ -Pin jshGetPinFromString(const char *s) { - // !!!FIX!!! This function needs an algorithm description. - - // built in constants - - if (s[0]=='B' && s[1]=='T' && s[2]=='N') { -#ifdef BTN1_PININDEX - if (!s[3]) return BTN1_PININDEX; - if (s[3]=='1' && !s[4]) return BTN1_PININDEX; -#endif -#ifdef BTN2_PININDEX - if (s[3]=='2' && !s[4]) return BTN2_PININDEX; -#endif -#ifdef BTN3_PININDEX - if (s[3]=='3' && !s[4]) return BTN3_PININDEX; -#endif -#ifdef BTN4_PININDEX - if (s[3]=='4' && !s[4]) return BTN4_PININDEX; -#endif - } - if (s[0]=='L' && s[1]=='E' && s[2]=='D') { -#ifdef LED1_PININDEX - if (!s[3]) return LED1_PININDEX; - if (s[3]=='1' && !s[4]) return LED1_PININDEX; -#endif -#ifdef LED2_PININDEX - if (s[3]=='2' && !s[4]) return LED2_PININDEX; -#endif -#ifdef LED3_PININDEX - if (s[3]=='3' && !s[4]) return LED3_PININDEX; -#endif -#ifdef LED4_PININDEX - if (s[3]=='4' && !s[4]) return LED4_PININDEX; -#endif -#ifdef LED5_PININDEX - if (s[3]=='5' && !s[4]) return LED5_PININDEX; -#endif -#ifdef LED6_PININDEX - if (s[3]=='6' && !s[4]) return LED6_PININDEX; -#endif -#ifdef LED7_PININDEX - if (s[3]=='7' && !s[4]) return LED7_PININDEX; -#endif -#ifdef LED8_PININDEX - if (s[3]=='8' && !s[4]) return LED8_PININDEX; -#endif - } - - if ((s[0]>='A' && s[0]<='H') && s[1]) { - int port = JSH_PORTA+s[0]-'A'; - int pin = -1; - if (s[1]>='0' && s[1]<='9') { - if (!s[2]) { // D0-D9 - pin = (s[1]-'0'); - } else if (s[2]>='0' && s[2]<='9') { - if (!s[3]) { - pin = ((s[1]-'0')*10 + (s[2]-'0')); - } else if (!s[4] && s[3]>='0' && s[3]<='9') { - pin = ((s[1]-'0')*100 + (s[2]-'0')*10 + (s[3]-'0')); - } - } - } - if (pin>=0) { -#ifdef PIN_NAMES_DIRECT - int i; - for (i=0;i=JSH_PORTA_OFFSET && -#endif - pin=JSH_PORTB_OFFSET && pin=JSH_PORTC_OFFSET && pin=JSH_PORTD_OFFSET && pin=JSH_PORTE_OFFSET && pin=JSH_PORTF_OFFSET && pin=JSH_PORTG_OFFSET && pin=JSH_PORTH_OFFSET && pinvarData.str[5]==0/*should never be more than 4 chars!*/) { - return jshGetPinFromString(&pinv->varData.str[0]); - } else if (jsvIsInt(pinv) /* This also tests for the Pin datatype */) { - return (Pin)jsvGetInteger(pinv); - } else return PIN_UNDEFINED; -} - -Pin jshGetPinFromVarAndUnLock(JsVar *pinv) { - Pin pin = jshGetPinFromVar(pinv); - jsvUnLock(pinv); - return pin; -} - - // ---------------------------------------------------------------------------- - - // Whether a pin's state has been set manually or not -BITFIELD_DECL(jshPinStateIsManual, JSH_PIN_COUNT); // TODO: This should be set to all 0 - -bool jshGetPinStateIsManual(Pin pin) { - return BITFIELD_GET(jshPinStateIsManual, pin); -} - -void jshSetPinStateIsManual(Pin pin, bool manual) { - BITFIELD_SET(jshPinStateIsManual, pin, manual); -} - - // ---------------------------------------------------------------------------- - -/** - * Get the value of a pin. - * \return The value of the pin. - */ -bool jshPinInput( - Pin pin //!< The pin to have the value retrieved. - ) { - bool value = false; - if (jshIsPinValid(pin)) { - if (!jshGetPinStateIsManual(pin)) - jshPinSetState(pin, JSHPINSTATE_GPIO_IN); - - value = jshPinGetValue(pin); - } - // Handle pin being invalid. - else jsExceptionHere(JSET_ERROR, "Invalid pin!"); - return value; -} - - -/** - * Set the value of a pin. - */ -void jshPinOutput( - Pin pin, //!< The pin to set. - bool value //!< The new value to set on the pin. - ) { - if (jshIsPinValid(pin)) { - if (!jshGetPinStateIsManual(pin)) - jshPinSetState(pin, JSHPINSTATE_GPIO_OUT); - jshPinSetValue(pin, value); - } - // Handle pin being invalid. - else jsExceptionHere(JSET_ERROR, "Invalid pin!"); -} - - -// ---------------------------------------------------------------------------- - -// Convert an event type flag into a jshPinFunction for an actual hardware device -JshPinFunction jshGetPinFunctionFromDevice(IOEventFlags device) { - switch (device) { - case EV_SERIAL1 : return JSH_USART1; - case EV_SERIAL2 : return JSH_USART2; - case EV_SERIAL3 : return JSH_USART3; - case EV_SERIAL4 : return JSH_USART4; - case EV_SERIAL5 : return JSH_USART5; - case EV_SERIAL6 : return JSH_USART6; - - case EV_SPI1 : return JSH_SPI1; - case EV_SPI2 : return JSH_SPI2; - case EV_SPI3 : return JSH_SPI3; - - case EV_I2C1 : return JSH_I2C1; - case EV_I2C2 : return JSH_I2C2; - case EV_I2C3 : return JSH_I2C3; - default: return 0; - } -} - -/** Try and find a specific type of function for the given pin. Can be given an invalid pin and will return 0. */ -JshPinFunction NO_INLINE jshGetPinFunctionForPin(Pin pin, JshPinFunction functionType) { - if (!jshIsPinValid(pin)) return 0; - int i; - for (i=0;i>JSH_SHIFT_INFO)); - if (info & JSH_TIMER_NEGATED) { - infoStrBuf[3]='N'; - infoStrBuf[4] = 0; - } else { - infoStrBuf[3] = 0; - } - } - int devIdx = 1 + ((((pinFunc&JSH_MASK_TYPE) - firstDevice) >> JSH_SHIFT_TYPE)); - - if (!devStr) { - jsiConsolePrintf("Couldn't convert pin function %d\n", pinFunc); - return; - } - if (flags & JSPFTS_DEVICE) strncat(buf, devStr, bufSize); - if (flags & JSPFTS_DEVICE_NUMBER) itostr(devIdx, &buf[strlen(buf)], 10); - if (flags & JSPFTS_SPACE) strncat(buf, " ", bufSize); - if (infoStr && (flags & JSPFTS_TYPE)) strncat(buf, infoStr, bufSize); -} - -/** Prints a list of capable pins, eg: - jshPrintCapablePins(..., "PWM", JSH_TIMER1, JSH_TIMERMAX, 0,0, false) - jshPrintCapablePins(..., "SPI", JSH_SPI1, JSH_SPIMAX, JSH_MASK_INFO,JSH_SPI_SCK, false) - jshPrintCapablePins(..., "Analog Input", 0,0,0,0, true) - for analogs */ -void NO_INLINE jshPrintCapablePins(Pin existingPin, const char *functionName, JshPinFunction typeMin, JshPinFunction typeMax, JshPinFunction pMask, JshPinFunction pData, bool printAnalogs) { - if (functionName) { - jsError("Pin %p is not capable of %s\nSuitable pins are:", existingPin, functionName); - } - - Pin pin; - int i,n=0; - for (pin=0;pin=typeMin && type<=typeMax && ((pinInfo[pin].functions[i]&pMask)==pData)) { - has = true; -#ifdef STM32F1 - af = pinInfo[pin].functions[i] & JSH_MASK_AF; -#endif - } - } - } - if (has) { - jsiConsolePrintf("%p",pin); -#ifdef STM32F1 - if (af!=JSH_AF0) jsiConsolePrint("(AF)"); -#endif - jsiConsolePrint(" "); - if (n++==8) { n=0; jsiConsolePrint("\n"); } - } - } - jsiConsolePrint("\n"); -} - -/** Find a device of the given type that works on the given pin. For instance: - * `jshGetDeviceFor(JSH_SPI1, JSH_SPIMAX, pin); - */ -JshPinFunction jshGetDeviceFor(JshPinFunction deviceMin, JshPinFunction deviceMax, Pin pin) { - if (!jshIsPinValid(pin)) return JSH_NOTHING; - int i; - for (i=0;i= deviceMin && - (f&JSH_MASK_TYPE) <= deviceMax) - return f; - } - return JSH_NOTHING; -} - -/** Like jshGetDeviceFor, but returns an actual Object (eg. SPI) if one can be found. */ -JsVar *jshGetDeviceObjectFor(JshPinFunction deviceMin, JshPinFunction deviceMax, Pin pin) { - JshPinFunction dev = jshGetDeviceFor(deviceMin, deviceMax, pin); - if (dev==JSH_NOTHING) return 0; - char devName[16]; - jshPinFunctionToString(dev, JSPFTS_DEVICE|JSPFTS_DEVICE_NUMBER, devName, sizeof(devName)); - JsVar *devVar = jsvObjectGetChild(execInfo.root, devName, 0); - if (devVar) return devVar; - return jswFindBuiltInFunction(0, devName); -} +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * ---------------------------------------------------------------------------- + * Utilities and definitions for handling Pins + * ---------------------------------------------------------------------------- + */ + +#include "jspin.h" +#include "jspininfo.h" // auto-generated +#include "jsinteractive.h" +#include "jshardware.h" + +// jshGetDeviceObjectFor +#include "jswrapper.h" + +#if defined(PICO) || defined(NUCLEOF401RE) || defined(NUCLEOF411RE) +#define PIN_NAMES_DIRECT // work out pin names directly from port + pin in pinInfo +#endif + +/** + * Validate that the pin is a good pin. + * \return True if the pin is valid. + */ +bool jshIsPinValid(Pin pin) { + // Note, PIN_UNDEFINED is always > JSH_PIN_COUNT + return pin < JSH_PIN_COUNT && pinInfo[pin].port != JSH_PORT_NONE; +} + +/** + * Get a pin value from an encoded strin. + * \return A pin value. + */ +Pin jshGetPinFromString(const char *s) { + // !!!FIX!!! This function needs an algorithm description. + + // built in constants + + if (s[0]=='B' && s[1]=='T' && s[2]=='N') { +#ifdef BTN1_PININDEX + if (!s[3]) return BTN1_PININDEX; + if (s[3]=='1' && !s[4]) return BTN1_PININDEX; +#endif +#ifdef BTN2_PININDEX + if (s[3]=='2' && !s[4]) return BTN2_PININDEX; +#endif +#ifdef BTN3_PININDEX + if (s[3]=='3' && !s[4]) return BTN3_PININDEX; +#endif +#ifdef BTN4_PININDEX + if (s[3]=='4' && !s[4]) return BTN4_PININDEX; +#endif + } + if (s[0]=='L' && s[1]=='E' && s[2]=='D') { +#ifdef LED1_PININDEX + if (!s[3]) return LED1_PININDEX; + if (s[3]=='1' && !s[4]) return LED1_PININDEX; +#endif +#ifdef LED2_PININDEX + if (s[3]=='2' && !s[4]) return LED2_PININDEX; +#endif +#ifdef LED3_PININDEX + if (s[3]=='3' && !s[4]) return LED3_PININDEX; +#endif +#ifdef LED4_PININDEX + if (s[3]=='4' && !s[4]) return LED4_PININDEX; +#endif +#ifdef LED5_PININDEX + if (s[3]=='5' && !s[4]) return LED5_PININDEX; +#endif +#ifdef LED6_PININDEX + if (s[3]=='6' && !s[4]) return LED6_PININDEX; +#endif +#ifdef LED7_PININDEX + if (s[3]=='7' && !s[4]) return LED7_PININDEX; +#endif +#ifdef LED8_PININDEX + if (s[3]=='8' && !s[4]) return LED8_PININDEX; +#endif + } + + if ((s[0]>='A' && s[0]<='H') && s[1]) { + int port = JSH_PORTA+s[0]-'A'; + int pin = -1; + if (s[1]>='0' && s[1]<='9') { + if (!s[2]) { // D0-D9 + pin = (s[1]-'0'); + } else if (s[2]>='0' && s[2]<='9') { + if (!s[3]) { + pin = ((s[1]-'0')*10 + (s[2]-'0')); + } else if (!s[4] && s[3]>='0' && s[3]<='9') { + pin = ((s[1]-'0')*100 + (s[2]-'0')*10 + (s[3]-'0')); + } + } + } + if (pin>=0) { +#ifdef PIN_NAMES_DIRECT + int i; + for (i=0;i=JSH_PORTA_OFFSET && +#endif + pin=JSH_PORTB_OFFSET && pin=JSH_PORTC_OFFSET && pin=JSH_PORTD_OFFSET && pin=JSH_PORTE_OFFSET && pin=JSH_PORTF_OFFSET && pin=JSH_PORTG_OFFSET && pin=JSH_PORTH_OFFSET && pinvarData.str[5]==0/*should never be more than 4 chars!*/) { + return jshGetPinFromString(&pinv->varData.str[0]); + } else if (jsvIsInt(pinv) /* This also tests for the Pin datatype */) { + return (Pin)jsvGetInteger(pinv); + } else return PIN_UNDEFINED; +} + +Pin jshGetPinFromVarAndUnLock(JsVar *pinv) { + Pin pin = jshGetPinFromVar(pinv); + jsvUnLock(pinv); + return pin; +} + + // ---------------------------------------------------------------------------- + + // Whether a pin's state has been set manually or not +BITFIELD_DECL(jshPinStateIsManual, JSH_PIN_COUNT); // TODO: This should be set to all 0 + +bool jshGetPinStateIsManual(Pin pin) { + return BITFIELD_GET(jshPinStateIsManual, pin); +} + +void jshSetPinStateIsManual(Pin pin, bool manual) { + BITFIELD_SET(jshPinStateIsManual, pin, manual); +} + + // ---------------------------------------------------------------------------- + +/** + * Get the value of a pin. + * \return The value of the pin. + */ +bool jshPinInput( + Pin pin //!< The pin to have the value retrieved. + ) { + bool value = false; + if (jshIsPinValid(pin)) { + if (!jshGetPinStateIsManual(pin)) + jshPinSetState(pin, JSHPINSTATE_GPIO_IN); + + value = jshPinGetValue(pin); + } + // Handle pin being invalid. + else jsExceptionHere(JSET_ERROR, "Invalid pin!"); + return value; +} + + +/** + * Set the value of a pin. + */ +void jshPinOutput( + Pin pin, //!< The pin to set. + bool value //!< The new value to set on the pin. + ) { + if (jshIsPinValid(pin)) { + if (!jshGetPinStateIsManual(pin)) + jshPinSetState(pin, JSHPINSTATE_GPIO_OUT); + jshPinSetValue(pin, value); + } + // Handle pin being invalid. + else jsExceptionHere(JSET_ERROR, "Invalid pin!"); +} + + +// ---------------------------------------------------------------------------- + +// Convert an event type flag into a jshPinFunction for an actual hardware device +JshPinFunction jshGetPinFunctionFromDevice(IOEventFlags device) { + switch (device) { + case EV_SERIAL1 : return JSH_USART1; + case EV_SERIAL2 : return JSH_USART2; + case EV_SERIAL3 : return JSH_USART3; + case EV_SERIAL4 : return JSH_USART4; + case EV_SERIAL5 : return JSH_USART5; + case EV_SERIAL6 : return JSH_USART6; + + case EV_SPI1 : return JSH_SPI1; + case EV_SPI2 : return JSH_SPI2; + case EV_SPI3 : return JSH_SPI3; + + case EV_I2C1 : return JSH_I2C1; + case EV_I2C2 : return JSH_I2C2; + case EV_I2C3 : return JSH_I2C3; + default: return 0; + } +} + +/** Try and find a specific type of function for the given pin. Can be given an invalid pin and will return 0. */ +JshPinFunction NO_INLINE jshGetPinFunctionForPin(Pin pin, JshPinFunction functionType) { + if (!jshIsPinValid(pin)) return 0; + int i; + for (i=0;i>JSH_SHIFT_INFO)); + if (info & JSH_TIMER_NEGATED) { + infoStrBuf[3]='N'; + infoStrBuf[4] = 0; + } else { + infoStrBuf[3] = 0; + } + } + int devIdx = 1 + ((((pinFunc&JSH_MASK_TYPE) - firstDevice) >> JSH_SHIFT_TYPE)); + + if (!devStr) { + jsiConsolePrintf("Couldn't convert pin function %d\n", pinFunc); + return; + } + if (flags & JSPFTS_DEVICE) strncat(buf, devStr, bufSize); + if (flags & JSPFTS_DEVICE_NUMBER) itostr(devIdx, &buf[strlen(buf)], 10); + if (flags & JSPFTS_SPACE) strncat(buf, " ", bufSize); + if (infoStr && (flags & JSPFTS_TYPE)) strncat(buf, infoStr, bufSize); +} + +/** Prints a list of capable pins, eg: + jshPrintCapablePins(..., "PWM", JSH_TIMER1, JSH_TIMERMAX, 0,0, false) + jshPrintCapablePins(..., "SPI", JSH_SPI1, JSH_SPIMAX, JSH_MASK_INFO,JSH_SPI_SCK, false) + jshPrintCapablePins(..., "Analog Input", 0,0,0,0, true) - for analogs */ +void NO_INLINE jshPrintCapablePins(Pin existingPin, const char *functionName, JshPinFunction typeMin, JshPinFunction typeMax, JshPinFunction pMask, JshPinFunction pData, bool printAnalogs) { + if (functionName) { + jsError("Pin %p is not capable of %s\nSuitable pins are:", existingPin, functionName); + } + + Pin pin; + int i,n=0; + for (pin=0;pin=typeMin && type<=typeMax && ((pinInfo[pin].functions[i]&pMask)==pData)) { + has = true; +#ifdef STM32F1 + af = pinInfo[pin].functions[i] & JSH_MASK_AF; +#endif + } + } + } + if (has) { + jsiConsolePrintf("%p",pin); +#ifdef STM32F1 + if (af!=JSH_AF0) jsiConsolePrint("(AF)"); +#endif + jsiConsolePrint(" "); + if (n++==8) { n=0; jsiConsolePrint("\n"); } + } + } + jsiConsolePrint("\n"); +} + +/** Find a device of the given type that works on the given pin. For instance: + * `jshGetDeviceFor(JSH_SPI1, JSH_SPIMAX, pin); + */ +JshPinFunction jshGetDeviceFor(JshPinFunction deviceMin, JshPinFunction deviceMax, Pin pin) { + if (!jshIsPinValid(pin)) return JSH_NOTHING; + int i; + for (i=0;i= deviceMin && + (f&JSH_MASK_TYPE) <= deviceMax) + return f; + } + return JSH_NOTHING; +} + +/** Like jshGetDeviceFor, but returns an actual Object (eg. SPI) if one can be found. */ +JsVar *jshGetDeviceObjectFor(JshPinFunction deviceMin, JshPinFunction deviceMax, Pin pin) { + JshPinFunction dev = jshGetDeviceFor(deviceMin, deviceMax, pin); + if (dev==JSH_NOTHING) return 0; + char devName[16]; + jshPinFunctionToString(dev, JSPFTS_DEVICE|JSPFTS_DEVICE_NUMBER, devName, sizeof(devName)); + JsVar *devVar = jsvObjectGetChild(execInfo.root, devName, 0); + if (devVar) return devVar; + return jswFindBuiltInFunction(0, devName); +} diff --git a/src/jsvariterator.c b/src/jsvariterator.c index d7fe41513..d76ebc5ff 100644 --- a/src/jsvariterator.c +++ b/src/jsvariterator.c @@ -1,530 +1,530 @@ -/* - * This file is part of Espruino, a JavaScript interpreter for Microcontrollers - * - * Copyright (C) 2013 Gordon Williams - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * ---------------------------------------------------------------------------- - * Iterators for Variables - * ---------------------------------------------------------------------------- - */ -#include "jsvariterator.h" - -/** - * Iterate over the contents of the content of a variable, calling callback for each. - * Contents may be: - * * numeric -> output - * * a string -> output each character - * * array/arraybuffer -> call itself on each element - * object -> call itself object.count times, on object.data - */ -bool jsvIterateCallback( - JsVar *data, // The data to iterate over. - void (*callback)(int item, void *callbackData), // The callback function invoke. - void *callbackData // Data to be passed to the callback function - ) { - bool ok = true; - // Handle the data being a single numeric. - if (jsvIsNumeric(data)) { - callback((int)jsvGetInteger(data), callbackData); - } - // Handle the data being an object. - else if (jsvIsObject(data)) { - JsVar *countVar = jsvObjectGetChild(data, "count", 0); - JsVar *dataVar = jsvObjectGetChild(data, "data", 0); - if (countVar && dataVar && jsvIsNumeric(countVar)) { - int n = (int)jsvGetInteger(countVar); - while (ok && n-- > 0) { - ok = jsvIterateCallback(dataVar, callback, callbackData); - } - } else { - jsWarn("If specifying an object, it must be of the form {data : ..., count : N}"); - } - jsvUnLock2(countVar, dataVar); - } - // Handle the data being a string - else if (jsvIsString(data)) { - JsvStringIterator it; - jsvStringIteratorNew(&it, data, 0); - while (jsvStringIteratorHasChar(&it) && ok) { - char ch = jsvStringIteratorGetChar(&it); - callback(ch, callbackData); - jsvStringIteratorNext(&it); - } - jsvStringIteratorFree(&it); - } - // Handle the data being an array buffer - else if (jsvIsArrayBuffer(data)) { - JsvArrayBufferIterator it; - jsvArrayBufferIteratorNew(&it, data, 0); - if (JSV_ARRAYBUFFER_GET_SIZE(it.type) == 1 && !JSV_ARRAYBUFFER_IS_SIGNED(it.type)) { - // faster for single byte arrays. - while (jsvArrayBufferIteratorHasElement(&it)) { - callback((int)(unsigned char)jsvStringIteratorGetChar(&it.it), callbackData); - jsvArrayBufferIteratorNext(&it); - } - } else { - while (jsvArrayBufferIteratorHasElement(&it)) { - callback((int)jsvArrayBufferIteratorGetIntegerValue(&it), callbackData); - jsvArrayBufferIteratorNext(&it); - } - } - jsvArrayBufferIteratorFree(&it); - } - // Handle the data being iterable - else if (jsvIsIterable(data)) { - JsvIterator it; - jsvIteratorNew(&it, data); - while (jsvIteratorHasElement(&it) && ok) { - JsVar *el = jsvIteratorGetValue(&it); - ok = jsvIterateCallback(el, callback, callbackData); - jsvUnLock(el); - jsvIteratorNext(&it); - } - jsvIteratorFree(&it); - } else { - jsWarn("Expecting a number or something iterable, got %t", data); - ok = false; - } - return ok; -} - - -/** - * An iterable callback that counts how many times it was called. - * This is a function that can be supplied to `jsvIterateCallback`. - */ -static void jsvIterateCallbackCountCb( - int n, //!< The current item being iterated. Not used. - void *data //!< A pointer to an int that counts how many times we were called. - ) { - NOT_USED(n); - int *count = (int*)data; - (*count)++; -} - - -/** - * Determine how many items are in this variable that will be iterated over. - * \return The number of iterations we will call for this variable. - */ -int jsvIterateCallbackCount(JsVar *var) { - // Actually iterate over the variable where the callback function merely increments a counter - // that is initially zero. The result will be the number of times the callback for iteration - // was invoked and hence the iteration count of the variable. - int count = 0; - jsvIterateCallback(var, jsvIterateCallbackCountCb, (void *)&count); - return count; -} - - -typedef struct { unsigned char *buf; unsigned int idx, length; } JsvIterateCallbackToBytesData; -static void jsvIterateCallbackToBytesCb(int data, void *userData) { - JsvIterateCallbackToBytesData *cbData = (JsvIterateCallbackToBytesData*)userData; - if (cbData->idx < cbData->length) - cbData->buf[cbData->idx] = (unsigned char)data; - cbData->idx++; -} -/** Write all data in array to the data pointer (of size dataSize bytes) */ -unsigned int jsvIterateCallbackToBytes(JsVar *var, unsigned char *data, unsigned int dataSize) { - JsvIterateCallbackToBytesData cbData; - cbData.buf = (unsigned char *)data; - cbData.idx = 0; - cbData.length = dataSize; - jsvIterateCallback(var, jsvIterateCallbackToBytesCb, (void*)&cbData); - return cbData.idx; -} - -// -------------------------------------------------------------------------------------------- - -void jsvStringIteratorNew(JsvStringIterator *it, JsVar *str, size_t startIdx) { - assert(jsvHasCharacterData(str)); - it->var = jsvLockAgain(str); - it->charsInVar = jsvGetCharactersInVar(str); - if (jsvIsFlatString(str)) { - /* Flat strings use the first var to store the size, and subsequent vars - to store the actual data, so we tweak charIdx to handle this */ - it->varIndex = -sizeof(JsVar); - it->charsInVar += sizeof(JsVar); - it->charIdx = sizeof(JsVar)+startIdx; - } else { - it->varIndex = 0; - it->charIdx = startIdx; - } - while (it->charIdx>0 && it->charIdx >= it->charsInVar) { - it->charIdx -= it->charsInVar; - it->varIndex += it->charsInVar; - if (it->var) { - if (jsvGetLastChild(it->var)) { - JsVar *next = jsvLock(jsvGetLastChild(it->var)); - jsvUnLock(it->var); - it->var = next; - it->charsInVar = jsvGetCharactersInVar(it->var); - } else { - jsvUnLock(it->var); - it->var = 0; - it->charsInVar = 0; - it->varIndex = startIdx - it->charIdx; - return; // at end of string - get out of loop - } - } - } -} - -void jsvStringIteratorNext(JsvStringIterator *it) { - jsvStringIteratorNextInline(it); -} - -void jsvStringIteratorGotoEnd(JsvStringIterator *it) { - assert(it->var); - while (jsvGetLastChild(it->var)) { - JsVar *next = jsvLock(jsvGetLastChild(it->var)); - jsvUnLock(it->var); - it->var = next; - it->varIndex += it->charsInVar; - it->charsInVar = jsvGetCharactersInVar(it->var); - } - if (it->charsInVar) it->charIdx = it->charsInVar-1; - else it->charIdx = 0; -} - -void jsvStringIteratorAppend(JsvStringIterator *it, char ch) { - if (!it->var) return; - if (it->charsInVar>0) { - assert(it->charIdx+1 == it->charsInVar /* check at end */); - it->charIdx++; - } else - assert(it->charIdx == 0); - /* Note: jsvGetMaxCharactersInVar will return the wrong length when - * applied to flat strings, but we don't care because the length will - * be smaller than charIdx, which will force a new string to be - * appended onto the end */ - if (it->charIdx >= jsvGetMaxCharactersInVar(it->var)) { - assert(!jsvGetLastChild(it->var)); - JsVar *next = jsvNewWithFlags(JSV_STRING_EXT_0); - if (!next) { - jsvUnLock(it->var); - it->var = 0; - it->charIdx = 0; - return; // out of memory - } - // we don't ref, because StringExts are never reffed as they only have one owner (and ALWAYS have an owner) - jsvSetLastChild(it->var, jsvGetRef(next)); - jsvUnLock(it->var); - it->var = next; - it->varIndex += it->charIdx; - it->charIdx = 0; // it's new, so empty - } - it->var->varData.str[it->charIdx] = ch; - it->charsInVar = it->charIdx+1; - jsvSetCharactersInVar(it->var, it->charsInVar); -} - - -// -------------------------------------------------------------------------------------------- -void jsvArrayBufferIteratorNew(JsvArrayBufferIterator *it, JsVar *arrayBuffer, size_t index) { - assert(jsvIsArrayBuffer(arrayBuffer)); - it->index = index; - it->type = arrayBuffer->varData.arraybuffer.type; - it->byteLength = arrayBuffer->varData.arraybuffer.length * JSV_ARRAYBUFFER_GET_SIZE(it->type); - it->byteOffset = arrayBuffer->varData.arraybuffer.byteOffset; - JsVar *arrayBufferData = jsvGetArrayBufferBackingString(arrayBuffer); - - it->byteLength += it->byteOffset; // because we'll check if we have more bytes using this - it->byteOffset = it->byteOffset + index*JSV_ARRAYBUFFER_GET_SIZE(it->type); - if (it->byteOffset>=(it->byteLength+1-JSV_ARRAYBUFFER_GET_SIZE(it->type))) { - jsvUnLock(arrayBufferData); - it->type = ARRAYBUFFERVIEW_UNDEFINED; - return; - } - jsvStringIteratorNew(&it->it, arrayBufferData, (size_t)it->byteOffset); - jsvUnLock(arrayBufferData); - it->hasAccessedElement = false; -} - -static void jsvArrayBufferIteratorGetValueData(JsvArrayBufferIterator *it, char *data) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; - assert(!it->hasAccessedElement); // we just haven't implemented this case yet - unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - for (i=0;iit); - if (dataLen!=1) jsvStringIteratorNext(&it->it); - } - if (dataLen!=1) it->hasAccessedElement = true; -} - -static JsVarInt jsvArrayBufferIteratorDataToInt(JsvArrayBufferIterator *it, char *data) { - unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - JsVarInt v = 0; - if (dataLen==1) v = *(int8_t*)data; - else if (dataLen==2) v = *(short*)data; - else if (dataLen==4) v = *(int*)data; - else assert(0); - if ((!JSV_ARRAYBUFFER_IS_SIGNED(it->type))) - v = v & (JsVarInt)((1UL << (8*dataLen))-1); - return v; -} - -static JsVarFloat jsvArrayBufferIteratorDataToFloat(JsvArrayBufferIterator *it, char *data) { - unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - JsVarFloat v = 0; - if (dataLen==4) v = *(float*)data; - else if (dataLen==8) v = *(double*)data; - else assert(0); - return v; -} - -JsVar *jsvArrayBufferIteratorGetValue(JsvArrayBufferIterator *it) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; - char data[8]; - jsvArrayBufferIteratorGetValueData(it, data); - if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { - return jsvNewFromFloat(jsvArrayBufferIteratorDataToFloat(it, data)); - } else { - JsVarInt i = jsvArrayBufferIteratorDataToInt(it, data); - if (it->type == ARRAYBUFFERVIEW_UINT32) - return jsvNewFromLongInteger((long long)(uint32_t)i); - return jsvNewFromInteger(i); - } -} - -JsVar *jsvArrayBufferIteratorGetValueAndRewind(JsvArrayBufferIterator *it) { - JsvStringIterator oldIt = jsvStringIteratorClone(&it->it); - JsVar *v = jsvArrayBufferIteratorGetValue(it); - jsvStringIteratorFree(&it->it); - it->it = oldIt; - it->hasAccessedElement = false; - return v; -} - -JsVarInt jsvArrayBufferIteratorGetIntegerValue(JsvArrayBufferIterator *it) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; - char data[8]; - jsvArrayBufferIteratorGetValueData(it, data); - if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { - return (JsVarInt)jsvArrayBufferIteratorDataToFloat(it, data); - } else { - return jsvArrayBufferIteratorDataToInt(it, data); - } -} - -JsVarFloat jsvArrayBufferIteratorGetFloatValue(JsvArrayBufferIterator *it) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; - char data[8]; - jsvArrayBufferIteratorGetValueData(it, data); - if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { - return jsvArrayBufferIteratorDataToFloat(it, data); - } else { - return (JsVarFloat)jsvArrayBufferIteratorDataToInt(it, data); - } -} - -static void jsvArrayBufferIteratorIntToData(char *data, unsigned int dataLen, int type, JsVarInt v) { - if (JSV_ARRAYBUFFER_IS_CLAMPED(type)) { - assert(dataLen==1 && !JSV_ARRAYBUFFER_IS_SIGNED(type)); // all we support right now - if (v<0) v=0; - if (v>255) v=255; - } - // we don't care about sign when writing - as it gets truncated - if (dataLen==1) { data[0] = (char)v; } - else if (dataLen==2) { *(short*)data = (short)v; } - else if (dataLen==4) { *(int*)data = (int)v; } - else if (dataLen==8) { *(long long*)data = (long long)v; } - else assert(0); -} - -static void jsvArrayBufferIteratorFloatToData(char *data, unsigned int dataLen, int type, JsVarFloat v) { - NOT_USED(type); - if (dataLen==4) { *(float*)data = (float)v; } - else if (dataLen==8) { *(double*)data = (double)v; } - else assert(0); -} - -void jsvArrayBufferIteratorSetIntegerValue(JsvArrayBufferIterator *it, JsVarInt v) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; - assert(!it->hasAccessedElement); // we just haven't implemented this case yet - char data[8]; - unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - - if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { - jsvArrayBufferIteratorFloatToData(data, dataLen, it->type, (JsVarFloat)v); - } else { - jsvArrayBufferIteratorIntToData(data, dataLen, it->type, v); - } - - for (i=0;iit, data[i]); - if (dataLen!=1) jsvStringIteratorNext(&it->it); - } - if (dataLen!=1) it->hasAccessedElement = true; -} - -void jsvArrayBufferIteratorSetValue(JsvArrayBufferIterator *it, JsVar *value) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; - assert(!it->hasAccessedElement); // we just haven't implemented this case yet - char data[8]; - unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - - if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { - jsvArrayBufferIteratorFloatToData(data, dataLen, it->type, jsvGetFloat(value)); - } else { - jsvArrayBufferIteratorIntToData(data, dataLen, it->type, jsvGetInteger(value)); - } - - for (i=0;iit, data[i]); - if (dataLen!=1) jsvStringIteratorNext(&it->it); - } - if (dataLen!=1) it->hasAccessedElement = true; -} - -void jsvArrayBufferIteratorSetByteValue(JsvArrayBufferIterator *it, char c) { - if (JSV_ARRAYBUFFER_GET_SIZE(it->type)!=1) { - assert(0); - return; - } - jsvStringIteratorSetChar(&it->it, c); -} - -void jsvArrayBufferIteratorSetValueAndRewind(JsvArrayBufferIterator *it, JsVar *value) { - JsvStringIterator oldIt = jsvStringIteratorClone(&it->it); - jsvArrayBufferIteratorSetValue(it, value); - jsvStringIteratorFree(&it->it); - it->it = oldIt; - it->hasAccessedElement = false; -} - - -JsVar* jsvArrayBufferIteratorGetIndex(JsvArrayBufferIterator *it) { - return jsvNewFromInteger((JsVarInt)it->index); -} - -bool jsvArrayBufferIteratorHasElement(JsvArrayBufferIterator *it) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return false; - if (it->hasAccessedElement) return true; - return it->byteOffset <= (it->byteLength-JSV_ARRAYBUFFER_GET_SIZE(it->type)); -} - -void jsvArrayBufferIteratorNext(JsvArrayBufferIterator *it) { - it->index++; - it->byteOffset += JSV_ARRAYBUFFER_GET_SIZE(it->type); - if (!it->hasAccessedElement) { - unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); - while (dataLen--) - jsvStringIteratorNext(&it->it); - } else - it->hasAccessedElement = false; -} - -void jsvArrayBufferIteratorFree(JsvArrayBufferIterator *it) { - if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; - jsvStringIteratorFree(&it->it); -} -// -------------------------------------------------------------------------------------------- -/* General Purpose iterator, for Strings, Arrays, Objects, Typed Arrays */ - -void jsvIteratorNew(JsvIterator *it, JsVar *obj) { - if (jsvIsArray(obj) || jsvIsObject(obj) || jsvIsFunction(obj)) { - it->type = JSVI_OBJECT; - jsvObjectIteratorNew(&it->it.obj, obj); - } else if (jsvIsArrayBuffer(obj)) { - it->type = JSVI_ARRAYBUFFER; - jsvArrayBufferIteratorNew(&it->it.buf, obj, 0); - } else if (jsvHasCharacterData(obj)) { - it->type = JSVI_STRING; - jsvStringIteratorNew(&it->it.str, obj, 0); - } else assert(0); -} - -JsVar *jsvIteratorGetKey(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : return jsvObjectIteratorGetKey(&it->it.obj); - case JSVI_STRING : return jsvMakeIntoVariableName(jsvNewFromInteger((JsVarInt)jsvStringIteratorGetIndex(&it->it.str)), 0); // some things expect a veriable name - case JSVI_ARRAYBUFFER : return jsvMakeIntoVariableName(jsvArrayBufferIteratorGetIndex(&it->it.buf), 0); // some things expect a veriable name - default: assert(0); return 0; - } -} - -JsVar *jsvIteratorGetValue(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : return jsvObjectIteratorGetValue(&it->it.obj); - case JSVI_STRING : { char buf[2] = {jsvStringIteratorGetChar(&it->it.str),0}; return jsvNewFromString(buf); } - case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetValueAndRewind(&it->it.buf); - default: assert(0); return 0; - } -} - -JsVarInt jsvIteratorGetIntegerValue(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : { - // fast path for arrays of ints - if (jsvIsNameInt(it->it.obj.var)) return (JsVarInt)jsvGetFirstChildSigned(it->it.obj.var); - return jsvGetIntegerAndUnLock(jsvObjectIteratorGetValue(&it->it.obj)); - } - case JSVI_STRING : return (JsVarInt)jsvStringIteratorGetChar(&it->it.str); - case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetIntegerValue(&it->it.buf); - default: assert(0); return 0; - } -} - -JsVarFloat jsvIteratorGetFloatValue(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : return jsvGetFloatAndUnLock(jsvObjectIteratorGetValue(&it->it.obj)); - case JSVI_STRING : return (JsVarFloat)jsvStringIteratorGetChar(&it->it.str); - case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetFloatValue(&it->it.buf); - default: assert(0); return 0; - } -} - -JsVar *jsvIteratorSetValue(JsvIterator *it, JsVar *value) { - switch (it->type) { - case JSVI_OBJECT : jsvObjectIteratorSetValue(&it->it.obj, value); break; - case JSVI_STRING : jsvStringIteratorSetChar(&it->it.str, (char)(jsvIsString(value) ? value->varData.str[0] : (char)jsvGetInteger(value))); break; - case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorSetValueAndRewind(&it->it.buf, value); break; - default: assert(0); break; - } - return value; -} - -bool jsvIteratorHasElement(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : return jsvObjectIteratorHasValue(&it->it.obj); - case JSVI_STRING : return jsvStringIteratorHasChar(&it->it.str); - case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorHasElement(&it->it.buf); - default: assert(0); return 0; - } -} - -void jsvIteratorNext(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : jsvObjectIteratorNext(&it->it.obj); break; - case JSVI_STRING : jsvStringIteratorNext(&it->it.str); break; - case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorNext(&it->it.buf); break; - default: assert(0); break; - } -} - -void jsvIteratorFree(JsvIterator *it) { - switch (it->type) { - case JSVI_OBJECT : jsvObjectIteratorFree(&it->it.obj); break; - case JSVI_STRING : jsvStringIteratorFree(&it->it.str); break; - case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorFree(&it->it.buf); break; - default: assert(0); break; - } -} - -JsvIterator jsvIteratorClone(JsvIterator *it) { - JsvIterator newit; - newit.type = it->type; - switch (it->type) { - case JSVI_OBJECT : newit.it.obj = jsvObjectIteratorClone(&it->it.obj); break; - case JSVI_STRING : newit.it.str = jsvStringIteratorClone(&it->it.str); break; - case JSVI_ARRAYBUFFER : newit.it.buf = jsvArrayBufferIteratorClone(&it->it.buf); break; - default: assert(0); break; - } - return newit; -} - +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * ---------------------------------------------------------------------------- + * Iterators for Variables + * ---------------------------------------------------------------------------- + */ +#include "jsvariterator.h" + +/** + * Iterate over the contents of the content of a variable, calling callback for each. + * Contents may be: + * * numeric -> output + * * a string -> output each character + * * array/arraybuffer -> call itself on each element + * object -> call itself object.count times, on object.data + */ +bool jsvIterateCallback( + JsVar *data, // The data to iterate over. + void (*callback)(int item, void *callbackData), // The callback function invoke. + void *callbackData // Data to be passed to the callback function + ) { + bool ok = true; + // Handle the data being a single numeric. + if (jsvIsNumeric(data)) { + callback((int)jsvGetInteger(data), callbackData); + } + // Handle the data being an object. + else if (jsvIsObject(data)) { + JsVar *countVar = jsvObjectGetChild(data, "count", 0); + JsVar *dataVar = jsvObjectGetChild(data, "data", 0); + if (countVar && dataVar && jsvIsNumeric(countVar)) { + int n = (int)jsvGetInteger(countVar); + while (ok && n-- > 0) { + ok = jsvIterateCallback(dataVar, callback, callbackData); + } + } else { + jsWarn("If specifying an object, it must be of the form {data : ..., count : N}"); + } + jsvUnLock2(countVar, dataVar); + } + // Handle the data being a string + else if (jsvIsString(data)) { + JsvStringIterator it; + jsvStringIteratorNew(&it, data, 0); + while (jsvStringIteratorHasChar(&it) && ok) { + char ch = jsvStringIteratorGetChar(&it); + callback(ch, callbackData); + jsvStringIteratorNext(&it); + } + jsvStringIteratorFree(&it); + } + // Handle the data being an array buffer + else if (jsvIsArrayBuffer(data)) { + JsvArrayBufferIterator it; + jsvArrayBufferIteratorNew(&it, data, 0); + if (JSV_ARRAYBUFFER_GET_SIZE(it.type) == 1 && !JSV_ARRAYBUFFER_IS_SIGNED(it.type)) { + // faster for single byte arrays. + while (jsvArrayBufferIteratorHasElement(&it)) { + callback((int)(unsigned char)jsvStringIteratorGetChar(&it.it), callbackData); + jsvArrayBufferIteratorNext(&it); + } + } else { + while (jsvArrayBufferIteratorHasElement(&it)) { + callback((int)jsvArrayBufferIteratorGetIntegerValue(&it), callbackData); + jsvArrayBufferIteratorNext(&it); + } + } + jsvArrayBufferIteratorFree(&it); + } + // Handle the data being iterable + else if (jsvIsIterable(data)) { + JsvIterator it; + jsvIteratorNew(&it, data); + while (jsvIteratorHasElement(&it) && ok) { + JsVar *el = jsvIteratorGetValue(&it); + ok = jsvIterateCallback(el, callback, callbackData); + jsvUnLock(el); + jsvIteratorNext(&it); + } + jsvIteratorFree(&it); + } else { + jsWarn("Expecting a number or something iterable, got %t", data); + ok = false; + } + return ok; +} + + +/** + * An iterable callback that counts how many times it was called. + * This is a function that can be supplied to `jsvIterateCallback`. + */ +static void jsvIterateCallbackCountCb( + int n, //!< The current item being iterated. Not used. + void *data //!< A pointer to an int that counts how many times we were called. + ) { + NOT_USED(n); + int *count = (int*)data; + (*count)++; +} + + +/** + * Determine how many items are in this variable that will be iterated over. + * \return The number of iterations we will call for this variable. + */ +int jsvIterateCallbackCount(JsVar *var) { + // Actually iterate over the variable where the callback function merely increments a counter + // that is initially zero. The result will be the number of times the callback for iteration + // was invoked and hence the iteration count of the variable. + int count = 0; + jsvIterateCallback(var, jsvIterateCallbackCountCb, (void *)&count); + return count; +} + + +typedef struct { unsigned char *buf; unsigned int idx, length; } JsvIterateCallbackToBytesData; +static void jsvIterateCallbackToBytesCb(int data, void *userData) { + JsvIterateCallbackToBytesData *cbData = (JsvIterateCallbackToBytesData*)userData; + if (cbData->idx < cbData->length) + cbData->buf[cbData->idx] = (unsigned char)data; + cbData->idx++; +} +/** Write all data in array to the data pointer (of size dataSize bytes) */ +unsigned int jsvIterateCallbackToBytes(JsVar *var, unsigned char *data, unsigned int dataSize) { + JsvIterateCallbackToBytesData cbData; + cbData.buf = (unsigned char *)data; + cbData.idx = 0; + cbData.length = dataSize; + jsvIterateCallback(var, jsvIterateCallbackToBytesCb, (void*)&cbData); + return cbData.idx; +} + +// -------------------------------------------------------------------------------------------- + +void jsvStringIteratorNew(JsvStringIterator *it, JsVar *str, size_t startIdx) { + assert(jsvHasCharacterData(str)); + it->var = jsvLockAgain(str); + it->charsInVar = jsvGetCharactersInVar(str); + if (jsvIsFlatString(str)) { + /* Flat strings use the first var to store the size, and subsequent vars + to store the actual data, so we tweak charIdx to handle this */ + it->varIndex = -sizeof(JsVar); + it->charsInVar += sizeof(JsVar); + it->charIdx = sizeof(JsVar)+startIdx; + } else { + it->varIndex = 0; + it->charIdx = startIdx; + } + while (it->charIdx>0 && it->charIdx >= it->charsInVar) { + it->charIdx -= it->charsInVar; + it->varIndex += it->charsInVar; + if (it->var) { + if (jsvGetLastChild(it->var)) { + JsVar *next = jsvLock(jsvGetLastChild(it->var)); + jsvUnLock(it->var); + it->var = next; + it->charsInVar = jsvGetCharactersInVar(it->var); + } else { + jsvUnLock(it->var); + it->var = 0; + it->charsInVar = 0; + it->varIndex = startIdx - it->charIdx; + return; // at end of string - get out of loop + } + } + } +} + +void jsvStringIteratorNext(JsvStringIterator *it) { + jsvStringIteratorNextInline(it); +} + +void jsvStringIteratorGotoEnd(JsvStringIterator *it) { + assert(it->var); + while (jsvGetLastChild(it->var)) { + JsVar *next = jsvLock(jsvGetLastChild(it->var)); + jsvUnLock(it->var); + it->var = next; + it->varIndex += it->charsInVar; + it->charsInVar = jsvGetCharactersInVar(it->var); + } + if (it->charsInVar) it->charIdx = it->charsInVar-1; + else it->charIdx = 0; +} + +void jsvStringIteratorAppend(JsvStringIterator *it, char ch) { + if (!it->var) return; + if (it->charsInVar>0) { + assert(it->charIdx+1 == it->charsInVar /* check at end */); + it->charIdx++; + } else + assert(it->charIdx == 0); + /* Note: jsvGetMaxCharactersInVar will return the wrong length when + * applied to flat strings, but we don't care because the length will + * be smaller than charIdx, which will force a new string to be + * appended onto the end */ + if (it->charIdx >= jsvGetMaxCharactersInVar(it->var)) { + assert(!jsvGetLastChild(it->var)); + JsVar *next = jsvNewWithFlags(JSV_STRING_EXT_0); + if (!next) { + jsvUnLock(it->var); + it->var = 0; + it->charIdx = 0; + return; // out of memory + } + // we don't ref, because StringExts are never reffed as they only have one owner (and ALWAYS have an owner) + jsvSetLastChild(it->var, jsvGetRef(next)); + jsvUnLock(it->var); + it->var = next; + it->varIndex += it->charIdx; + it->charIdx = 0; // it's new, so empty + } + it->var->varData.str[it->charIdx] = ch; + it->charsInVar = it->charIdx+1; + jsvSetCharactersInVar(it->var, it->charsInVar); +} + + +// -------------------------------------------------------------------------------------------- +void jsvArrayBufferIteratorNew(JsvArrayBufferIterator *it, JsVar *arrayBuffer, size_t index) { + assert(jsvIsArrayBuffer(arrayBuffer)); + it->index = index; + it->type = arrayBuffer->varData.arraybuffer.type; + it->byteLength = arrayBuffer->varData.arraybuffer.length * JSV_ARRAYBUFFER_GET_SIZE(it->type); + it->byteOffset = arrayBuffer->varData.arraybuffer.byteOffset; + JsVar *arrayBufferData = jsvGetArrayBufferBackingString(arrayBuffer); + + it->byteLength += it->byteOffset; // because we'll check if we have more bytes using this + it->byteOffset = it->byteOffset + index*JSV_ARRAYBUFFER_GET_SIZE(it->type); + if (it->byteOffset>=(it->byteLength+1-JSV_ARRAYBUFFER_GET_SIZE(it->type))) { + jsvUnLock(arrayBufferData); + it->type = ARRAYBUFFERVIEW_UNDEFINED; + return; + } + jsvStringIteratorNew(&it->it, arrayBufferData, (size_t)it->byteOffset); + jsvUnLock(arrayBufferData); + it->hasAccessedElement = false; +} + +static void jsvArrayBufferIteratorGetValueData(JsvArrayBufferIterator *it, char *data) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; + assert(!it->hasAccessedElement); // we just haven't implemented this case yet + unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + for (i=0;iit); + if (dataLen!=1) jsvStringIteratorNext(&it->it); + } + if (dataLen!=1) it->hasAccessedElement = true; +} + +static JsVarInt jsvArrayBufferIteratorDataToInt(JsvArrayBufferIterator *it, char *data) { + unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + JsVarInt v = 0; + if (dataLen==1) v = *(int8_t*)data; + else if (dataLen==2) v = *(short*)data; + else if (dataLen==4) v = *(int*)data; + else assert(0); + if ((!JSV_ARRAYBUFFER_IS_SIGNED(it->type))) + v = v & (JsVarInt)((1UL << (8*dataLen))-1); + return v; +} + +static JsVarFloat jsvArrayBufferIteratorDataToFloat(JsvArrayBufferIterator *it, char *data) { + unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + JsVarFloat v = 0; + if (dataLen==4) v = *(float*)data; + else if (dataLen==8) v = *(double*)data; + else assert(0); + return v; +} + +JsVar *jsvArrayBufferIteratorGetValue(JsvArrayBufferIterator *it) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; + char data[8]; + jsvArrayBufferIteratorGetValueData(it, data); + if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { + return jsvNewFromFloat(jsvArrayBufferIteratorDataToFloat(it, data)); + } else { + JsVarInt i = jsvArrayBufferIteratorDataToInt(it, data); + if (it->type == ARRAYBUFFERVIEW_UINT32) + return jsvNewFromLongInteger((long long)(uint32_t)i); + return jsvNewFromInteger(i); + } +} + +JsVar *jsvArrayBufferIteratorGetValueAndRewind(JsvArrayBufferIterator *it) { + JsvStringIterator oldIt = jsvStringIteratorClone(&it->it); + JsVar *v = jsvArrayBufferIteratorGetValue(it); + jsvStringIteratorFree(&it->it); + it->it = oldIt; + it->hasAccessedElement = false; + return v; +} + +JsVarInt jsvArrayBufferIteratorGetIntegerValue(JsvArrayBufferIterator *it) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; + char data[8]; + jsvArrayBufferIteratorGetValueData(it, data); + if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { + return (JsVarInt)jsvArrayBufferIteratorDataToFloat(it, data); + } else { + return jsvArrayBufferIteratorDataToInt(it, data); + } +} + +JsVarFloat jsvArrayBufferIteratorGetFloatValue(JsvArrayBufferIterator *it) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return 0; + char data[8]; + jsvArrayBufferIteratorGetValueData(it, data); + if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { + return jsvArrayBufferIteratorDataToFloat(it, data); + } else { + return (JsVarFloat)jsvArrayBufferIteratorDataToInt(it, data); + } +} + +static void jsvArrayBufferIteratorIntToData(char *data, unsigned int dataLen, int type, JsVarInt v) { + if (JSV_ARRAYBUFFER_IS_CLAMPED(type)) { + assert(dataLen==1 && !JSV_ARRAYBUFFER_IS_SIGNED(type)); // all we support right now + if (v<0) v=0; + if (v>255) v=255; + } + // we don't care about sign when writing - as it gets truncated + if (dataLen==1) { data[0] = (char)v; } + else if (dataLen==2) { *(short*)data = (short)v; } + else if (dataLen==4) { *(int*)data = (int)v; } + else if (dataLen==8) { *(long long*)data = (long long)v; } + else assert(0); +} + +static void jsvArrayBufferIteratorFloatToData(char *data, unsigned int dataLen, int type, JsVarFloat v) { + NOT_USED(type); + if (dataLen==4) { *(float*)data = (float)v; } + else if (dataLen==8) { *(double*)data = (double)v; } + else assert(0); +} + +void jsvArrayBufferIteratorSetIntegerValue(JsvArrayBufferIterator *it, JsVarInt v) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; + assert(!it->hasAccessedElement); // we just haven't implemented this case yet + char data[8]; + unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + + if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { + jsvArrayBufferIteratorFloatToData(data, dataLen, it->type, (JsVarFloat)v); + } else { + jsvArrayBufferIteratorIntToData(data, dataLen, it->type, v); + } + + for (i=0;iit, data[i]); + if (dataLen!=1) jsvStringIteratorNext(&it->it); + } + if (dataLen!=1) it->hasAccessedElement = true; +} + +void jsvArrayBufferIteratorSetValue(JsvArrayBufferIterator *it, JsVar *value) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; + assert(!it->hasAccessedElement); // we just haven't implemented this case yet + char data[8]; + unsigned int i,dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + + if (JSV_ARRAYBUFFER_IS_FLOAT(it->type)) { + jsvArrayBufferIteratorFloatToData(data, dataLen, it->type, jsvGetFloat(value)); + } else { + jsvArrayBufferIteratorIntToData(data, dataLen, it->type, jsvGetInteger(value)); + } + + for (i=0;iit, data[i]); + if (dataLen!=1) jsvStringIteratorNext(&it->it); + } + if (dataLen!=1) it->hasAccessedElement = true; +} + +void jsvArrayBufferIteratorSetByteValue(JsvArrayBufferIterator *it, char c) { + if (JSV_ARRAYBUFFER_GET_SIZE(it->type)!=1) { + assert(0); + return; + } + jsvStringIteratorSetChar(&it->it, c); +} + +void jsvArrayBufferIteratorSetValueAndRewind(JsvArrayBufferIterator *it, JsVar *value) { + JsvStringIterator oldIt = jsvStringIteratorClone(&it->it); + jsvArrayBufferIteratorSetValue(it, value); + jsvStringIteratorFree(&it->it); + it->it = oldIt; + it->hasAccessedElement = false; +} + + +JsVar* jsvArrayBufferIteratorGetIndex(JsvArrayBufferIterator *it) { + return jsvNewFromInteger((JsVarInt)it->index); +} + +bool jsvArrayBufferIteratorHasElement(JsvArrayBufferIterator *it) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return false; + if (it->hasAccessedElement) return true; + return it->byteOffset <= (it->byteLength-JSV_ARRAYBUFFER_GET_SIZE(it->type)); +} + +void jsvArrayBufferIteratorNext(JsvArrayBufferIterator *it) { + it->index++; + it->byteOffset += JSV_ARRAYBUFFER_GET_SIZE(it->type); + if (!it->hasAccessedElement) { + unsigned int dataLen = JSV_ARRAYBUFFER_GET_SIZE(it->type); + while (dataLen--) + jsvStringIteratorNext(&it->it); + } else + it->hasAccessedElement = false; +} + +void jsvArrayBufferIteratorFree(JsvArrayBufferIterator *it) { + if (it->type == ARRAYBUFFERVIEW_UNDEFINED) return; + jsvStringIteratorFree(&it->it); +} +// -------------------------------------------------------------------------------------------- +/* General Purpose iterator, for Strings, Arrays, Objects, Typed Arrays */ + +void jsvIteratorNew(JsvIterator *it, JsVar *obj) { + if (jsvIsArray(obj) || jsvIsObject(obj) || jsvIsFunction(obj)) { + it->type = JSVI_OBJECT; + jsvObjectIteratorNew(&it->it.obj, obj); + } else if (jsvIsArrayBuffer(obj)) { + it->type = JSVI_ARRAYBUFFER; + jsvArrayBufferIteratorNew(&it->it.buf, obj, 0); + } else if (jsvHasCharacterData(obj)) { + it->type = JSVI_STRING; + jsvStringIteratorNew(&it->it.str, obj, 0); + } else assert(0); +} + +JsVar *jsvIteratorGetKey(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : return jsvObjectIteratorGetKey(&it->it.obj); + case JSVI_STRING : return jsvMakeIntoVariableName(jsvNewFromInteger((JsVarInt)jsvStringIteratorGetIndex(&it->it.str)), 0); // some things expect a veriable name + case JSVI_ARRAYBUFFER : return jsvMakeIntoVariableName(jsvArrayBufferIteratorGetIndex(&it->it.buf), 0); // some things expect a veriable name + default: assert(0); return 0; + } +} + +JsVar *jsvIteratorGetValue(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : return jsvObjectIteratorGetValue(&it->it.obj); + case JSVI_STRING : { char buf[2] = {jsvStringIteratorGetChar(&it->it.str),0}; return jsvNewFromString(buf); } + case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetValueAndRewind(&it->it.buf); + default: assert(0); return 0; + } +} + +JsVarInt jsvIteratorGetIntegerValue(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : { + // fast path for arrays of ints + if (jsvIsNameInt(it->it.obj.var)) return (JsVarInt)jsvGetFirstChildSigned(it->it.obj.var); + return jsvGetIntegerAndUnLock(jsvObjectIteratorGetValue(&it->it.obj)); + } + case JSVI_STRING : return (JsVarInt)jsvStringIteratorGetChar(&it->it.str); + case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetIntegerValue(&it->it.buf); + default: assert(0); return 0; + } +} + +JsVarFloat jsvIteratorGetFloatValue(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : return jsvGetFloatAndUnLock(jsvObjectIteratorGetValue(&it->it.obj)); + case JSVI_STRING : return (JsVarFloat)jsvStringIteratorGetChar(&it->it.str); + case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorGetFloatValue(&it->it.buf); + default: assert(0); return 0; + } +} + +JsVar *jsvIteratorSetValue(JsvIterator *it, JsVar *value) { + switch (it->type) { + case JSVI_OBJECT : jsvObjectIteratorSetValue(&it->it.obj, value); break; + case JSVI_STRING : jsvStringIteratorSetChar(&it->it.str, (char)(jsvIsString(value) ? value->varData.str[0] : (char)jsvGetInteger(value))); break; + case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorSetValueAndRewind(&it->it.buf, value); break; + default: assert(0); break; + } + return value; +} + +bool jsvIteratorHasElement(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : return jsvObjectIteratorHasValue(&it->it.obj); + case JSVI_STRING : return jsvStringIteratorHasChar(&it->it.str); + case JSVI_ARRAYBUFFER : return jsvArrayBufferIteratorHasElement(&it->it.buf); + default: assert(0); return 0; + } +} + +void jsvIteratorNext(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : jsvObjectIteratorNext(&it->it.obj); break; + case JSVI_STRING : jsvStringIteratorNext(&it->it.str); break; + case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorNext(&it->it.buf); break; + default: assert(0); break; + } +} + +void jsvIteratorFree(JsvIterator *it) { + switch (it->type) { + case JSVI_OBJECT : jsvObjectIteratorFree(&it->it.obj); break; + case JSVI_STRING : jsvStringIteratorFree(&it->it.str); break; + case JSVI_ARRAYBUFFER : jsvArrayBufferIteratorFree(&it->it.buf); break; + default: assert(0); break; + } +} + +JsvIterator jsvIteratorClone(JsvIterator *it) { + JsvIterator newit; + newit.type = it->type; + switch (it->type) { + case JSVI_OBJECT : newit.it.obj = jsvObjectIteratorClone(&it->it.obj); break; + case JSVI_STRING : newit.it.str = jsvStringIteratorClone(&it->it.str); break; + case JSVI_ARRAYBUFFER : newit.it.buf = jsvArrayBufferIteratorClone(&it->it.buf); break; + default: assert(0); break; + } + return newit; +} + diff --git a/src/jswrap_io.c b/src/jswrap_io.c index 479a8cb05..f04024f66 100644 --- a/src/jswrap_io.c +++ b/src/jswrap_io.c @@ -1,576 +1,576 @@ -/* - * This file is part of Espruino, a JavaScript interpreter for Microcontrollers - * - * Copyright (C) 2013 Gordon Williams - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * ---------------------------------------------------------------------------- - * This file is designed to be parsed during the build process - * - * JavaScript Hardware IO Functions - * ---------------------------------------------------------------------------- - */ -#include "jswrap_io.h" -#include "jsvar.h" -#include "jswrap_arraybuffer.h" // for jswrap_io_peek - -/*JSON{ - "type" : "function", - "name" : "peek8", - "generate_full" : "jswrap_io_peek(addr,count,1)", - "params" : [ - ["addr", "int", "The address in memory to read"], - ["count", "int", "(optional) the number of items to read. If >1 a Uint8Array will be returned."] - ], - "return" : ["JsVar","The value of memory at the given location"] -} -Read 8 bits of memory at the given location - DANGEROUS! - */ -/*JSON{ - "type" : "function", - "name" : "poke8", - "generate_full" : "jswrap_io_poke(addr,value,1)", - "params" : [ - ["addr","int","The address in memory to write"], - ["value","JsVar","The value to write, or an array of values"] - ] -} -Write 8 bits of memory at the given location - VERY DANGEROUS! - */ -/*JSON{ - "type" : "function", - "name" : "peek16", - "generate_full" : "jswrap_io_peek(addr,count,2)", - "params" : [ - ["addr","int","The address in memory to read"], - ["count","int","(optional) the number of items to read. If >1 a Uint16Array will be returned."] - ], - "return" : ["JsVar","The value of memory at the given location"] -} -Read 16 bits of memory at the given location - DANGEROUS! - */ -/*JSON{ - "type" : "function", - "name" : "poke16", - "generate_full" : "jswrap_io_poke(addr,value,2)", - "params" : [ - ["addr","int","The address in memory to write"], - ["value","JsVar","The value to write, or an array of values"] - ] -} -Write 16 bits of memory at the given location - VERY DANGEROUS! - */ -/*JSON{ - "type" : "function", - "name" : "peek32", - "generate_full" : "jswrap_io_peek(addr,count,4)", - "params" : [ - ["addr","int","The address in memory to read"], - ["count","int","(optional) the number of items to read. If >1 a Uint32Array will be returned."] - ], - "return" : ["JsVar","The value of memory at the given location"] -} -Read 32 bits of memory at the given location - DANGEROUS! - */ -/*JSON{ - "type" : "function", - "name" : "poke32", - "generate_full" : "jswrap_io_poke(addr,value,4)", - "params" : [ - ["addr","int","The address in memory to write"], - ["value","JsVar","The value to write, or an array of values"] - ] -} -Write 32 bits of memory at the given location - VERY DANGEROUS! - */ - -uint32_t _jswrap_io_peek(JsVarInt addr, int wordSize) { - if (wordSize==1) return (uint32_t)*(unsigned char*)(size_t)addr; - if (wordSize==2) return (uint32_t)*(unsigned short*)(size_t)addr; - if (wordSize==4) return (uint32_t)*(unsigned int*)(size_t)addr; - return 0; -} - -JsVar *jswrap_io_peek(JsVarInt addr, JsVarInt count, int wordSize) { - if (count<=1) { - return jsvNewFromLongInteger((long long)_jswrap_io_peek(addr, wordSize)); - } else { - JsVarDataArrayBufferViewType aType; - if (wordSize==1) aType=ARRAYBUFFERVIEW_UINT8; - if (wordSize==2) aType=ARRAYBUFFERVIEW_UINT16; - if (wordSize==4) aType=ARRAYBUFFERVIEW_UINT32; - JsVar *arr = jsvNewTypedArray(aType, count); - if (!arr) return 0; - JsvArrayBufferIterator it; - jsvArrayBufferIteratorNew(&it, arr, 0); - while (jsvArrayBufferIteratorHasElement(&it)) { - jsvArrayBufferIteratorSetIntegerValue(&it, (JsVarInt)_jswrap_io_peek(addr, wordSize)); - addr += wordSize; - jsvArrayBufferIteratorNext(&it); - } - jsvArrayBufferIteratorFree(&it); - return arr; - } -} - -void _jswrap_io_poke(JsVarInt addr, uint32_t data, int wordSize) { - if (wordSize==1) (*(unsigned char*)(size_t)addr) = (unsigned char)data; - else if (wordSize==2) (*(unsigned short*)(size_t)addr) = (unsigned short)data; - else if (wordSize==4) (*(unsigned int*)(size_t)addr) = (unsigned int)data; -} - -void jswrap_io_poke(JsVarInt addr, JsVar *data, int wordSize) { - if (jsvIsNumeric(data)) { - _jswrap_io_poke(addr, (uint32_t)jsvGetInteger(data), wordSize); - } else if (jsvIsIterable(data)) { - JsvIterator it; - jsvIteratorNew(&it, data); - while (jsvIteratorHasElement(&it)) { - _jswrap_io_poke(addr, (uint32_t)jsvIteratorGetIntegerValue(&it), wordSize); - addr += wordSize; - jsvIteratorNext(&it); - } - jsvIteratorFree(&it); - } -} - - -/*JSON{ - "type" : "function", - "name" : "analogRead", - "generate" : "jshPinAnalog", - "params" : [ - ["pin","pin",["The pin to use","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `ADC` markers."]] - ], - "return" : ["float","The analog Value of the Pin between 0 and 1"] -} -Get the analog value of the given pin - -This is different to Arduino which only returns an integer between 0 and 1023 - -However only pins connected to an ADC will work (see the datasheet) - - **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"analog"` - */ -/*JSON{ - "type" : "function", - "name" : "analogWrite", - "generate" : "jswrap_io_analogWrite", - "params" : [ - ["pin","pin",["The pin to use","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `PWM` or `DAC` markers."]], - ["value","float","A value between 0 and 1"], - ["options","JsVar",["An object containing options for analog output - see below"]] - ] -} -Set the analog Value of a pin. It will be output using PWM. - -Objects can contain: - -* `freq` - pulse frequency in Hz, eg. ```analogWrite(A0,0.5,{ freq : 10 });``` - specifying a frequency will force PWM output, even if the pin has a DAC -* `soft` - boolean, If true software PWM is used if available. -* `forceSoft` - boolean, If true software PWM is used even - - **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` - */ -void jswrap_io_analogWrite(Pin pin, JsVarFloat value, JsVar *options) { - JsVarFloat freq = 0; - JshAnalogOutputFlags flags = JSAOF_NONE; - if (jsvIsObject(options)) { - freq = jsvGetFloatAndUnLock(jsvObjectGetChild(options, "freq", 0)); - if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "forceSoft", 0))) - flags |= JSAOF_FORCE_SOFTWARE; - else if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "soft", 0))) - flags |= JSAOF_ALLOW_SOFTWARE; - } - - jshPinAnalogOutput(pin, value, freq, flags); -} - -/*JSON{ - "type" : "function", - "name" : "digitalPulse", - "generate" : "jswrap_io_digitalPulse", - "params" : [ - ["pin","pin","The pin to use"], - ["value","bool","Whether to pulse high (true) or low (false)"], - ["time","JsVar","A time in milliseconds, or an array of times (in which case a square wave will be output starting with a pulse of 'value')"] - ] -} -Pulse the pin with the value for the given time in milliseconds. It uses a hardware timer to produce accurate pulses, and returns immediately (before the pulse has finished). Use `digitalPulse(A0,1,0)` to wait until a previous pulse has finished. - -eg. `digitalPulse(A0,1,5);` pulses A0 high for 5ms. `digitalPulse(A0,1,[5,2,4]);` pulses A0 high for 5ms, low for 2ms, and high for 4ms - - **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` - -digitalPulse is for SHORT pulses that need to be very accurate. If you're doing anything over a few milliseconds, use setTimeout instead. - */ -void jswrap_io_digitalPulse(Pin pin, bool value, JsVar *times) { - if (jsvIsNumeric(times)) { - JsVarFloat time = jsvGetFloat(times); - if (time<0 || isnan(time)) { - jsExceptionHere(JSET_ERROR, "Pulse Time given for digitalPulse is less than 0, or not a number"); - } else { - jshPinPulse(pin, value, time); - } - } else if (jsvIsIterable(times)) { - // iterable, so output a square wave - JsvIterator it; - jsvIteratorNew(&it, times); - while (jsvIteratorHasElement(&it)) { - JsVarFloat time = jsvIteratorGetFloatValue(&it); - if (time>=0 && !isnan(time)) - jshPinPulse(pin, value, time); - value = !value; - jsvIteratorNext(&it); - } - jsvIteratorFree(&it); - } else { - jsExceptionHere(JSET_ERROR, "Expecting a number or array, got %t", times); - } -} - -/*JSON{ - "type" : "function", - "name" : "digitalWrite", - "generate" : "jswrap_io_digitalWrite", - "params" : [ - ["pin", "JsVar","The pin to use"], - ["value", "int","Whether to pulse high (true) or low (false)"] - ] -} -Set the digital value of the given pin. - - **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` - -If pin argument is an array of pins (eg. `[A2,A1,A0]`) the value argument will be treated -as an array of bits where the last array element is the least significant bit. - -In this case, pin values are set last significant bit first (from the right-hand side -of the array of pins). This means you can use the same pin multiple times, for -example `digitalWrite([A1,A1,A0,A0],0b0101)` would pulse A0 followed by A1. -*/ - -/** - * Set the output of a GPIO. - */ -void jswrap_io_digitalWrite( - JsVar *pinVar, //!< A pin or pins. - JsVarInt value //!< The value of the output. - ) { - // Handle the case where it is an array of pins. - if (jsvIsArray(pinVar)) { - JsVarRef pinName = jsvGetLastChild(pinVar); // NOTE: start at end and work back! - while (pinName) { - JsVar *pinNamePtr = jsvLock(pinName); - JsVar *pinPtr = jsvSkipName(pinNamePtr); - jshPinOutput(jshGetPinFromVar(pinPtr), value&1); - jsvUnLock(pinPtr); - pinName = jsvGetPrevSibling(pinNamePtr); - jsvUnLock(pinNamePtr); - value = value>>1; // next bit down - } - } - // Handle the case where it is a single pin. - else { - Pin pin = jshGetPinFromVar(pinVar); - jshPinOutput(pin, value != 0); - } -} - - -/*JSON{ - "type" : "function", - "name" : "digitalRead", - "generate" : "jswrap_io_digitalRead", - "params" : [ - ["pin","JsVar","The pin to use"] - ], - "return" : ["int","The digital Value of the Pin"] -} -Get the digital value of the given pin. - - **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"input"` - -If the pin argument is an array of pins (eg. `[A2,A1,A0]`) the value returned will be an number where -the last array element is the least significant bit, for example if `A0=A1=1` and `A2=0`, `digitalRead([A2,A1,A0]) == 0b011` -*/ - -/** - * Read the value of a GPIO pin. - */ -JsVarInt jswrap_io_digitalRead(JsVar *pinVar) { - // Hadnle the case where it is an array of pins. - if (jsvIsArray(pinVar)) { - int pins = 0; - JsVarInt value = 0; - JsvObjectIterator it; - jsvObjectIteratorNew(&it, pinVar); - while (jsvObjectIteratorHasValue(&it)) { - JsVar *pinPtr = jsvObjectIteratorGetValue(&it); - value = (value<<1) | (JsVarInt)jshPinInput(jshGetPinFromVar(pinPtr)); - jsvUnLock(pinPtr); - jsvObjectIteratorNext(&it); - pins++; - } - jsvObjectIteratorFree(&it); - if (pins==0) return 0; // return undefined if array empty - return value; - } - // Handle the case where it is a single pin. - else { - Pin pin = jshGetPinFromVar(pinVar); - return jshPinInput(pin); - } -} - -/*JSON{ - "type" : "function", - "name" : "pinMode", - "generate" : "jswrap_io_pinMode", - "params" : [ - ["pin","pin","The pin to set pin mode for"], - ["mode","JsVar","The mode - a string that is either 'analog', 'input', 'input_pullup', 'input_pulldown', 'output', 'opendrain', 'af_output' or 'af_opendrain'. Do not include this argument if you want to revert to automatic pin mode setting."] - ] -} -Set the mode of the given pin. - - * `analog` - Analog input - * `input` - Digital input - * `input_pullup` - Digital input with internal ~40k pull-up resistor - * `input_pulldown` - Digital input with internal ~40k pull-down resistor - * `output` - Digital output - * `opendrain` - Digital output that only ever pulls down to 0v. Sending a logical `1` leaves the pin open circuit - * `af_output` - Digital output from built-in peripheral - * `af_opendrain` - Digital output from built-in peripheral that only ever pulls down to 0v. - * Sending a logical `1` leaves the pin open circuit - - **Note:** `digitalRead`/`digitalWrite`/etc set the pin mode automatically *unless* `pinMode` has been called first. If you want `digitalRead`/etc to set the pin mode automatically after you have called `pinMode`, simply call it again with no mode argument: `pinMode(pin)` -*/ - -/** - * Set the mode of a pin. - */ -void jswrap_io_pinMode( - Pin pin, //!< The pin to set. - JsVar *mode //!< The new mode of the pin. - ) { - if (!jshIsPinValid(pin)) { - jsExceptionHere(JSET_ERROR, "Invalid pin"); - return; - } - JshPinState m = JSHPINSTATE_UNDEFINED; - if (jsvIsString(mode)) { - if (jsvIsStringEqual(mode, "analog")) m = JSHPINSTATE_ADC_IN; - else if (jsvIsStringEqual(mode, "input")) m = JSHPINSTATE_GPIO_IN; - else if (jsvIsStringEqual(mode, "input_pullup")) m = JSHPINSTATE_GPIO_IN_PULLUP; - else if (jsvIsStringEqual(mode, "input_pulldown")) m = JSHPINSTATE_GPIO_IN_PULLDOWN; - else if (jsvIsStringEqual(mode, "output")) m = JSHPINSTATE_GPIO_OUT; - else if (jsvIsStringEqual(mode, "opendrain")) m = JSHPINSTATE_GPIO_OUT_OPENDRAIN; - else if (jsvIsStringEqual(mode, "af_output")) m = JSHPINSTATE_AF_OUT; - else if (jsvIsStringEqual(mode, "af_opendrain")) m = JSHPINSTATE_AF_OUT_OPENDRAIN; - } - if (m != JSHPINSTATE_UNDEFINED) { - jshSetPinStateIsManual(pin, true); - jshPinSetState(pin, m); - } else { - jshSetPinStateIsManual(pin, false); - if (!jsvIsUndefined(mode)) { - jsExceptionHere(JSET_ERROR, "Unknown pin mode"); - } - } -} - -/*JSON{ - "type" : "function", - "name" : "getPinMode", - "generate" : "jswrap_io_getPinMode", - "params" : [ - ["pin","pin","The pin to check"] - ], - "return" : ["JsVar","The pin mode, as a string"] -} -Return the current mode of the given pin. See `pinMode` for more information. - */ -JsVar *jswrap_io_getPinMode(Pin pin) { - if (!jshIsPinValid(pin)) { - jsExceptionHere(JSET_ERROR, "Invalid pin"); - return 0; - } - JshPinState m = jshPinGetState(pin)&JSHPINSTATE_MASK; - const char *text = 0; - switch (m) { - case JSHPINSTATE_ADC_IN : text = "analog"; break; - case JSHPINSTATE_GPIO_IN : text = "input"; break; - case JSHPINSTATE_GPIO_IN_PULLUP : text = "input_pullup"; break; - case JSHPINSTATE_GPIO_IN_PULLDOWN : text = "input_pulldown"; break; - case JSHPINSTATE_GPIO_OUT : text = "output"; break; - case JSHPINSTATE_GPIO_OUT_OPENDRAIN : text = "opendrain"; break; - case JSHPINSTATE_AF_OUT : text = "af_output"; break; - case JSHPINSTATE_AF_OUT_OPENDRAIN : text = "af_opendrain"; break; - default: break; - } - if (text) return jsvNewFromString(text); - return 0; -} - -/*JSON{ - "type" : "function", - "name" : "setWatch", - "generate" : "jswrap_interface_setWatch", - "params" : [ - ["function","JsVar","A Function or String to be executed"], - ["pin","pin","The pin to watch"], - ["options","JsVar",["If this is a boolean or integer, it determines whether to call this once (false = default) or every time a change occurs (true)","If this is an object, it can contain the following information: ```{ repeat: true/false(default), edge:'rising'/'falling'/'both'(default), debounce:10}```. `debounce` is the time in ms to wait for bounces to subside, or 0."]] - ], - "return" : ["JsVar","An ID that can be passed to clearWatch"] -} -Call the function specified when the pin changes. Watches set with `setWatch` can be removed using `clearWatch`. - -The function may also take an argument, which is an object of type `{state:bool, time:float, lastTime:float}`. - - * `state` is whether the pin is currently a `1` or a `0` - * `time` is the time in seconds at which the pin changed state - * `lastTime` is the time in seconds at which the **pin last changed state**. When using `edge:'rising'` or `edge:'falling'`, this is not the same as when the function was last called. - -For instance, if you want to measure the length of a positive pulse you could use `setWatch(function(e) { console.log(e.time-e.lastTime); }, BTN, { repeat:true, edge:'falling' });`. -This will only be called on the falling edge of the pulse, but will be able to measure the width of the pulse because `e.lastTime` is the time of the rising edge. - -Internally, an interrupt writes the time of the pin's state change into a queue, and the function -supplied to `setWatch` is executed only from the main message loop. However, if the callback is a -native function `void (bool state)` then you can add `irq:true` to options, which will cause the -function to be called from within the IRQ. When doing this, interrupts will happen on both edges -and there will be no debouncing. - -**Note:** The STM32 chip (used in the [Espruino Board](/EspruinoBoard) and [Pico](/Pico)) cannot -watch two pins with the same number - eg `A0` and `B0`. - - */ -JsVar *jswrap_interface_setWatch(JsVar *func, Pin pin, JsVar *repeatOrObject) { - if (!jshIsPinValid(pin)) { - jsError("Invalid pin"); - return 0; - } - - if (!jsiIsWatchingPin(pin) && !jshCanWatch(pin)) { - jsWarn("Unable to set watch. You may already have a watch on a pin with the same number (eg. A0 and B0)"); - return 0; - } - - bool repeat = false; - JsVarFloat debounce = 0; - int edge = 0; - bool isIRQ = false; - if (jsvIsObject(repeatOrObject)) { - JsVar *v; - repeat = jsvGetBoolAndUnLock(jsvObjectGetChild(repeatOrObject, "repeat", 0)); - debounce = jsvGetFloatAndUnLock(jsvObjectGetChild(repeatOrObject, "debounce", 0)); - if (isnan(debounce) || debounce<0) debounce=0; - v = jsvObjectGetChild(repeatOrObject, "edge", 0); - if (jsvIsString(v)) { - if (jsvIsStringEqual(v, "rising")) edge=1; - else if (jsvIsStringEqual(v, "falling")) edge=-1; - else if (jsvIsStringEqual(v, "both")) edge=0; - else jsWarn("'edge' in setWatch should be a string - either 'rising', 'falling' or 'both'"); - } else if (!jsvIsUndefined(v)) - jsWarn("'edge' in setWatch should be a string - either 'rising', 'falling' or 'both'"); - jsvUnLock(v); - isIRQ = jsvGetBoolAndUnLock(jsvObjectGetChild(repeatOrObject, "irq", 0)); - } else - repeat = jsvGetBool(repeatOrObject); - - JsVarInt itemIndex = -1; - if (!jsvIsFunction(func) && !jsvIsString(func)) { - jsExceptionHere(JSET_ERROR, "Function or String not supplied!"); - } else { - // Create a new watch - JsVar *watchPtr = jsvNewWithFlags(JSV_OBJECT); - if (watchPtr) { - jsvObjectSetChildAndUnLock(watchPtr, "pin", jsvNewFromPin(pin)); - if (repeat) jsvObjectSetChildAndUnLock(watchPtr, "recur", jsvNewFromBool(repeat)); - if (debounce>0) jsvObjectSetChildAndUnLock(watchPtr, "debounce", jsvNewFromInteger((JsVarInt)jshGetTimeFromMilliseconds(debounce))); - if (edge) jsvObjectSetChildAndUnLock(watchPtr, "edge", jsvNewFromInteger(edge)); - jsvObjectSetChild(watchPtr, "callback", func); // no unlock intentionally - } - - // If nothing already watching the pin, set up a watch - IOEventFlags exti = EV_NONE; - if (!jsiIsWatchingPin(pin)) - exti = jshPinWatch(pin, true); - // disable event callbacks by default - if (exti) { - jshSetEventCallback(exti, 0); - if (isIRQ) { - if (jsvIsNativeFunction(func)) { - jshSetEventCallback(exti, (JshEventCallbackCallback)jsvGetNativeFunctionPtr(func)); - } else { - jsExceptionHere(JSET_ERROR, "irq=true set, but function is not a native function"); - } - } - } else { - if (isIRQ) - jsExceptionHere(JSET_ERROR, "irq=true set, but watch is already used"); - } - - - JsVar *watchArrayPtr = jsvLock(watchArray); - itemIndex = jsvArrayAddToEnd(watchArrayPtr, watchPtr, 1) - 1; - jsvUnLock2(watchArrayPtr, watchPtr); - - - } - return (itemIndex>=0) ? jsvNewFromInteger(itemIndex) : 0/*undefined*/; -} - -/*JSON{ - "type" : "function", - "name" : "clearWatch", - "generate" : "jswrap_interface_clearWatch", - "params" : [ - ["id","JsVar","The id returned by a previous call to setWatch"] - ] -} -Clear the Watch that was created with setWatch. If no parameter is supplied, all watches will be removed. - */ -void jswrap_interface_clearWatch(JsVar *idVar) { - - if (jsvIsUndefined(idVar)) { - JsVar *watchArrayPtr = jsvLock(watchArray); - JsvObjectIterator it; - jsvObjectIteratorNew(&it, watchArrayPtr); - while (jsvObjectIteratorHasValue(&it)) { - JsVar *watchPtr = jsvObjectIteratorGetValue(&it); - JsVar *watchPin = jsvObjectGetChild(watchPtr, "pin", 0); - jshPinWatch(jshGetPinFromVar(watchPin), false); - jsvUnLock2(watchPin, watchPtr); - jsvObjectIteratorNext(&it); - } - jsvObjectIteratorFree(&it); - // remove all items - jsvRemoveAllChildren(watchArrayPtr); - jsvUnLock(watchArrayPtr); - } else { - JsVar *watchArrayPtr = jsvLock(watchArray); - JsVar *watchNamePtr = jsvFindChildFromVar(watchArrayPtr, idVar, false); - jsvUnLock(watchArrayPtr); - if (watchNamePtr) { // child is a 'name' - JsVar *watchPtr = jsvSkipName(watchNamePtr); - Pin pin = jshGetPinFromVarAndUnLock(jsvObjectGetChild(watchPtr, "pin", 0)); - jsvUnLock(watchPtr); - - JsVar *watchArrayPtr = jsvLock(watchArray); - jsvRemoveChild(watchArrayPtr, watchNamePtr); - jsvUnLock2(watchNamePtr, watchArrayPtr); - - // Now check if this pin is still being watched - if (!jsiIsWatchingPin(pin)) - jshPinWatch(pin, false); // 'unwatch' pin - } else { - jsExceptionHere(JSET_ERROR, "Unknown Watch"); - } - } -} - - +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * ---------------------------------------------------------------------------- + * This file is designed to be parsed during the build process + * + * JavaScript Hardware IO Functions + * ---------------------------------------------------------------------------- + */ +#include "jswrap_io.h" +#include "jsvar.h" +#include "jswrap_arraybuffer.h" // for jswrap_io_peek + +/*JSON{ + "type" : "function", + "name" : "peek8", + "generate_full" : "jswrap_io_peek(addr,count,1)", + "params" : [ + ["addr", "int", "The address in memory to read"], + ["count", "int", "(optional) the number of items to read. If >1 a Uint8Array will be returned."] + ], + "return" : ["JsVar","The value of memory at the given location"] +} +Read 8 bits of memory at the given location - DANGEROUS! + */ +/*JSON{ + "type" : "function", + "name" : "poke8", + "generate_full" : "jswrap_io_poke(addr,value,1)", + "params" : [ + ["addr","int","The address in memory to write"], + ["value","JsVar","The value to write, or an array of values"] + ] +} +Write 8 bits of memory at the given location - VERY DANGEROUS! + */ +/*JSON{ + "type" : "function", + "name" : "peek16", + "generate_full" : "jswrap_io_peek(addr,count,2)", + "params" : [ + ["addr","int","The address in memory to read"], + ["count","int","(optional) the number of items to read. If >1 a Uint16Array will be returned."] + ], + "return" : ["JsVar","The value of memory at the given location"] +} +Read 16 bits of memory at the given location - DANGEROUS! + */ +/*JSON{ + "type" : "function", + "name" : "poke16", + "generate_full" : "jswrap_io_poke(addr,value,2)", + "params" : [ + ["addr","int","The address in memory to write"], + ["value","JsVar","The value to write, or an array of values"] + ] +} +Write 16 bits of memory at the given location - VERY DANGEROUS! + */ +/*JSON{ + "type" : "function", + "name" : "peek32", + "generate_full" : "jswrap_io_peek(addr,count,4)", + "params" : [ + ["addr","int","The address in memory to read"], + ["count","int","(optional) the number of items to read. If >1 a Uint32Array will be returned."] + ], + "return" : ["JsVar","The value of memory at the given location"] +} +Read 32 bits of memory at the given location - DANGEROUS! + */ +/*JSON{ + "type" : "function", + "name" : "poke32", + "generate_full" : "jswrap_io_poke(addr,value,4)", + "params" : [ + ["addr","int","The address in memory to write"], + ["value","JsVar","The value to write, or an array of values"] + ] +} +Write 32 bits of memory at the given location - VERY DANGEROUS! + */ + +uint32_t _jswrap_io_peek(JsVarInt addr, int wordSize) { + if (wordSize==1) return (uint32_t)*(unsigned char*)(size_t)addr; + if (wordSize==2) return (uint32_t)*(unsigned short*)(size_t)addr; + if (wordSize==4) return (uint32_t)*(unsigned int*)(size_t)addr; + return 0; +} + +JsVar *jswrap_io_peek(JsVarInt addr, JsVarInt count, int wordSize) { + if (count<=1) { + return jsvNewFromLongInteger((long long)_jswrap_io_peek(addr, wordSize)); + } else { + JsVarDataArrayBufferViewType aType; + if (wordSize==1) aType=ARRAYBUFFERVIEW_UINT8; + if (wordSize==2) aType=ARRAYBUFFERVIEW_UINT16; + if (wordSize==4) aType=ARRAYBUFFERVIEW_UINT32; + JsVar *arr = jsvNewTypedArray(aType, count); + if (!arr) return 0; + JsvArrayBufferIterator it; + jsvArrayBufferIteratorNew(&it, arr, 0); + while (jsvArrayBufferIteratorHasElement(&it)) { + jsvArrayBufferIteratorSetIntegerValue(&it, (JsVarInt)_jswrap_io_peek(addr, wordSize)); + addr += wordSize; + jsvArrayBufferIteratorNext(&it); + } + jsvArrayBufferIteratorFree(&it); + return arr; + } +} + +void _jswrap_io_poke(JsVarInt addr, uint32_t data, int wordSize) { + if (wordSize==1) (*(unsigned char*)(size_t)addr) = (unsigned char)data; + else if (wordSize==2) (*(unsigned short*)(size_t)addr) = (unsigned short)data; + else if (wordSize==4) (*(unsigned int*)(size_t)addr) = (unsigned int)data; +} + +void jswrap_io_poke(JsVarInt addr, JsVar *data, int wordSize) { + if (jsvIsNumeric(data)) { + _jswrap_io_poke(addr, (uint32_t)jsvGetInteger(data), wordSize); + } else if (jsvIsIterable(data)) { + JsvIterator it; + jsvIteratorNew(&it, data); + while (jsvIteratorHasElement(&it)) { + _jswrap_io_poke(addr, (uint32_t)jsvIteratorGetIntegerValue(&it), wordSize); + addr += wordSize; + jsvIteratorNext(&it); + } + jsvIteratorFree(&it); + } +} + + +/*JSON{ + "type" : "function", + "name" : "analogRead", + "generate" : "jshPinAnalog", + "params" : [ + ["pin","pin",["The pin to use","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `ADC` markers."]] + ], + "return" : ["float","The analog Value of the Pin between 0 and 1"] +} +Get the analog value of the given pin + +This is different to Arduino which only returns an integer between 0 and 1023 + +However only pins connected to an ADC will work (see the datasheet) + + **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"analog"` + */ +/*JSON{ + "type" : "function", + "name" : "analogWrite", + "generate" : "jswrap_io_analogWrite", + "params" : [ + ["pin","pin",["The pin to use","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `PWM` or `DAC` markers."]], + ["value","float","A value between 0 and 1"], + ["options","JsVar",["An object containing options for analog output - see below"]] + ] +} +Set the analog Value of a pin. It will be output using PWM. + +Objects can contain: + +* `freq` - pulse frequency in Hz, eg. ```analogWrite(A0,0.5,{ freq : 10 });``` - specifying a frequency will force PWM output, even if the pin has a DAC +* `soft` - boolean, If true software PWM is used if available. +* `forceSoft` - boolean, If true software PWM is used even + + **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` + */ +void jswrap_io_analogWrite(Pin pin, JsVarFloat value, JsVar *options) { + JsVarFloat freq = 0; + JshAnalogOutputFlags flags = JSAOF_NONE; + if (jsvIsObject(options)) { + freq = jsvGetFloatAndUnLock(jsvObjectGetChild(options, "freq", 0)); + if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "forceSoft", 0))) + flags |= JSAOF_FORCE_SOFTWARE; + else if (jsvGetBoolAndUnLock(jsvObjectGetChild(options, "soft", 0))) + flags |= JSAOF_ALLOW_SOFTWARE; + } + + jshPinAnalogOutput(pin, value, freq, flags); +} + +/*JSON{ + "type" : "function", + "name" : "digitalPulse", + "generate" : "jswrap_io_digitalPulse", + "params" : [ + ["pin","pin","The pin to use"], + ["value","bool","Whether to pulse high (true) or low (false)"], + ["time","JsVar","A time in milliseconds, or an array of times (in which case a square wave will be output starting with a pulse of 'value')"] + ] +} +Pulse the pin with the value for the given time in milliseconds. It uses a hardware timer to produce accurate pulses, and returns immediately (before the pulse has finished). Use `digitalPulse(A0,1,0)` to wait until a previous pulse has finished. + +eg. `digitalPulse(A0,1,5);` pulses A0 high for 5ms. `digitalPulse(A0,1,[5,2,4]);` pulses A0 high for 5ms, low for 2ms, and high for 4ms + + **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` + +digitalPulse is for SHORT pulses that need to be very accurate. If you're doing anything over a few milliseconds, use setTimeout instead. + */ +void jswrap_io_digitalPulse(Pin pin, bool value, JsVar *times) { + if (jsvIsNumeric(times)) { + JsVarFloat time = jsvGetFloat(times); + if (time<0 || isnan(time)) { + jsExceptionHere(JSET_ERROR, "Pulse Time given for digitalPulse is less than 0, or not a number"); + } else { + jshPinPulse(pin, value, time); + } + } else if (jsvIsIterable(times)) { + // iterable, so output a square wave + JsvIterator it; + jsvIteratorNew(&it, times); + while (jsvIteratorHasElement(&it)) { + JsVarFloat time = jsvIteratorGetFloatValue(&it); + if (time>=0 && !isnan(time)) + jshPinPulse(pin, value, time); + value = !value; + jsvIteratorNext(&it); + } + jsvIteratorFree(&it); + } else { + jsExceptionHere(JSET_ERROR, "Expecting a number or array, got %t", times); + } +} + +/*JSON{ + "type" : "function", + "name" : "digitalWrite", + "generate" : "jswrap_io_digitalWrite", + "params" : [ + ["pin", "JsVar","The pin to use"], + ["value", "int","Whether to pulse high (true) or low (false)"] + ] +} +Set the digital value of the given pin. + + **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"output"` + +If pin argument is an array of pins (eg. `[A2,A1,A0]`) the value argument will be treated +as an array of bits where the last array element is the least significant bit. + +In this case, pin values are set last significant bit first (from the right-hand side +of the array of pins). This means you can use the same pin multiple times, for +example `digitalWrite([A1,A1,A0,A0],0b0101)` would pulse A0 followed by A1. +*/ + +/** + * Set the output of a GPIO. + */ +void jswrap_io_digitalWrite( + JsVar *pinVar, //!< A pin or pins. + JsVarInt value //!< The value of the output. + ) { + // Handle the case where it is an array of pins. + if (jsvIsArray(pinVar)) { + JsVarRef pinName = jsvGetLastChild(pinVar); // NOTE: start at end and work back! + while (pinName) { + JsVar *pinNamePtr = jsvLock(pinName); + JsVar *pinPtr = jsvSkipName(pinNamePtr); + jshPinOutput(jshGetPinFromVar(pinPtr), value&1); + jsvUnLock(pinPtr); + pinName = jsvGetPrevSibling(pinNamePtr); + jsvUnLock(pinNamePtr); + value = value>>1; // next bit down + } + } + // Handle the case where it is a single pin. + else { + Pin pin = jshGetPinFromVar(pinVar); + jshPinOutput(pin, value != 0); + } +} + + +/*JSON{ + "type" : "function", + "name" : "digitalRead", + "generate" : "jswrap_io_digitalRead", + "params" : [ + ["pin","JsVar","The pin to use"] + ], + "return" : ["int","The digital Value of the Pin"] +} +Get the digital value of the given pin. + + **Note:** if you didn't call `pinMode` beforehand then this function will also reset pin's state to `"input"` + +If the pin argument is an array of pins (eg. `[A2,A1,A0]`) the value returned will be an number where +the last array element is the least significant bit, for example if `A0=A1=1` and `A2=0`, `digitalRead([A2,A1,A0]) == 0b011` +*/ + +/** + * Read the value of a GPIO pin. + */ +JsVarInt jswrap_io_digitalRead(JsVar *pinVar) { + // Hadnle the case where it is an array of pins. + if (jsvIsArray(pinVar)) { + int pins = 0; + JsVarInt value = 0; + JsvObjectIterator it; + jsvObjectIteratorNew(&it, pinVar); + while (jsvObjectIteratorHasValue(&it)) { + JsVar *pinPtr = jsvObjectIteratorGetValue(&it); + value = (value<<1) | (JsVarInt)jshPinInput(jshGetPinFromVar(pinPtr)); + jsvUnLock(pinPtr); + jsvObjectIteratorNext(&it); + pins++; + } + jsvObjectIteratorFree(&it); + if (pins==0) return 0; // return undefined if array empty + return value; + } + // Handle the case where it is a single pin. + else { + Pin pin = jshGetPinFromVar(pinVar); + return jshPinInput(pin); + } +} + +/*JSON{ + "type" : "function", + "name" : "pinMode", + "generate" : "jswrap_io_pinMode", + "params" : [ + ["pin","pin","The pin to set pin mode for"], + ["mode","JsVar","The mode - a string that is either 'analog', 'input', 'input_pullup', 'input_pulldown', 'output', 'opendrain', 'af_output' or 'af_opendrain'. Do not include this argument if you want to revert to automatic pin mode setting."] + ] +} +Set the mode of the given pin. + + * `analog` - Analog input + * `input` - Digital input + * `input_pullup` - Digital input with internal ~40k pull-up resistor + * `input_pulldown` - Digital input with internal ~40k pull-down resistor + * `output` - Digital output + * `opendrain` - Digital output that only ever pulls down to 0v. Sending a logical `1` leaves the pin open circuit + * `af_output` - Digital output from built-in peripheral + * `af_opendrain` - Digital output from built-in peripheral that only ever pulls down to 0v. + * Sending a logical `1` leaves the pin open circuit + + **Note:** `digitalRead`/`digitalWrite`/etc set the pin mode automatically *unless* `pinMode` has been called first. If you want `digitalRead`/etc to set the pin mode automatically after you have called `pinMode`, simply call it again with no mode argument: `pinMode(pin)` +*/ + +/** + * Set the mode of a pin. + */ +void jswrap_io_pinMode( + Pin pin, //!< The pin to set. + JsVar *mode //!< The new mode of the pin. + ) { + if (!jshIsPinValid(pin)) { + jsExceptionHere(JSET_ERROR, "Invalid pin"); + return; + } + JshPinState m = JSHPINSTATE_UNDEFINED; + if (jsvIsString(mode)) { + if (jsvIsStringEqual(mode, "analog")) m = JSHPINSTATE_ADC_IN; + else if (jsvIsStringEqual(mode, "input")) m = JSHPINSTATE_GPIO_IN; + else if (jsvIsStringEqual(mode, "input_pullup")) m = JSHPINSTATE_GPIO_IN_PULLUP; + else if (jsvIsStringEqual(mode, "input_pulldown")) m = JSHPINSTATE_GPIO_IN_PULLDOWN; + else if (jsvIsStringEqual(mode, "output")) m = JSHPINSTATE_GPIO_OUT; + else if (jsvIsStringEqual(mode, "opendrain")) m = JSHPINSTATE_GPIO_OUT_OPENDRAIN; + else if (jsvIsStringEqual(mode, "af_output")) m = JSHPINSTATE_AF_OUT; + else if (jsvIsStringEqual(mode, "af_opendrain")) m = JSHPINSTATE_AF_OUT_OPENDRAIN; + } + if (m != JSHPINSTATE_UNDEFINED) { + jshSetPinStateIsManual(pin, true); + jshPinSetState(pin, m); + } else { + jshSetPinStateIsManual(pin, false); + if (!jsvIsUndefined(mode)) { + jsExceptionHere(JSET_ERROR, "Unknown pin mode"); + } + } +} + +/*JSON{ + "type" : "function", + "name" : "getPinMode", + "generate" : "jswrap_io_getPinMode", + "params" : [ + ["pin","pin","The pin to check"] + ], + "return" : ["JsVar","The pin mode, as a string"] +} +Return the current mode of the given pin. See `pinMode` for more information. + */ +JsVar *jswrap_io_getPinMode(Pin pin) { + if (!jshIsPinValid(pin)) { + jsExceptionHere(JSET_ERROR, "Invalid pin"); + return 0; + } + JshPinState m = jshPinGetState(pin)&JSHPINSTATE_MASK; + const char *text = 0; + switch (m) { + case JSHPINSTATE_ADC_IN : text = "analog"; break; + case JSHPINSTATE_GPIO_IN : text = "input"; break; + case JSHPINSTATE_GPIO_IN_PULLUP : text = "input_pullup"; break; + case JSHPINSTATE_GPIO_IN_PULLDOWN : text = "input_pulldown"; break; + case JSHPINSTATE_GPIO_OUT : text = "output"; break; + case JSHPINSTATE_GPIO_OUT_OPENDRAIN : text = "opendrain"; break; + case JSHPINSTATE_AF_OUT : text = "af_output"; break; + case JSHPINSTATE_AF_OUT_OPENDRAIN : text = "af_opendrain"; break; + default: break; + } + if (text) return jsvNewFromString(text); + return 0; +} + +/*JSON{ + "type" : "function", + "name" : "setWatch", + "generate" : "jswrap_interface_setWatch", + "params" : [ + ["function","JsVar","A Function or String to be executed"], + ["pin","pin","The pin to watch"], + ["options","JsVar",["If this is a boolean or integer, it determines whether to call this once (false = default) or every time a change occurs (true)","If this is an object, it can contain the following information: ```{ repeat: true/false(default), edge:'rising'/'falling'/'both'(default), debounce:10}```. `debounce` is the time in ms to wait for bounces to subside, or 0."]] + ], + "return" : ["JsVar","An ID that can be passed to clearWatch"] +} +Call the function specified when the pin changes. Watches set with `setWatch` can be removed using `clearWatch`. + +The function may also take an argument, which is an object of type `{state:bool, time:float, lastTime:float}`. + + * `state` is whether the pin is currently a `1` or a `0` + * `time` is the time in seconds at which the pin changed state + * `lastTime` is the time in seconds at which the **pin last changed state**. When using `edge:'rising'` or `edge:'falling'`, this is not the same as when the function was last called. + +For instance, if you want to measure the length of a positive pulse you could use `setWatch(function(e) { console.log(e.time-e.lastTime); }, BTN, { repeat:true, edge:'falling' });`. +This will only be called on the falling edge of the pulse, but will be able to measure the width of the pulse because `e.lastTime` is the time of the rising edge. + +Internally, an interrupt writes the time of the pin's state change into a queue, and the function +supplied to `setWatch` is executed only from the main message loop. However, if the callback is a +native function `void (bool state)` then you can add `irq:true` to options, which will cause the +function to be called from within the IRQ. When doing this, interrupts will happen on both edges +and there will be no debouncing. + +**Note:** The STM32 chip (used in the [Espruino Board](/EspruinoBoard) and [Pico](/Pico)) cannot +watch two pins with the same number - eg `A0` and `B0`. + + */ +JsVar *jswrap_interface_setWatch(JsVar *func, Pin pin, JsVar *repeatOrObject) { + if (!jshIsPinValid(pin)) { + jsError("Invalid pin"); + return 0; + } + + if (!jsiIsWatchingPin(pin) && !jshCanWatch(pin)) { + jsWarn("Unable to set watch. You may already have a watch on a pin with the same number (eg. A0 and B0)"); + return 0; + } + + bool repeat = false; + JsVarFloat debounce = 0; + int edge = 0; + bool isIRQ = false; + if (jsvIsObject(repeatOrObject)) { + JsVar *v; + repeat = jsvGetBoolAndUnLock(jsvObjectGetChild(repeatOrObject, "repeat", 0)); + debounce = jsvGetFloatAndUnLock(jsvObjectGetChild(repeatOrObject, "debounce", 0)); + if (isnan(debounce) || debounce<0) debounce=0; + v = jsvObjectGetChild(repeatOrObject, "edge", 0); + if (jsvIsString(v)) { + if (jsvIsStringEqual(v, "rising")) edge=1; + else if (jsvIsStringEqual(v, "falling")) edge=-1; + else if (jsvIsStringEqual(v, "both")) edge=0; + else jsWarn("'edge' in setWatch should be a string - either 'rising', 'falling' or 'both'"); + } else if (!jsvIsUndefined(v)) + jsWarn("'edge' in setWatch should be a string - either 'rising', 'falling' or 'both'"); + jsvUnLock(v); + isIRQ = jsvGetBoolAndUnLock(jsvObjectGetChild(repeatOrObject, "irq", 0)); + } else + repeat = jsvGetBool(repeatOrObject); + + JsVarInt itemIndex = -1; + if (!jsvIsFunction(func) && !jsvIsString(func)) { + jsExceptionHere(JSET_ERROR, "Function or String not supplied!"); + } else { + // Create a new watch + JsVar *watchPtr = jsvNewWithFlags(JSV_OBJECT); + if (watchPtr) { + jsvObjectSetChildAndUnLock(watchPtr, "pin", jsvNewFromPin(pin)); + if (repeat) jsvObjectSetChildAndUnLock(watchPtr, "recur", jsvNewFromBool(repeat)); + if (debounce>0) jsvObjectSetChildAndUnLock(watchPtr, "debounce", jsvNewFromInteger((JsVarInt)jshGetTimeFromMilliseconds(debounce))); + if (edge) jsvObjectSetChildAndUnLock(watchPtr, "edge", jsvNewFromInteger(edge)); + jsvObjectSetChild(watchPtr, "callback", func); // no unlock intentionally + } + + // If nothing already watching the pin, set up a watch + IOEventFlags exti = EV_NONE; + if (!jsiIsWatchingPin(pin)) + exti = jshPinWatch(pin, true); + // disable event callbacks by default + if (exti) { + jshSetEventCallback(exti, 0); + if (isIRQ) { + if (jsvIsNativeFunction(func)) { + jshSetEventCallback(exti, (JshEventCallbackCallback)jsvGetNativeFunctionPtr(func)); + } else { + jsExceptionHere(JSET_ERROR, "irq=true set, but function is not a native function"); + } + } + } else { + if (isIRQ) + jsExceptionHere(JSET_ERROR, "irq=true set, but watch is already used"); + } + + + JsVar *watchArrayPtr = jsvLock(watchArray); + itemIndex = jsvArrayAddToEnd(watchArrayPtr, watchPtr, 1) - 1; + jsvUnLock2(watchArrayPtr, watchPtr); + + + } + return (itemIndex>=0) ? jsvNewFromInteger(itemIndex) : 0/*undefined*/; +} + +/*JSON{ + "type" : "function", + "name" : "clearWatch", + "generate" : "jswrap_interface_clearWatch", + "params" : [ + ["id","JsVar","The id returned by a previous call to setWatch"] + ] +} +Clear the Watch that was created with setWatch. If no parameter is supplied, all watches will be removed. + */ +void jswrap_interface_clearWatch(JsVar *idVar) { + + if (jsvIsUndefined(idVar)) { + JsVar *watchArrayPtr = jsvLock(watchArray); + JsvObjectIterator it; + jsvObjectIteratorNew(&it, watchArrayPtr); + while (jsvObjectIteratorHasValue(&it)) { + JsVar *watchPtr = jsvObjectIteratorGetValue(&it); + JsVar *watchPin = jsvObjectGetChild(watchPtr, "pin", 0); + jshPinWatch(jshGetPinFromVar(watchPin), false); + jsvUnLock2(watchPin, watchPtr); + jsvObjectIteratorNext(&it); + } + jsvObjectIteratorFree(&it); + // remove all items + jsvRemoveAllChildren(watchArrayPtr); + jsvUnLock(watchArrayPtr); + } else { + JsVar *watchArrayPtr = jsvLock(watchArray); + JsVar *watchNamePtr = jsvFindChildFromVar(watchArrayPtr, idVar, false); + jsvUnLock(watchArrayPtr); + if (watchNamePtr) { // child is a 'name' + JsVar *watchPtr = jsvSkipName(watchNamePtr); + Pin pin = jshGetPinFromVarAndUnLock(jsvObjectGetChild(watchPtr, "pin", 0)); + jsvUnLock(watchPtr); + + JsVar *watchArrayPtr = jsvLock(watchArray); + jsvRemoveChild(watchArrayPtr, watchNamePtr); + jsvUnLock2(watchNamePtr, watchArrayPtr); + + // Now check if this pin is still being watched + if (!jsiIsWatchingPin(pin)) + jshPinWatch(pin, false); // 'unwatch' pin + } else { + jsExceptionHere(JSET_ERROR, "Unknown Watch"); + } + } +} + + diff --git a/src/jswrap_spi_i2c.c b/src/jswrap_spi_i2c.c index 98beb2003..76b057ac7 100644 --- a/src/jswrap_spi_i2c.c +++ b/src/jswrap_spi_i2c.c @@ -1,628 +1,628 @@ -/* - * This file is part of Espruino, a JavaScript interpreter for Microcontrollers - * - * Copyright (C) 2013 Gordon Williams - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * ---------------------------------------------------------------------------- - * This file is designed to be parsed during the build process - * - * JavaScript SPI and I2C Functions - * ---------------------------------------------------------------------------- - */ -#include "jsspi.h" -#include "jswrap_spi_i2c.h" -#include "jsdevices.h" -#include "jsinteractive.h" -#include "jswrap_arraybuffer.h" - -/*JSON{ - "type" : "class", - "class" : "SPI" -} -This class allows use of the built-in SPI ports. Currently it is SPI master only. - */ - -/*JSON{ - "type" : "object", - "name" : "SPI1", - "instanceof" : "SPI", - "#if" : "SPI_COUNT>=1" -} -The first SPI port - */ -/*JSON{ - "type" : "object", - "name" : "SPI2", - "instanceof" : "SPI", - "#if" : "SPI_COUNT>=2" -} -The second SPI port - */ -/*JSON{ - "type" : "object", - "name" : "SPI3", - "instanceof" : "SPI", - "#if" : "SPI_COUNT>=3" -} -The third SPI port - */ - -/*JSON{ - "type" : "constructor", - "class" : "SPI", - "name" : "SPI", - "generate" : "jswrap_spi_constructor" -} -Create a software SPI port. This has limited functionality (no baud rate), but it can work on any pins. - -Use `SPI.setup` to configure this port. - */ -JsVar *jswrap_spi_constructor() { - return jsvNewWithFlags(JSV_OBJECT); -} - -/*JSON{ - "type" : "staticmethod", - "class" : "SPI", - "name" : "find", - "generate_full" : "jshGetDeviceObjectFor(JSH_SPI1, JSH_SPIMAX, pin)", - "params" : [ - ["pin","pin","A pin to search with"] - ], - "return" : ["JsVar","An object of type `SPI`, or `undefined` if one couldn't be found."] -} -Try and find an SPI hardware device that will work on this pin (eg. `SPI1`) - -May return undefined if no device can be found. -*/ - -/*JSON{ - "type" : "method", - "class" : "SPI", - "name" : "setup", - "generate" : "jswrap_spi_setup", - "params" : [ - ["options","JsVar",["An optional structure containing extra information on initialising the SPI port","Please note that baud rate is set to the nearest that can be managed - which may be -+ 50%","```{sck:pin, miso:pin, mosi:pin, baud:integer=100000, mode:integer=0, order:'msb'/'lsb'='msb' }```","If sck,miso and mosi are left out, they will automatically be chosen. However if one or more is specified then the unspecified pins will not be set up.","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `SPI` marker.","The SPI ```mode``` is between 0 and 3 - see http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Clock_polarity_and_phase","On STM32F1-based parts, you cannot mix AF and non-AF pins (SPI pins are usually grouped on the chip - and you can't mix pins from two groups). Espruino will not warn you about this."]] - ] -} -Set up this SPI port as an SPI Master. - */ -void jswrap_spi_setup( - JsVar *parent, //!< The variable that is the class instance of this function. - JsVar *options //!< The options controlling SPI. - ) { - // - // Design: The options variable is a JS Object which contains a series of settings. These - // settings are parsed by `jsspiPopulateSPIInfo` to populate a C structure of type - // `JshSPIInfo`. - // - // The options are also hung off the class instance variable in a property symbolically called - // DEVICE_OPTIONS_NAME ("_options"). - // - IOEventFlags device = jsiGetDeviceFromClass(parent); - JshSPIInfo inf; - - // Debug - // jsiConsolePrintf("jswrap_spi_setup called parent=%v, options=%v\n", parent, options); - - jsspiPopulateSPIInfo(&inf, options); - - if (DEVICE_IS_SPI(device)) { - jshSPISetup(device, &inf); -#ifdef LINUX - if (jsvIsObject(options)) { - jsvObjectSetChildAndUnLock(parent, "path", jsvObjectGetChild(options, "path", 0)); - } -#endif - } else if (device == EV_NONE) { - // software mode - at least configure pins properly - if (inf.pinSCK != PIN_UNDEFINED) - jshPinSetState(inf.pinSCK, JSHPINSTATE_GPIO_OUT); - if (inf.pinMISO != PIN_UNDEFINED) - jshPinSetState(inf.pinMISO, JSHPINSTATE_GPIO_IN); - if (inf.pinMOSI != PIN_UNDEFINED) - jshPinSetState(inf.pinMOSI, JSHPINSTATE_GPIO_OUT); - } else return; - // Set up options, so we can initialise it on startup - if (options) - jsvUnLock(jsvSetNamedChild(parent, options, DEVICE_OPTIONS_NAME)); - else - jsvRemoveNamedChild(parent, DEVICE_OPTIONS_NAME); -} - - -/*JSON{ - "type" : "method", - "class" : "SPI", - "name" : "send", - "generate" : "jswrap_spi_send", - "params" : [ - ["data","JsVar","The data to send - either an Integer, Array, String, or Object of the form `{data: ..., count:#}`"], - ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised."] - ], - "return" : ["JsVar","The data that was returned"] -} -Send data down SPI, and return the result. Sending an integer will return an integer, a String will return a String, and anything else will return a Uint8Array. - -Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. - -For maximum speeds, please pass either Strings or Typed Arrays as arguments. Note that you can even pass arrays of arrays, like `[1,[2,3,4],5]` - - */ -typedef struct { - spi_sender spiSend; //!< A function to be called to send SPI data. - spi_sender_data spiSendData; //!< Control information on the nature of the SPI interface. - int rxAmt; //!< - int txAmt; //!< - JsvArrayBufferIterator it; //!< A buffer to hold the response data from MISO -} jswrap_spi_send_data; - - -/** - * Send a single byte to the SPI device, used ad callback. - */ -void jswrap_spi_send_cb( - int c, //!< The byte to send through SPI. - jswrap_spi_send_data *data //!< Control information on how to send to SPI. - ) { - // Invoke the SPI send function to transmit the single byte. - int result = data->spiSend(c, &data->spiSendData); - if (c>=0) data->txAmt++; - if (result>=0) { - jsvArrayBufferIteratorSetByteValue(&data->it, (char)result); - jsvArrayBufferIteratorNext(&data->it); - data->rxAmt++; - } -} - - -/** - * Send data through SPI. - * The data can be in a variety of formats including: - * * `numeric` - A single byte is transmitted. - * * `string` - Each character in the string is transmitted. - * * `iterable` - An iterable object is transmitted. - * \return the Received bytes (MISO). This is byte array. - */ -JsVar *jswrap_spi_send( - JsVar *parent, //!< A description of the SPI device to send data through. - JsVar *srcdata, //!< The data to send through SPI. - Pin nss_pin //!< The pin to toggle low then high (CS) - ) { - // Debug - // jsiConsolePrintf("jswrap_spi_send called: parent=%j, srcdata=%j, nss_pin=%p\n", parent, srcdata, nss_pin); - NOT_USED(parent); - IOEventFlags device = jsiGetDeviceFromClass(parent); - - jswrap_spi_send_data data; - if (!jsspiGetSendFunction(parent, &data.spiSend, &data.spiSendData)) - return 0; - - JsVar *dst = 0; - - // we're sending and receiving - if (DEVICE_IS_SPI(device)) jshSPISetReceive(device, true); - - // assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); - - // Now that we are setup, we can send the data. - - // Handle the data being a single byte value - if (jsvIsNumeric(srcdata)) { - int r = data.spiSend((unsigned char)jsvGetInteger(srcdata), &data.spiSendData); - if (r<0) r = data.spiSend(-1, &data.spiSendData); - dst = jsvNewFromInteger(r); // retrieve the byte (no send!) - } - // Handle the data being a string - else if (jsvIsString(srcdata)) { - dst = jsvNewFromEmptyString(); - JsvStringIterator it; - jsvStringIteratorNew(&it, srcdata, 0); - int incount = 0, outcount = 0; - while (jsvStringIteratorHasChar(&it) && !jspIsInterrupted()) { - unsigned char in = (unsigned char)jsvStringIteratorGetChar(&it); - incount++; - int out = data.spiSend(in, &data.spiSendData); - if (out>=0) { - outcount++; - char outc = (char)out; - jsvAppendStringBuf(dst, (char*)&outc, 1); - } - jsvStringIteratorNext(&it); - } - jsvStringIteratorFree(&it); - // finally add the remaining bytes (no send!) - while (outcount < incount && !jspIsInterrupted()) { - outcount++; - unsigned char out = (unsigned char)data.spiSend(-1, &data.spiSendData); - jsvAppendStringBuf(dst, (char*)&out, 1); - } - } - // Handle the data being an iterable. - else { - int nBytes = jsvIterateCallbackCount(srcdata); - dst = jsvNewTypedArray(ARRAYBUFFERVIEW_UINT8, nBytes); - if (dst) { - data.rxAmt = data.txAmt = 0; - jsvArrayBufferIteratorNew(&data.it, dst, 0); - // Write data - jsvIterateCallback(srcdata, (void (*)(int, void *))jswrap_spi_send_cb, &data); - // Wait until SPI send is finished, and flush data - while (data.rxAmt < data.txAmt && !jspIsInterrupted()) - jswrap_spi_send_cb(-1, &data); - jsvArrayBufferIteratorFree(&data.it); - } - } - - // de-assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); - return dst; -} - - -/*JSON{ - "type" : "method", - "class" : "SPI", - "name" : "write", - "generate" : "jswrap_spi_write", - "params" : [ - ["data","JsVarArray",["One or more items to write. May be ints, strings, arrays, or objects of the form `{data: ..., count:#}`.","If the last argument is a pin, it is taken to be the NSS pin"]] - ] -} -Write a character or array of characters to SPI - without reading the result back. - -For maximum speeds, please pass either Strings or Typed Arrays as arguments. - */ -void jswrap_spi_write( - JsVar *parent, //!< - JsVar *args //!< - ) { - NOT_USED(parent); - IOEventFlags device = jsiGetDeviceFromClass(parent); - - spi_sender spiSend; - spi_sender_data spiSendData; - if (!jsspiGetSendFunction(parent, &spiSend, &spiSendData)) - return; - - Pin nss_pin = PIN_UNDEFINED; - // If the last value is a pin, use it as the NSS pin - JsVarInt len = jsvGetArrayLength(args); - if (len > 0) { - JsVar *last = jsvGetArrayItem(args, len-1); // look at the last value - if (jsvIsPin(last)) { - nss_pin = jshGetPinFromVar(last); - jsvUnLock(jsvArrayPop(args)); - } - jsvUnLock(last); - } - - // we're only sending (no receive) - if (DEVICE_IS_SPI(device)) jshSPISetReceive(device, false); - - // assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); - // Write data - jsvIterateCallback(args, (void (*)(int, void *))spiSend, &spiSendData); - // Wait until SPI send is finished, and flush data - if (DEVICE_IS_SPI(device)) - jshSPIWait(device); - // de-assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); -} - -/*JSON{ - "type" : "method", - "class" : "SPI", - "name" : "send4bit", - "generate" : "jswrap_spi_send4bit", - "params" : [ - ["data","JsVar","The data to send - either an integer, array, or string"], - ["bit0","int32","The 4 bits to send for a 0 (MSB first)"], - ["bit1","int32","The 4 bits to send for a 1 (MSB first)"], - ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised."] - ] -} -Send data down SPI, using 4 bits for each 'real' bit (MSB first). This can be useful for faking one-wire style protocols - -Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. - */ -void jswrap_spi_send4bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin) { - NOT_USED(parent); - IOEventFlags device = jsiGetDeviceFromClass(parent); - if (!DEVICE_IS_SPI(device)) { - jsExceptionHere(JSET_ERROR, "SPI.send4bit only works on hardware SPI"); - return; - } - - jshSPISet16(device, true); // 16 bit output - - if (bit0==0 && bit1==0) { - bit0 = 0x01; - bit1 = 0x03; - } - bit0 = bit0 & 0x0F; - bit1 = bit1 & 0x0F; - - if (!jshIsDeviceInitialised(device)) { - JshSPIInfo inf; - jshSPIInitInfo(&inf); - jshSPISetup(device, &inf); - } - - // we're just sending (no receive) - jshSPISetReceive(device, false); - // assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); - - // send data - if (jsvIsNumeric(srcdata)) { - jsspiSend4bit(device, (unsigned char)jsvGetInteger(srcdata), bit0, bit1); - } else if (jsvIsIterable(srcdata)) { - jshInterruptOff(); - JsvIterator it; - jsvIteratorNew(&it, srcdata); - while (jsvIteratorHasElement(&it)) { - unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it); - jsspiSend4bit(device, in, bit0, bit1); - jsvIteratorNext(&it); - } - jsvIteratorFree(&it); - jshInterruptOn(); - } else { - jsExceptionHere(JSET_ERROR, "Variable type %t not suited to transmit operation", srcdata); - } - - jshSPIWait(device); // wait until SPI send finished and clear the RX buffer - - // de-assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); - jshSPISet16(device, false); // back to 8 bit -} - -/*JSON{ - "type" : "method", - "class" : "SPI", - "name" : "send8bit", - "ifndef" : "SAVE_ON_FLASH", - "generate" : "jswrap_spi_send8bit", - "params" : [ - ["data","JsVar","The data to send - either an integer, array, or string"], - ["bit0","int32","The 8 bits to send for a 0 (MSB first)"], - ["bit1","int32","The 8 bits to send for a 1 (MSB first)"], - ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised"] - ] -} -Send data down SPI, using 8 bits for each 'real' bit (MSB first). This can be useful for faking one-wire style protocols - -Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. - */ -void jswrap_spi_send8bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin) { - NOT_USED(parent); - IOEventFlags device = jsiGetDeviceFromClass(parent); - if (!DEVICE_IS_SPI(device)) { - jsExceptionHere(JSET_ERROR, "SPI.send8bit only works on hardware SPI"); - return; - } - jshSPISet16(device, true); // 16 bit output - - if (bit0==0 && bit1==0) { - bit0 = 0x03; - bit1 = 0x0F; - } - bit0 = bit0 & 0xFF; - bit1 = bit1 & 0xFF; - - if (!jshIsDeviceInitialised(device)) { - JshSPIInfo inf; - jshSPIInitInfo(&inf); - jshSPISetup(device, &inf); - } - - // we're just sending (no receive) - jshSPISetReceive(device, false); - // assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); - - // send data - if (jsvIsNumeric(srcdata)) { - jsspiSend8bit(device, (unsigned char)jsvGetInteger(srcdata), bit0, bit1); - } else if (jsvIsIterable(srcdata)) { - jshInterruptOff(); - JsvIterator it; - jsvIteratorNew(&it, srcdata); - while (jsvIteratorHasElement(&it)) { - unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it); - jsspiSend8bit(device, in, bit0, bit1); - jsvIteratorNext(&it); - } - jsvIteratorFree(&it); - jshInterruptOn(); - } else { - jsExceptionHere(JSET_ERROR, "Variable type %t not suited to transmit operation", srcdata); - } - - jshSPIWait(device); // wait until SPI send finished and clear the RX buffer - - // de-assert NSS - if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); - jshSPISet16(device, false); // back to 8 bit -} - -/*JSON{ - "type" : "class", - "class" : "I2C" -} -This class allows use of the built-in I2C ports. Currently it allows I2C Master mode only. - -All addresses are in 7 bit format. If you have an 8 bit address then you need to shift it one bit to the right. - */ - -/*JSON{ - "type" : "staticmethod", - "class" : "I2C", - "name" : "find", - "generate_full" : "jshGetDeviceObjectFor(JSH_I2C1, JSH_I2CMAX, pin)", - "params" : [ - ["pin","pin","A pin to search with"] - ], - "return" : ["JsVar","An object of type `I2C`, or `undefined` if one couldn't be found."] -} -Try and find an I2C hardware device that will work on this pin (eg. `I2C1`) - -May return undefined if no device can be found. -*/ - -/*JSON{ - "type" : "object", - "name" : "I2C1", - "instanceof" : "I2C", - "#if" : "I2C_COUNT>=1" -} -The first I2C port - */ -/*JSON{ - "type" : "object", - "name" : "I2C2", - "instanceof" : "I2C", - "#if" : "I2C_COUNT>=2" -} -The second I2C port - */ -/*JSON{ - "type" : "object", - "name" : "I2C3", - "instanceof" : "I2C", - "#if" : "I2C_COUNT>=3" -} -The third I2C port - */ - - - -/*JSON{ - "type" : "method", - "class" : "I2C", - "name" : "setup", - "generate" : "jswrap_i2c_setup", - "params" : [ - ["options","JsVar",["An optional structure containing extra information on initialising the I2C port","```{scl:pin, sda:pin, bitrate:100000}```","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `I2C` marker. Note that 400000kHz is the maximum bitrate for most parts."]] - ] -} -Set up this I2C port - -If not specified in options, the default pins are used (usually the lowest numbered pins on the lowest port that supports this peripheral) - */ -void jswrap_i2c_setup(JsVar *parent, JsVar *options) { - IOEventFlags device = jsiGetDeviceFromClass(parent); - if (!DEVICE_IS_I2C(device)) return; - JshI2CInfo inf; - jshI2CInitInfo(&inf); - if (jsvIsObject(options)) { - inf.pinSCL = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "scl", 0)); - inf.pinSDA = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "sda", 0)); - JsVar *v = jsvObjectGetChild(options, "bitrate", 0); - if (v) - inf.bitrate = jsvGetIntegerAndUnLock(v); - } - jshI2CSetup(device, &inf); - // Set up options, so we can initialise it on startup - if (options) - jsvUnLock(jsvSetNamedChild(parent, options, DEVICE_OPTIONS_NAME)); - else - jsvRemoveNamedChild(parent, DEVICE_OPTIONS_NAME); -} - - -static NO_INLINE int i2c_get_address(JsVar *address, bool *sendStop) { - *sendStop = true; - if (jsvIsObject(address)) { - JsVar *stopVar = jsvObjectGetChild(address, "stop", 0); - if (stopVar) *sendStop = jsvGetBoolAndUnLock(stopVar); - return jsvGetIntegerAndUnLock(jsvObjectGetChild(address, "address", 0)); - } else - return jsvGetInteger(address); -} - - -/*JSON{ - "type" : "method", - "class" : "I2C", - "name" : "writeTo", - "generate" : "jswrap_i2c_writeTo", - "params" : [ - ["address","JsVar","The 7 bit address of the device to transmit to, or an object of the form `{address:12, stop:false}` to send this data without a STOP signal."], - ["data","JsVarArray","One or more items to write. May be ints, strings, arrays, or objects of the form `{data: ..., count:#}`."] - ] -} -Transmit to the slave device with the given address. This is like Arduino's beginTransmission, write, and endTransmission rolled up into one. - */ - -void jswrap_i2c_writeTo(JsVar *parent, JsVar *addressVar, JsVar *args) { - IOEventFlags device = jsiGetDeviceFromClass(parent); - if (!DEVICE_IS_I2C(device)) return; - - bool sendStop = true; - int address = i2c_get_address(addressVar, &sendStop); - - size_t l = (size_t)jsvIterateCallbackCount(args); - if (l+256 > jsuGetFreeStack()) { - jsExceptionHere(JSET_ERROR, "Not enough free stack to send this amount of data"); - return; - } - - unsigned char *data = (unsigned char *)alloca(l); - jsvIterateCallbackToBytes(args, data, l); - - jshI2CWrite(device, (unsigned char)address, l, data, sendStop); -} - -/*JSON{ - "type" : "method", - "class" : "I2C", - "name" : "readFrom", - "generate" : "jswrap_i2c_readFrom", - "params" : [ - ["address","JsVar","The 7 bit address of the device to request bytes from, or an object of the form `{address:12, stop:false}` to send this data without a STOP signal."], - ["quantity","int32","The number of bytes to request"] - ], - "return" : ["JsVar","The data that was returned - as a Uint8Array"], - "return_object" : "Uint8Array" -} -Request bytes from the given slave device, and return them as a Uint8Array (packed array of bytes). This is like using Arduino Wire's requestFrom, available and read functions. Sends a STOP - */ -JsVar *jswrap_i2c_readFrom(JsVar *parent, JsVar *addressVar, int nBytes) { - IOEventFlags device = jsiGetDeviceFromClass(parent); - if (!DEVICE_IS_I2C(device)) return 0; - - bool sendStop = true; - int address = i2c_get_address(addressVar, &sendStop); - - if (nBytes<=0) - return 0; - if ((unsigned int)nBytes+256 > jsuGetFreeStack()) { - jsExceptionHere(JSET_ERROR, "Not enough free stack to receive this amount of data"); - return 0; - } - unsigned char *buf = (unsigned char *)alloca((size_t)nBytes); - - jshI2CRead(device, (unsigned char)address, nBytes, buf, sendStop); - - JsVar *array = jsvNewTypedArray(ARRAYBUFFERVIEW_UINT8, nBytes); - if (array) { - JsvArrayBufferIterator it; - jsvArrayBufferIteratorNew(&it, array, 0); - unsigned int i; - for (i=0;i<(unsigned)nBytes;i++) { - jsvArrayBufferIteratorSetByteValue(&it, (char)buf[i]); - jsvArrayBufferIteratorNext(&it); - } - jsvArrayBufferIteratorFree(&it); - } - return array; -} +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * ---------------------------------------------------------------------------- + * This file is designed to be parsed during the build process + * + * JavaScript SPI and I2C Functions + * ---------------------------------------------------------------------------- + */ +#include "jsspi.h" +#include "jswrap_spi_i2c.h" +#include "jsdevices.h" +#include "jsinteractive.h" +#include "jswrap_arraybuffer.h" + +/*JSON{ + "type" : "class", + "class" : "SPI" +} +This class allows use of the built-in SPI ports. Currently it is SPI master only. + */ + +/*JSON{ + "type" : "object", + "name" : "SPI1", + "instanceof" : "SPI", + "#if" : "SPI_COUNT>=1" +} +The first SPI port + */ +/*JSON{ + "type" : "object", + "name" : "SPI2", + "instanceof" : "SPI", + "#if" : "SPI_COUNT>=2" +} +The second SPI port + */ +/*JSON{ + "type" : "object", + "name" : "SPI3", + "instanceof" : "SPI", + "#if" : "SPI_COUNT>=3" +} +The third SPI port + */ + +/*JSON{ + "type" : "constructor", + "class" : "SPI", + "name" : "SPI", + "generate" : "jswrap_spi_constructor" +} +Create a software SPI port. This has limited functionality (no baud rate), but it can work on any pins. + +Use `SPI.setup` to configure this port. + */ +JsVar *jswrap_spi_constructor() { + return jsvNewWithFlags(JSV_OBJECT); +} + +/*JSON{ + "type" : "staticmethod", + "class" : "SPI", + "name" : "find", + "generate_full" : "jshGetDeviceObjectFor(JSH_SPI1, JSH_SPIMAX, pin)", + "params" : [ + ["pin","pin","A pin to search with"] + ], + "return" : ["JsVar","An object of type `SPI`, or `undefined` if one couldn't be found."] +} +Try and find an SPI hardware device that will work on this pin (eg. `SPI1`) + +May return undefined if no device can be found. +*/ + +/*JSON{ + "type" : "method", + "class" : "SPI", + "name" : "setup", + "generate" : "jswrap_spi_setup", + "params" : [ + ["options","JsVar",["An optional structure containing extra information on initialising the SPI port","Please note that baud rate is set to the nearest that can be managed - which may be -+ 50%","```{sck:pin, miso:pin, mosi:pin, baud:integer=100000, mode:integer=0, order:'msb'/'lsb'='msb' }```","If sck,miso and mosi are left out, they will automatically be chosen. However if one or more is specified then the unspecified pins will not be set up.","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `SPI` marker.","The SPI ```mode``` is between 0 and 3 - see http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Clock_polarity_and_phase","On STM32F1-based parts, you cannot mix AF and non-AF pins (SPI pins are usually grouped on the chip - and you can't mix pins from two groups). Espruino will not warn you about this."]] + ] +} +Set up this SPI port as an SPI Master. + */ +void jswrap_spi_setup( + JsVar *parent, //!< The variable that is the class instance of this function. + JsVar *options //!< The options controlling SPI. + ) { + // + // Design: The options variable is a JS Object which contains a series of settings. These + // settings are parsed by `jsspiPopulateSPIInfo` to populate a C structure of type + // `JshSPIInfo`. + // + // The options are also hung off the class instance variable in a property symbolically called + // DEVICE_OPTIONS_NAME ("_options"). + // + IOEventFlags device = jsiGetDeviceFromClass(parent); + JshSPIInfo inf; + + // Debug + // jsiConsolePrintf("jswrap_spi_setup called parent=%v, options=%v\n", parent, options); + + jsspiPopulateSPIInfo(&inf, options); + + if (DEVICE_IS_SPI(device)) { + jshSPISetup(device, &inf); +#ifdef LINUX + if (jsvIsObject(options)) { + jsvObjectSetChildAndUnLock(parent, "path", jsvObjectGetChild(options, "path", 0)); + } +#endif + } else if (device == EV_NONE) { + // software mode - at least configure pins properly + if (inf.pinSCK != PIN_UNDEFINED) + jshPinSetState(inf.pinSCK, JSHPINSTATE_GPIO_OUT); + if (inf.pinMISO != PIN_UNDEFINED) + jshPinSetState(inf.pinMISO, JSHPINSTATE_GPIO_IN); + if (inf.pinMOSI != PIN_UNDEFINED) + jshPinSetState(inf.pinMOSI, JSHPINSTATE_GPIO_OUT); + } else return; + // Set up options, so we can initialise it on startup + if (options) + jsvUnLock(jsvSetNamedChild(parent, options, DEVICE_OPTIONS_NAME)); + else + jsvRemoveNamedChild(parent, DEVICE_OPTIONS_NAME); +} + + +/*JSON{ + "type" : "method", + "class" : "SPI", + "name" : "send", + "generate" : "jswrap_spi_send", + "params" : [ + ["data","JsVar","The data to send - either an Integer, Array, String, or Object of the form `{data: ..., count:#}`"], + ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised."] + ], + "return" : ["JsVar","The data that was returned"] +} +Send data down SPI, and return the result. Sending an integer will return an integer, a String will return a String, and anything else will return a Uint8Array. + +Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. + +For maximum speeds, please pass either Strings or Typed Arrays as arguments. Note that you can even pass arrays of arrays, like `[1,[2,3,4],5]` + + */ +typedef struct { + spi_sender spiSend; //!< A function to be called to send SPI data. + spi_sender_data spiSendData; //!< Control information on the nature of the SPI interface. + int rxAmt; //!< + int txAmt; //!< + JsvArrayBufferIterator it; //!< A buffer to hold the response data from MISO +} jswrap_spi_send_data; + + +/** + * Send a single byte to the SPI device, used ad callback. + */ +void jswrap_spi_send_cb( + int c, //!< The byte to send through SPI. + jswrap_spi_send_data *data //!< Control information on how to send to SPI. + ) { + // Invoke the SPI send function to transmit the single byte. + int result = data->spiSend(c, &data->spiSendData); + if (c>=0) data->txAmt++; + if (result>=0) { + jsvArrayBufferIteratorSetByteValue(&data->it, (char)result); + jsvArrayBufferIteratorNext(&data->it); + data->rxAmt++; + } +} + + +/** + * Send data through SPI. + * The data can be in a variety of formats including: + * * `numeric` - A single byte is transmitted. + * * `string` - Each character in the string is transmitted. + * * `iterable` - An iterable object is transmitted. + * \return the Received bytes (MISO). This is byte array. + */ +JsVar *jswrap_spi_send( + JsVar *parent, //!< A description of the SPI device to send data through. + JsVar *srcdata, //!< The data to send through SPI. + Pin nss_pin //!< The pin to toggle low then high (CS) + ) { + // Debug + // jsiConsolePrintf("jswrap_spi_send called: parent=%j, srcdata=%j, nss_pin=%p\n", parent, srcdata, nss_pin); + NOT_USED(parent); + IOEventFlags device = jsiGetDeviceFromClass(parent); + + jswrap_spi_send_data data; + if (!jsspiGetSendFunction(parent, &data.spiSend, &data.spiSendData)) + return 0; + + JsVar *dst = 0; + + // we're sending and receiving + if (DEVICE_IS_SPI(device)) jshSPISetReceive(device, true); + + // assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); + + // Now that we are setup, we can send the data. + + // Handle the data being a single byte value + if (jsvIsNumeric(srcdata)) { + int r = data.spiSend((unsigned char)jsvGetInteger(srcdata), &data.spiSendData); + if (r<0) r = data.spiSend(-1, &data.spiSendData); + dst = jsvNewFromInteger(r); // retrieve the byte (no send!) + } + // Handle the data being a string + else if (jsvIsString(srcdata)) { + dst = jsvNewFromEmptyString(); + JsvStringIterator it; + jsvStringIteratorNew(&it, srcdata, 0); + int incount = 0, outcount = 0; + while (jsvStringIteratorHasChar(&it) && !jspIsInterrupted()) { + unsigned char in = (unsigned char)jsvStringIteratorGetChar(&it); + incount++; + int out = data.spiSend(in, &data.spiSendData); + if (out>=0) { + outcount++; + char outc = (char)out; + jsvAppendStringBuf(dst, (char*)&outc, 1); + } + jsvStringIteratorNext(&it); + } + jsvStringIteratorFree(&it); + // finally add the remaining bytes (no send!) + while (outcount < incount && !jspIsInterrupted()) { + outcount++; + unsigned char out = (unsigned char)data.spiSend(-1, &data.spiSendData); + jsvAppendStringBuf(dst, (char*)&out, 1); + } + } + // Handle the data being an iterable. + else { + int nBytes = jsvIterateCallbackCount(srcdata); + dst = jsvNewTypedArray(ARRAYBUFFERVIEW_UINT8, nBytes); + if (dst) { + data.rxAmt = data.txAmt = 0; + jsvArrayBufferIteratorNew(&data.it, dst, 0); + // Write data + jsvIterateCallback(srcdata, (void (*)(int, void *))jswrap_spi_send_cb, &data); + // Wait until SPI send is finished, and flush data + while (data.rxAmt < data.txAmt && !jspIsInterrupted()) + jswrap_spi_send_cb(-1, &data); + jsvArrayBufferIteratorFree(&data.it); + } + } + + // de-assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); + return dst; +} + + +/*JSON{ + "type" : "method", + "class" : "SPI", + "name" : "write", + "generate" : "jswrap_spi_write", + "params" : [ + ["data","JsVarArray",["One or more items to write. May be ints, strings, arrays, or objects of the form `{data: ..., count:#}`.","If the last argument is a pin, it is taken to be the NSS pin"]] + ] +} +Write a character or array of characters to SPI - without reading the result back. + +For maximum speeds, please pass either Strings or Typed Arrays as arguments. + */ +void jswrap_spi_write( + JsVar *parent, //!< + JsVar *args //!< + ) { + NOT_USED(parent); + IOEventFlags device = jsiGetDeviceFromClass(parent); + + spi_sender spiSend; + spi_sender_data spiSendData; + if (!jsspiGetSendFunction(parent, &spiSend, &spiSendData)) + return; + + Pin nss_pin = PIN_UNDEFINED; + // If the last value is a pin, use it as the NSS pin + JsVarInt len = jsvGetArrayLength(args); + if (len > 0) { + JsVar *last = jsvGetArrayItem(args, len-1); // look at the last value + if (jsvIsPin(last)) { + nss_pin = jshGetPinFromVar(last); + jsvUnLock(jsvArrayPop(args)); + } + jsvUnLock(last); + } + + // we're only sending (no receive) + if (DEVICE_IS_SPI(device)) jshSPISetReceive(device, false); + + // assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); + // Write data + jsvIterateCallback(args, (void (*)(int, void *))spiSend, &spiSendData); + // Wait until SPI send is finished, and flush data + if (DEVICE_IS_SPI(device)) + jshSPIWait(device); + // de-assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); +} + +/*JSON{ + "type" : "method", + "class" : "SPI", + "name" : "send4bit", + "generate" : "jswrap_spi_send4bit", + "params" : [ + ["data","JsVar","The data to send - either an integer, array, or string"], + ["bit0","int32","The 4 bits to send for a 0 (MSB first)"], + ["bit1","int32","The 4 bits to send for a 1 (MSB first)"], + ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised."] + ] +} +Send data down SPI, using 4 bits for each 'real' bit (MSB first). This can be useful for faking one-wire style protocols + +Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. + */ +void jswrap_spi_send4bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin) { + NOT_USED(parent); + IOEventFlags device = jsiGetDeviceFromClass(parent); + if (!DEVICE_IS_SPI(device)) { + jsExceptionHere(JSET_ERROR, "SPI.send4bit only works on hardware SPI"); + return; + } + + jshSPISet16(device, true); // 16 bit output + + if (bit0==0 && bit1==0) { + bit0 = 0x01; + bit1 = 0x03; + } + bit0 = bit0 & 0x0F; + bit1 = bit1 & 0x0F; + + if (!jshIsDeviceInitialised(device)) { + JshSPIInfo inf; + jshSPIInitInfo(&inf); + jshSPISetup(device, &inf); + } + + // we're just sending (no receive) + jshSPISetReceive(device, false); + // assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); + + // send data + if (jsvIsNumeric(srcdata)) { + jsspiSend4bit(device, (unsigned char)jsvGetInteger(srcdata), bit0, bit1); + } else if (jsvIsIterable(srcdata)) { + jshInterruptOff(); + JsvIterator it; + jsvIteratorNew(&it, srcdata); + while (jsvIteratorHasElement(&it)) { + unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it); + jsspiSend4bit(device, in, bit0, bit1); + jsvIteratorNext(&it); + } + jsvIteratorFree(&it); + jshInterruptOn(); + } else { + jsExceptionHere(JSET_ERROR, "Variable type %t not suited to transmit operation", srcdata); + } + + jshSPIWait(device); // wait until SPI send finished and clear the RX buffer + + // de-assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); + jshSPISet16(device, false); // back to 8 bit +} + +/*JSON{ + "type" : "method", + "class" : "SPI", + "name" : "send8bit", + "ifndef" : "SAVE_ON_FLASH", + "generate" : "jswrap_spi_send8bit", + "params" : [ + ["data","JsVar","The data to send - either an integer, array, or string"], + ["bit0","int32","The 8 bits to send for a 0 (MSB first)"], + ["bit1","int32","The 8 bits to send for a 1 (MSB first)"], + ["nss_pin","pin","An nSS pin - this will be lowered before SPI output and raised afterwards (optional). There will be a small delay between when this is lowered and when sending starts, and also between sending finishing and it being raised"] + ] +} +Send data down SPI, using 8 bits for each 'real' bit (MSB first). This can be useful for faking one-wire style protocols + +Sending multiple bytes in one call to send is preferable as they can then be transmitted end to end. Using multiple calls to send() will result in significantly slower transmission speeds. + */ +void jswrap_spi_send8bit(JsVar *parent, JsVar *srcdata, int bit0, int bit1, Pin nss_pin) { + NOT_USED(parent); + IOEventFlags device = jsiGetDeviceFromClass(parent); + if (!DEVICE_IS_SPI(device)) { + jsExceptionHere(JSET_ERROR, "SPI.send8bit only works on hardware SPI"); + return; + } + jshSPISet16(device, true); // 16 bit output + + if (bit0==0 && bit1==0) { + bit0 = 0x03; + bit1 = 0x0F; + } + bit0 = bit0 & 0xFF; + bit1 = bit1 & 0xFF; + + if (!jshIsDeviceInitialised(device)) { + JshSPIInfo inf; + jshSPIInitInfo(&inf); + jshSPISetup(device, &inf); + } + + // we're just sending (no receive) + jshSPISetReceive(device, false); + // assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, false); + + // send data + if (jsvIsNumeric(srcdata)) { + jsspiSend8bit(device, (unsigned char)jsvGetInteger(srcdata), bit0, bit1); + } else if (jsvIsIterable(srcdata)) { + jshInterruptOff(); + JsvIterator it; + jsvIteratorNew(&it, srcdata); + while (jsvIteratorHasElement(&it)) { + unsigned char in = (unsigned char)jsvIteratorGetIntegerValue(&it); + jsspiSend8bit(device, in, bit0, bit1); + jsvIteratorNext(&it); + } + jsvIteratorFree(&it); + jshInterruptOn(); + } else { + jsExceptionHere(JSET_ERROR, "Variable type %t not suited to transmit operation", srcdata); + } + + jshSPIWait(device); // wait until SPI send finished and clear the RX buffer + + // de-assert NSS + if (nss_pin!=PIN_UNDEFINED) jshPinOutput(nss_pin, true); + jshSPISet16(device, false); // back to 8 bit +} + +/*JSON{ + "type" : "class", + "class" : "I2C" +} +This class allows use of the built-in I2C ports. Currently it allows I2C Master mode only. + +All addresses are in 7 bit format. If you have an 8 bit address then you need to shift it one bit to the right. + */ + +/*JSON{ + "type" : "staticmethod", + "class" : "I2C", + "name" : "find", + "generate_full" : "jshGetDeviceObjectFor(JSH_I2C1, JSH_I2CMAX, pin)", + "params" : [ + ["pin","pin","A pin to search with"] + ], + "return" : ["JsVar","An object of type `I2C`, or `undefined` if one couldn't be found."] +} +Try and find an I2C hardware device that will work on this pin (eg. `I2C1`) + +May return undefined if no device can be found. +*/ + +/*JSON{ + "type" : "object", + "name" : "I2C1", + "instanceof" : "I2C", + "#if" : "I2C_COUNT>=1" +} +The first I2C port + */ +/*JSON{ + "type" : "object", + "name" : "I2C2", + "instanceof" : "I2C", + "#if" : "I2C_COUNT>=2" +} +The second I2C port + */ +/*JSON{ + "type" : "object", + "name" : "I2C3", + "instanceof" : "I2C", + "#if" : "I2C_COUNT>=3" +} +The third I2C port + */ + + + +/*JSON{ + "type" : "method", + "class" : "I2C", + "name" : "setup", + "generate" : "jswrap_i2c_setup", + "params" : [ + ["options","JsVar",["An optional structure containing extra information on initialising the I2C port","```{scl:pin, sda:pin, bitrate:100000}```","You can find out which pins to use by looking at [your board's reference page](#boards) and searching for pins with the `I2C` marker. Note that 400000kHz is the maximum bitrate for most parts."]] + ] +} +Set up this I2C port + +If not specified in options, the default pins are used (usually the lowest numbered pins on the lowest port that supports this peripheral) + */ +void jswrap_i2c_setup(JsVar *parent, JsVar *options) { + IOEventFlags device = jsiGetDeviceFromClass(parent); + if (!DEVICE_IS_I2C(device)) return; + JshI2CInfo inf; + jshI2CInitInfo(&inf); + if (jsvIsObject(options)) { + inf.pinSCL = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "scl", 0)); + inf.pinSDA = jshGetPinFromVarAndUnLock(jsvObjectGetChild(options, "sda", 0)); + JsVar *v = jsvObjectGetChild(options, "bitrate", 0); + if (v) + inf.bitrate = jsvGetIntegerAndUnLock(v); + } + jshI2CSetup(device, &inf); + // Set up options, so we can initialise it on startup + if (options) + jsvUnLock(jsvSetNamedChild(parent, options, DEVICE_OPTIONS_NAME)); + else + jsvRemoveNamedChild(parent, DEVICE_OPTIONS_NAME); +} + + +static NO_INLINE int i2c_get_address(JsVar *address, bool *sendStop) { + *sendStop = true; + if (jsvIsObject(address)) { + JsVar *stopVar = jsvObjectGetChild(address, "stop", 0); + if (stopVar) *sendStop = jsvGetBoolAndUnLock(stopVar); + return jsvGetIntegerAndUnLock(jsvObjectGetChild(address, "address", 0)); + } else + return jsvGetInteger(address); +} + + +/*JSON{ + "type" : "method", + "class" : "I2C", + "name" : "writeTo", + "generate" : "jswrap_i2c_writeTo", + "params" : [ + ["address","JsVar","The 7 bit address of the device to transmit to, or an object of the form `{address:12, stop:false}` to send this data without a STOP signal."], + ["data","JsVarArray","One or more items to write. May be ints, strings, arrays, or objects of the form `{data: ..., count:#}`."] + ] +} +Transmit to the slave device with the given address. This is like Arduino's beginTransmission, write, and endTransmission rolled up into one. + */ + +void jswrap_i2c_writeTo(JsVar *parent, JsVar *addressVar, JsVar *args) { + IOEventFlags device = jsiGetDeviceFromClass(parent); + if (!DEVICE_IS_I2C(device)) return; + + bool sendStop = true; + int address = i2c_get_address(addressVar, &sendStop); + + size_t l = (size_t)jsvIterateCallbackCount(args); + if (l+256 > jsuGetFreeStack()) { + jsExceptionHere(JSET_ERROR, "Not enough free stack to send this amount of data"); + return; + } + + unsigned char *data = (unsigned char *)alloca(l); + jsvIterateCallbackToBytes(args, data, l); + + jshI2CWrite(device, (unsigned char)address, l, data, sendStop); +} + +/*JSON{ + "type" : "method", + "class" : "I2C", + "name" : "readFrom", + "generate" : "jswrap_i2c_readFrom", + "params" : [ + ["address","JsVar","The 7 bit address of the device to request bytes from, or an object of the form `{address:12, stop:false}` to send this data without a STOP signal."], + ["quantity","int32","The number of bytes to request"] + ], + "return" : ["JsVar","The data that was returned - as a Uint8Array"], + "return_object" : "Uint8Array" +} +Request bytes from the given slave device, and return them as a Uint8Array (packed array of bytes). This is like using Arduino Wire's requestFrom, available and read functions. Sends a STOP + */ +JsVar *jswrap_i2c_readFrom(JsVar *parent, JsVar *addressVar, int nBytes) { + IOEventFlags device = jsiGetDeviceFromClass(parent); + if (!DEVICE_IS_I2C(device)) return 0; + + bool sendStop = true; + int address = i2c_get_address(addressVar, &sendStop); + + if (nBytes<=0) + return 0; + if ((unsigned int)nBytes+256 > jsuGetFreeStack()) { + jsExceptionHere(JSET_ERROR, "Not enough free stack to receive this amount of data"); + return 0; + } + unsigned char *buf = (unsigned char *)alloca((size_t)nBytes); + + jshI2CRead(device, (unsigned char)address, nBytes, buf, sendStop); + + JsVar *array = jsvNewTypedArray(ARRAYBUFFERVIEW_UINT8, nBytes); + if (array) { + JsvArrayBufferIterator it; + jsvArrayBufferIteratorNew(&it, array, 0); + unsigned int i; + for (i=0;i<(unsigned)nBytes;i++) { + jsvArrayBufferIteratorSetByteValue(&it, (char)buf[i]); + jsvArrayBufferIteratorNext(&it); + } + jsvArrayBufferIteratorFree(&it); + } + return array; +} diff --git a/targets/esp8266/user_main.c b/targets/esp8266/user_main.c index d1b736daf..2ab983ae7 100644 --- a/targets/esp8266/user_main.c +++ b/targets/esp8266/user_main.c @@ -1,271 +1,271 @@ -/* - * This file is part of Espruino/ESP8266, a JavaScript interpreter for ESP8266 - * - * - * 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/. - */ - -#include -#include -#include -#include -#include - -//#define FAKE_STDLIB -#define _GCC_WRAP_STDINT_H -typedef long long int64_t; - -#include -#include -#include -#include "ESP8266_board.h" - -// --- Constants -// The size of the task queue -#define TASK_QUEUE_LENGTH 10 - -// Should we introduce a ticker to say we are still alive? -#define EPS8266_BOARD_HEARTBEAT - -// --- Forward definitions -static void mainLoop(); - -// --- File local variables - -// The task queue for the app -static os_event_t taskAppQueue[TASK_QUEUE_LENGTH]; - -// Flag indicating whether or not main loop processing is suspended. -static bool suspendMainLoopFlag = false; - -// Time structure for main loop time suspension. -static os_timer_t mainLoopSuspendTimer; - -// --- Functions - -#if 0 -/** - * A callback function to be invoked when a line has been entered on the telnet client. - * Here we want to pass that line to the JS parser for processing. - */ -static void telnetLineCB(char *line) { - jsiConsolePrintf("LineCB: %s", line); - // Pass the line to the interactive module ... - - jshPushIOCharEvents(jsiGetConsoleDevice(), line, strlen(line)); - //jspEvaluate(line, true); - //jsiDumpState(); - telnet_send("JS> "); -} // End of lineCB - - -/** - * When we have been allocated a TCP/IP address, this function is called back. Obviously - * there is little we can do at the network level until we have an IP. - */ -static void gotIpCallback() { - telnet_startListening(telnetLineCB); -} // End of gotIpCallback -#endif - -static char *rst_codes[] = { - "power on", "wdt reset", "exception", "soft wdt", "restart", "deep sleep", "reset pin", -}; -static char *flash_maps[] = { - "512KB:256/256", "256KB", "1MB:512/512", "2MB:512/512", "4MB:512/512", - "2MB:1024/1024", "4MB:1024/1024" -}; - -/** - * Dump the ESP8266 restart information. - * This is purely for debugging. - * When an ESP8266 crashes, before it ends, it records its exception information. - * This function retrieves that data and logs it. - */ -static void dumpRestart() { - struct rst_info *rstInfo = system_get_rst_info(); - os_printf("Restart info:\n"); - os_printf(" reason: %d=%s\n", rstInfo->reason, rst_codes[rstInfo->reason]); - os_printf(" exccause: %x\n", rstInfo->exccause); - os_printf(" epc1: %x\n", rstInfo->epc1); - os_printf(" epc2: %x\n", rstInfo->epc2); - os_printf(" epc3: %x\n", rstInfo->epc3); - os_printf(" excvaddr: %x\n", rstInfo->excvaddr); - os_printf(" depc: %x\n", rstInfo->depc); - - uint32_t fid = spi_flash_get_id(); - os_printf("Flash map %s, manuf 0x%02lX chip 0x%04lX\n", flash_maps[system_get_flash_size_map()], - fid & 0xff, (fid&0xff00)|((fid>>16)&0xff)); -} - - -/** - * Queue a task for the main loop. - */ -static void queueTaskMainLoop() { - system_os_post(TASK_APP_QUEUE, TASK_APP_MAINLOOP, 0); -} - - -/** - * Suspend processing the main loop for a period of time. - */ -void suspendMainLoop( - uint32 interval //!< suspension interval in milliseconds - ) { - suspendMainLoopFlag = true; - os_timer_arm(&mainLoopSuspendTimer, interval, 0 /* No repeat */); -} - - -/** - * Enable main loop processing. - */ -static void enableMainLoop() { - suspendMainLoopFlag = false; - queueTaskMainLoop(); -} - -/** - * Idle callback from the SDK, triggers an idle loop iteration - */ -static void idle(void) { - // The idle callback comes form the SDK's ets_run function just before it puts the - // processor to sleep waiting for an interrupt to occur. I.e. it's really a - // "I am about to idle the processor" interrupt not a persistent "I am idle" - // callback that comes over and over. - // We thus have to use this callback to trigger something so it doesn't in fact go - // idle. - system_os_post(TASK_APP_QUEUE, TASK_APP_MAINLOOP, 0); -} - - -/** - * The event handler for ESP8266 tasks as created by system_os_post() on the TASK_APP_QUEUE. - */ -static void eventHandler( - os_event_t *pEvent //!< - ) { - - switch (pEvent->sig) { - // Handle the main loop event. - case TASK_APP_MAINLOOP: - mainLoop(); - break; - // Handle the event to process received data. - case TASK_APP_RX_DATA: - { - // Get the data from the UART RX buffer. If the size of the returned data is - // not zero, then push it onto the Espruino processing queue for characters. - char pBuffer[100]; - int size = getRXBuffer(pBuffer, sizeof(pBuffer)); - if (size > 0) { - jshPushIOCharEvents(jsiGetConsoleDevice(), pBuffer, size); - } - } - break; - // Handle the unknown event type. - default: - os_printf("user_main: eventHandler: Unknown task type: %d", - pEvent->sig); - break; - } -} - - -static uint32 lastTime = 0; - -/** - * Perform the main loop processing. - * This is where work is performed - * as often as possible. - */ -static void mainLoop() { - if (suspendMainLoopFlag == true) { - return; - } - jsiLoop(); - -#ifdef EPS8266_BOARD_HEARTBEAT - if (system_get_time() - lastTime > 1000 * 1000 * 5) { - lastTime = system_get_time(); - os_printf("tick: %ld, heap: %ld\n", - (uint32)(jshGetSystemTime()), system_get_free_heap_size()); - } -#endif - - // Setup for another callback - //queueTaskMainLoop(); - suspendMainLoop(0); // HACK to get around SDK 1.4 bug -} - - -/** - * The ESP8266 provides a mechanism to register a callback that is invoked when initialization - * of the ESP8266 is complete. This is the implementation of that callback. At this point - * we can assume that the ESP8266 is fully ready to do work for us. - */ -static void initDone() { - os_printf("initDone invoked\n"); - - // Discard any junk data in the input as this is a boot. - //uart_rx_discard(); - - jshInit(); // Initialize the hardware - jsvInit(); // Initialize the variables - jsiInit(true); // Initialize the interactive subsystem - - // Register the event handlers. - system_os_task(eventHandler, TASK_APP_QUEUE, taskAppQueue, TASK_QUEUE_LENGTH); - - // At this point, our JavaScript environment should be up and running. - - // Initialize the networking subsystem. - jswrap_ESP8266WiFi_init(); - - // Register the idle callback handler to run the main loop - //ets_set_idle_cb(idle_cb, NULL); // - queueTaskMainLoop(); // get things going without idle callback - - return; -} - - -/** - * This is a required function needed for ESP8266 SDK. It allows RF parameters, in particular - * whether to calibrate the RF, to be set before the SDK does the calibration, which happens - * before user_init() is called. - */ -void user_rf_pre_init() { - os_printf("Time sys=%lu rtc=%lu\n", system_get_time(), system_get_rtc_time()); -} - - -/** - * The main entry point in an ESP8266 application. - * It is where the logic of ESP8266 starts. - */ -void user_init() { - system_timer_reinit(); // use microsecond os_timer_* - // Initialize the UART devices - uart_init(BIT_RATE_115200, BIT_RATE_115200); - os_delay_us(10000); // give the uart a break - UART_SetPrintPort(1); - system_set_os_print(1); - - // Dump the restart exception information. - dumpRestart(); - os_printf("Heap: %d\n", system_get_free_heap_size()); - os_printf("Variables: %d @%dea = %ldbytes\n", JSVAR_CACHE_SIZE, sizeof(JsVar), - JSVAR_CACHE_SIZE * sizeof(JsVar)); - os_printf("Time sys=%lu rtc=%lu\n", system_get_time(), system_get_rtc_time()); - - // Register the ESP8266 initialization callback. - system_init_done_cb(initDone); - - // Do NOT attempt to auto connect to an access point. - //wifi_station_set_auto_connect(0); - os_timer_setfn(&mainLoopSuspendTimer, enableMainLoop, NULL); -} +/* + * This file is part of Espruino/ESP8266, a JavaScript interpreter for ESP8266 + * + * + * 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/. + */ + +#include +#include +#include +#include +#include + +//#define FAKE_STDLIB +#define _GCC_WRAP_STDINT_H +typedef long long int64_t; + +#include +#include +#include +#include "ESP8266_board.h" + +// --- Constants +// The size of the task queue +#define TASK_QUEUE_LENGTH 10 + +// Should we introduce a ticker to say we are still alive? +#define EPS8266_BOARD_HEARTBEAT + +// --- Forward definitions +static void mainLoop(); + +// --- File local variables + +// The task queue for the app +static os_event_t taskAppQueue[TASK_QUEUE_LENGTH]; + +// Flag indicating whether or not main loop processing is suspended. +static bool suspendMainLoopFlag = false; + +// Time structure for main loop time suspension. +static os_timer_t mainLoopSuspendTimer; + +// --- Functions + +#if 0 +/** + * A callback function to be invoked when a line has been entered on the telnet client. + * Here we want to pass that line to the JS parser for processing. + */ +static void telnetLineCB(char *line) { + jsiConsolePrintf("LineCB: %s", line); + // Pass the line to the interactive module ... + + jshPushIOCharEvents(jsiGetConsoleDevice(), line, strlen(line)); + //jspEvaluate(line, true); + //jsiDumpState(); + telnet_send("JS> "); +} // End of lineCB + + +/** + * When we have been allocated a TCP/IP address, this function is called back. Obviously + * there is little we can do at the network level until we have an IP. + */ +static void gotIpCallback() { + telnet_startListening(telnetLineCB); +} // End of gotIpCallback +#endif + +static char *rst_codes[] = { + "power on", "wdt reset", "exception", "soft wdt", "restart", "deep sleep", "reset pin", +}; +static char *flash_maps[] = { + "512KB:256/256", "256KB", "1MB:512/512", "2MB:512/512", "4MB:512/512", + "2MB:1024/1024", "4MB:1024/1024" +}; + +/** + * Dump the ESP8266 restart information. + * This is purely for debugging. + * When an ESP8266 crashes, before it ends, it records its exception information. + * This function retrieves that data and logs it. + */ +static void dumpRestart() { + struct rst_info *rstInfo = system_get_rst_info(); + os_printf("Restart info:\n"); + os_printf(" reason: %d=%s\n", rstInfo->reason, rst_codes[rstInfo->reason]); + os_printf(" exccause: %x\n", rstInfo->exccause); + os_printf(" epc1: %x\n", rstInfo->epc1); + os_printf(" epc2: %x\n", rstInfo->epc2); + os_printf(" epc3: %x\n", rstInfo->epc3); + os_printf(" excvaddr: %x\n", rstInfo->excvaddr); + os_printf(" depc: %x\n", rstInfo->depc); + + uint32_t fid = spi_flash_get_id(); + os_printf("Flash map %s, manuf 0x%02lX chip 0x%04lX\n", flash_maps[system_get_flash_size_map()], + fid & 0xff, (fid&0xff00)|((fid>>16)&0xff)); +} + + +/** + * Queue a task for the main loop. + */ +static void queueTaskMainLoop() { + system_os_post(TASK_APP_QUEUE, TASK_APP_MAINLOOP, 0); +} + + +/** + * Suspend processing the main loop for a period of time. + */ +void suspendMainLoop( + uint32 interval //!< suspension interval in milliseconds + ) { + suspendMainLoopFlag = true; + os_timer_arm(&mainLoopSuspendTimer, interval, 0 /* No repeat */); +} + + +/** + * Enable main loop processing. + */ +static void enableMainLoop() { + suspendMainLoopFlag = false; + queueTaskMainLoop(); +} + +/** + * Idle callback from the SDK, triggers an idle loop iteration + */ +static void idle(void) { + // The idle callback comes form the SDK's ets_run function just before it puts the + // processor to sleep waiting for an interrupt to occur. I.e. it's really a + // "I am about to idle the processor" interrupt not a persistent "I am idle" + // callback that comes over and over. + // We thus have to use this callback to trigger something so it doesn't in fact go + // idle. + system_os_post(TASK_APP_QUEUE, TASK_APP_MAINLOOP, 0); +} + + +/** + * The event handler for ESP8266 tasks as created by system_os_post() on the TASK_APP_QUEUE. + */ +static void eventHandler( + os_event_t *pEvent //!< + ) { + + switch (pEvent->sig) { + // Handle the main loop event. + case TASK_APP_MAINLOOP: + mainLoop(); + break; + // Handle the event to process received data. + case TASK_APP_RX_DATA: + { + // Get the data from the UART RX buffer. If the size of the returned data is + // not zero, then push it onto the Espruino processing queue for characters. + char pBuffer[100]; + int size = getRXBuffer(pBuffer, sizeof(pBuffer)); + if (size > 0) { + jshPushIOCharEvents(jsiGetConsoleDevice(), pBuffer, size); + } + } + break; + // Handle the unknown event type. + default: + os_printf("user_main: eventHandler: Unknown task type: %d", + pEvent->sig); + break; + } +} + + +static uint32 lastTime = 0; + +/** + * Perform the main loop processing. + * This is where work is performed + * as often as possible. + */ +static void mainLoop() { + if (suspendMainLoopFlag == true) { + return; + } + jsiLoop(); + +#ifdef EPS8266_BOARD_HEARTBEAT + if (system_get_time() - lastTime > 1000 * 1000 * 5) { + lastTime = system_get_time(); + os_printf("tick: %ld, heap: %ld\n", + (uint32)(jshGetSystemTime()), system_get_free_heap_size()); + } +#endif + + // Setup for another callback + //queueTaskMainLoop(); + suspendMainLoop(0); // HACK to get around SDK 1.4 bug +} + + +/** + * The ESP8266 provides a mechanism to register a callback that is invoked when initialization + * of the ESP8266 is complete. This is the implementation of that callback. At this point + * we can assume that the ESP8266 is fully ready to do work for us. + */ +static void initDone() { + os_printf("initDone invoked\n"); + + // Discard any junk data in the input as this is a boot. + //uart_rx_discard(); + + jshInit(); // Initialize the hardware + jsvInit(); // Initialize the variables + jsiInit(true); // Initialize the interactive subsystem + + // Register the event handlers. + system_os_task(eventHandler, TASK_APP_QUEUE, taskAppQueue, TASK_QUEUE_LENGTH); + + // At this point, our JavaScript environment should be up and running. + + // Initialize the networking subsystem. + jswrap_ESP8266WiFi_init(); + + // Register the idle callback handler to run the main loop + //ets_set_idle_cb(idle_cb, NULL); // + queueTaskMainLoop(); // get things going without idle callback + + return; +} + + +/** + * This is a required function needed for ESP8266 SDK. It allows RF parameters, in particular + * whether to calibrate the RF, to be set before the SDK does the calibration, which happens + * before user_init() is called. + */ +void user_rf_pre_init() { + os_printf("Time sys=%lu rtc=%lu\n", system_get_time(), system_get_rtc_time()); +} + + +/** + * The main entry point in an ESP8266 application. + * It is where the logic of ESP8266 starts. + */ +void user_init() { + system_timer_reinit(); // use microsecond os_timer_* + // Initialize the UART devices + uart_init(BIT_RATE_115200, BIT_RATE_115200); + os_delay_us(10000); // give the uart a break + UART_SetPrintPort(1); + system_set_os_print(1); + + // Dump the restart exception information. + dumpRestart(); + os_printf("Heap: %d\n", system_get_free_heap_size()); + os_printf("Variables: %d @%dea = %ldbytes\n", JSVAR_CACHE_SIZE, sizeof(JsVar), + JSVAR_CACHE_SIZE * sizeof(JsVar)); + os_printf("Time sys=%lu rtc=%lu\n", system_get_time(), system_get_rtc_time()); + + // Register the ESP8266 initialization callback. + system_init_done_cb(initDone); + + // Do NOT attempt to auto connect to an access point. + //wifi_station_set_auto_connect(0); + os_timer_setfn(&mainLoopSuspendTimer, enableMainLoop, NULL); +} From 1ae02cdcf647fd6a1ae51a982a2fe671aab43591 Mon Sep 17 00:00:00 2001 From: Neil Kolban Date: Mon, 5 Oct 2015 23:52:12 -0500 Subject: [PATCH 10/10] Unix EOL marker. Issue #600. --- libs/network/network.c | 462 ++++++++++++++++++++--------------------- 1 file changed, 231 insertions(+), 231 deletions(-) diff --git a/libs/network/network.c b/libs/network/network.c index 296bede5e..c51de50e0 100644 --- a/libs/network/network.c +++ b/libs/network/network.c @@ -1,231 +1,231 @@ -/* - * This file is part of Espruino, a JavaScript interpreter for Microcontrollers - * - * Copyright (C) 2013 Gordon Williams - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * ---------------------------------------------------------------------------- - * Contains functions for handling JsNetwork and doing common networking tasks - * ---------------------------------------------------------------------------- - */ -#include "network.h" -#include "jsparse.h" - -#if defined(USE_CC3000) - #include "network_cc3000.h" -#endif -#if defined(USE_WIZNET) - #include "network_wiznet.h" -#endif -#if defined(USE_ESP8266) - #include "network_esp8266.h" -#endif -#if defined(LINUX) - #include "network_linux.h" -#endif -#include "network_js.h" - -JsNetworkState networkState = -#ifdef LINUX - NETWORKSTATE_ONLINE -#else - NETWORKSTATE_OFFLINE -#endif - ; - -JsNetwork *networkCurrentStruct = 0; - -uint32_t networkParseIPAddress(const char *ip) { - int n = 0; - uint32_t addr = 0; - while (*ip) { - if (*ip>='0' && *ip<='9') { - n = n*10 + (*ip-'0'); - } else if (*ip=='.') { - addr = (addr>>8) | (uint32_t)(n<<24); - n=0; - } else { - return 0; // not an ip address - } - ip++; - } - addr = (addr>>8) | (uint32_t)(n<<24); - return addr; -} - -/* given 6 pairs of 8 bit hex numbers separated by ':', parse them into a - * 6 byte array. returns false on failure */ -bool networkParseMACAddress(unsigned char *addr, const char *ip) { - int n = 0; - int i = 0; - while (*ip) { - int v = chtod(*ip); - if (v>=0 && v<16) { - n = n*16 + v; - } else if (*ip==':') { - addr[i++] = (unsigned char)n; - n=0; - if (i>5) return false; // too many items! - } else { - return false; // not a mac address - } - ip++; - } - addr[i] = (unsigned char)n; - return i==5; -} - -JsVar *networkGetAddressAsString(unsigned char *ip, int nBytes, unsigned int base, char separator) { - char data[64] = ""; - int i = 0, dir = 1, l = 0; - if (nBytes<0) { - i = (-nBytes)-1; - nBytes = -1; - dir=-1; - } - for (;i!=nBytes;i+=dir) { - if (base==16) { - data[l++] = itoch(ip[i]>>4); - data[l++] = itoch(ip[i]&15); - } else { - itostr((int)ip[i], &data[l], base); - } - l = (int)strlen(data); - if (i+dir!=nBytes && separator) { - data[l++] = separator; - data[l] = 0; - } - } - - return jsvNewFromString(data); -} - -void networkPutAddressAsString(JsVar *object, const char *name, unsigned char *ip, int nBytes, unsigned int base, char separator) { - jsvObjectSetChildAndUnLock(object, name, networkGetAddressAsString(ip, nBytes, base, separator)); -} - -/** Some devices (CC3000) store the IP address with the first element last, so we must flip it */ -unsigned long networkFlipIPAddress(unsigned long addr) { - return - ((addr&0xFF)<<24) | - ((addr&0xFF00)<<8) | - ((addr&0xFF0000)>>8) | - ((addr&0xFF000000)>>24); -} - -/** - * Get the IP address of a hostname. - * Retrieve the IP address of a hostname and return it in the address of the - * ip address passed in. If the hostname is as dotted decimal string, we will - * decode that immediately otherwise we will use the network adapter's `gethostbyname` - * function to resolve the hostname. - * - * A value of 0 returned for an IP address means we could NOT resolve the hostname. - * A value of 0xFFFFFFFF for an IP address means that we haven't found it YET. - */ -void networkGetHostByName( - JsNetwork *net, //!< The network we are using for resolution. - char *hostName, //!< The hostname to be resolved. - uint32_t *out_ip_addr //!< The address where the returned IP address will be stored. - ) { - assert(hostName != NULL); - assert(out_ip_addr != NULL); - - // Set the default IP address returned to be 0 that indicates not found. - *out_ip_addr = 0; - - // first try and simply parse the IP address as a string - *out_ip_addr = networkParseIPAddress(hostName); - - // If we did not get an IP address from the string, then try and resolve it by - // calling the network gethostbyname. - if (!*out_ip_addr) { - net->gethostbyname(net, hostName, out_ip_addr); - } -} - - - -void networkCreate(JsNetwork *net, JsNetworkType type) { - net->networkVar = jsvNewStringOfLength(sizeof(JsNetworkData)); - if (!net->networkVar) return; - net->data.type = type; - net->data.device = EV_NONE; - net->data.pinCS = PIN_UNDEFINED; - net->data.pinIRQ = PIN_UNDEFINED; - net->data.pinEN = PIN_UNDEFINED; - jsvObjectSetChildAndUnLock(execInfo.hiddenRoot, NETWORK_VAR_NAME, net->networkVar); - networkSet(net); - networkGetFromVar(net); -} - -bool networkWasCreated() { - JsVar *v = jsvObjectGetChild(execInfo.hiddenRoot, NETWORK_VAR_NAME, 0); - if (v) { - jsvUnLock(v); - return true; - } else { - return false; - } -} - -bool networkGetFromVar(JsNetwork *net) { - net->networkVar = jsvObjectGetChild(execInfo.hiddenRoot, NETWORK_VAR_NAME, 0); - if (!net->networkVar) { -#ifdef LINUX - networkCreate(net, JSNETWORKTYPE_SOCKET); - return net->networkVar != 0; -#else - return false; -#endif - } - jsvGetString(net->networkVar, (char *)&net->data, sizeof(JsNetworkData)+1/*trailing zero*/); - - switch (net->data.type) { -#if defined(USE_CC3000) - case JSNETWORKTYPE_CC3000 : netSetCallbacks_cc3000(net); break; -#endif -#if defined(USE_WIZNET) - case JSNETWORKTYPE_W5500 : netSetCallbacks_wiznet(net); break; -#endif -#if defined(USE_ESP8266) - case JSNETWORKTYPE_ESP8266_BOARD : netSetCallbacks_esp8266_board(net); break; -#endif -#if defined(LINUX) - case JSNETWORKTYPE_SOCKET : netSetCallbacks_linux(net); break; -#endif - case JSNETWORKTYPE_JS : netSetCallbacks_js(net); break; - default: - jsError("Unknown network device %d", net->data.type); - networkFree(net); - return false; - } - networkCurrentStruct = net; - return true; -} - -bool networkGetFromVarIfOnline(JsNetwork *net) { - bool found = networkGetFromVar(net); - if (!found || networkState != NETWORKSTATE_ONLINE) { - jsError("Not connected to the internet"); - if (found) networkFree(net); - return false; - } - return true; -} - -void networkSet(JsNetwork *net) { - jsvSetString(net->networkVar, (char *)&net->data, sizeof(JsNetworkData)); -} - -void networkFree(JsNetwork *net) { - networkCurrentStruct = 0; - jsvUnLock(net->networkVar); -} - -JsNetwork *networkGetCurrent() { - return networkCurrentStruct; -} +/* + * This file is part of Espruino, a JavaScript interpreter for Microcontrollers + * + * Copyright (C) 2013 Gordon Williams + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * ---------------------------------------------------------------------------- + * Contains functions for handling JsNetwork and doing common networking tasks + * ---------------------------------------------------------------------------- + */ +#include "network.h" +#include "jsparse.h" + +#if defined(USE_CC3000) + #include "network_cc3000.h" +#endif +#if defined(USE_WIZNET) + #include "network_wiznet.h" +#endif +#if defined(USE_ESP8266) + #include "network_esp8266.h" +#endif +#if defined(LINUX) + #include "network_linux.h" +#endif +#include "network_js.h" + +JsNetworkState networkState = +#ifdef LINUX + NETWORKSTATE_ONLINE +#else + NETWORKSTATE_OFFLINE +#endif + ; + +JsNetwork *networkCurrentStruct = 0; + +uint32_t networkParseIPAddress(const char *ip) { + int n = 0; + uint32_t addr = 0; + while (*ip) { + if (*ip>='0' && *ip<='9') { + n = n*10 + (*ip-'0'); + } else if (*ip=='.') { + addr = (addr>>8) | (uint32_t)(n<<24); + n=0; + } else { + return 0; // not an ip address + } + ip++; + } + addr = (addr>>8) | (uint32_t)(n<<24); + return addr; +} + +/* given 6 pairs of 8 bit hex numbers separated by ':', parse them into a + * 6 byte array. returns false on failure */ +bool networkParseMACAddress(unsigned char *addr, const char *ip) { + int n = 0; + int i = 0; + while (*ip) { + int v = chtod(*ip); + if (v>=0 && v<16) { + n = n*16 + v; + } else if (*ip==':') { + addr[i++] = (unsigned char)n; + n=0; + if (i>5) return false; // too many items! + } else { + return false; // not a mac address + } + ip++; + } + addr[i] = (unsigned char)n; + return i==5; +} + +JsVar *networkGetAddressAsString(unsigned char *ip, int nBytes, unsigned int base, char separator) { + char data[64] = ""; + int i = 0, dir = 1, l = 0; + if (nBytes<0) { + i = (-nBytes)-1; + nBytes = -1; + dir=-1; + } + for (;i!=nBytes;i+=dir) { + if (base==16) { + data[l++] = itoch(ip[i]>>4); + data[l++] = itoch(ip[i]&15); + } else { + itostr((int)ip[i], &data[l], base); + } + l = (int)strlen(data); + if (i+dir!=nBytes && separator) { + data[l++] = separator; + data[l] = 0; + } + } + + return jsvNewFromString(data); +} + +void networkPutAddressAsString(JsVar *object, const char *name, unsigned char *ip, int nBytes, unsigned int base, char separator) { + jsvObjectSetChildAndUnLock(object, name, networkGetAddressAsString(ip, nBytes, base, separator)); +} + +/** Some devices (CC3000) store the IP address with the first element last, so we must flip it */ +unsigned long networkFlipIPAddress(unsigned long addr) { + return + ((addr&0xFF)<<24) | + ((addr&0xFF00)<<8) | + ((addr&0xFF0000)>>8) | + ((addr&0xFF000000)>>24); +} + +/** + * Get the IP address of a hostname. + * Retrieve the IP address of a hostname and return it in the address of the + * ip address passed in. If the hostname is as dotted decimal string, we will + * decode that immediately otherwise we will use the network adapter's `gethostbyname` + * function to resolve the hostname. + * + * A value of 0 returned for an IP address means we could NOT resolve the hostname. + * A value of 0xFFFFFFFF for an IP address means that we haven't found it YET. + */ +void networkGetHostByName( + JsNetwork *net, //!< The network we are using for resolution. + char *hostName, //!< The hostname to be resolved. + uint32_t *out_ip_addr //!< The address where the returned IP address will be stored. + ) { + assert(hostName != NULL); + assert(out_ip_addr != NULL); + + // Set the default IP address returned to be 0 that indicates not found. + *out_ip_addr = 0; + + // first try and simply parse the IP address as a string + *out_ip_addr = networkParseIPAddress(hostName); + + // If we did not get an IP address from the string, then try and resolve it by + // calling the network gethostbyname. + if (!*out_ip_addr) { + net->gethostbyname(net, hostName, out_ip_addr); + } +} + + + +void networkCreate(JsNetwork *net, JsNetworkType type) { + net->networkVar = jsvNewStringOfLength(sizeof(JsNetworkData)); + if (!net->networkVar) return; + net->data.type = type; + net->data.device = EV_NONE; + net->data.pinCS = PIN_UNDEFINED; + net->data.pinIRQ = PIN_UNDEFINED; + net->data.pinEN = PIN_UNDEFINED; + jsvObjectSetChildAndUnLock(execInfo.hiddenRoot, NETWORK_VAR_NAME, net->networkVar); + networkSet(net); + networkGetFromVar(net); +} + +bool networkWasCreated() { + JsVar *v = jsvObjectGetChild(execInfo.hiddenRoot, NETWORK_VAR_NAME, 0); + if (v) { + jsvUnLock(v); + return true; + } else { + return false; + } +} + +bool networkGetFromVar(JsNetwork *net) { + net->networkVar = jsvObjectGetChild(execInfo.hiddenRoot, NETWORK_VAR_NAME, 0); + if (!net->networkVar) { +#ifdef LINUX + networkCreate(net, JSNETWORKTYPE_SOCKET); + return net->networkVar != 0; +#else + return false; +#endif + } + jsvGetString(net->networkVar, (char *)&net->data, sizeof(JsNetworkData)+1/*trailing zero*/); + + switch (net->data.type) { +#if defined(USE_CC3000) + case JSNETWORKTYPE_CC3000 : netSetCallbacks_cc3000(net); break; +#endif +#if defined(USE_WIZNET) + case JSNETWORKTYPE_W5500 : netSetCallbacks_wiznet(net); break; +#endif +#if defined(USE_ESP8266) + case JSNETWORKTYPE_ESP8266_BOARD : netSetCallbacks_esp8266_board(net); break; +#endif +#if defined(LINUX) + case JSNETWORKTYPE_SOCKET : netSetCallbacks_linux(net); break; +#endif + case JSNETWORKTYPE_JS : netSetCallbacks_js(net); break; + default: + jsError("Unknown network device %d", net->data.type); + networkFree(net); + return false; + } + networkCurrentStruct = net; + return true; +} + +bool networkGetFromVarIfOnline(JsNetwork *net) { + bool found = networkGetFromVar(net); + if (!found || networkState != NETWORKSTATE_ONLINE) { + jsError("Not connected to the internet"); + if (found) networkFree(net); + return false; + } + return true; +} + +void networkSet(JsNetwork *net) { + jsvSetString(net->networkVar, (char *)&net->data, sizeof(JsNetworkData)); +} + +void networkFree(JsNetwork *net) { + networkCurrentStruct = 0; + jsvUnLock(net->networkVar); +} + +JsNetwork *networkGetCurrent() { + return networkCurrentStruct; +}