diff --git a/examples/StandardFirmataEthernet/StandardFirmataEthernet.ino b/examples/StandardFirmataEthernet/StandardFirmataEthernet.ino index 9bc927bf..c1d2399d 100644 --- a/examples/StandardFirmataEthernet/StandardFirmataEthernet.ino +++ b/examples/StandardFirmataEthernet/StandardFirmataEthernet.ino @@ -868,14 +868,14 @@ void setup() Firmata.attach(START_SYSEX, sysexCallback); Firmata.attach(SYSTEM_RESET, systemResetCallback); - // StandardFirmataEthernet communicates with Ethernet shields over SPI. Therefor all + // StandardFirmataEthernet communicates with Ethernet shields over SPI. Therefore all // SPI pins must be set to IGNORE. Otherwise Firmata would break SPI communication. // add Pin 10 and configure pin 53 as output if using a MEGA with an Ethernet shield. // ignore SPI and pin 4 that is SS for SD-Card on Ethernet-shield for (byte i = 0; i < TOTAL_PINS; i++) { if (IS_PIN_SPI(i) - || 4 == i // SD-Card on Ethernet-shiedl uses pin 4 for SS + || 4 == i // SD-Card on Ethernet-shield uses pin 4 for SS || 10 == i // Ethernet-shield uses pin 10 for SS #if defined(__AVR_ATmega32U4__) || 24 == i // On Leonardo, pin 24 maps to D4 and pin 28 maps to D10 diff --git a/examples/StandardFirmataWiFi/StandardFirmataWiFi.ino b/examples/StandardFirmataWiFi/StandardFirmataWiFi.ino new file mode 100644 index 00000000..7f8478b6 --- /dev/null +++ b/examples/StandardFirmataWiFi/StandardFirmataWiFi.ino @@ -0,0 +1,982 @@ +/* + Firmata is a generic protocol for communicating with microcontrollers + from software on a host computer. It is intended to work with + any host computer software package. + + To download a host software package, please clink on the following link + to open the list of Firmata client libraries your default browser. + + https://github.com/firmata/arduino#firmata-client-libraries + + Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved. + Copyright (C) 2010-2011 Paul Stoffregen. All rights reserved. + Copyright (C) 2009 Shigeru Kobayashi. All rights reserved. + Copyright (C) 2009-2016 Jeff Hoefs. All rights reserved. + Copyright (C) 2015-2016 Jesse Frush. 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. + + Last updated by Jesse Frush: January 7th, 2016 +*/ + +/* + README + + 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) + - Adafruit HUZZAH CC3000 WiFi Shield (support coming soon) + + Follow the instructions in the wifiConfig.h file (wifiConfig.h tab in Arduino IDE) to + configure your particular hardware. + + 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 + - Arduino 101 + - Arduino Mega + + NOTE: If you are using an Arduino WiFi (legacy) shield you cannot use the following pins on + the following boards. Firmata will ignore any requests to use these pins: + + - Arduino Uno or other ATMega328 boards: (D4, D7, D10, D11, D12, D13) + - Arduino Mega: (D4, D7, D10, D50, D51, D52, D53) + - Arduino Due, Zero or Leonardo: (D4, D7, D10) + + If you are using an Arduino WiFi 101 shield you cannot use the following pins on the following + boards: + + - Arduino Due or Zero: (D5, D7, D10) + - Arduino Mega: (D5, D7, D10, D50, D52, D53) +*/ + +#include +#include +#include + +/* + * Uncomment the #define SERIAL_DEBUG line below to receive serial output messages relating to your connection + * that may help in the event of connection issues. If defined, some boards may not begin executing this sketch + * until the Serial console is opened. + */ +//#define SERIAL_DEBUG +#include "utility/firmataDebug.h" + +// follow the instructions in wifiConfig.h to configure your particular hardware +#include "wifiConfig.h" + +#define I2C_WRITE B00000000 +#define I2C_READ B00001000 +#define I2C_READ_CONTINUOUSLY B00010000 +#define I2C_STOP_READING B00011000 +#define I2C_READ_WRITE_MODE_MASK B00011000 +#define I2C_10BIT_ADDRESS_MODE_MASK B00100000 +#define I2C_END_TX_MASK B01000000 +#define I2C_STOP_TX 1 +#define I2C_RESTART_TX 0 +#define I2C_MAX_QUERIES 8 +#define I2C_REGISTER_NOT_SPECIFIED -1 + +// the minimum interval for sampling analog input +#define MINIMUM_SAMPLING_INTERVAL 1 + +#define WIFI_MAX_CONN_ATTEMPTS 3 + +/*============================================================================== + * GLOBAL VARIABLES + *============================================================================*/ + +#ifdef STATIC_IP_ADDRESS + IPAddress local_ip(STATIC_IP_ADDRESS); +#endif + +int wifiConnectionAttemptCounter = 0; +int wifiStatus = WL_IDLE_STATUS; + +/* analog inputs */ +int analogInputsToReport = 0; // bitwise array to store pin reporting + +/* digital input ports */ +byte reportPINs[TOTAL_PORTS]; // 1 = report this port, 0 = silence +byte previousPINs[TOTAL_PORTS]; // previous 8 bits sent + +/* pins configuration */ +byte pinConfig[TOTAL_PINS]; // configuration of every pin +byte portConfigInputs[TOTAL_PORTS]; // each bit: 1 = pin in INPUT, 0 = anything else +int pinState[TOTAL_PINS]; // any value that has been written + +/* timer variables */ +unsigned long currentMillis; // store the current value from millis() +unsigned long previousMillis; // for comparison with currentMillis +unsigned int samplingInterval = 19; // how often to sample analog inputs (in ms) + +/* i2c data */ +struct i2c_device_info { + byte addr; + int reg; + byte bytes; + byte stopTX; +}; + +/* for i2c read continuous mode */ +i2c_device_info query[I2C_MAX_QUERIES]; + +byte i2cRxData[32]; +boolean isI2CEnabled = false; +signed char queryIndex = -1; +// default delay time between i2c read request and Wire.requestFrom() +unsigned int i2cReadDelayTime = 0; + +Servo servos[MAX_SERVOS]; +byte servoPinMap[TOTAL_PINS]; +byte detachedServos[MAX_SERVOS]; +byte detachedServoCount = 0; +byte servoCount = 0; + +boolean isResetting = false; + +/* utility functions */ +void wireWrite(byte data) +{ +#if ARDUINO >= 100 + Wire.write((byte)data); +#else + Wire.send(data); +#endif +} + +byte wireRead(void) +{ +#if ARDUINO >= 100 + return Wire.read(); +#else + return Wire.receive(); +#endif +} + +/*============================================================================== + * FUNCTIONS + *============================================================================*/ + +void attachServo(byte pin, int minPulse, int maxPulse) +{ + if (servoCount < MAX_SERVOS) { + // reuse indexes of detached servos until all have been reallocated + if (detachedServoCount > 0) { + servoPinMap[pin] = detachedServos[detachedServoCount - 1]; + if (detachedServoCount > 0) detachedServoCount--; + } else { + servoPinMap[pin] = servoCount; + servoCount++; + } + if (minPulse > 0 && maxPulse > 0) { + servos[servoPinMap[pin]].attach(PIN_TO_DIGITAL(pin), minPulse, maxPulse); + } else { + servos[servoPinMap[pin]].attach(PIN_TO_DIGITAL(pin)); + } + } else { + Firmata.sendString("Max servos attached"); + } +} + +void detachServo(byte pin) +{ + servos[servoPinMap[pin]].detach(); + // if we're detaching the last servo, decrement the count + // otherwise store the index of the detached servo + if (servoPinMap[pin] == servoCount && servoCount > 0) { + servoCount--; + } else if (servoCount > 0) { + // keep track of detached servos because we want to reuse their indexes + // before incrementing the count of attached servos + detachedServoCount++; + detachedServos[detachedServoCount - 1] = servoPinMap[pin]; + } + + servoPinMap[pin] = 255; +} + +void readAndReportData(byte address, int theRegister, byte numBytes, byte stopTX) { + // allow I2C requests that don't require a register read + // for example, some devices using an interrupt pin to signify new data available + // do not always require the register read so upon interrupt you call Wire.requestFrom() + if (theRegister != I2C_REGISTER_NOT_SPECIFIED) { + Wire.beginTransmission(address); + wireWrite((byte)theRegister); + Wire.endTransmission(stopTX); // default = true + // do not set a value of 0 + if (i2cReadDelayTime > 0) { + // delay is necessary for some devices such as WiiNunchuck + delayMicroseconds(i2cReadDelayTime); + } + } else { + theRegister = 0; // fill the register with a dummy value + } + + Wire.requestFrom(address, numBytes); // all bytes are returned in requestFrom + + // check to be sure correct number of bytes were returned by slave + if (numBytes < Wire.available()) { + Firmata.sendString("I2C: Too many bytes received"); + } else if (numBytes > Wire.available()) { + Firmata.sendString("I2C: Too few bytes received"); + } + + i2cRxData[0] = address; + i2cRxData[1] = theRegister; + + for (int i = 0; i < numBytes && Wire.available(); i++) { + i2cRxData[2 + i] = wireRead(); + } + + // send slave address, register and received bytes + Firmata.sendSysex(SYSEX_I2C_REPLY, numBytes + 2, i2cRxData); +} + +void outputPort(byte portNumber, byte portValue, byte forceSend) +{ + // pins not configured as INPUT are cleared to zeros + portValue = portValue & portConfigInputs[portNumber]; + // only send if the value is different than previously sent + if (forceSend || previousPINs[portNumber] != portValue) { + Firmata.sendDigitalPort(portNumber, portValue); + previousPINs[portNumber] = portValue; + } +} + +/* ----------------------------------------------------------------------------- + * check all the active digital inputs for change of state, then add any events + * to the Stream output queue using Stream.write() */ +void checkDigitalInputs(void) +{ + /* Using non-looping code allows constants to be given to readPort(). + * The compiler will apply substantial optimizations if the inputs + * to readPort() are compile-time constants. */ + if (TOTAL_PORTS > 0 && reportPINs[0]) outputPort(0, readPort(0, portConfigInputs[0]), false); + if (TOTAL_PORTS > 1 && reportPINs[1]) outputPort(1, readPort(1, portConfigInputs[1]), false); + if (TOTAL_PORTS > 2 && reportPINs[2]) outputPort(2, readPort(2, portConfigInputs[2]), false); + if (TOTAL_PORTS > 3 && reportPINs[3]) outputPort(3, readPort(3, portConfigInputs[3]), false); + if (TOTAL_PORTS > 4 && reportPINs[4]) outputPort(4, readPort(4, portConfigInputs[4]), false); + if (TOTAL_PORTS > 5 && reportPINs[5]) outputPort(5, readPort(5, portConfigInputs[5]), false); + if (TOTAL_PORTS > 6 && reportPINs[6]) outputPort(6, readPort(6, portConfigInputs[6]), false); + if (TOTAL_PORTS > 7 && reportPINs[7]) outputPort(7, readPort(7, portConfigInputs[7]), false); + if (TOTAL_PORTS > 8 && reportPINs[8]) outputPort(8, readPort(8, portConfigInputs[8]), false); + if (TOTAL_PORTS > 9 && reportPINs[9]) outputPort(9, readPort(9, portConfigInputs[9]), false); + if (TOTAL_PORTS > 10 && reportPINs[10]) outputPort(10, readPort(10, portConfigInputs[10]), false); + if (TOTAL_PORTS > 11 && reportPINs[11]) outputPort(11, readPort(11, portConfigInputs[11]), false); + if (TOTAL_PORTS > 12 && reportPINs[12]) outputPort(12, readPort(12, portConfigInputs[12]), false); + if (TOTAL_PORTS > 13 && reportPINs[13]) outputPort(13, readPort(13, portConfigInputs[13]), false); + if (TOTAL_PORTS > 14 && reportPINs[14]) outputPort(14, readPort(14, portConfigInputs[14]), false); + if (TOTAL_PORTS > 15 && reportPINs[15]) outputPort(15, readPort(15, portConfigInputs[15]), false); +} + +// ----------------------------------------------------------------------------- +/* 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 + */ +void setPinModeCallback(byte pin, int mode) +{ + if (pinConfig[pin] == PIN_MODE_IGNORE) + return; + + if (pinConfig[pin] == PIN_MODE_I2C && isI2CEnabled && mode != PIN_MODE_I2C) { + // disable i2c so pins can be used for other functions + // the following if statements should reconfigure the pins properly + disableI2CPins(); + } + if (IS_PIN_DIGITAL(pin) && mode != PIN_MODE_SERVO) { + if (servoPinMap[pin] < MAX_SERVOS && servos[servoPinMap[pin]].attached()) { + detachServo(pin); + } + } + if (IS_PIN_ANALOG(pin)) { + reportAnalogCallback(PIN_TO_ANALOG(pin), mode == PIN_MODE_ANALOG ? 1 : 0); // turn on/off reporting + } + if (IS_PIN_DIGITAL(pin)) { + if (mode == INPUT || mode == PIN_MODE_PULLUP) { + portConfigInputs[pin / 8] |= (1 << (pin & 7)); + } else { + portConfigInputs[pin / 8] &= ~(1 << (pin & 7)); + } + } + pinState[pin] = 0; + switch (mode) { + case PIN_MODE_ANALOG: + if (IS_PIN_ANALOG(pin)) { + if (IS_PIN_DIGITAL(pin)) { + pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver +#if ARDUINO <= 100 + // deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6 + digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups +#endif + } + pinConfig[pin] = PIN_MODE_ANALOG; + } + break; + case INPUT: + if (IS_PIN_DIGITAL(pin)) { + pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver +#if ARDUINO <= 100 + // deprecated since Arduino 1.0.1 - TODO: drop support in Firmata 2.6 + digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups +#endif + pinConfig[pin] = INPUT; + } + break; + case PIN_MODE_PULLUP: + if (IS_PIN_DIGITAL(pin)) { + pinMode(PIN_TO_DIGITAL(pin), INPUT_PULLUP); + pinConfig[pin] = PIN_MODE_PULLUP; + pinState[pin] = 1; + } + break; + case OUTPUT: + if (IS_PIN_DIGITAL(pin)) { + digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable PWM + pinMode(PIN_TO_DIGITAL(pin), OUTPUT); + pinConfig[pin] = OUTPUT; + } + break; + case PIN_MODE_PWM: + if (IS_PIN_PWM(pin)) { + pinMode(PIN_TO_PWM(pin), OUTPUT); + analogWrite(PIN_TO_PWM(pin), 0); + pinConfig[pin] = PIN_MODE_PWM; + } + break; + case PIN_MODE_SERVO: + if (IS_PIN_DIGITAL(pin)) { + pinConfig[pin] = PIN_MODE_SERVO; + if (servoPinMap[pin] == 255 || !servos[servoPinMap[pin]].attached()) { + // pass -1 for min and max pulse values to use default values set + // by Servo library + attachServo(pin, -1, -1); + } + } + break; + case PIN_MODE_I2C: + if (IS_PIN_I2C(pin)) { + // mark the pin as i2c + // the user must call I2C_CONFIG to enable I2C for a device + pinConfig[pin] = PIN_MODE_I2C; + } + break; + default: + Firmata.sendString("Unknown pin mode"); // TODO: put error msgs in EEPROM + } + // TODO: save status to EEPROM here, if changed +} + +/* + * Sets the value of an individual pin. Useful if you want to set a pin value but + * are not tracking the digital port state. + * Can only be used on pins configured as OUTPUT. + * Cannot be used to enable pull-ups on Digital INPUT pins. + */ +void setPinValueCallback(byte pin, int value) +{ + if (pin < TOTAL_PINS && IS_PIN_DIGITAL(pin)) { + if (pinConfig[pin] == OUTPUT) { + pinState[pin] = value; + digitalWrite(PIN_TO_DIGITAL(pin), value); + } + } +} + +void analogWriteCallback(byte pin, int value) +{ + if (pin < TOTAL_PINS) { + switch (pinConfig[pin]) { + case PIN_MODE_SERVO: + if (IS_PIN_DIGITAL(pin)) + servos[servoPinMap[pin]].write(value); + pinState[pin] = value; + break; + case PIN_MODE_PWM: + if (IS_PIN_PWM(pin)) + analogWrite(PIN_TO_PWM(pin), value); + pinState[pin] = value; + break; + } + } +} + +void digitalWriteCallback(byte port, int value) +{ + byte pin, lastPin, pinValue, mask = 1, pinWriteMask = 0; + + if (port < TOTAL_PORTS) { + // create a mask of the pins on this port that are writable. + lastPin = port * 8 + 8; + if (lastPin > TOTAL_PINS) lastPin = TOTAL_PINS; + for (pin = port * 8; pin < lastPin; pin++) { + // do not disturb non-digital pins (eg, Rx & Tx) + if (IS_PIN_DIGITAL(pin)) { + // do not touch pins in PWM, ANALOG, SERVO or other modes + if (pinConfig[pin] == OUTPUT || pinConfig[pin] == INPUT) { + pinValue = ((byte)value & mask) ? 1 : 0; + if (pinConfig[pin] == OUTPUT) { + pinWriteMask |= mask; + } else if (pinConfig[pin] == INPUT && pinValue == 1 && pinState[pin] != 1) { + // only handle INPUT here for backwards compatibility +#if ARDUINO > 100 + pinMode(pin, INPUT_PULLUP); +#else + // only write to the INPUT pin to enable pullups if Arduino v1.0.0 or earlier + pinWriteMask |= mask; +#endif + } + pinState[pin] = pinValue; + } + } + mask = mask << 1; + } + writePort(port, (byte)value, pinWriteMask); + } +} + + +// ----------------------------------------------------------------------------- +/* sets bits in a bit array (int) to toggle the reporting of the analogIns + */ +//void FirmataClass::setAnalogPinReporting(byte pin, byte state) { +//} +void reportAnalogCallback(byte analogPin, int value) +{ + if (analogPin < TOTAL_ANALOG_PINS) { + if (value == 0) { + analogInputsToReport = analogInputsToReport & ~ (1 << analogPin); + } else { + analogInputsToReport = analogInputsToReport | (1 << analogPin); + // prevent during system reset or all analog pin values will be reported + // which may report noise for unconnected analog pins + if (!isResetting) { + // Send pin value immediately. This is helpful when connected via + // ethernet, wi-fi or bluetooth so pin states can be known upon + // reconnecting. + Firmata.sendAnalog(analogPin, analogRead(analogPin)); + } + } + } + // TODO: save status to EEPROM here, if changed +} + +void reportDigitalCallback(byte port, int value) +{ + if (port < TOTAL_PORTS) { + reportPINs[port] = (byte)value; + // Send port value immediately. This is helpful when connected via + // ethernet, wi-fi or bluetooth so pin states can be known upon + // reconnecting. + if (value) outputPort(port, readPort(port, portConfigInputs[port]), true); + } + // do not disable analog reporting on these 8 pins, to allow some + // pins used for digital, others analog. Instead, allow both types + // of reporting to be enabled, but check if the pin is configured + // as analog when sampling the analog inputs. Likewise, while + // scanning digital pins, portConfigInputs will mask off values from any + // pins configured as analog +} + +/*============================================================================== + * SYSEX-BASED commands + *============================================================================*/ + +void sysexCallback(byte command, byte argc, byte *argv) +{ + byte mode; + byte stopTX; + byte slaveAddress; + byte data; + int slaveRegister; + unsigned int delayTime; + + switch (command) { + case I2C_REQUEST: + mode = argv[1] & I2C_READ_WRITE_MODE_MASK; + if (argv[1] & I2C_10BIT_ADDRESS_MODE_MASK) { + Firmata.sendString("10-bit addressing not supported"); + return; + } + else { + slaveAddress = argv[0]; + } + + // need to invert the logic here since 0 will be default for client + // libraries that have not updated to add support for restart tx + if (argv[1] & I2C_END_TX_MASK) { + stopTX = I2C_RESTART_TX; + } + else { + stopTX = I2C_STOP_TX; // default + } + + switch (mode) { + case I2C_WRITE: + Wire.beginTransmission(slaveAddress); + for (byte i = 2; i < argc; i += 2) { + data = argv[i] + (argv[i + 1] << 7); + wireWrite(data); + } + Wire.endTransmission(); + delayMicroseconds(70); + break; + case I2C_READ: + if (argc == 6) { + // a slave register is specified + slaveRegister = argv[2] + (argv[3] << 7); + data = argv[4] + (argv[5] << 7); // bytes to read + } + else { + // a slave register is NOT specified + slaveRegister = I2C_REGISTER_NOT_SPECIFIED; + data = argv[2] + (argv[3] << 7); // bytes to read + } + readAndReportData(slaveAddress, (int)slaveRegister, data, stopTX); + break; + case I2C_READ_CONTINUOUSLY: + if ((queryIndex + 1) >= I2C_MAX_QUERIES) { + // too many queries, just ignore + Firmata.sendString("too many I2C queries"); + break; + } + if (argc == 6) { + // a slave register is specified + slaveRegister = argv[2] + (argv[3] << 7); + data = argv[4] + (argv[5] << 7); // bytes to read + } + else { + // a slave register is NOT specified + slaveRegister = (int)I2C_REGISTER_NOT_SPECIFIED; + data = argv[2] + (argv[3] << 7); // bytes to read + } + queryIndex++; + query[queryIndex].addr = slaveAddress; + query[queryIndex].reg = slaveRegister; + query[queryIndex].bytes = data; + query[queryIndex].stopTX = stopTX; + break; + case I2C_STOP_READING: + byte queryIndexToSkip; + // if read continuous mode is enabled for only 1 i2c device, disable + // read continuous reporting for that device + if (queryIndex <= 0) { + queryIndex = -1; + } else { + // if read continuous mode is enabled for multiple devices, + // determine which device to stop reading and remove it's data from + // the array, shifiting other array data to fill the space + for (byte i = 0; i < queryIndex + 1; i++) { + if (query[i].addr == slaveAddress) { + queryIndexToSkip = i; + break; + } + } + + for (byte i = queryIndexToSkip; i < queryIndex + 1; i++) { + if (i < I2C_MAX_QUERIES) { + query[i].addr = query[i + 1].addr; + query[i].reg = query[i + 1].reg; + query[i].bytes = query[i + 1].bytes; + query[i].stopTX = query[i + 1].stopTX; + } + } + queryIndex--; + } + break; + default: + break; + } + break; + case I2C_CONFIG: + delayTime = (argv[0] + (argv[1] << 7)); + + if (delayTime > 0) { + i2cReadDelayTime = delayTime; + } + + if (!isI2CEnabled) { + enableI2CPins(); + } + + break; + case SERVO_CONFIG: + if (argc > 4) { + // these vars are here for clarity, they'll optimized away by the compiler + byte pin = argv[0]; + int minPulse = argv[1] + (argv[2] << 7); + int maxPulse = argv[3] + (argv[4] << 7); + + if (IS_PIN_DIGITAL(pin)) { + if (servoPinMap[pin] < MAX_SERVOS && servos[servoPinMap[pin]].attached()) { + detachServo(pin); + } + attachServo(pin, minPulse, maxPulse); + setPinModeCallback(pin, PIN_MODE_SERVO); + } + } + break; + case SAMPLING_INTERVAL: + if (argc > 1) { + samplingInterval = argv[0] + (argv[1] << 7); + if (samplingInterval < MINIMUM_SAMPLING_INTERVAL) { + samplingInterval = MINIMUM_SAMPLING_INTERVAL; + } + } else { + //Firmata.sendString("Not enough data"); + } + break; + case EXTENDED_ANALOG: + if (argc > 1) { + int val = argv[1]; + if (argc > 2) val |= (argv[2] << 7); + if (argc > 3) val |= (argv[3] << 14); + analogWriteCallback(argv[0], val); + } + break; + case CAPABILITY_QUERY: + Firmata.write(START_SYSEX); + Firmata.write(CAPABILITY_RESPONSE); + for (byte pin = 0; pin < TOTAL_PINS; pin++) { + if (IS_PIN_DIGITAL(pin)) { + Firmata.write((byte)INPUT); + Firmata.write(1); + Firmata.write((byte)PIN_MODE_PULLUP); + Firmata.write(1); + Firmata.write((byte)OUTPUT); + Firmata.write(1); + } + if (IS_PIN_ANALOG(pin)) { + Firmata.write(PIN_MODE_ANALOG); + Firmata.write(10); // 10 = 10-bit resolution + } + if (IS_PIN_PWM(pin)) { + Firmata.write(PIN_MODE_PWM); + Firmata.write(8); // 8 = 8-bit resolution + } + if (IS_PIN_DIGITAL(pin)) { + Firmata.write(PIN_MODE_SERVO); + Firmata.write(14); + } + if (IS_PIN_I2C(pin)) { + Firmata.write(PIN_MODE_I2C); + Firmata.write(1); // TODO: could assign a number to map to SCL or SDA + } + Firmata.write(127); + } + Firmata.write(END_SYSEX); + break; + case PIN_STATE_QUERY: + if (argc > 0) { + byte pin = argv[0]; + Firmata.write(START_SYSEX); + Firmata.write(PIN_STATE_RESPONSE); + Firmata.write(pin); + if (pin < TOTAL_PINS) { + Firmata.write((byte)pinConfig[pin]); + Firmata.write((byte)pinState[pin] & 0x7F); + if (pinState[pin] & 0xFF80) Firmata.write((byte)(pinState[pin] >> 7) & 0x7F); + if (pinState[pin] & 0xC000) Firmata.write((byte)(pinState[pin] >> 14) & 0x7F); + } + Firmata.write(END_SYSEX); + } + break; + case ANALOG_MAPPING_QUERY: + Firmata.write(START_SYSEX); + Firmata.write(ANALOG_MAPPING_RESPONSE); + for (byte pin = 0; pin < TOTAL_PINS; pin++) { + Firmata.write(IS_PIN_ANALOG(pin) ? PIN_TO_ANALOG(pin) : 127); + } + Firmata.write(END_SYSEX); + break; + } +} + +void enableI2CPins() +{ + byte i; + // is there a faster way to do this? would probaby require importing + // Arduino.h to get SCL and SDA pins + for (i = 0; i < TOTAL_PINS; i++) { + if (IS_PIN_I2C(i)) { + // mark pins as i2c so they are ignore in non i2c data requests + setPinModeCallback(i, PIN_MODE_I2C); + } + } + + isI2CEnabled = true; + + Wire.begin(); +} + +/* disable the i2c pins so they can be used for other functions */ +void disableI2CPins() { + isI2CEnabled = false; + // disable read continuous mode for all devices + queryIndex = -1; +} + +/*============================================================================== + * SETUP() + *============================================================================*/ + +void systemResetCallback() +{ + isResetting = true; + + // initialize a defalt state + // TODO: option to load config from EEPROM instead of default + + if (isI2CEnabled) { + disableI2CPins(); + } + + for (byte i = 0; i < TOTAL_PORTS; i++) { + reportPINs[i] = false; // by default, reporting off + portConfigInputs[i] = 0; // until activated + previousPINs[i] = 0; + } + + for (byte i = 0; i < TOTAL_PINS; i++) { + // pins with analog capability default to analog input + // otherwise, pins default to digital output + if (IS_PIN_ANALOG(i)) { + // turns off pullup, configures everything + setPinModeCallback(i, PIN_MODE_ANALOG); + } else if (IS_PIN_DIGITAL(i)) { + // sets the output to 0, configures portConfigInputs + setPinModeCallback(i, OUTPUT); + } + + servoPinMap[i] = 255; + } + // by default, do not report any analog inputs + analogInputsToReport = 0; + + detachedServoCount = 0; + servoCount = 0; + + /* send digital inputs to set the initial state on the host computer, + * since once in the loop(), this firmware will only send on change */ + /* + TODO: this can never execute, since no pins default to digital input + but it will be needed when/if we support EEPROM stored config + for (byte i=0; i < TOTAL_PORTS; i++) { + outputPort(i, readPort(i, portConfigInputs[i]), true); + } + */ + isResetting = false; +} + +void printWifiStatus() { +#if defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) + 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) + { + // print the SSID of the network you're attached to: + DEBUG_PRINT( "SSID: " ); + +#if defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) + DEBUG_PRINTLN( WiFi.SSID() ); +#endif //defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) + + // print your WiFi shield's IP address: + DEBUG_PRINT( "IP Address: " ); + +#if defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) + IPAddress ip = WiFi.localIP(); + DEBUG_PRINTLN( ip ); +#endif //defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) + + // print the received signal strength: + DEBUG_PRINT( "signal strength (RSSI): " ); + +#if defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) + long rssi = WiFi.RSSI(); + DEBUG_PRINT( rssi ); +#endif //defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) + + DEBUG_PRINTLN( " dBm" ); + } +} + +void setup() +{ + /* + * WIFI SETUP + */ + DEBUG_BEGIN(9600); + + /* + * 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." ); +#elif defined(ARDUINO_WIFI_SHIELD) + DEBUG_PRINTLN( "using the legacy WiFi library." ); +#elif defined(HUZZAH_WIFI) + DEBUG_PRINTLN( "using the HUZZAH WiFi library." ); +//else should never happen here as error-checking in wifiConfig.h will catch this +#endif //defined(WIFI_101) + + /* + * Configure WiFi IP Address + */ +#ifdef STATIC_IP_ADDRESS + DEBUG_PRINT( "Using static IP: " ); + DEBUG_PRINTLN( local_ip ); + //you can also provide a static IP in the begin() functions, but this simplifies + //ifdef logic in this sketch due to support for all different encryption types. + stream.config( local_ip ); +#else + 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; + } + +#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; + } + +#else //OPEN network + DEBUG_PRINTLN( "Connecting to an open network ..." ); + while(wifiStatus != WL_CONNECTED) { + wifiStatus = stream.begin( ssid, SERVER_PORT ); + delay(5000); // TODO - determine minimum delay + if (++wifiConnectionAttemptCounter > WIFI_MAX_CONN_ATTEMPTS) break; + } +#endif //defined(WIFI_WEP_SECURITY) + + DEBUG_PRINTLN( "WiFi setup done" ); + printWifiStatus(); + + /* + * FIRMATA SETUP + */ + Firmata.setFirmwareVersion(FIRMATA_MAJOR_VERSION, FIRMATA_MINOR_VERSION); + + Firmata.attach(ANALOG_MESSAGE, analogWriteCallback); + Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback); + Firmata.attach(REPORT_ANALOG, reportAnalogCallback); + Firmata.attach(REPORT_DIGITAL, reportDigitalCallback); + Firmata.attach(SET_PIN_MODE, setPinModeCallback); + Firmata.attach(SET_DIGITAL_PIN_VALUE, setPinValueCallback); + 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. + + 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__) + ) { +#elif defined (WIFI_101) + if (IS_IGNORE_WIFI101_SHIELD(i)) { +#elif defined (HUZZAH_WIFI) + // TODO + if (false) { +#else + if (false) { +#endif + pinConfig[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; + +#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 + + // start up Network Firmata: + Firmata.begin(stream); + systemResetCallback(); // reset to default config +} + +/*============================================================================== + * LOOP() + *============================================================================*/ +void loop() +{ + byte pin, analogPin; + + /* DIGITALREAD - as fast as possible, check for changes and output them to the + * Stream buffer using Stream.write() */ + checkDigitalInputs(); + + /* STREAMREAD - processing incoming messagse as soon as possible, while still + * checking digital inputs. */ + while (Firmata.available()) { + Firmata.processInput(); + } + + // TODO - ensure that Stream buffer doesn't go over 60 bytes + + currentMillis = millis(); + if (currentMillis - previousMillis > samplingInterval) { + previousMillis += samplingInterval; + /* ANALOGREAD - do all analogReads() at the configured sampling interval */ + for (pin = 0; pin < TOTAL_PINS; pin++) { + if (IS_PIN_ANALOG(pin) && pinConfig[pin] == PIN_MODE_ANALOG) { + analogPin = PIN_TO_ANALOG(pin); + if (analogInputsToReport & (1 << analogPin)) { + Firmata.sendAnalog(analogPin, analogRead(analogPin)); + } + } + } + // report i2c data for all device with read continuous mode enabled + if (queryIndex > -1) { + for (byte i = 0; i < queryIndex + 1; i++) { + readAndReportData(query[i].addr, query[i].reg, query[i].bytes, query[i].stopTX); + } + } + } + + stream.maintain(); +} diff --git a/examples/StandardFirmataWiFi/wifiConfig.h b/examples/StandardFirmataWiFi/wifiConfig.h new file mode 100644 index 00000000..24790e16 --- /dev/null +++ b/examples/StandardFirmataWiFi/wifiConfig.h @@ -0,0 +1,153 @@ +/*============================================================================== + * WIFI CONFIGURATION + * + * You must configure your particular hardware. Follow the steps below. + *============================================================================*/ + +// STEP 1 [REQUIRED] +// Uncomment / comment the appropriate set of includes for your hardware (OPTION A, B or C) +// Option A is enabled by default. + +/* + * OPTION A: Configure for Arduino WiFi shield + * + * This will configure StandardFirmataWiFi to use the original WiFi library (deprecated) provided + * with the Arduino IDE. It is supported by the Arduino WiFi shield (a discontinued product) and + * is compatible with 802.11 B/G networks. + * + * To configure StandardFirmataWiFi to use the Arduino WiFi shield + * leave the #define below uncommented. + */ +#define ARDUINO_WIFI_SHIELD + +//do not modify these next 4 lines +#ifdef ARDUINO_WIFI_SHIELD +#include "utility/WiFiStream.h" +WiFiStream stream; +#endif + +/* + * OPTION B: Configure for WiFi 101 + * + * This will configure StandardFirmataWiFi to use the WiFi101 library, which works with the Arduino WiFi101 + * shield and devices that have the WiFi101 chip built in (such as the MKR1000). It is compatible + * with 802.11 B/G/N networks. + * + * To enable, uncomment the #define WIFI_101 below and verify the #define values under + * options A and C are commented out. + * + * IMPORTANT: You must have the WiFI 101 library installed. To easily install this library, opent the library manager via: + * Arduino IDE Menus: Sketch > Include Library > Manage Libraries > filter search for "WiFi101" > Select the result and click 'install' + */ +//#define WIFI_101 + +//do not modify these next 4 lines +#ifdef WIFI_101 +#include "utility/WiFi101Stream.h" +WiFi101Stream stream; +#endif + +/* + * OPTION C: Configure for HUZZAH + * + * HUZZAH is not yet supported, this will be added in a later revision to StandardFirmataWiFi + */ + +//------------------------------ +// TODO +//------------------------------ +//#define HUZZAH_WIFI + + +// STEP 2 [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] +// if you want to use a static IP (v4) address, uncomment the line below. You can also change the IP. +// if this line is commented out, the WiFi shield will attempt to get an IP from the DHCP server +// #define STATIC_IP_ADDRESS 192,168,1,113 + +// STEP 4 [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] +// determine your network security type (OPTION A, B, or C). Option A is the most common, and the default. + + +/* + * OPTION A: WPA / WPA2 + * + * WPA is the most common network security type. A passphrase is required to connect to this type. + * + * To enable, leave #define WIFI_WPA_SECURITY uncommented below, set your wpa_passphrase value appropriately, + * and do not uncomment the #define values under options B and C + */ +#define WIFI_WPA_SECURITY + +#ifdef WIFI_WPA_SECURITY +char wpa_passphrase[] = "your_wpa_passphrase"; +#endif //WIFI_WPA_SECURITY + +/* + * OPTION B: WEP + * + * WEP is a less common (and regarded as less safe) security type. A WEP key and its associated index are required + * to connect to this type. + * + * To enable, Uncomment the #define below, set your wep_index and wep_key values appropriately, and verify + * the #define values under options A and C are commented out. + */ +//#define WIFI_WEP_SECURITY + +#ifdef WIFI_WEP_SECURITY +//The wep_index below is a zero-indexed value. +//Valid indices are [0-3], even if your router/gateway numbers your keys [1-4]. +byte wep_index = 0; +char wep_key[] = "your_wep_key"; +#endif //WIFI_WEP_SECURITY + + +/* + * OPTION C: Open network (no security) + * + * Open networks have no security, can be connected to by any device that knows the ssid, and are unsafe. + * + * To enable, uncomment #define WIFI_NO_SECURITY below and verify the #define values + * under options A and B are commented out. + */ +//#define WIFI_NO_SECURITY + +/*============================================================================== + * CONFIGURATION ERROR CHECK (don't change anything here) + *============================================================================*/ + +#if ((defined(ARDUINO_WIFI_SHIELD) && (defined(WIFI_101) || defined(HUZZAH_WIFI))) || (defined(WIFI_101) && defined(HUZZAH_WIFI))) +#error "you may not define more than one wifi device type in wifiConfig.h." +#endif //WIFI device type check + +#if !(defined(ARDUINO_WIFI_SHIELD) || defined(WIFI_101) || defined(HUZZAH_WIFI)) +#error "you must define a wifi device type in wifiConfig.h." +#endif + +#if ((defined(WIFI_NO_SECURITY) && (defined(WIFI_WEP_SECURITY) || defined(WIFI_WPA_SECURITY))) || (defined(WIFI_WEP_SECURITY) && defined(WIFI_WPA_SECURITY))) +#error "you may not define more than one security type at the same time in wifiConfig.h." +#endif //WIFI_* security define check + +#if !(defined(WIFI_NO_SECURITY) || defined(WIFI_WEP_SECURITY) || defined(WIFI_WPA_SECURITY)) +#error "you must define a wifi security type in wifiConfig.h." +#endif //WIFI_* security define check + +/*============================================================================== + * PIN IGNORE MACROS (don't change anything here) + *============================================================================*/ + +// 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 +// TODO - need to differentiate between Arduino WiFi1 101 Shield and Arduino MKR1000 +#define IS_IGNORE_WIFI101_SHIELD(p) ((p) == 10 || (IS_PIN_SPI(p) && (p) != SS) || (p) == 5 || (p) == 7) + +// 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) + diff --git a/utility/WiFi101Stream.cpp b/utility/WiFi101Stream.cpp new file mode 100644 index 00000000..0c7945de --- /dev/null +++ b/utility/WiFi101Stream.cpp @@ -0,0 +1,4 @@ +/* + * Implementation is in WiFi101Stream.h to avoid linker issues. Legacy WiFi and modern WiFi101 both define WiFiClass which + * will cause linker errors whenever Firmata.h is included. + */ \ No newline at end of file diff --git a/utility/WiFi101Stream.h b/utility/WiFi101Stream.h new file mode 100644 index 00000000..da3ffbf4 --- /dev/null +++ b/utility/WiFi101Stream.h @@ -0,0 +1,243 @@ +#ifndef WIFI101_STREAM_H +#define WIFI101_STREAM_H + +#include +#include +#include + + +class WiFi101Stream : public Stream +{ +private: + WiFiServer _server = WiFiServer(23); + WiFiClient _client; + + //configuration members + IPAddress _local_ip; + uint16_t _port = 0; + uint8_t _key_idx = 0; //WEP + const char *_key = nullptr; //WEP + const char *_passphrase = nullptr; //WPA + char *_ssid = nullptr; + + inline int connect_client() + { + if( !( _client && _client.connected() ) ) + { + WiFiClient newClient = _server.available(); + if( !newClient ) + { + return 0; + } + + _client = newClient; + } + return 1; + } + + inline bool is_ready() + { + uint8_t status = WiFi.status(); + return !( status == WL_NO_SHIELD || status == WL_CONNECTED ); + } + +public: + WiFi101Stream() {}; + + // allows another way to configure a static IP before begin is called + inline void config(IPAddress local_ip) + { + _local_ip = local_ip; + WiFi.config( local_ip ); + } + + // get DCHP IP + inline IPAddress localIP() + { + return WiFi.localIP(); + } + + inline bool maintain() + { + if( connect_client() ) return true; + + stop(); + int result = 0; + if( WiFi.status() != WL_CONNECTED ) + { + if( _local_ip ) + { + WiFi.config( _local_ip ); + } + + if( _passphrase ) + { + result = WiFi.begin( _ssid, _passphrase); + } + else if( _key_idx && _key ) + { + result = WiFi.begin( _ssid, _key_idx, _key ); + } + else + { + result = WiFi.begin( _ssid ); + } + } + if( result == 0 ) return false; + + _server = WiFiServer( _port ); + _server.begin(); + return result; + } + +/****************************************************************************** + * Connection functions with DHCP + ******************************************************************************/ + + //OPEN networks + inline int begin(char *ssid, uint16_t port) + { + if( !is_ready() ) return 0; + + _ssid = ssid; + _port = port; + int result = WiFi.begin( ssid ); + if( result == 0 ) return 0; + + _server = WiFiServer( port ); + _server.begin(); + return result; + } + + //WEP-encrypted networks + inline int begin(char *ssid, uint8_t key_idx, const char *key, uint16_t port) + { + 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; + } + + //WPA-encrypted networks + inline int begin(char *ssid, const char *passphrase, uint16_t port) + { + if( !is_ready() ) return 0; + + _ssid = ssid; + _port = port; + _passphrase = passphrase; + + int result = WiFi.begin( ssid, passphrase); + if( result == 0 ) return 0; + + _server = WiFiServer( port ); + _server.begin(); + return result; + } + +/****************************************************************************** + * Connection functions without DHCP + ******************************************************************************/ + + //OPEN networks with static IP + inline int begin(char *ssid, IPAddress local_ip, uint16_t port) + { + 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; + } + + //WEP-encrypted networks with static IP + inline int begin(char *ssid, IPAddress local_ip, uint8_t key_idx, const char *key, uint16_t port) + { + 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; + } + + //WPA-encrypted networks with static IP + inline int begin(char *ssid, IPAddress local_ip, const char *passphrase, uint16_t port) + { + 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; + } + +/****************************************************************************** + * Stream implementations + ******************************************************************************/ + + inline int available() + { + return connect_client() ? _client.available() : 0; + } + + inline void flush() + { + if( _client ) _client.flush(); + } + + inline int peek() + { + return connect_client() ? _client.peek(): 0; + } + + inline int read() + { + return connect_client() ? _client.read() : -1; + } + + inline void stop() + { + _client.stop(); + } + + inline size_t write(uint8_t byte) + { + if( connect_client() ) _client.write( byte ); + } +}; + +#endif //WIFI101_STREAM_H diff --git a/utility/WiFiStream.cpp b/utility/WiFiStream.cpp new file mode 100644 index 00000000..f12f4c32 --- /dev/null +++ b/utility/WiFiStream.cpp @@ -0,0 +1,4 @@ +/* + * Implementation is in WiFiStream.h to avoid linker issues. Legacy WiFi and modern WiFi101 both define WiFiClass which + * will cause linker errors whenever Firmata.h is included. + */ \ No newline at end of file diff --git a/utility/WiFiStream.h b/utility/WiFiStream.h new file mode 100644 index 00000000..303dc1b8 --- /dev/null +++ b/utility/WiFiStream.h @@ -0,0 +1,242 @@ +#ifndef WIFI_STREAM_H +#define WIFI_STREAM_H + +#include +#include +#include + +class WiFiStream : public Stream +{ +private: + WiFiServer _server = WiFiServer(23); + WiFiClient _client; + + //configuration members + IPAddress _local_ip; + uint16_t _port = 0; + uint8_t _key_idx = 0; //WEP + const char *_key = nullptr; //WEP + const char *_passphrase = nullptr; //WPA + char *_ssid = nullptr; + + inline int connect_client() + { + if( !( _client && _client.connected() ) ) + { + WiFiClient newClient = _server.available(); + if( !newClient ) + { + return 0; + } + + _client = newClient; + } + return 1; + } + + inline bool is_ready() + { + uint8_t status = WiFi.status(); + return !( status == WL_NO_SHIELD || status == WL_CONNECTED ); + } + +public: + WiFiStream() {}; + + // allows another way to configure a static IP before begin is called + inline void config(IPAddress local_ip) + { + _local_ip = local_ip; + WiFi.config( local_ip ); + } + + // get DCHP IP + inline IPAddress localIP() + { + return WiFi.localIP(); + } + + inline bool maintain() + { + if( connect_client() ) return true; + + stop(); + int result = 0; + if( WiFi.status() != WL_CONNECTED ) + { + if( _local_ip ) + { + WiFi.config( _local_ip ); + } + + if( _passphrase ) + { + result = WiFi.begin( _ssid, _passphrase); + } + else if( _key_idx && _key ) + { + result = WiFi.begin( _ssid, _key_idx, _key ); + } + else + { + result = WiFi.begin( _ssid ); + } + } + if( result == 0 ) return false; + + _server = WiFiServer( _port ); + _server.begin(); + return result; + } + +/****************************************************************************** + * Connection functions with DHCP + ******************************************************************************/ + + //OPEN networks + inline int begin(char *ssid, uint16_t port) + { + if( !is_ready() ) return 0; + + _ssid = ssid; + _port = port; + int result = WiFi.begin( ssid ); + if( result == 0 ) return 0; + + _server = WiFiServer( port ); + _server.begin(); + return result; + } + + //WEP-encrypted networks + inline int begin(char *ssid, uint8_t key_idx, const char *key, uint16_t port) + { + 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; + } + + //WPA-encrypted networks + inline int begin(char *ssid, const char *passphrase, uint16_t port) + { + if( !is_ready() ) return 0; + + _ssid = ssid; + _port = port; + _passphrase = passphrase; + + int result = WiFi.begin( ssid, passphrase); + if( result == 0 ) return 0; + + _server = WiFiServer( port ); + _server.begin(); + return result; + } + +/****************************************************************************** + * Connection functions without DHCP + ******************************************************************************/ + + //OPEN networks with static IP + inline int begin(char *ssid, IPAddress local_ip, uint16_t port) + { + 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; + } + + //WEP-encrypted networks with static IP + inline int begin(char *ssid, IPAddress local_ip, uint8_t key_idx, const char *key, uint16_t port) + { + 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; + } + + //WPA-encrypted networks with static IP + inline int begin(char *ssid, IPAddress local_ip, const char *passphrase, uint16_t port) + { + 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; + } + +/****************************************************************************** + * Stream implementations + ******************************************************************************/ + + inline int available() + { + return connect_client() ? _client.available() : 0; + } + + inline void flush() + { + if( _client ) _client.flush(); + } + + inline int peek() + { + return connect_client() ? _client.peek(): 0; + } + + inline int read() + { + return connect_client() ? _client.read() : -1; + } + + inline void stop() + { + _client.stop(); + } + + inline size_t write(uint8_t byte) + { + if( connect_client() ) _client.write( byte ); + } +}; + +#endif //WIFI_STREAM_H