-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ble_gatt_server): added ble gatt server (#157)
* feat(ble_gatt_server): added ble gatt server * Add esp-nimble-cpp (espp fork) submodule * Add ble_gatt_server component with device info service, battery service, and helpful cli / menu * Add ble gatt server example * Update docs * Rebuild docs * Update ci * readme: update * ignore esp-nimble-cpp for static analysis
- Loading branch information
Showing
124 changed files
with
2,268 additions
and
191 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,3 +28,6 @@ | |
[submodule "external/magic_enum"] | ||
path = external/magic_enum | ||
url = [email protected]:neargye/magic_enum | ||
[submodule "components/esp-nimble-cpp"] | ||
path = components/esp-nimble-cpp | ||
url = [email protected]:esp-cpp/esp-nimble-cpp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
idf_component_register( | ||
INCLUDE_DIRS "include" | ||
SRC_DIRS "src" | ||
REQUIRES "esp-nimble-cpp" "base_component" "cli" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# The following lines of boilerplate have to be in your project's CMakeLists | ||
# in this exact order for cmake to work correctly | ||
cmake_minimum_required(VERSION 3.5) | ||
|
||
include($ENV{IDF_PATH}/tools/cmake/project.cmake) | ||
|
||
|
||
# add the component directories that we want to use | ||
set(EXTRA_COMPONENT_DIRS | ||
"../../../components/" | ||
) | ||
|
||
set( | ||
COMPONENTS | ||
"main esptool_py ble_gatt_server cli" | ||
CACHE STRING | ||
"List of components to include" | ||
) | ||
|
||
project(ble_gatt_server_example) | ||
|
||
set(CMAKE_CXX_STANDARD 20) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# BLE GATT Server Example | ||
|
||
This example shows how to use the `espp::BleGattServer` class to create and manage | ||
a BLE GATT server. | ||
|
||
## How to use example | ||
|
||
### Hardware Required | ||
|
||
This example should run on any ESP32s3 development board as it requires no | ||
peripheral connections. | ||
|
||
### Build and Flash | ||
|
||
Build the project and flash it to the board, then run monitor tool to view serial output: | ||
|
||
``` | ||
idf.py -p PORT flash monitor | ||
``` | ||
|
||
(Replace PORT with the name of the serial port to use.) | ||
|
||
(To exit the serial monitor, type ``Ctrl-]``.) | ||
|
||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. | ||
|
||
## Example Output | ||
|
||
![CleanShot 2024-02-28 at 10 29 10](https://github.com/esp-cpp/espp/assets/213467/07fa35f7-4c4b-44d8-812f-4e14d54e7164) | ||
|
||
![image](https://github.com/esp-cpp/espp/assets/213467/86d026f9-dea7-4efe-8bc3-1b06307b5eaa) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
idf_component_register(SRC_DIRS "." | ||
INCLUDE_DIRS ".") |
109 changes: 109 additions & 0 deletions
109
components/ble_gatt_server/example/main/ble_gatt_server_example.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
#include <chrono> | ||
#include <vector> | ||
|
||
#include "ble_gatt_server.hpp" | ||
#include "ble_gatt_server_menu.hpp" | ||
#include "cli.hpp" | ||
#include "logger.hpp" | ||
|
||
using namespace std::chrono_literals; | ||
|
||
extern "C" void app_main(void) { | ||
espp::Logger logger({.tag = "BLE GATT Server Example", .level = espp::Logger::Verbosity::INFO}); | ||
logger.info("Starting"); | ||
|
||
//! [ble gatt server example] | ||
|
||
// NOTE: esp-nimble-cpp already depends on nvs_flash and initializes | ||
// nvs_flash in the NimBLEDevice::init(), so we don't have to do that | ||
// to store bonding info | ||
|
||
// create the GATT server | ||
espp::BleGattServer ble_gatt_server; | ||
std::string device_name = "Espp BLE GATT Server"; | ||
ble_gatt_server.set_log_level(espp::Logger::Verbosity::INFO); | ||
ble_gatt_server.set_callbacks({ | ||
.connect_callback = [&](NimBLEConnInfo &conn_info) { logger.debug("Device connected"); }, | ||
.disconnect_callback = | ||
[&](NimBLEConnInfo &conn_info) { logger.debug("Device disconnected"); }, | ||
.authentication_complete_callback = | ||
[&](NimBLEConnInfo &conn_info) { logger.debug("Device authenticated"); }, | ||
}); | ||
ble_gatt_server.init(device_name); | ||
ble_gatt_server.set_advertise_on_disconnect(true); | ||
|
||
// let's configure the security | ||
bool bonding = true; | ||
bool mitm = false; | ||
bool secure_connections = true; | ||
ble_gatt_server.set_security(bonding, mitm, secure_connections); | ||
// and some i/o and key config | ||
ble_gatt_server.set_io_capabilities(BLE_HS_IO_NO_INPUT_OUTPUT); | ||
ble_gatt_server.set_init_key_distribution(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); | ||
ble_gatt_server.set_resp_key_distribution(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID); | ||
|
||
// you can create a service and add it to the server using | ||
// ble_gatt_server.server().addService() | ||
|
||
// now start the services | ||
ble_gatt_server.start_services(); // starts the device info service and battery service | ||
// NOTE: we could also directly start them ourselves if we wanted to | ||
// control the order of starting the services | ||
// e.g.: | ||
// ble_gatt_server.battery_service().start(); | ||
// ble_gatt_server.device_info_service().start(); | ||
|
||
// now start the gatt server | ||
ble_gatt_server.start(); | ||
|
||
// let's set some of the service data | ||
auto &battery_service = ble_gatt_server.battery_service(); | ||
battery_service.set_battery_level(99); | ||
|
||
auto &device_info_service = ble_gatt_server.device_info_service(); | ||
uint8_t vendor_source = 0x01; | ||
uint16_t vid = 0xCafe; | ||
uint16_t pid = 0xFace; | ||
uint16_t product_version = 0x0100; | ||
device_info_service.set_pnp_id(vendor_source, vid, pid, product_version); | ||
device_info_service.set_manufacturer_name("ESP-CPP"); | ||
device_info_service.set_model_number("esp-ble-01"); | ||
device_info_service.set_serial_number("1234567890"); | ||
device_info_service.set_software_version("1.0.0"); | ||
device_info_service.set_firmware_version("1.0.0"); | ||
device_info_service.set_hardware_version("1.0.0"); | ||
|
||
// now lets start advertising | ||
espp::BleGattServer::AdvertisingData adv_data = { | ||
.name = device_name, | ||
}; | ||
espp::BleGattServer::AdvertisingParameters adv_params = {}; | ||
ble_gatt_server.start_advertising(adv_data, adv_params); | ||
|
||
// now lets update the battery level every second for a little while | ||
uint8_t battery_level = 99; | ||
for (int i = 0; i < 200; i++) { | ||
auto start = std::chrono::steady_clock::now(); | ||
|
||
// update the battery level | ||
battery_service.set_battery_level(battery_level); | ||
battery_level = (battery_level % 100) + 1; | ||
|
||
// sleep | ||
std::this_thread::sleep_until(start + 1s); | ||
} | ||
|
||
// Now let's test and use the BLE menu (CLI) | ||
// turn off some of the logs so that it doesn't clutter up the CLI | ||
ble_gatt_server.set_log_level(espp::Logger::Verbosity::WARN); | ||
// and make the CLI | ||
auto ble_menu = espp::make_ble_gatt_server_menu(ble_gatt_server); | ||
cli::SetColor(); | ||
cli::Cli cli(std::move(ble_menu)); | ||
cli.ExitAction([](auto &out) { out << "Goodbye and thanks for all the fish.\n"; }); | ||
|
||
espp::Cli input(cli); | ||
input.SetInputHistorySize(10); | ||
input.Start(); // this will not return until the user enters the `exit` command. | ||
//! [ble gatt server example] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Name, Type, SubType, Offset, Size | ||
nvs, data, nvs, 0x9000, 0x6000 | ||
phy_init, data, phy, 0xf000, 0x1000 | ||
factory, app, factory, 0x10000, 2M | ||
littlefs, data, spiffs, , 1M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
CONFIG_IDF_TARGET="esp32s3" | ||
|
||
# on the ESP32S3, which has native USB, we need to set the console so that the | ||
# CLI can be configured correctly: | ||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y | ||
|
||
# Common ESP-related | ||
# | ||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 | ||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 | ||
|
||
CONFIG_FREERTOS_HZ=1000 | ||
|
||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y | ||
|
||
# | ||
# BT config | ||
# | ||
CONFIG_BT_ENABLED=y | ||
CONFIG_BT_BLUEDROID_ENABLED=n | ||
CONFIG_BT_NIMBLE_ENABLED=y | ||
CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y | ||
CONFIG_BT_NIMBLE_NVS_PERSIST=y | ||
CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=100 | ||
CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192 | ||
|
||
# Set the default Tx power level (P9 = +9dBm = the default) | ||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y | ||
|
||
# Support modem sleep (low power mode) | ||
# CONFIG_BT_CTRL_MODEM_SLEEP=y | ||
|
||
# Set the ESP-NIMBLE-CPP Config | ||
CONFIG_NIMBLE_CPP_LOG_LEVEL_NONE=y | ||
|
||
# the cli library requires exceptions right now... | ||
CONFIG_COMPILER_CXX_EXCEPTIONS=y |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
#pragma once | ||
|
||
#include <sdkconfig.h> | ||
|
||
#include <algorithm> | ||
#include <ctime> | ||
#include <functional> | ||
#include <string> | ||
#include <vector> | ||
|
||
#if CONFIG_BT_NIMBLE_ENABLED | ||
|
||
#include "NimBLEDevice.h" | ||
|
||
#include "base_component.hpp" | ||
|
||
namespace espp { | ||
/// Battery Service | ||
/// This class is responsible for creating and managing the Battery Service. | ||
/// | ||
/// The service is created with the following characteristics: | ||
/// - Battery Level (read, notify, unencrypted) | ||
/// | ||
/// The Battery Level characteristic is a standard characteristic defined by | ||
/// the Bluetooth SIG. It is used to report the current battery level of the | ||
/// device. | ||
class BatteryService : public BaseComponent { | ||
public: | ||
/// Constructor | ||
/// \param log_level The log level for the component | ||
explicit BatteryService(espp::Logger::Verbosity log_level = espp::Logger::Verbosity::WARN) | ||
: BaseComponent("BatteryService", log_level) {} | ||
|
||
/// Initialize the Battery Service | ||
/// \param server The BLE server to add the service to | ||
void init(NimBLEServer *server) { make_service(server); } | ||
|
||
/// Start the service | ||
/// \note This must be called after the service has been initialized | ||
void start() { | ||
if (!service_) { | ||
logger_.error("Service not created"); | ||
return; | ||
} | ||
if (!service_->start()) { | ||
logger_.error("Failed to start service"); | ||
return; | ||
} | ||
} | ||
|
||
/// Get the service | ||
/// \return The Battery Service | ||
NimBLEService *get_service() { return service_; } | ||
|
||
/// Get the UUID of the service | ||
/// \return The UUID of the service | ||
NimBLEUUID uuid() { | ||
if (service_) { | ||
return service_->getUUID(); | ||
} | ||
return NimBLEUUID(BATTERY_SERVICE_UUID); | ||
} | ||
|
||
/// Set the battery level | ||
/// \param level The battery level | ||
/// \note The level is clamped to the range [0, 100] | ||
/// \note This must be called after the service has been initialized | ||
void set_battery_level(uint8_t level) { | ||
if (!battery_level_) { | ||
logger_.error("Battery level characteristic not created"); | ||
return; | ||
} | ||
// ensure the level is within the valid range | ||
level = std::min(level, BATTERY_LEVEL_MAX); | ||
battery_level_->setValue(&level, 1); | ||
battery_level_->notify(); | ||
} | ||
|
||
/// Get the battery level | ||
/// \return The battery level | ||
uint8_t get_battery_level() { | ||
if (!battery_level_) { | ||
logger_.error("Battery level characteristic not created"); | ||
return 0; | ||
} | ||
return battery_level_->getValue().getValue<uint8_t>(); | ||
} | ||
|
||
protected: | ||
static constexpr uint8_t BATTERY_LEVEL_MAX = 100; ///< Maximum battery level | ||
static constexpr uint16_t BATTERY_LEVEL_UNIT = 0x27AD; ///< Unit is percentage | ||
static constexpr uint16_t BATTERY_SERVICE_UUID = 0x180F; ///< Battery Service UUID | ||
static constexpr uint16_t BATTERY_LEVEL_CHAR_UUID = 0x2A19; ///< Battery Level Characteristic UUID | ||
|
||
void make_service(NimBLEServer *server) { | ||
service_ = server->createService(NimBLEUUID(BATTERY_SERVICE_UUID)); | ||
if (!service_) { | ||
logger_.error("Failed to create service"); | ||
return; | ||
} | ||
|
||
battery_level_ = service_->createCharacteristic( | ||
NimBLEUUID(BATTERY_LEVEL_CHAR_UUID), NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY); | ||
auto battery_level_desc = | ||
reinterpret_cast<NimBLE2904 *>(battery_level_->createDescriptor((uint16_t)0x2904)); | ||
battery_level_desc->setFormat(NimBLE2904::FORMAT_UINT8); | ||
battery_level_desc->setNamespace(1); | ||
battery_level_desc->setUnit(BATTERY_LEVEL_UNIT); | ||
} | ||
|
||
NimBLEService *service_{nullptr}; | ||
NimBLECharacteristic *battery_level_{nullptr}; | ||
}; | ||
} // namespace espp | ||
|
||
#endif // CONFIG_BT_NIMBLE_ENABLED |
Oops, something went wrong.