Skip to content

Commit

Permalink
feat(ble_gatt_server): added ble gatt server (#157)
Browse files Browse the repository at this point in the history
* 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
finger563 authored Feb 28, 2024
1 parent 761b524 commit ba90fcd
Show file tree
Hide file tree
Showing 124 changed files with 2,268 additions and 191 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
target: esp32s3
- path: 'components/bldc_motor/example'
target: esp32s3
- path: 'components/ble_gatt_server/example'
target: esp32s3
- path: 'components/bm8563/example'
target: esp32
- path: 'components/button/example'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/static_analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ jobs:
esp_idf_version: release/v5.2

# (Optional) cppcheck args
cppcheck_args: -i$GITHUB_WORKSPACE/lib -i$GITHUB_WORKSPACE/external -i$GITHUB_WORKSPACE/components/esp_littlefs -i$GITHUB_WORKSPACE/components/lvgl -i$GITHUB_WORKSPACE/components/esp-dsp --force --enable=all --inline-suppr --inconclusive --platform=mips32 --suppressions-list=$GITHUB_WORKSPACE/suppressions.txt
cppcheck_args: -i$GITHUB_WORKSPACE/lib -i$GITHUB_WORKSPACE/external -i$GITHUB_WORKSPACE/components/esp_littlefs -i$GITHUB_WORKSPACE/components/esp-nimble-cpp -i$GITHUB_WORKSPACE/components/lvgl -i$GITHUB_WORKSPACE/components/esp-dsp --force --enable=all --inline-suppr --inconclusive --platform=mips32 --suppressions-list=$GITHUB_WORKSPACE/suppressions.txt
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions components/ble_gatt_server/CMakeLists.txt
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"
)
22 changes: 22 additions & 0 deletions components/ble_gatt_server/example/CMakeLists.txt
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)
31 changes: 31 additions & 0 deletions components/ble_gatt_server/example/README.md
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)
2 changes: 2 additions & 0 deletions components/ble_gatt_server/example/main/CMakeLists.txt
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 components/ble_gatt_server/example/main/ble_gatt_server_example.cpp
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]
}
5 changes: 5 additions & 0 deletions components/ble_gatt_server/example/partitions.csv
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
37 changes: 37 additions & 0 deletions components/ble_gatt_server/example/sdkconfig.defaults
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
116 changes: 116 additions & 0 deletions components/ble_gatt_server/include/battery_service.hpp
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
Loading

0 comments on commit ba90fcd

Please sign in to comment.