diff --git a/EmotiBit.cpp b/EmotiBit.cpp index 4f8e1c6f..824a5618 100644 --- a/EmotiBit.cpp +++ b/EmotiBit.cpp @@ -110,8 +110,6 @@ uint8_t EmotiBit::setup(String firmwareVariant) #endif #ifdef ARDUINO_FEATHER_ESP32 - esp_bt_controller_disable(); - // ToDo: assess similarity with btStop(); setCpuFrequencyMhz(CPU_HZ / 1000000); // 80MHz has been tested working to save battery life #endif @@ -455,6 +453,12 @@ uint8_t EmotiBit::setup(String firmwareVariant) while (!Serial.available() && millis() - now < 2000) { +#ifdef ARDUINO_FEATHER_ESP32 + if (digitalRead(buttonPin) && !_bluetoothEnabled ) { + Serial.println("Bluetooth Enabled"); + _bluetoothEnabled = true; + } +#endif // ARDUINO_FEATHER_ESP32 } #ifdef ADAFRUIT_FEATHER_M0 AdcCorrection::AdcCorrectionValues adcCorrectionValues; @@ -952,52 +956,69 @@ uint8_t EmotiBit::setup(String firmwareVariant) Serial.println(factoryTestSerialOutput); sleep(true); } - //WiFi Setup; - Serial.print("\nSetting up WiFi\n"); -#if defined(ADAFRUIT_FEATHER_M0) - WiFi.setPins(8, 7, 4, 2); - WiFi.lowPowerMode(); -#endif - printEmotiBitInfo(); - // turn BLUE on to signify we are trying to connect to WiFi - led.setState(EmotiBitLedController::Led::BLUE, true, true); - uint16_t attemptDelay = 20000; // in mS. ESP32 has been observed to take >10 seconds to resolve an enterprise connection - uint8_t maxAttemptsPerCred = 1; - uint32_t timeout = attemptDelay * maxAttemptsPerCred * _emotiBitWiFi.getNumCredentials() * 2; // Try cycling through all credentials at least 2x before giving up and trying a restart - if (_emotiBitWiFi.isEnterpriseNetworkListed()) - { - // enterprise network is listed in network credential list. - // restart MCU after timeout - _emotiBitWiFi.begin(timeout, maxAttemptsPerCred, attemptDelay); - } - else + + if (_bluetoothEnabled == true) { - // only personal networks listed in credentials list. - // keep trying to connect to networks without any timeout - _emotiBitWiFi.begin(-1, maxAttemptsPerCred, attemptDelay); - } - if (_emotiBitWiFi.status(false) != WL_CONNECTED) - { - // Could not connect to network. software restart and begin setup again. - restartMcu(); + if (!setPowerMode(PowerMode::BLUETOOTH)) + { + _bluetoothEnabled = false; + } } - led.setState(EmotiBitLedController::Led::BLUE, false, true); - if (testingMode == TestingMode::FACTORY_TEST) + + if (_bluetoothEnabled == false) { - // Add Pass or fail - // ToDo: add mechanism to detect fail/pass - EmotiBitFactoryTest::updateOutputString(factoryTestSerialOutput, EmotiBitFactoryTest::TypeTag::WIFI, EmotiBitFactoryTest::TypeTag::TEST_PASS); +#ifdef ARDUINO_FEATHER_ESP32 + // ToDo: assess similarity with btStop(); + esp_bt_controller_disable(); +#endif //ARDUINO_FEATHER_ESP32 + //WiFi Setup; + Serial.print("\nSetting up WiFi\n"); +#if defined(ADAFRUIT_FEATHER_M0) + WiFi.setPins(8, 7, 4, 2); + WiFi.lowPowerMode(); +#endif + printEmotiBitInfo(); + // turn BLUE on to signify we are trying to connect to WiFi + led.setState(EmotiBitLedController::Led::BLUE, true, true); + uint16_t attemptDelay = 20000; // in mS. ESP32 has been observed to take >10 seconds to resolve an enterprise connection + uint8_t maxAttemptsPerCred = 1; + uint32_t timeout = attemptDelay * maxAttemptsPerCred * _emotiBitWiFi.getNumCredentials() * 2; // Try cycling through all credentials at least 2x before giving up and trying a restart + if (_emotiBitWiFi.isEnterpriseNetworkListed()) + { + // enterprise network is listed in network credential list. + // restart MCU after timeout + _emotiBitWiFi.begin(timeout, maxAttemptsPerCred, attemptDelay); + } + else + { + // only personal networks listed in credentials list. + // keep trying to connect to networks without any timeout + _emotiBitWiFi.begin(-1, maxAttemptsPerCred, attemptDelay); + } + if (_emotiBitWiFi.status(false) != WL_CONNECTED) + { + // Could not connect to network. software restart and begin setup again. + restartMcu(); + } + led.setState(EmotiBitLedController::Led::BLUE, false, true); + if (testingMode == TestingMode::FACTORY_TEST) + { + // Add Pass or fail + // ToDo: add mechanism to detect fail/pass + EmotiBitFactoryTest::updateOutputString(factoryTestSerialOutput, EmotiBitFactoryTest::TypeTag::WIFI, EmotiBitFactoryTest::TypeTag::TEST_PASS); + } + Serial.println(" WiFi setup Completed"); + #ifdef ARDUINO_FEATHER_ESP32 + Serial.println("Setting up FTP"); + Serial.println("Setting Protocol"); + _fileTransferManager.setProtocol(FileTransferManager::Protocol::FTP); + Serial.println("Setting Auth"); + _fileTransferManager.setFtpAuth("ftp", "ftp"); + #endif + + setPowerMode(PowerMode::NORMAL_POWER); } - Serial.println(" WiFi setup Completed"); - #ifdef ARDUINO_FEATHER_ESP32 - Serial.println("Setting up FTP"); - Serial.println("Setting Protocol"); - _fileTransferManager.setProtocol(FileTransferManager::Protocol::FTP); - Serial.println("Setting Auth"); - _fileTransferManager.setFtpAuth("ftp", "ftp"); - #endif - - setPowerMode(PowerMode::NORMAL_POWER); + typeTags[(uint8_t)EmotiBit::DataType::EDA] = EmotiBitPacket::TypeTag::EDA; typeTags[(uint8_t)EmotiBit::DataType::EDL] = EmotiBitPacket::TypeTag::EDL; typeTags[(uint8_t)EmotiBit::DataType::EDR] = EmotiBitPacket::TypeTag::EDR; @@ -1499,7 +1520,11 @@ void EmotiBit::parseIncomingControlPackets(String &controlPackets, uint16_t &pac static String packet; static EmotiBitPacket::Header header; int16_t dataStartChar = 0; + #ifdef ARDUINO_FEATHER_ESP32 + while (_emotiBitWiFi.readControl(packet) > 0 || _emotiBitBluetooth.readControl(packet) > 0) //Bluetooth and WiFi control packets are read in the same loop + #else while (_emotiBitWiFi.readControl(packet) > 0) + #endif //ARDUINO_FEATHER_ESP32 { Serial.println(packet); dataStartChar = EmotiBitPacket::getHeader(packet, header); @@ -1574,6 +1599,9 @@ void EmotiBit::parseIncomingControlPackets(String &controlPackets, uint16_t &pac else if (header.typeTag.equals(EmotiBitPacket::TypeTag::MODE_WIRELESS_OFF)) { setPowerMode(EmotiBit::PowerMode::WIRELESS_OFF); } + else if (header.typeTag.equals(EmotiBitPacket::TypeTag::MODE_BLUETOOTH)) { + setPowerMode(EmotiBit::PowerMode::BLUETOOTH); + } else if (header.typeTag.equals(EmotiBitPacket::TypeTag::MODE_HIBERNATE)) { setPowerMode(EmotiBit::PowerMode::HIBERNATE); } @@ -3199,7 +3227,11 @@ void EmotiBit::readSensors() else { // WiFi connected status LED +#ifdef ARDUINO_FEATHER_ESP32 + if (_emotiBitWiFi.isConnected() || _emotiBitBluetooth.deviceConnected) +#else if (_emotiBitWiFi.isConnected()) +#endif // ARDUINO_FEATHER_ESP32 { // Connected to oscilloscope // turn LED on @@ -3207,28 +3239,32 @@ void EmotiBit::readSensors() } else { - if (_emotiBitWiFi.status(false) == WL_CONNECTED) // ToDo: assess if WiFi.status() is thread/interrupt safe + if (_emotiBitWiFi.status(false) == WL_CONNECTED || _bluetoothEnabled) // ToDo: assess if WiFi.status() is thread/interrupt safe { // Not connected to oscilloscope, but connected to wifi // blink LED - static unsigned long onTime = 125; // msec - static unsigned long totalTime = 500; // msec - static bool wifiConnectedBlinkState = false; + unsigned long onTime = 125; // msec + unsigned long totalTime = 500; // msec + if (_bluetoothEnabled) + { + totalTime = 250; // msec + } + static bool ConnectedBlinkState = false; - static unsigned long wifiConnBlinkTimer = millis(); + static unsigned long ConnBlinkTimer = millis(); unsigned long timeNow = millis(); - if (timeNow - wifiConnBlinkTimer < onTime) + if (timeNow - ConnBlinkTimer < onTime) { led.setState(EmotiBitLedController::Led::BLUE, true); } - else if (timeNow - wifiConnBlinkTimer < totalTime) + else if (timeNow - ConnBlinkTimer < totalTime) { led.setState(EmotiBitLedController::Led::BLUE, false); } else { - wifiConnBlinkTimer = timeNow; + ConnBlinkTimer = timeNow; } } else @@ -3446,6 +3482,11 @@ void EmotiBit::sendData() if (getPowerMode() == PowerMode::NORMAL_POWER) { _emotiBitWiFi.sendData(s); } + if (getPowerMode() == PowerMode::BLUETOOTH) { + #ifdef ARDUINO_FEATHER_ESP32 + _emotiBitBluetooth.sendData(s); + #endif //ARDUINO_FEATHER_ESP32 + } writeSdCardMessage(s); firstIndex = lastIndex + 1; } @@ -3476,13 +3517,18 @@ void EmotiBit::sendData() String s = _outDataPackets.substring(firstIndex, lastIndex + 1); - if (getPowerMode() == PowerMode::NORMAL_POWER) { - _emotiBitWiFi.sendData(s); + if (getPowerMode() == PowerMode::NORMAL_POWER) { + _emotiBitWiFi.sendData(s); + } + if (getPowerMode() == PowerMode::BLUETOOTH) { + #ifdef ARDUINO_FEATHER_ESP32 + _emotiBitBluetooth.sendData(s); + #endif //ARDUINO_FEATHER_ESP32 + } + writeSdCardMessage(s); + firstIndex = lastIndex + 1; } - writeSdCardMessage(s); - firstIndex = lastIndex + 1; - } - _outDataPackets = ""; + _outDataPackets = ""; } } @@ -3707,72 +3753,140 @@ EmotiBit::PowerMode EmotiBit::getPowerMode() return _powerMode; } -void EmotiBit::setPowerMode(PowerMode mode) +bool EmotiBit::setPowerMode(PowerMode mode) { - _powerMode = mode; - String modePacket; - sendModePacket(modePacket, _outDataPacketCounter); - if (getPowerMode() == PowerMode::NORMAL_POWER) + bool ret = false; + if (mode == PowerMode::NORMAL_POWER) { - Serial.println("PowerMode::NORMAL_POWER"); - if (_emotiBitWiFi.isOff()) + Serial.println("SetPowerMode(NORMAL_POWER)"); + if (_bluetoothEnabled ) { - unsigned long beginTime = millis(); - _emotiBitWiFi.begin(100, 1, 100); // This ToDo: create a async begin option - Serial.print("Total WiFi.begin() = "); - Serial.println(millis() - beginTime); + Serial.println("SetPowerMode() FAILED: BLUETOOTH <-> WIFI requires device reset"); } + else + { + if (_emotiBitWiFi.isOff()) + { + unsigned long beginTime = millis(); + _emotiBitWiFi.begin(100, 1, 100); // This ToDo: create a async begin option + Serial.print("Total WiFi.begin() = "); + Serial.println(millis() - beginTime); + } #ifdef ADAFRUIT_FEATHER_M0 - // For ADAFRUIT_FEATHER_M0, lowPowerMode() is a good balance of performance and battery - WiFi.lowPowerMode(); - // For ESP32 the default WIFI_PS_MIN_MODEM is probably optimal https://www.mischianti.org/2021/03/06/esp32-practical-power-saving-manage-wifi-and-cpu-1/ + // For ADAFRUIT_FEATHER_M0, lowPowerMode() is a good balance of performance and battery + WiFi.lowPowerMode(); + // For ESP32 the default WIFI_PS_MIN_MODEM is probably optimal https://www.mischianti.org/2021/03/06/esp32-practical-power-saving-manage-wifi-and-cpu-1/ #endif - modePacketInterval = NORMAL_POWER_MODE_PACKET_INTERVAL; + modePacketInterval = NORMAL_POWER_MODE_PACKET_INTERVAL; + Serial.println("PowerMode::NORMAL_POWER"); + _powerMode = mode; + ret = true; + } } - else if (getPowerMode() == PowerMode::LOW_POWER) - { - Serial.println("PowerMode::LOW_POWER"); - if (_emotiBitWiFi.isOff()) + else if (mode == PowerMode::LOW_POWER) + { + Serial.println("SetPowerMode(LOW_POWER)"); + if (_bluetoothEnabled ) { - unsigned long beginTime = millis(); - _emotiBitWiFi.begin(100, 1, 100); // This ToDo: create a async begin option - Serial.print("Total WiFi.begin() = "); - Serial.println(millis() - beginTime); + Serial.println("SetPowerMode() FAILED: BLUETOOTH <-> WIFI requires device reset"); } + else + { + if (_emotiBitWiFi.isOff()) + { + unsigned long beginTime = millis(); + _emotiBitWiFi.begin(100, 1, 100); // This ToDo: create a async begin option + Serial.print("Total WiFi.begin() = "); + Serial.println(millis() - beginTime); + } #ifdef ADAFRUIT_FEATHER_M0 - WiFi.lowPowerMode(); + WiFi.lowPowerMode(); #endif - modePacketInterval = LOW_POWER_MODE_PACKET_INTERVAL; + modePacketInterval = LOW_POWER_MODE_PACKET_INTERVAL; + Serial.println("PowerMode::LOW_POWER"); + _powerMode = mode; + ret = true; + } } - else if (getPowerMode() == PowerMode::MAX_LOW_POWER) + else if (mode == PowerMode::MAX_LOW_POWER) { - Serial.println("PowerMode::MAX_LOW_POWER"); - if (_emotiBitWiFi.isOff()) + Serial.println("SetPowerMode(MAX_LOW_POWER)"); + if (_bluetoothEnabled ) { - unsigned long beginTime = millis(); - _emotiBitWiFi.begin(100, 1, 100); // This ToDo: create a async begin option - Serial.print("Total WiFi.begin() = "); - Serial.println(millis() - beginTime); + Serial.println("SetPowerMode() FAILED: BLUETOOTH <-> WIFI requires device reset"); } + else + { + if (_emotiBitWiFi.isOff()) + { + unsigned long beginTime = millis(); + _emotiBitWiFi.begin(100, 1, 100); // This ToDo: create a async begin option + Serial.print("Total WiFi.begin() = "); + Serial.println(millis() - beginTime); + } #ifdef ADAFRUIT_FEATHER_M0 - WiFi.maxLowPowerMode(); + WiFi.maxLowPowerMode(); #endif - // ToDo: for ESP32 There may be some value to explore WIFI_PS_MAX_MODEM https://www.mischianti.org/2021/03/06/esp32-practical-power-saving-manage-wifi-and-cpu-1/ - modePacketInterval = LOW_POWER_MODE_PACKET_INTERVAL; + // ToDo: for ESP32 There may be some value to explore WIFI_PS_MAX_MODEM https://www.mischianti.org/2021/03/06/esp32-practical-power-saving-manage-wifi-and-cpu-1/ + modePacketInterval = LOW_POWER_MODE_PACKET_INTERVAL; + Serial.println("PowerMode::MAX_LOW_POWER"); + _powerMode = mode; + ret = true; + } } - else if (getPowerMode() == PowerMode::WIRELESS_OFF) + else if (mode == PowerMode::WIRELESS_OFF) { + Serial.println("SetPowerMode(WIRELESS_OFF)"); + if (_bluetoothEnabled ) + { +#ifdef ARDUINO_FEATHER_ESP32 // consider moving this ifdef into EmotiBitBluetooth + _emotiBitBluetooth.end(); + // Note: we're NOT setting _bluetoothEnabled = false here because BLUETOOTH <-> WIFI requires device reset +#endif //ARDUINO_FEATHER_ESP32 + } + else + { + _emotiBitWiFi.end(); + } Serial.println("PowerMode::WIRELESS_OFF"); - _emotiBitWiFi.end(); + _powerMode = mode; + ret = true; } - else if (getPowerMode() == PowerMode::HIBERNATE) + else if (mode == PowerMode::BLUETOOTH) + { + Serial.println("SetPowerMode(BLUETOOTH)"); + if (!_bluetoothEnabled ) + { + Serial.println("SetPowerMode() FAILED: BLUETOOTH <-> WIFI requires device reset"); + } + else + { +#ifdef ARDUINO_FEATHER_ESP32 // consider moving this ifdef into EmotiBitBluetooth + if (_emotiBitBluetooth.isOff()) + { + if (_emotiBitBluetooth.begin(emotibitDeviceId) == 0) // ToDo: add EmotiBitBluetooth::SUCCESS for improved readability + { + Serial.println("PowerMode::BLUETOOTH"); + _powerMode = mode; + ret = true; + } + } + // ToDo: consider adding serial output to clarify !isOff() and !begin() +#endif //ARDUINO_FEATHER_ESP32 + } + } + else if (mode == PowerMode::HIBERNATE) { Serial.println("PowerMode::HIBERNATE"); + _powerMode = mode; } else { Serial.println("PowerMode Not Recognized"); } + String modePacket; + sendModePacket(modePacket, _outDataPacketCounter); + return ret; } void EmotiBit::writeSerialData(EmotiBit::DataType t) @@ -4329,7 +4443,8 @@ void EmotiBit::processDebugInputs(String &debugPackets, uint16_t &packetNumber) _emotibitNvmController.eraseEeprom(); } } - else if (c == '>') { + else if (c == '>') + { _sendTestData = true; Serial.println("Entering Sending Test Data Mode"); } @@ -4679,7 +4794,15 @@ void EmotiBit::printEmotiBitInfo() Serial.print(ip); Serial.println("\""); } - + + if (!_bluetoothEnabled ) + { + Serial.print("WiFi Enabled"); + } + else + { + Serial.print("Bluetooth Enabled"); +} Serial.println("}}]"); } diff --git a/EmotiBit.h b/EmotiBit.h index 5368853d..9d9199e9 100644 --- a/EmotiBit.h +++ b/EmotiBit.h @@ -21,7 +21,7 @@ #ifdef ARDUINO_FEATHER_ESP32 #include #include "driver/adc.h" -#include +#include //consider moving into bluetooth #else #include #include @@ -39,6 +39,9 @@ #include "FileTransferManager.h" #endif #include "EmotiBitConfigManager.h" +#ifdef ARDUINO_FEATHER_ESP32 +#include "EmotiBitBluetooth.h" +#endif class EmotiBit { @@ -266,6 +269,7 @@ class EmotiBit { MAX_LOW_POWER, // data not sent, time-syncing accuracy low LOW_POWER, // data not sent, time-syncing accuracy high NORMAL_POWER, // data sending, time-syncing accuracy high + BLUETOOTH, length }; @@ -280,6 +284,9 @@ class EmotiBit { EmotiBitEda emotibitEda; EmotiBitNvmController _emotibitNvmController; #ifdef ARDUINO_FEATHER_ESP32 + EmotiBitBluetooth _emotiBitBluetooth; + #endif //ARDUINO_FEATHER_ESP32 + #ifdef ARDUINO_FEATHER_ESP32 FileTransferManager _fileTransferManager; #endif EmotiBitConfigManager _emotibitConfigManager; @@ -429,6 +436,7 @@ class EmotiBit { DataType _serialData = DataType::length; volatile bool buttonPressed = false; bool startBufferOverflowTest = false; + bool _bluetoothEnabled = false; void setupFailed(const String failureMode, int buttonPin = -1, bool configFileError = false); bool setupSdCard(bool loadConfig = true); @@ -444,7 +452,8 @@ class EmotiBit { void attachShortButtonPress(void(*shortButtonPressFunction)(void)); void attachLongButtonPress(void(*longButtonPressFunction)(void)); PowerMode getPowerMode(); - void setPowerMode(PowerMode mode); + //void setPowerMode(PowerMode mode); + bool setPowerMode(PowerMode mode); bool writeSdCardMessage(const String &s); int freeMemory(); bool loadConfigFile(const String &filename); @@ -459,7 +468,6 @@ class EmotiBit { bool processThermopileData(); // placeholder until separate EmotiBitThermopile controller is implemented void writeSerialData(EmotiBit::DataType t); void printEmotiBitInfo(); - /** * Copies data buffer of the specified DataType into the passed array diff --git a/EmotiBitBluetooth.cpp b/EmotiBitBluetooth.cpp new file mode 100644 index 00000000..3bb25d5e --- /dev/null +++ b/EmotiBitBluetooth.cpp @@ -0,0 +1,226 @@ +#ifdef ARDUINO_FEATHER_ESP32 +#include "EmotiBitBluetooth.h" + +uint8_t EmotiBitBluetooth::begin(const String& emotibitDeviceId) +{ + if (pServer) + { + EmotiBitBluetooth::reconnect(); + Serial.println("Bluetooth already initialized, reconnecting..."); + return 0; // Success + } + + _emotibitDeviceId = emotibitDeviceId; + + Serial.println("Bluetooth tag detected, turning on bluetooth."); + BLEDevice::init(("EmotiBit: " + _emotibitDeviceId).c_str()); + + pServer = BLEDevice::createServer(); + + if (!pServer) + { + Serial.println("ERROR: Failed to create BLE server"); + return 1; + } + + pServer->setCallbacks(new MyServerCallbacks(this)); + BLEService* pService = pServer->createService(EMOTIBIT_SERVICE_UUID); + + pDataTxCharacteristic = pService->createCharacteristic(EMOTIBIT_DATA_TX_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_NOTIFY); + if (!pDataTxCharacteristic) + { + Serial.println("ERROR: Failed to create TX characteristic"); + return 1; + } + pDataTxCharacteristic->addDescriptor(new BLE2902()); + + pDataRxCharacteristic = pService->createCharacteristic(EMOTIBIT_DATA_RX_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_WRITE); + if (!pDataRxCharacteristic) + { + Serial.println("ERROR: Failed to create RX characteristic"); + return 1; + } + pDataRxCharacteristic->setCallbacks(new MyCallbacks()); + + pService->start(); + + EmotiBitBluetooth::startAdvertising(); + _bluetoothReconnect = true; // Allow reconnection after disconnection + Serial.println("BLE advertising started"); + + return 0; +} + +void EmotiBitBluetooth::MyServerCallbacks::onConnect(BLEServer* pServer) +{ + server -> deviceConnected = true; + Serial.println("BLE client connected"); +} + +void EmotiBitBluetooth::MyServerCallbacks::onDisconnect(BLEServer* pServer) +{ + server -> deviceConnected = false; + Serial.println("BLE client disconnected"); + //need to restart advertising to allow new connections after disconnection if accidentally disconnected + if (server -> _bluetoothReconnect) + { + server -> reconnect(); + Serial.println("Restarted BLE advertising"); + } +} + +void EmotiBitBluetooth::MyCallbacks::onWrite(BLECharacteristic *pCharacteristic) +{ + std::string rxValue = pCharacteristic->getValue(); + if (rxValue.length() > 0) { + //Serial.print("Received: "); + //Serial.println(rxValue.c_str()); + } +} + +void EmotiBitBluetooth::setDeviceId(const String& emotibitDeviceId) +{ + _emotibitDeviceId = emotibitDeviceId; +} + +void EmotiBitBluetooth::sendData(const String &message) +{ + if (deviceConnected) + { + //TODO: consider truncating via MTU if message is too long + if (pDataTxCharacteristic == nullptr) + { + //Serial.println("ERROR: pDataTxCharacteristic is NULL!"); + return; + } + + //Serial.print("BLE TX: Message length="); + //Serial.print(message.length()); + + // Set the value + pDataTxCharacteristic->setValue(message.c_str()); + + // Call notify - note: doesn't return success/failure status + pDataTxCharacteristic->notify(); + + // Check descriptor status + BLEDescriptor* p2902 = pDataTxCharacteristic->getDescriptorByUUID(BLEUUID((uint16_t)0x2902)); + if (p2902) + { + const uint8_t* val = p2902->getValue(); + if (val) + { + //Serial.print(" | Notifications enabled="); + //Serial.print((val[0] & 0x01) ? "YES" : "NO"); + } + } + + //Serial.print(" | Char ptr=0x"); + //Serial.print((uint32_t)pDataTxCharacteristic, HEX); + //Serial.print(" | Message: "); + //Serial.println(message.c_str()); + } + else + { + //ToDO: consider gating behind a debug flag + Serial.println("unable to send data: deviceConnected=false"); + } +} + +uint8_t EmotiBitBluetooth::readControl(String& packet) +{ + uint8_t numPackets = 0; + packet = ""; + if (deviceConnected) + { + std::string rxValue = pDataRxCharacteristic->getValue(); + if (!rxValue.empty()) + { + //Serial.print("Received: "); + //Serial.println(rxValue.c_str()); + _receivedControlMessage += String(rxValue.c_str()); + + //CLEAR THE CHAR VALUE SO WE DON’T REUSE IT + pDataRxCharacteristic->setValue(""); + } + + String tempPacket = ""; + while (_receivedControlMessage.length() > 0) + { + int c = _receivedControlMessage[0]; + _receivedControlMessage.remove(0, 1); + + if (c == (int)EmotiBitPacket::PACKET_DELIMITER_CSV) + { + numPackets++; + packet = tempPacket; + tempPacket = ""; + _receivedControlMessage = ""; + return numPackets; + } + else + { + if (c == 0) { + // Throw out null term + } + else + { + tempPacket += (char)c; + } + } + } + } + return numPackets; +} + +bool EmotiBitBluetooth::isOff() +{ + return _bluetoothOff; +} + +void EmotiBitBluetooth::end() +{ + if (pServer && deviceConnected) + { + //tear down the old connection + pServer->disconnect(0); + Serial.println("BLE client disconnected by end()"); + } + if (pServer) + { + pServer->getAdvertising()->stop(); + Serial.println("BLE advertising stopped"); + } + _bluetoothOff = true; + _bluetoothReconnect = false; +} + +void EmotiBitBluetooth::reconnect() +{ + if (pServer) + { + EmotiBitBluetooth::startAdvertising(); + Serial.println("BLE advertising restarted after reconnect"); + _bluetoothOff = false; + _bluetoothReconnect = true; + } + else + { + Serial.println("ERROR: pServer is NULL, cannot reconnect"); + } +} + +void EmotiBitBluetooth::startAdvertising() +{ + if (pServer) + { + pServer->getAdvertising()->start(); + Serial.println("BLE advertising started"); + } + else + { + Serial.println("ERROR: pServer is NULL, cannot start advertising"); + } +} + +#endif //ARDUINO_FEATHER_ESP32 \ No newline at end of file diff --git a/EmotiBitBluetooth.h b/EmotiBitBluetooth.h new file mode 100644 index 00000000..4bc66c91 --- /dev/null +++ b/EmotiBitBluetooth.h @@ -0,0 +1,119 @@ +/**************************************************************************/ +/*! + @file EmotiBitBluetooth.h + + This file facilitates the use of Bluetooth on the EmotiBit + + EmotiBit invests time and resources providing this open source code, + please support EmotiBit and open-source hardware by purchasing + products from EmotiBit! + + Written by Joseph Jacobson for EmotiBit. + + BSD license, all text here must be included in any redistribution +*/ +/**************************************************************************/ +#ifdef ARDUINO_FEATHER_ESP32 +#pragma once +/*! +* @brief inclusions for BLE Device, Server, Utils, 2902, and Arduino +*/ +#include "Arduino.h" +#include +#include +#include +#include +#include "EmotiBitPacket.h" + +#define EMOTIBIT_SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" +#define EMOTIBIT_DATA_RX_CHARACTERISTIC_UUID "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" +#define EMOTIBIT_DATA_TX_CHARACTERISTIC_UUID "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" + +/*! + * @brief Handles Bluetooth communication for EmotiBit. +*/ +class EmotiBitBluetooth { + public: + BLEServer* pServer = nullptr; ///points to the server + + BLECharacteristic* pDataTxCharacteristic = nullptr; ///points to the data tx characteristic + BLECharacteristic* pDataRxCharacteristic = nullptr; ///points to the data rx characteristic + + bool deviceConnected = false; ///boolean to check if device is connected + String _emotibitDeviceId = ""; ///string to hold device id + String _receivedControlMessage = ""; + bool _bluetoothOff = true; + bool _bluetoothReconnect = false; + + /*! + * @brief Server callbacks for connections + */ + class MyServerCallbacks: public BLEServerCallbacks { + public: + MyServerCallbacks(EmotiBitBluetooth* server) : server(server) {} + void onConnect(BLEServer* pServer); + void onDisconnect(BLEServer* pServer); + private: + EmotiBitBluetooth* server; + }; + + /*! + * @brief Characteristic callbacks for data transfer + */ + class MyCallbacks : public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic); + }; + + /*! + * @brief Initializes the BLE device and starts advertising + * @param emotibitDeviceId ID from setDeviceId + * @return 1 on success, 0 on failure + */ + //TO DO use int for error handling + uint8_t begin(const String& emotibitDeviceId); + + /*! + * @brief Sends data over BLE + * @param message data to be sent + */ + void sendData(const String &message); + + /*! + * @brief Checks if the device is connected to a BLE client + * @param emotibitDeviceId + */ + void setDeviceId(const String& emotibitDeviceId); + + /*! + * @brief Reads control messages from the BLE characteristic + * @param packet the control message packet + */ + uint8_t readControl(String& packet); + + //void update();for when we sync data over BLE + //move to emotibit + //void sdCardFileNaming(); for when we choose bluetooth and there is no rb start time + + /*! + * @brief Ends the BLE connection + */ + void end(); + + /*! + * @brief Checks if the Bluetooth is off + * @return if Bluetooth is off, returns true, otherwise false + */ + bool isOff(); + + /*! + * @brief Reconnects the BLE server if disconnected + */ + void reconnect(); + + /*! + * @brief Starts Advertising + */ + void startAdvertising(); +}; + +#endif //ARDUINO_FEATHER_ESP32 \ No newline at end of file diff --git a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino index c9fbebe0..482afbdb 100644 --- a/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino +++ b/EmotiBit_stock_firmware/EmotiBit_stock_firmware.ino @@ -10,16 +10,32 @@ float data[dataSize]; void onShortButtonPress() { - // toggle wifi on/off - if (emotibit.getPowerMode() == EmotiBit::PowerMode::NORMAL_POWER) + if (emotibit._bluetoothEnabled == true) { - emotibit.setPowerMode(EmotiBit::PowerMode::WIRELESS_OFF); - Serial.println("PowerMode::WIRELESS_OFF"); + if (emotibit.getPowerMode() == EmotiBit::PowerMode::BLUETOOTH) + { + emotibit.setPowerMode(EmotiBit::PowerMode::WIRELESS_OFF); + Serial.println("PowerMode::WIRELESS_OFF"); + } + else + { + emotibit.setPowerMode(EmotiBit::PowerMode::BLUETOOTH); + Serial.println("PowerMode::BLUETOOTH"); + } } else { - emotibit.setPowerMode(EmotiBit::PowerMode::NORMAL_POWER); - Serial.println("PowerMode::NORMAL_POWER"); + // toggle wifi on/off + if (emotibit.getPowerMode() == EmotiBit::PowerMode::NORMAL_POWER) + { + emotibit.setPowerMode(EmotiBit::PowerMode::WIRELESS_OFF); + Serial.println("PowerMode::WIRELESS_OFF"); + } + else + { + emotibit.setPowerMode(EmotiBit::PowerMode::NORMAL_POWER); + Serial.println("PowerMode::NORMAL_POWER"); + } } } diff --git a/board_feather_esp32.ini b/board_feather_esp32.ini index 16ba6c5d..6b1999cf 100644 --- a/board_feather_esp32.ini +++ b/board_feather_esp32.ini @@ -8,4 +8,5 @@ build_flags = ; change MCU frequency board_build.f_cpu = 240000000L extra_scripts = pre:../pio_scripts/renameFw.py -firmware_name_board_name = feather_esp32 \ No newline at end of file +firmware_name_board_name = feather_esp32 +board_build.partitions = huge_app.csv \ No newline at end of file