diff --git a/examples/StandardFirmataWiFi/StandardFirmataWiFi.ino b/examples/StandardFirmataWiFi/StandardFirmataWiFi.ino index e3cb2379..448ccd4a 100644 --- a/examples/StandardFirmataWiFi/StandardFirmataWiFi.ino +++ b/examples/StandardFirmataWiFi/StandardFirmataWiFi.ino @@ -22,21 +22,21 @@ See file LICENSE.txt for further informations on licensing terms. - Last updated by Jeff Hoefs: April 10th, 2016 + Last updated by Jeff Hoefs: April 24th, 2016 */ /* README - StandardFirmataWiFi is a WiFi server application. You will need a Firmata client library with - a network transport in order to establish a connection with StandardFirmataWiFi. + StandardFirmataWiFi enables the use of Firmata over a TCP connection. It can be configured as + either a TCP server or TCP client. To use StandardFirmataWiFi you will need to have one of the following boards or shields: - Arduino WiFi Shield (or clone) - Arduino WiFi Shield 101 - - Arduino MKR1000 board (built-in WiFi 101) + - Arduino MKR1000 board - ESP8266 WiFi board compatible with ESP8266 Arduino core Follow the instructions in the wifiConfig.h file (wifiConfig.h tab in Arduino IDE) to @@ -46,13 +46,13 @@ - WiFi Shield 101 requires version 0.7.0 or higher of the WiFi101 library (available in Arduino 1.6.8 or higher, or update the library via the Arduino Library Manager or clone from source: https://github.com/arduino-libraries/WiFi101) - - ESP8266 requires the Arduino ESP8266 core which can be obtained here: + - ESP8266 requires the Arduino ESP8266 core v2.1.0 or higher which can be obtained here: https://github.com/esp8266/Arduino - In order to use the WiFi Shield 101 with Firmata you will need a board with at least - 35k of Flash memory. This means you cannot use the WiFi Shield 101 with an Arduino Uno - or any other ATmega328p-based microcontroller or with an Arduino Leonardo or other - ATmega32u4-based microcontroller. Some boards that will work are: + In order to use the WiFi Shield 101 with Firmata you will need a board with at least 35k of Flash + memory. This means you cannot use the WiFi Shield 101 with an Arduino Uno or any other + ATmega328p-based microcontroller or with an Arduino Leonardo or other ATmega32u4-based + microcontroller. Some boards that will work are: - Arduino Zero - Arduino Due @@ -87,8 +87,7 @@ /* * Uncomment the following include to enable interfacing with Serial devices via hardware or - * software serial. Note that if enabled, this sketch will likely consume too much memory to run on - * an Arduino Uno or Leonardo or other ATmega328p-based or ATmega32u4-based boards. + * software serial. */ //#include "utility/SerialFirmata.h" @@ -110,7 +109,7 @@ // the minimum interval for sampling analog input #define MINIMUM_SAMPLING_INTERVAL 1 -#define WIFI_MAX_CONN_ATTEMPTS 3 +#define MAX_CONN_ATTEMPTS 20 // [500 ms] -> 10 s /*============================================================================== * GLOBAL VARIABLES @@ -130,8 +129,8 @@ IPAddress subnet(SUBNET_MASK); IPAddress gateway(GATEWAY_IP_ADDRESS); #endif -int wifiConnectionAttemptCounter = 0; -int wifiStatus = WL_IDLE_STATUS; +int connectionAttempts = 0; +bool streamConnected = false; /* analog inputs */ int analogInputsToReport = 0; // bitwise array to store pin reporting @@ -308,6 +307,12 @@ void checkDigitalInputs(void) if (TOTAL_PORTS > 15 && reportPINs[15]) outputPort(15, readPort(15, portConfigInputs[15]), false); } +// ----------------------------------------------------------------------------- +// function forward declarations for xtensa compiler (ESP8266) +void enableI2CPins(); +void disableI2CPins(); +void reportAnalogCallback(byte analogPin, int value); + // ----------------------------------------------------------------------------- /* sets the pin mode to the correct state and sets the relevant bits in the * two bit-arrays that track Digital I/O and PWM status @@ -825,53 +830,85 @@ void systemResetCallback() isResetting = false; } +/* + * Called when a TCP connection is either connected or disconnected. + * TODO: + * - report connected or reconnected state to host (to be added to protocol) + * - report current state to host (to be added to protocol) + */ +void hostConnectionCallback(byte state) +{ + switch (state) { + case HOST_CONNECTION_CONNECTED: + DEBUG_PRINTLN( "TCP connection established" ); + break; + case HOST_CONNECTION_DISCONNECTED: + DEBUG_PRINTLN( "TCP connection disconnected" ); + break; + } +} + +/* + * Print the status of the WiFi connection. This is the connection to the access point rather + * than the TCP connection. + */ void printWifiStatus() { -#if defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) || defined(ESP8266_WIFI) if ( WiFi.status() != WL_CONNECTED ) { DEBUG_PRINT( "WiFi connection failed. Status value: " ); DEBUG_PRINTLN( WiFi.status() ); } else -#endif //defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) || defined(ESP8266_WIFI) { // print the SSID of the network you're attached to: DEBUG_PRINT( "SSID: " ); - -#if defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) || defined(ESP8266_WIFI) DEBUG_PRINTLN( WiFi.SSID() ); -#endif //defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) || defined(ESP8266_WIFI) // print your WiFi shield's IP address: DEBUG_PRINT( "IP Address: " ); - -#if defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) || defined(ESP8266_WIFI) IPAddress ip = WiFi.localIP(); DEBUG_PRINTLN( ip ); -#endif //defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) || defined(ESP8266_WIFI) // print the received signal strength: DEBUG_PRINT( "signal strength (RSSI): " ); - -#if defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) || defined(ESP8266_WIFI) long rssi = WiFi.RSSI(); DEBUG_PRINT( rssi ); -#endif //defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) || defined(ESP8266_WIFI) - DEBUG_PRINTLN( " dBm" ); } } -void setup() +/* + * StandardFirmataWiFi communicates with WiFi shields over SPI. Therefore all + * SPI pins must be set to IGNORE. Otherwise Firmata would break SPI communication. + * Additional pins may also need to be ignored depending on the particular board or + * shield in use. + */ +void ignorePins() { - /* - * WIFI SETUP - */ - DEBUG_BEGIN(9600); +#ifdef IS_IGNORE_PIN + for (byte i = 0; i < TOTAL_PINS; i++) { + if (IS_IGNORE_PIN(i)) { + Firmata.setPinMode(i, PIN_MODE_IGNORE); + } + } +#endif - /* - * This statement will clarify how a connection is being made - */ + //Set up controls for the Arduino WiFi Shield SS for the SD Card +#ifdef ARDUINO_WIFI_SHIELD + // Arduino WiFi Shield has SD SS wired to D4 + pinMode(PIN_TO_DIGITAL(4), OUTPUT); // switch off SD card bypassing Firmata + digitalWrite(PIN_TO_DIGITAL(4), HIGH); // SS is active low; + +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + pinMode(PIN_TO_DIGITAL(53), OUTPUT); // configure hardware SS as output on MEGA +#endif //defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + +#endif //ARDUINO_WIFI_SHIELD +} + +void initTransport() +{ + // This statement will clarify how a connection is being made DEBUG_PRINT( "StandardFirmataWiFi will attempt a WiFi connection " ); #if defined(WIFI_101) DEBUG_PRINTLN( "using the WiFi 101 library." ); @@ -884,13 +921,11 @@ void setup() //else should never happen here as error-checking in wifiConfig.h will catch this #endif //defined(WIFI_101) - /* - * Configure WiFi IP Address - */ + // Configure WiFi IP Address #ifdef STATIC_IP_ADDRESS DEBUG_PRINT( "Using static IP: " ); DEBUG_PRINTLN( local_ip ); -#ifdef ESP8266_WIFI +#if defined(ESP8266_WIFI) || (defined(SUBNET_MASK) && defined(GATEWAY_IP_ADDRESS)) stream.config( local_ip , gateway, subnet ); #else // you can also provide a static IP in the begin() functions, but this simplifies @@ -901,45 +936,35 @@ void setup() DEBUG_PRINTLN( "IP will be requested from DHCP ..." ); #endif - /* - * Configure WiFi security - */ -#if defined(WIFI_WEP_SECURITY) - while (wifiStatus != WL_CONNECTED) { - DEBUG_PRINT( "Attempting to connect to WEP SSID: " ); - DEBUG_PRINTLN(ssid); - wifiStatus = stream.begin( ssid, wep_index, wep_key, SERVER_PORT ); - delay(5000); // TODO - determine minimum delay - if (++wifiConnectionAttemptCounter > WIFI_MAX_CONN_ATTEMPTS) break; - } + stream.attach(hostConnectionCallback); + // Configure WiFi security and initiate WiFi connection +#if defined(WIFI_WEP_SECURITY) + DEBUG_PRINT( "Attempting to connect to WEP SSID: " ); + DEBUG_PRINTLN(ssid); + stream.begin(ssid, wep_index, wep_key); #elif defined(WIFI_WPA_SECURITY) - while (wifiStatus != WL_CONNECTED) { - DEBUG_PRINT( "Attempting to connect to WPA SSID: " ); - DEBUG_PRINTLN(ssid); - wifiStatus = stream.begin(ssid, wpa_passphrase, SERVER_PORT); - delay(5000); // TODO - determine minimum delay - if (++wifiConnectionAttemptCounter > WIFI_MAX_CONN_ATTEMPTS) break; - } - + DEBUG_PRINT( "Attempting to connect to WPA SSID: " ); + DEBUG_PRINTLN(ssid); + stream.begin(ssid, wpa_passphrase); #else //OPEN network - while (wifiStatus != WL_CONNECTED) { - DEBUG_PRINTLN( "Attempting to connect to open SSID: " ); - DEBUG_PRINTLN(ssid); - wifiStatus = stream.begin(ssid, SERVER_PORT); - delay(5000); // TODO - determine minimum delay - if (++wifiConnectionAttemptCounter > WIFI_MAX_CONN_ATTEMPTS) break; - } + DEBUG_PRINTLN( "Attempting to connect to open SSID: " ); + DEBUG_PRINTLN(ssid); + stream.begin(ssid); #endif //defined(WIFI_WEP_SECURITY) - DEBUG_PRINTLN( "WiFi setup done" ); + + // Wait for connection to access point to be established. + while (WiFi.status() != WL_CONNECTED && ++connectionAttempts <= MAX_CONN_ATTEMPTS) { + delay(500); + DEBUG_PRINT("."); + } printWifiStatus(); +} - /* - * FIRMATA SETUP - */ +void initFirmata() +{ Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION); - Firmata.attach(ANALOG_MESSAGE, analogWriteCallback); Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback); Firmata.attach(REPORT_ANALOG, reportAnalogCallback); @@ -949,47 +974,20 @@ void setup() Firmata.attach(START_SYSEX, sysexCallback); Firmata.attach(SYSTEM_RESET, systemResetCallback); - // StandardFirmataWiFi communicates with WiFi shields over SPI. Therefore all - // SPI pins must be set to IGNORE. Otherwise Firmata would break SPI communication. - // Additional pins may also need to be ignored depending on the particular board or - // shield in use. + ignorePins(); - for (byte i = 0; i < TOTAL_PINS; i++) { -#if defined(ARDUINO_WIFI_SHIELD) - if (IS_IGNORE_WIFI_SHIELD(i) - #if defined(__AVR_ATmega32U4__) - || 24 == i // On Leonardo, pin 24 maps to D4 and pin 28 maps to D10 - || 28 == i - #endif //defined(__AVR_ATmega32U4__) - ) { -// don't ignore pins when using Wi-Fi 101 library with the MKR1000 -#elif defined (WIFI_101) && !defined(ARDUINO_SAMD_MKR1000) - if (IS_IGNORE_WIFI101_SHIELD(i)) { -#elif defined (HUZZAH_WIFI) - // TODO - if (false) { -#else - if (false) { -#endif - Firmata.setPinMode(i, PIN_MODE_IGNORE); - } - } - - //Set up controls for the Arduino WiFi Shield SS for the SD Card -#ifdef ARDUINO_WIFI_SHIELD - // Arduino WiFi, Arduino WiFi Shield and Arduino Yun all have SD SS wired to D4 - pinMode(PIN_TO_DIGITAL(4), OUTPUT); // switch off SD card bypassing Firmata - digitalWrite(PIN_TO_DIGITAL(4), HIGH); // SS is active low; + // Initialize Firmata to use the WiFi stream object as the transport. + Firmata.begin(stream); + systemResetCallback(); // reset to default config +} -#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) - pinMode(PIN_TO_DIGITAL(53), OUTPUT); // configure hardware SS as output on MEGA -#endif //defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) +void setup() +{ + DEBUG_BEGIN(9600); -#endif //ARDUINO_WIFI_SHIELD + initTransport(); - // start up Network Firmata: - Firmata.begin(stream); - systemResetCallback(); // reset to default config + initFirmata(); } /*============================================================================== diff --git a/examples/StandardFirmataWiFi/wifiConfig.h b/examples/StandardFirmataWiFi/wifiConfig.h index 84b3acd0..bedc7447 100644 --- a/examples/StandardFirmataWiFi/wifiConfig.h +++ b/examples/StandardFirmataWiFi/wifiConfig.h @@ -3,13 +3,13 @@ * * You must configure your particular hardware. Follow the steps below. * - * Currently StandardFirmataWiFi is configured as a Wi-Fi server. An option to - * configure as a Wi-Fi client will be added in the future. + * By default, StandardFirmataWiFi is configured as a TCP server, to configure + * as a TCP client, see STEP 2. *============================================================================*/ // STEP 1 [REQUIRED] // Uncomment / comment the appropriate set of includes for your hardware (OPTION A, B or C) -// Option A is enabled by default. +// Arduino MKR1000 or ESP8266 are enabled by default if compiling for either of those boards. /* * OPTION A: Configure for Arduino MKR1000 or Arduino WiFi Shield 101 @@ -30,16 +30,16 @@ */ //#define WIFI_101 -//do not modify the following 10 lines +//do not modify the following 11 lines #if defined(ARDUINO_SAMD_MKR1000) && !defined(WIFI_101) // automatically include if compiling for MRK1000 #define WIFI_101 #endif #ifdef WIFI_101 #include -#include "utility/WiFiStream.h" -WiFiStream stream; -#define WIFI_LIB_INCLUDED +#include "utility/WiFiClientStream.h" +#include "utility/WiFiServerStream.h" + #define WIFI_LIB_INCLUDED #endif /* @@ -57,8 +57,8 @@ WiFiStream stream; //do not modify the following 10 lines #ifdef ARDUINO_WIFI_SHIELD #include -#include "utility/WiFiStream.h" -WiFiStream stream; +#include "utility/WiFiClientStream.h" +#include "utility/WiFiServerStream.h" #ifdef WIFI_LIB_INCLUDED #define MULTIPLE_WIFI_LIB_INCLUDES #else @@ -75,8 +75,8 @@ WiFiStream stream; * The appropriate libraries are included automatically when compiling for the ESP8266 so * continue on to STEP 2. * - * IMPORTANT: You must have the esp8266 board support installed. To easily install this board, open - * see the instructions here: https://github.com/esp8266/Arduino#installing-with-boards-manager. + * IMPORTANT: You must have the esp8266 board support installed. To easily install this board see + * the instructions here: https://github.com/esp8266/Arduino#installing-with-boards-manager. */ //do not modify the following 14 lines #ifdef ESP8266 @@ -85,8 +85,8 @@ WiFiStream stream; #endif #ifdef ESP8266_WIFI #include -#include "utility/WiFiStream.h" -WiFiStream stream; +#include "utility/WiFiClientStream.h" +#include "utility/WiFiServerStream.h" #ifdef WIFI_LIB_INCLUDED #define MULTIPLE_WIFI_LIB_INCLUDES #else @@ -108,12 +108,18 @@ WiFiStream stream; //#define HUZZAH_WIFI -// STEP 2 [REQUIRED for all boards and shields] +// STEP 2 [OPTIONAL for all boards and shields] +// If you want to setup you board as a TCP client, uncomment the following define and replace +// the IP address with the IP address of your server. +//#define SERVER_IP 10, 0, 0, 15 + + +// STEP 3 [REQUIRED for all boards and shields] // replace this with your wireless network SSID char ssid[] = "your_network_name"; -// STEP 3 [OPTIONAL for all boards and shields] +// STEP 4 [OPTIONAL for all boards and shields] // If you want to use a static IP (v4) address, uncomment the line below. You can also change the IP. // If the first line is commented out, the WiFi shield will attempt to get an IP from the DHCP server. // If you are using a static IP with the ESP8266 then you must also uncomment the SUBNET and GATEWAY. @@ -122,12 +128,12 @@ char ssid[] = "your_network_name"; //#define GATEWAY_IP_ADDRESS 0,0,0,0 // REQUIRED for ESP8266_WIFI, optional for others -// STEP 4 [REQUIRED for all boards and shields] +// STEP 5 [REQUIRED for all boards and shields] // define your port number here, you will need this to open a TCP connection to your Arduino #define SERVER_PORT 3030 -// STEP 5 [REQUIRED for all boards and shields] +// STEP 6 [REQUIRED for all boards and shields] // determine your network security type (OPTION A, B, or C). Option A is the most common, and the // default. @@ -200,14 +206,35 @@ char wep_key[] = "your_wep_key"; #error "you must choose between WIFI_NO_SECURITY and WIFI_WPA_SECURITY" #endif +/*============================================================================== + * WIFI STREAM (don't change anything here) + *============================================================================*/ + +#ifdef SERVER_IP + WiFiClientStream stream(IPAddress(SERVER_IP), SERVER_PORT); +#else + WiFiServerStream stream(SERVER_PORT); +#endif + /*============================================================================== * PIN IGNORE MACROS (don't change anything here) *============================================================================*/ +#if defined(WIFI_101) && !defined(ARDUINO_SAMD_MKR1000) // ignore SPI pins, pin 5 (reset WiFi101 shield), pin 7 (WiFi handshake) and pin 10 (WiFi SS) -// also don't ignore SS pin if it's not pin 10 -// Not needed for Arduino MKR1000. -#define IS_IGNORE_WIFI101_SHIELD(p) ((p) == 10 || (IS_PIN_SPI(p) && (p) != SS) || (p) == 5 || (p) == 7) +// also don't ignore SS pin if it's not pin 10. Not needed for Arduino MKR1000. +#define IS_IGNORE_PIN(p) ((p) == 10 || (IS_PIN_SPI(p) && (p) != SS) || (p) == 5 || (p) == 7) + +#elif defined(ARDUINO_WIFI_SHIELD) && defined(__AVR_ATmega32U4__) +// ignore SPI pins, pin 4 (SS for SD-Card on WiFi-shield), pin 7 (WiFi handshake) and pin 10 (WiFi SS) +// On Leonardo, pin 24 maps to D4 and pin 28 maps to D10 +#define IS_IGNORE_PIN(p) ((IS_PIN_SPI(p) || (p) == 4) || (p) == 7 || (p) == 10 || (p) == 24 || (p) == 28) +#elif defined(ARDUINO_WIFI_SHIELD) // ignore SPI pins, pin 4 (SS for SD-Card on WiFi-shield), pin 7 (WiFi handshake) and pin 10 (WiFi SS) -#define IS_IGNORE_WIFI_SHIELD(p) ((IS_PIN_SPI(p) || (p) == 4) || (p) == 7 || (p) == 10) +#define IS_IGNORE_PIN(p) ((IS_PIN_SPI(p) || (p) == 4) || (p) == 7 || (p) == 10) + +#elif defined(ESP8266_WIFI) && defined(SERIAL_DEBUG) +#define IS_IGNORE_PIN(p) ((p) == 1) + +#endif diff --git a/utility/WiFiClientStream.h b/utility/WiFiClientStream.h new file mode 100644 index 00000000..7fd30af6 --- /dev/null +++ b/utility/WiFiClientStream.h @@ -0,0 +1,105 @@ +/* + WiFiClientStream.h + + An Arduino Stream that wraps an instance of a WiFiClient. For use + with legacy Arduino WiFi shield and other boards and shields that + are compatible with the Arduino WiFi library. + + Copyright (C) 2016 Jens B. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Parts of this class are based on + + - EthernetClientStream - Copyright (C) 2013 Norbert Truchsess. All rights reserved. + + published under the same license. + + Last updated April 23rd, 2016 + */ + +#ifndef WIFI_CLIENT_STREAM_H +#define WIFI_CLIENT_STREAM_H + +#include "WiFiStream.h" + +#define MILLIS_RECONNECT 5000 + +class WiFiClientStream : public WiFiStream +{ +protected: + uint32_t _time_connect = 0; + + /** + * check if TCP client is connected + * @return true if connected + */ + virtual inline bool connect_client() + { + if ( _connected ) + { + if ( _client && _client.connected() ) return true; + stop(); + } + + // active TCP connect + if ( WiFi.status() == WL_CONNECTED ) + { + // if the client is disconnected, try to reconnect every 5 seconds + if ( millis() - _time_connect >= MILLIS_RECONNECT ) + { + _connected = _client.connect( _remote_ip, _port ); + if ( !_connected ) + { + _time_connect = millis(); + } + else if ( _currentHostConnectionCallback ) + { + (*_currentHostConnectionCallback)(HOST_CONNECTION_CONNECTED); + } + } + } + + return _connected; + } + +public: + /** + * create a WiFi stream with a TCP client + */ + WiFiClientStream(IPAddress server_ip, uint16_t server_port) : WiFiStream(server_ip, server_port) {} + + /** + * maintain WiFi and TCP connection + * @return true if WiFi and TCP connection are established + */ + virtual inline bool maintain() + { + return connect_client(); + } + + /** + * stop client connection + */ + virtual inline void stop() + { + if ( _client) + { + _client.stop(); + if ( _currentHostConnectionCallback ) + { + (*_currentHostConnectionCallback)(HOST_CONNECTION_DISCONNECTED); + } + } + _connected = false; + _time_connect = millis(); + } + +}; + +#endif //WIFI_CLIENT_STREAM_H diff --git a/utility/WiFiServerStream.h b/utility/WiFiServerStream.h new file mode 100644 index 00000000..1404b056 --- /dev/null +++ b/utility/WiFiServerStream.h @@ -0,0 +1,107 @@ +/* + WiFiServerStream.h + + An Arduino Stream extension for a WiFiClient or WiFiServer to be used + with legacy Arduino WiFi shield and other boards and shields that + are compatible with the Arduino WiFi library. + + Copyright (C) 2016 Jens B. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + See file LICENSE.txt for further informations on licensing terms. + + Parts of this class are based on + + - WiFiStream - Copyright (C) 2015-2016 Jesse Frush. All rights reserved. + + published under the same license. + + Last updated April 23rd, 2016 + */ + +#ifndef WIFI_SERVER_STREAM_H +#define WIFI_SERVER_STREAM_H + +#include "WiFiStream.h" + +class WiFiServerStream : public WiFiStream +{ +protected: + WiFiServer _server = WiFiServer(3030); + bool _listening = false; + + /** + * check if TCP client is connected + * @return true if connected + */ + virtual inline bool connect_client() + { + if ( _connected ) + { + if ( _client && _client.connected() ) return true; + stop(); + } + + // passive TCP connect (accept) + WiFiClient newClient = _server.available(); + if ( !newClient ) return false; + _client = newClient; + _connected = true; + if ( _currentHostConnectionCallback ) + { + (*_currentHostConnectionCallback)(HOST_CONNECTION_CONNECTED); + } + + return true; + } + +public: + /** + * create a WiFi stream with a TCP server + */ + WiFiServerStream(uint16_t server_port) : WiFiStream(server_port) {} + + /** + * maintain WiFi and TCP connection + * @return true if WiFi and TCP connection are established + */ + virtual inline bool maintain() + { + if ( connect_client() ) return true; + + stop(); + + if ( !_listening && WiFi.status() == WL_CONNECTED ) + { + // start TCP server after first WiFi connect + _server = WiFiServer(_port); + _server.begin(); + _listening = true; + } + + return false; + } + + /** + * stop client connection + */ + virtual inline void stop() + { + if ( _client) + { + _client.stop(); + if ( _currentHostConnectionCallback ) + { + (*_currentHostConnectionCallback)(HOST_CONNECTION_DISCONNECTED); + } + } + _connected = false; + } + +}; + +#endif //WIFI_SERVER_STREAM_H diff --git a/utility/WiFiStream.h b/utility/WiFiStream.h index 4f21c109..4d83fe8d 100644 --- a/utility/WiFiStream.h +++ b/utility/WiFiStream.h @@ -1,10 +1,12 @@ /* WiFiStream.h - An Arduino Stream that wraps an instance of a WiFi server. For use + + An Arduino Stream extension for a WiFiClient or WiFiServer to be used with legacy Arduino WiFi shield and other boards and shields that are compatible with the Arduino WiFi library. Copyright (C) 2015-2016 Jesse Frush. All rights reserved. + Copyright (C) 2016 Jens B. All rights reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -13,7 +15,7 @@ See file LICENSE.txt for further informations on licensing terms. - Last updated April 10th, 2016 + Last updated April 23rd, 2016 */ #ifndef WIFI_STREAM_H @@ -22,59 +24,56 @@ #include #include +#define HOST_CONNECTION_DISCONNECTED 0 +#define HOST_CONNECTION_CONNECTED 1 + +extern "C" { + // callback function types + typedef void (*hostConnectionCallbackFunction)(byte); +} + class WiFiStream : public Stream { -private: - WiFiServer _server = WiFiServer(23); +protected: WiFiClient _client; + bool _connected = false; + hostConnectionCallbackFunction _currentHostConnectionCallback; //configuration members - IPAddress _local_ip; - IPAddress _gateway; + IPAddress _local_ip; // DHCP IPAddress _subnet; - uint16_t _port = 0; - uint8_t _key_idx = 0; //WEP + IPAddress _gateway; + IPAddress _remote_ip; + uint16_t _port; + uint8_t _key_idx; //WEP const char *_key = nullptr; //WEP const char *_passphrase = nullptr; //WPA char *_ssid = nullptr; - bool _new_connection = false; - inline int connect_client() - { - if( !( _client && _client.connected() ) ) - { - WiFiClient newClient = _server.available(); - if( !newClient ) - { - return 0; - } - - _client = newClient; - } - return 1; - } + /** + * check if TCP client is connected + * @return true if connected + */ + virtual bool connect_client() = 0; - inline bool is_new_connection() - { - if (_new_connection && WiFi.status() == WL_CONNECTED) { - _new_connection = false; - return true; - } - _new_connection = true; - return false; - } +public: + /** constructor for TCP server */ + WiFiStream(uint16_t server_port) : _port(server_port) {} - inline bool is_ready() - { - uint8_t status = WiFi.status(); - return !( status == WL_NO_SHIELD || status == WL_CONNECTED ); - } + /** constructor for TCP client */ + WiFiStream(IPAddress server_ip, uint16_t server_port) : _remote_ip(server_ip), _port(server_port) {} -public: - WiFiStream() {}; + inline void attach( hostConnectionCallbackFunction newFunction ) { _currentHostConnectionCallback = newFunction; } + +/****************************************************************************** + * network configuration + ******************************************************************************/ #ifndef ESP8266 - // allows another way to configure a static IP before begin is called + /** + * configure a static local IP address without defining the local network + * DHCP will be used as long as local IP address is not defined + */ inline void config(IPAddress local_ip) { _local_ip = local_ip; @@ -82,12 +81,15 @@ class WiFiStream : public Stream } #endif - // allows another way to configure a static IP before begin is called + /** + * configure a static local IP address + * DHCP will be used as long as local IP address is not defined + */ inline void config(IPAddress local_ip, IPAddress gateway, IPAddress subnet) { _local_ip = local_ip; - _gateway = gateway; _subnet = subnet; + _gateway = gateway; #ifndef ESP8266 WiFi.config( local_ip, IPAddress(0, 0, 0, 0), gateway, subnet ); #else @@ -95,200 +97,103 @@ class WiFiStream : public Stream #endif } - // get DCHP IP - inline IPAddress localIP() - { - return WiFi.localIP(); - } - /** - * @return true if connected + * @return local IP address */ - inline bool maintain() + inline IPAddress getLocalIP() { - if( connect_client() ) return true; - - stop(); - int result = 0; - if( WiFi.status() != WL_CONNECTED ) - { - if( _local_ip ) - { -#ifndef ESP8266 - WiFi.config( _local_ip ); -#else - WiFi.config( _local_ip, _gateway, _subnet ); -#endif - } - - if( _passphrase ) - { -#ifndef ESP8266 - result = WiFi.begin( _ssid, _passphrase); -#else - WiFi.begin( _ssid, _passphrase); - result = WiFi.status(); -#endif - } -#ifndef ESP8266 - else if( _key_idx && _key ) - { - result = WiFi.begin( _ssid, _key_idx, _key ); - } -#endif - else - { -#ifndef ESP8266 - result = WiFi.begin( _ssid); -#else - WiFi.begin( _ssid); - result = WiFi.status(); -#endif - } - } - if( result == 0 ) return false; - - _server = WiFiServer( _port ); - _server.begin(); - return result; + return WiFi.localIP(); } /****************************************************************************** - * Connection functions with DHCP + * network functions ******************************************************************************/ - //OPEN networks - inline int begin(char *ssid, uint16_t port) - { - if( is_new_connection() ) return WL_CONNECTED; - if( !is_ready() ) return 0; - - _ssid = ssid; - _port = port; - - int result = WiFi.begin(ssid); - // will always return 0 for ESP8266 - if( result == 0 ) return 0; - - _server = WiFiServer( port ); - _server.begin(); - return result; - } - -#ifndef ESP8266 - //WEP-encrypted networks - inline int begin(char *ssid, uint8_t key_idx, const char *key, uint16_t port) + /** + * maintain WiFi and TCP connection + * @return true if WiFi and TCP connection are established + */ + virtual bool maintain() = 0; + +#ifdef ESP8266 + /** + * get status of TCP connection + * @return status of TCP connection + * CLOSED = 0 (typical) + * LISTEN = 1 (not used) + * SYN_SENT = 2 + * SYN_RCVD = 3 + * ESTABLISHED = 4 (typical) + * FIN_WAIT_1 = 5 + * FIN_WAIT_2 = 6 + * CLOSE_WAIT = 7 + * CLOSING = 8 + * LAST_ACK = 9 + * TIME_WAIT = 10 + */ + inline uint8_t status() { - if( is_new_connection() ) return WL_CONNECTED; - if( !is_ready() ) return 0; - - _ssid = ssid; - _port = port; - _key_idx = key_idx; - _key = key; - - int result = WiFi.begin( ssid, key_idx, key ); - if( result == 0 ) return 0; - - _server = WiFiServer( port ); - _server.begin(); - return result; + return _client.status(); } #endif - //WPA-encrypted networks - inline int begin(char *ssid, const char *passphrase, uint16_t port) - { - // TODO - figure out a cleaner way to handle this. The issue is that with the ESP8266 - // WiFi.begin does not wait so the connect state is is therefore not updated until the - // next time begin is called. The call to !is_ready() below however returns 0 if - // WL_CONNECTED is true. This is to allow a new connection with different parameters than - // the original connection. is_new_connection is a temporary solution. - if( is_new_connection() ) return WL_CONNECTED; - if( !is_ready() ) return 0; - - _ssid = ssid; - _port = port; - _passphrase = passphrase; - - int result = WiFi.begin(ssid, passphrase); - // will always return 0 for ESP8266 - if( result == 0 ) return 0; - - _server = WiFiServer( port ); - _server.begin(); - return result; - } + /** + * close TCP client connection + */ + virtual void stop() = 0; /****************************************************************************** - * Connection functions without DHCP + * WiFi configuration ******************************************************************************/ -// ESP8266 requires gateway and subnet so the following functions are not compatible -#ifndef ESP8266 - //OPEN networks with static IP - inline int begin(char *ssid, IPAddress local_ip, uint16_t port) + /** + * initialize WiFi without security (open) and initiate client connection + * if WiFi connection is already established + * @return WL_CONNECTED if WiFi connection is established + */ + inline int begin(char *ssid) { - if( is_new_connection() ) return WL_CONNECTED; - if( !is_ready() ) return 0; - _ssid = ssid; - _port = port; - _local_ip = local_ip; - - WiFi.config( local_ip ); - int result = WiFi.begin( ssid ); - if( result == 0 ) return 0; - _server = WiFiServer( port ); - _server.begin(); - return result; + WiFi.begin(ssid); + int result = WiFi.status(); + return WiFi.status(); } - //WEP-encrypted networks with static IP - inline int begin(char *ssid, IPAddress local_ip, uint8_t key_idx, const char *key, uint16_t port) +#ifndef ESP8266 + /** + * initialize WiFi with WEP security and initiate client connection + * if WiFi connection is already established + * @return WL_CONNECTED if WiFi connection is established + */ + inline int begin(char *ssid, uint8_t key_idx, const char *key) { - if( is_new_connection() ) return WL_CONNECTED; - if( !is_ready() ) return 0; - _ssid = ssid; - _port = port; - _local_ip = local_ip; _key_idx = key_idx; _key = key; - WiFi.config( local_ip ); - int result = WiFi.begin( ssid, key_idx, key ); - if( result == 0 ) return 0; - - _server = WiFiServer( port ); - _server.begin(); - return result; + WiFi.begin( ssid, key_idx, key ); + return WiFi.status(); } +#endif - //WPA-encrypted networks with static IP - inline int begin(char *ssid, IPAddress local_ip, const char *passphrase, uint16_t port) + /** + * initialize WiFi with WPA-PSK security and initiate client connection + * if WiFi connection is already established + * @return WL_CONNECTED if WiFi connection is established + */ + inline int begin(char *ssid, const char *passphrase) { - if( is_new_connection() ) return WL_CONNECTED; - if( !is_ready() ) return 0; - _ssid = ssid; - _port = port; - _local_ip = local_ip; _passphrase = passphrase; - WiFi.config( local_ip ); - int result = WiFi.begin( ssid, passphrase); - if( result == 0 ) return 0; - - _server = WiFiServer( port ); - _server.begin(); - return result; + WiFi.begin(ssid, passphrase); + return WiFi.status(); } -#endif + /****************************************************************************** - * Stream implementations + * stream functions ******************************************************************************/ inline int available() @@ -311,15 +216,11 @@ class WiFiStream : public Stream return connect_client() ? _client.read() : -1; } - inline void stop() - { - _client.stop(); - } - inline size_t write(uint8_t byte) { - if( connect_client() ) _client.write( byte ); + return connect_client() ? _client.write( byte ) : 0; } + }; #endif //WIFI_STREAM_H