diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7e03cfe54..4088eb28a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -49,6 +49,7 @@ jobs: ARDUINO_CLI_VERSION: 0.34.2 ARDUINO_BLE_VERSION: 1.3.6 ENERGIA_IDE_VERSION: 1.8.10E23 + NIM_BLE_VERSION: 1.4.1 BOARD: ${{ matrix.board }} # Steps represent a sequence of tasks that will be executed as part of the job @@ -214,6 +215,10 @@ jobs: arduino --pref "custom_UploadSpeed=esp32c6_921600" --save-prefs ; cd $HOME/.arduino15/packages/esp32/hardware/esp32/3.0.0-alpha3 ; sed -i "s\echo '-DARDUINO_CORE_BUILD'\echo -DARDUINO_CORE_BUILD\g" platform.txt ; + wget https://github.com/h2zero/NimBLE-Arduino/archive/refs/tags/${NIM_BLE_VERSION}.tar.gz ; + tar xzf ${NIM_BLE_VERSION}.tar.gz ; + rm ${NIM_BLE_VERSION}.tar.gz ; + mv NimBLE-Arduino-${NIM_BLE_VERSION} $HOME/Arduino/libraries/ ; cd $GITHUB_WORKSPACE ; fi if [[ "$BOARD" =~ "STM32:stm32:" ]]; then diff --git a/software/firmware/source/SoftRF/src/driver/Bluetooth.h b/software/firmware/source/SoftRF/src/driver/Bluetooth.h index 312d5d49f..032ccbd53 100644 --- a/software/firmware/source/SoftRF/src/driver/Bluetooth.h +++ b/software/firmware/source/SoftRF/src/driver/Bluetooth.h @@ -32,7 +32,12 @@ enum #endif #if defined(ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) +#include "../system/SoC.h" +#if defined(USE_NIMBLE) +#include "../platform/bluetooth/NimBLE.h" +#else #include "../platform/bluetooth/Bluedroid.h" +#endif /* USE_NIMBLE */ #elif defined(ARDUINO_ARCH_NRF52) #include "../platform/bluetooth/Bluefruit.h" #elif defined(ARDUINO_ARCH_RP2040) && defined(ARDUINO_RASPBERRY_PI_PICO_W) diff --git a/software/firmware/source/SoftRF/src/platform/ESP32.cpp b/software/firmware/source/SoftRF/src/platform/ESP32.cpp index fd96427fb..e3bf4dc74 100644 --- a/software/firmware/source/SoftRF/src/platform/ESP32.cpp +++ b/software/firmware/source/SoftRF/src/platform/ESP32.cpp @@ -4082,8 +4082,9 @@ void handleMainEvent(AceButton* button, uint8_t eventType, switch (eventType) { #if defined(USE_SA8X8) case AceButton::kEventPressed: - if (button == &button_ptt && - Voice_Frequency > 0 && + if (button == &button_ptt && + hw_info.rf == RF_IC_SA8X8 && + Voice_Frequency > 0 && (settings->power_save & POWER_SAVE_NORECEIVE)) { bool playback = false; #if !defined(EXCLUDE_VOICE_MESSAGE) @@ -4135,8 +4136,9 @@ void handleMainEvent(AceButton* button, uint8_t eventType, } #endif /* USE_OLED */ #if defined(USE_SA8X8) - if (button == &button_ptt && - Voice_Frequency > 0 && + if (button == &button_ptt && + hw_info.rf == RF_IC_SA8X8 && + Voice_Frequency > 0 && (settings->power_save & POWER_SAVE_NORECEIVE)) { controller.receive(); sa868_Tx_LED_state(false); diff --git a/software/firmware/source/SoftRF/src/platform/ESP32.h b/software/firmware/source/SoftRF/src/platform/ESP32.h index 98d2892dc..8b94126ac 100644 --- a/software/firmware/source/SoftRF/src/platform/ESP32.h +++ b/software/firmware/source/SoftRF/src/platform/ESP32.h @@ -402,10 +402,12 @@ extern const USB_Device_List_t supported_USB_devices[]; #define EXCLUDE_WATCHOUT_MODE #undef USE_NMEALIB #undef ENABLE_PROL +//#define USE_NIMBLE #endif /* C6 */ #endif /* CONFIG_IDF_TARGET_ESP32SX | C3 | C6 */ #else //#define ENABLE_BT_VOICE +//#define USE_NIMBLE #endif /* NOT CONFIG_IDF_TARGET_ESP32 */ #define POWER_SAVING_WIFI_TIMEOUT 600000UL /* 10 minutes */ diff --git a/software/firmware/source/SoftRF/src/platform/bluetooth/Bluedroid.cpp b/software/firmware/source/SoftRF/src/platform/bluetooth/Bluedroid.cpp index a6653938a..ef3bd9045 100644 --- a/software/firmware/source/SoftRF/src/platform/bluetooth/Bluedroid.cpp +++ b/software/firmware/source/SoftRF/src/platform/bluetooth/Bluedroid.cpp @@ -19,8 +19,9 @@ #if defined(ESP32) #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#include "../../system/SoC.h" +#if defined(CONFIG_BLUEDROID_ENABLED) && !defined(USE_NIMBLE) /* * BLE code is based on Neil Kolban example for IDF: * https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp @@ -34,7 +35,6 @@ #include "esp_gap_bt_api.h" -#include "../../system/SoC.h" #include "../../driver/EEPROM.h" #include "../../driver/Bluetooth.h" #include "../../driver/WiFi.h" diff --git a/software/firmware/source/SoftRF/src/platform/bluetooth/NimBLE.cpp b/software/firmware/source/SoftRF/src/platform/bluetooth/NimBLE.cpp new file mode 100644 index 000000000..e8f2aea05 --- /dev/null +++ b/software/firmware/source/SoftRF/src/platform/bluetooth/NimBLE.cpp @@ -0,0 +1,388 @@ +/* + * NimBLE.cpp + * Copyright (C) 2024 Linar Yusupov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#if defined(ESP32) +#include "sdkconfig.h" + +#include "../../system/SoC.h" + +#if defined(USE_NIMBLE) +/* + * BLE code is based on Neil Kolban example for IDF: + * https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp + * Ported to Arduino ESP32 by Evandro Copercini + * HM-10 emulation and adaptation for SoftRF is done by Linar Yusupov. + */ +#include +#include +#include + +#include "esp_gap_bt_api.h" + +#include "../../driver/EEPROM.h" +#include "../../driver/Bluetooth.h" +#include "../../driver/WiFi.h" +#include "../../driver/Battery.h" + +#include + +NimBLEServer* pServer = NULL; +NimBLECharacteristic* pUARTCharacteristic = NULL; +NimBLECharacteristic* pBATCharacteristic = NULL; + +NimBLECharacteristic* pModelCharacteristic = NULL; +NimBLECharacteristic* pSerialCharacteristic = NULL; +NimBLECharacteristic* pFirmwareCharacteristic = NULL; +NimBLECharacteristic* pHardwareCharacteristic = NULL; +NimBLECharacteristic* pSoftwareCharacteristic = NULL; +NimBLECharacteristic* pManufacturerCharacteristic = NULL; + +bool deviceConnected = false; +bool oldDeviceConnected = false; + +#if defined(USE_BLE_MIDI) +NimBLECharacteristic* pMIDICharacteristic = NULL; +#endif /* USE_BLE_MIDI */ + +cbuf *BLE_FIFO_RX, *BLE_FIFO_TX; + +String BT_name = HOSTNAME; + +static unsigned long BLE_Notify_TimeMarker = 0; +static unsigned long BLE_Advertising_TimeMarker = 0; + +// NimBLEDescriptor UserDescriptor(NimBLEUUID((uint16_t)0x2901)); + +class MyServerCallbacks: public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer) { + deviceConnected = true; + }; + + void onDisconnect(NimBLEServer* pServer) { + deviceConnected = false; + BLE_Advertising_TimeMarker = millis(); + } +}; + +class UARTCallbacks: public BLECharacteristicCallbacks { + void onWrite(NimBLECharacteristic *pUARTCharacteristic) { +#if defined(ESP_IDF_VERSION_MAJOR) && ESP_IDF_VERSION_MAJOR>=5 + String rxValue = pUARTCharacteristic->getValue(); +#else + std::string rxValue = pUARTCharacteristic->getValue(); +#endif /* ESP_IDF_VERSION_MAJOR */ + + if (rxValue.length() > 0) { + BLE_FIFO_RX->write(rxValue.c_str(), + (BLE_FIFO_RX->room() > rxValue.length() ? + rxValue.length() : BLE_FIFO_RX->room())); + } + } +}; + +static void ESP32_Bluetooth_setup() +{ + BT_name += "-"; + BT_name += String(SoC->getChipId() & 0x00FFFFFFU, HEX); + + switch (settings->bluetooth) + { + case BLUETOOTH_LE_HM10_SERIAL: + { + BLE_FIFO_RX = new cbuf(BLE_FIFO_RX_SIZE); + BLE_FIFO_TX = new cbuf(BLE_FIFO_TX_SIZE); + + // Create the BLE Device + NimBLEDevice::init((BT_name+"-LE").c_str()); + + /* + * Set the MTU of the packets sent, + * maximum is 500, Apple needs 23 apparently. + */ + // NimBLEDevice::setMTU(23); + + // Create the BLE Server + pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + NimBLEService *pService = pServer->createService(NimBLEUUID(UART_SERVICE_UUID16)); + + // Create a BLE Characteristic + pUARTCharacteristic = pService->createCharacteristic( + NimBLEUUID(UART_CHARACTERISTIC_UUID16), + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::NOTIFY | + NIMBLE_PROPERTY::WRITE_NR + ); + +// UserDescriptor.setValue("HMSoft"); +// pUARTCharacteristic->addDescriptor(&UserDescriptor); + + pUARTCharacteristic->setCallbacks(new UARTCallbacks()); + + // Start the service + pService->start(); + + // Create the BLE Service + pService = pServer->createService(NimBLEUUID(UUID16_SVC_BATTERY)); + + // Create a BLE Characteristic + pBATCharacteristic = pService->createCharacteristic( + NimBLEUUID(UUID16_CHR_BATTERY_LEVEL), + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::NOTIFY + ); + + // Start the service + pService->start(); + + // Create the BLE Service + pService = pServer->createService(NimBLEUUID(UUID16_SVC_DEVICE_INFORMATION)); + + // Create BLE Characteristics + pModelCharacteristic = pService->createCharacteristic( + NimBLEUUID(UUID16_CHR_MODEL_NUMBER_STRING), + NIMBLE_PROPERTY::READ + ); + pSerialCharacteristic = pService->createCharacteristic( + NimBLEUUID(UUID16_CHR_SERIAL_NUMBER_STRING), + NIMBLE_PROPERTY::READ + ); + pFirmwareCharacteristic = pService->createCharacteristic( + NimBLEUUID(UUID16_CHR_FIRMWARE_REVISION_STRING), + NIMBLE_PROPERTY::READ + ); + pHardwareCharacteristic = pService->createCharacteristic( + NimBLEUUID(UUID16_CHR_HARDWARE_REVISION_STRING), + NIMBLE_PROPERTY::READ + ); + pSoftwareCharacteristic = pService->createCharacteristic( + NimBLEUUID(UUID16_CHR_SOFTWARE_REVISION_STRING), + NIMBLE_PROPERTY::READ + ); + pManufacturerCharacteristic = pService->createCharacteristic( + NimBLEUUID(UUID16_CHR_MANUFACTURER_NAME_STRING), + NIMBLE_PROPERTY::READ + ); + + const char *Model = hw_info.model == SOFTRF_MODEL_STANDALONE ? "Standalone Edition" : + hw_info.model == SOFTRF_MODEL_PRIME_MK2 ? "Prime Mark II" : + hw_info.model == SOFTRF_MODEL_PRIME_MK3 ? "Prime Mark III" : + hw_info.model == SOFTRF_MODEL_HAM ? "Ham Edition" : + hw_info.model == SOFTRF_MODEL_MIDI ? "Midi Edition" : + "Unknown"; + char SerialNum[9]; + snprintf(SerialNum, sizeof(SerialNum), "%08X", SoC->getChipId()); + + const char *Firmware = "Arduino ESP32 " ARDUINO_ESP32_RELEASE; + + char Hardware[9]; + snprintf(Hardware, sizeof(Hardware), "%08X", hw_info.revision); + + const char *Manufacturer = SOFTRF_IDENT; + const char *Software = SOFTRF_FIRMWARE_VERSION; + + pModelCharacteristic-> setValue((uint8_t *) Model, strlen(Model)); + pSerialCharacteristic-> setValue((uint8_t *) SerialNum, strlen(SerialNum)); + pFirmwareCharacteristic-> setValue((uint8_t *) Firmware, strlen(Firmware)); + pHardwareCharacteristic-> setValue((uint8_t *) Hardware, strlen(Hardware)); + pSoftwareCharacteristic-> setValue((uint8_t *) Software, strlen(Software)); + pManufacturerCharacteristic->setValue((uint8_t *) Manufacturer, strlen(Manufacturer)); + + // Start the service + pService->start(); + +#if defined(USE_BLE_MIDI) + // Create the BLE Service + pService = pServer->createService(NimBLEUUID(MIDI_SERVICE_UUID)); + + // Create a BLE Characteristic + pMIDICharacteristic = pService->createCharacteristic( + NimBLEUUID(MIDI_CHARACTERISTIC_UUID), + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE | + NIMBLE_PROPERTY::NOTIFY | + NIMBLE_PROPERTY::WRITE_NR + ); + + // Start the service + pService->start(); +#endif /* USE_BLE_MIDI */ + + // Start advertising + NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); +#if 0 + pAdvertising->addServiceUUID(NimBLEUUID(UART_SERVICE_UUID16)); + pAdvertising->addServiceUUID(NimBLEUUID(UUID16_SVC_BATTERY)); +#if defined(USE_BLE_MIDI) + pAdvertising->addServiceUUID(NimBLEUUID(MIDI_SERVICE_UUID)); +#endif /* USE_BLE_MIDI */ +#else + /* work around https://github.com/espressif/arduino-esp32/issues/6750 */ + NimBLEAdvertisementData BLEAdvData; + BLEAdvData.setFlags(0x06); + BLEAdvData.setCompleteServices(NimBLEUUID(UART_SERVICE_UUID16)); + BLEAdvData.setCompleteServices(NimBLEUUID(UUID16_SVC_BATTERY)); +#if defined(USE_BLE_MIDI) + BLEAdvData.setCompleteServices(NimBLEUUID(MIDI_SERVICE_UUID)); +#endif /* USE_BLE_MIDI */ + pAdvertising->setAdvertisementData(BLEAdvData); +#endif + pAdvertising->setScanResponse(true); + pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue + pAdvertising->setMaxPreferred(0x12); + NimBLEDevice::startAdvertising(); + + BLE_Advertising_TimeMarker = millis(); + } + break; + case BLUETOOTH_NONE: + case BLUETOOTH_SPP: + case BLUETOOTH_A2DP_SOURCE: + default: + break; + } +} + +static void ESP32_Bluetooth_loop() +{ + switch (settings->bluetooth) + { + case BLUETOOTH_LE_HM10_SERIAL: + { + // notify changed value + // bluetooth stack will go into congestion, if too many packets are sent + if (deviceConnected && (millis() - BLE_Notify_TimeMarker > 10)) { /* < 18000 baud */ + + uint8_t chunk[BLE_MAX_WRITE_CHUNK_SIZE]; + size_t size = BLE_FIFO_TX->available(); + size = size < BLE_MAX_WRITE_CHUNK_SIZE ? size : BLE_MAX_WRITE_CHUNK_SIZE; + + if (size > 0) { + BLE_FIFO_TX->read((char *) chunk, size); + + pUARTCharacteristic->setValue(chunk, size); + pUARTCharacteristic->notify(); + } + + BLE_Notify_TimeMarker = millis(); + } + // disconnecting + if (!deviceConnected && oldDeviceConnected && (millis() - BLE_Advertising_TimeMarker > 500) ) { + // give the bluetooth stack the chance to get things ready + pServer->startAdvertising(); // restart advertising + oldDeviceConnected = deviceConnected; + BLE_Advertising_TimeMarker = millis(); + } + // connecting + if (deviceConnected && !oldDeviceConnected) { + // do stuff here on connecting + oldDeviceConnected = deviceConnected; + } + if (deviceConnected && isTimeToBattery()) { + uint8_t battery_level = Battery_charge(); + + pBATCharacteristic->setValue(&battery_level, 1); + pBATCharacteristic->notify(); + } + } + break; + case BLUETOOTH_NONE: + case BLUETOOTH_SPP: + case BLUETOOTH_A2DP_SOURCE: + default: + break; + } +} + +static void ESP32_Bluetooth_fini() +{ + /* TBD */ +} + +static int ESP32_Bluetooth_available() +{ + int rval = 0; + + switch (settings->bluetooth) + { + case BLUETOOTH_LE_HM10_SERIAL: + rval = BLE_FIFO_RX->available(); + break; + case BLUETOOTH_NONE: + case BLUETOOTH_A2DP_SOURCE: + case BLUETOOTH_SPP: + default: + break; + } + + return rval; +} + +static int ESP32_Bluetooth_read() +{ + int rval = -1; + + switch (settings->bluetooth) + { + case BLUETOOTH_LE_HM10_SERIAL: + rval = BLE_FIFO_RX->read(); + break; + case BLUETOOTH_NONE: + case BLUETOOTH_A2DP_SOURCE: + case BLUETOOTH_SPP: + default: + break; + } + + return rval; +} + +static size_t ESP32_Bluetooth_write(const uint8_t *buffer, size_t size) +{ + size_t rval = size; + + switch (settings->bluetooth) + { + case BLUETOOTH_LE_HM10_SERIAL: + rval = BLE_FIFO_TX->write((char *) buffer, + (BLE_FIFO_TX->room() > size ? size : BLE_FIFO_TX->room())); + break; + case BLUETOOTH_NONE: + case BLUETOOTH_A2DP_SOURCE: + case BLUETOOTH_SPP: + default: + break; + } + + return rval; +} + +IODev_ops_t ESP32_Bluetooth_ops = { + "ESP32 Bluetooth", + ESP32_Bluetooth_setup, + ESP32_Bluetooth_loop, + ESP32_Bluetooth_fini, + ESP32_Bluetooth_available, + ESP32_Bluetooth_read, + ESP32_Bluetooth_write +}; + +#endif /* USE_NIMBLE */ +#endif /* ESP32 */ diff --git a/software/firmware/source/SoftRF/src/platform/bluetooth/NimBLE.h b/software/firmware/source/SoftRF/src/platform/bluetooth/NimBLE.h new file mode 100644 index 000000000..6ed99552a --- /dev/null +++ b/software/firmware/source/SoftRF/src/platform/bluetooth/NimBLE.h @@ -0,0 +1,55 @@ +/* + * NimBLE.h + * Copyright (C) 2024 Linar Yusupov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef NIMBLE_H +#define NIMBLE_H + +#define UART_SERVICE_UUID16 ((uint16_t) 0xFFE0) +#define UART_CHARACTERISTIC_UUID16 ((uint16_t) 0xFFE1) +#define UART_SERVICE_UUID128 "0000ffe0-0000-1000-8000-00805f9b34fb" +#define UART_CHARACTERISTIC_UUID128 "0000ffe1-0000-1000-8000-00805f9b34fb" + +#define MIDI_SERVICE_UUID "03b80e5a-ede8-4b33-a751-6ce34ec4c700" +#define MIDI_CHARACTERISTIC_UUID "7772e5db-3868-4112-a1a9-f2669d106bf3" + +#define UUID16_SVC_BATTERY ((uint16_t) 0x180F) +#define UUID16_CHR_BATTERY_LEVEL ((uint16_t) 0x2A19) + +#define UUID16_SVC_DEVICE_INFORMATION ((uint16_t) 0x180A) +#define UUID16_CHR_MODEL_NUMBER_STRING ((uint16_t) 0x2A24) +#define UUID16_CHR_SERIAL_NUMBER_STRING ((uint16_t) 0x2A25) +#define UUID16_CHR_FIRMWARE_REVISION_STRING ((uint16_t) 0x2A26) +#define UUID16_CHR_HARDWARE_REVISION_STRING ((uint16_t) 0x2A27) +#define UUID16_CHR_SOFTWARE_REVISION_STRING ((uint16_t) 0x2A28) +#define UUID16_CHR_MANUFACTURER_NAME_STRING ((uint16_t) 0x2A29) + +#define SENSBOX_SERVICE_UUID "aba27100-143b-4b81-a444-edcd0000f020" +#define NAVIGATION_CHARACTERISTIC_UUID "aba27100-143b-4b81-a444-edcd0000f022" +#define MOVEMENT_CHARACTERISTIC_UUID "aba27100-143b-4b81-a444-edcd0000f023" +#define GPS2_CHARACTERISTIC_UUID "aba27100-143b-4b81-a444-edcd0000f024" +#define SYSTEM_CHARACTERISTIC_UUID "aba27100-143b-4b81-a444-edcd0000f025" + +/* (FLAA x MAX_TRACKING_OBJECTS + GNGGA + GNRMC + FLAU) x 80 symbols */ +#define BLE_FIFO_TX_SIZE 1024 +#define BLE_FIFO_RX_SIZE 256 + +#define BLE_MAX_WRITE_CHUNK_SIZE 20 + +extern IODev_ops_t ESP32_Bluetooth_ops; + +#endif /* NIMBLE_H */