diff --git a/EmotiBit.cpp b/EmotiBit.cpp index 4f8e1c6f..5c619d1a 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) && !_enableBluetooth) { + Serial.println("Bluetooth Enabled"); + _enableBluetooth = true; + } +#endif // ARDUINO_FEATHER_ESP32 } #ifdef ADAFRUIT_FEATHER_M0 AdcCorrection::AdcCorrectionValues adcCorrectionValues; @@ -952,6 +956,20 @@ uint8_t EmotiBit::setup(String firmwareVariant) Serial.println(factoryTestSerialOutput); sleep(true); } + + if (_enableBluetooth == true) + { + #ifdef ARDUINO_FEATHER_ESP32 + setPowerMode(PowerMode::BLUETOOTH); + #endif // ARDUINO_FEATHER_ESP32 + } + + else + { + #ifdef ARDUINO_FEATHER_ESP32 + esp_bt_controller_disable(); + // ToDo: assess similarity with btStop(); + #endif //WiFi Setup; Serial.print("\nSetting up WiFi\n"); #if defined(ADAFRUIT_FEATHER_M0) @@ -998,6 +1016,8 @@ uint8_t EmotiBit::setup(String firmwareVariant) #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 +1519,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 +1598,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); } @@ -3196,6 +3223,62 @@ void EmotiBit::readSensors() led.setState(EmotiBitLedController::Led::BLUE, true); led.setState(EmotiBitLedController::Led::YELLOW, true); } + + if (getPowerMode() == PowerMode::BLUETOOTH) { +#ifdef ARDUINO_FEATHER_ESP32 + // Bluetooth connected status LED + if (_emotiBitBluetooth.deviceConnected) { + led.setState(EmotiBitLedController::Led::BLUE, true); + } + else { + // blink LED + static unsigned long onTime = 125; // msec + static unsigned long totalTime = 250; // msec changed to 250 + static bool bleConnectedBlinkState = false; + + static unsigned long bleConnBlinkTimer = millis(); + + unsigned long timeNow = millis(); + if (timeNow - bleConnBlinkTimer < onTime) + { + led.setState(EmotiBitLedController::Led::BLUE, true); + } + else if (timeNow - bleConnBlinkTimer < totalTime) + { + led.setState(EmotiBitLedController::Led::BLUE, false); + } + else + { + bleConnBlinkTimer = timeNow; + } + } + // Battery LED + if (battIndicationSeq) + { + led.setState(EmotiBitLedController::Led::YELLOW, true); + } + else + { + led.setState(EmotiBitLedController::Led::YELLOW, false); + } + + // Recording status LED + if (_sdWrite) + { + static uint32_t recordBlinkDuration = millis(); + if (millis() - recordBlinkDuration >= 500) + { + led.setState(EmotiBitLedController::Led::RED, !led.getState(EmotiBitLedController::Led::RED)); + recordBlinkDuration = millis(); + } + } + else if (!_sdWrite && led.getState(EmotiBitLedController::Led::RED) == true) + { + led.setState(EmotiBitLedController::Led::RED, false); + } + +#endif //ARDUINO_FEATHER_ESP32 + } else { // WiFi connected status LED @@ -3446,6 +3529,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 +3564,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 = ""; } } @@ -3763,7 +3856,29 @@ void EmotiBit::setPowerMode(PowerMode mode) else if (getPowerMode() == PowerMode::WIRELESS_OFF) { Serial.println("PowerMode::WIRELESS_OFF"); + + if (_enableBluetooth == true) + { +#ifdef ARDUINO_FEATHER_ESP32 + _emotiBitBluetooth.end(); +// _enableBluetooth = false; +#endif //ARDUINO_FEATHER_ESP32 + } + else + { _emotiBitWiFi.end(); + } + } + else if (getPowerMode() == PowerMode::BLUETOOTH) + { +#ifdef ARDUINO_FEATHER_ESP32 + if (_emotiBitBluetooth.isOff()) + { + Serial.println("PowerMode::BLUETOOTH"); + _emotiBitBluetooth.begin(emotibitDeviceId); + } + +#endif //ARDUINO_FEATHER_ESP32 } else if (getPowerMode() == PowerMode::HIBERNATE) { @@ -4331,12 +4446,13 @@ void EmotiBit::processDebugInputs(String &debugPackets, uint16_t &packetNumber) } else if (c == '>') { _sendTestData = true; + Serial.println("Entering Sending Test Data Mode"); } else if (c == '<') { - _sendTestData = false; - Serial.println("Exiting Sending Test Data Mode"); + _sendTestData = true; + Serial.println("Entering Sending Test Data Mode"); } else if (c == '@' && _sendTestData == true) { diff --git a/EmotiBit.h b/EmotiBit.h index 5368853d..e5212188 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 _enableBluetooth = false; void setupFailed(const String failureMode, int buttonPin = -1, bool configFileError = false); bool setupSdCard(bool loadConfig = true); @@ -459,7 +467,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..67c54903 --- /dev/null +++ b/EmotiBitBluetooth.cpp @@ -0,0 +1,208 @@ +#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); + pDataTxCharacteristic->addDescriptor(new BLE2902()); + + pDataRxCharacteristic = pService->createCharacteristic(EMOTIBIT_DATA_RX_CHARACTERISTIC_UUID, BLECharacteristic::PROPERTY_WRITE); + 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) { + 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 { + 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..27a12c1b --- /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..64c45285 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._enableBluetooth == 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