Skip to content

Commit f6a9c21

Browse files
authored
feat(gfps_service): Add Google Fast Pair Service (#161)
* feat(gfps_service): Add Google Fast Pair Service * Add google/nearby submodule under external folder * Add gfps_service component implementing the device specific google/nearby support as well as a wrapper service for GFPS * Add gfps_service example * Update and rebuild docs (and clean up some extraneous files) * Update CI * rebuild docs * readme: update
1 parent a104339 commit f6a9c21

File tree

130 files changed

+3188
-462
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+3188
-462
lines changed

Diff for: .github/workflows/build.yml

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ jobs:
5555
target: esp32
5656
- path: 'components/ft5x06/example'
5757
target: esp32s3
58+
- path: 'components/gfps_service/example'
59+
target: esp32s3
5860
- path: 'components/gt911/example'
5961
target: esp32s3
6062
- path: 'components/hid-rp/example'

Diff for: .gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@
3434
[submodule "external/hid-rp"]
3535
path = external/hid-rp
3636
url = https://github.com/intergatedcircuits/hid-rp
37+
[submodule "external/nearby"]
38+
path = external/nearby
39+
url = [email protected]:google/nearby

Diff for: components/gfps_service/CMakeLists.txt

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
idf_component_register(
2+
INCLUDE_DIRS "../../external/nearby/embedded/client/source" "../../external/nearby/embedded/common/source" "../../external/nearby/embedded/common/target" "include"
3+
SRC_DIRS "../../external/nearby/embedded/client/source" "../../external/nearby/embedded/common/source" "../../external/nearby/embedded/common/source/mbedtls" "src"
4+
REQUIRES "esp_hw_support" "esp-nimble-cpp" "mbedtls" "nvs_flash" "logger" "task" "timer" "base_component"
5+
)
6+
add_definitions(-DNEARBY_TRACE_LEVEL=6) # log levels are VERBOSE=1, DEBUG, INFO, WARN, ERROR, OFF=6
7+
add_definitions(-DNEARBY_PLATFORM_USE_MBEDTLS=1)
8+
add_definitions(-DNEARBY_FP_ENABLE_BATTERY_NOTIFICATION=0)
9+
add_definitions(-DNEARBY_FP_ENABLE_ADDITIONAL_DATA=0)
10+
add_definitions(-DNEARBY_FP_MESSAGE_STREAM=0)
11+
# add_definitions(-DNEARBY_PLATFORM_HAS_SE)
12+
# add_definitions(-DNEARBY_FP_HAVE_BLE_ADDRESS_ROTATION=0)
13+
# add_definitions(-DNEARBY_FP_ENABLE_SASS=0) # smart audio source switching
14+
add_definitions(-DNEARBY_FP_RETROACTIVE_PAIRING=1) # not sure what this is...
15+
add_definitions(-DNEARBY_FP_BLE_ONLY=1)
16+
add_definitions(-DNEARBY_FP_PREFER_BLE_BONDING=1)
17+
add_definitions(-DNEARBY_FP_PREFER_LE_TRANSPORT=1)

Diff for: components/gfps_service/Kconfig.projbuild

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
menu "GFPS Configuration"
2+
config GFPS_MODEL_ID
3+
hex "GFPS Model ID"
4+
default 0x0
5+
help
6+
Set the model id (24 bit) used for Google Fast Pair Service (GFPS)
7+
8+
config GFPS_ANTISPOOFING_PRIVATE_KEY
9+
string "GFPS Anti-Spoofing Private Key"
10+
default ""
11+
help
12+
Set the GFPS anti-spoofing private key, as a base64 uncompressed string.
13+
endmenu

Diff for: components/gfps_service/example/CMakeLists.txt

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# The following lines of boilerplate have to be in your project's CMakeLists
2+
# in this exact order for cmake to work correctly
3+
cmake_minimum_required(VERSION 3.5)
4+
5+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
6+
7+
8+
# add the component directories that we want to use
9+
set(EXTRA_COMPONENT_DIRS
10+
"../../../components/"
11+
)
12+
13+
set(
14+
COMPONENTS
15+
"main esptool_py ble_gatt_server gfps_service"
16+
CACHE STRING
17+
"List of components to include"
18+
)
19+
20+
project(gfps_service_example)
21+
22+
set(CMAKE_CXX_STANDARD 20)

Diff for: components/gfps_service/example/README.md

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Google Fast Pair Service (GFPS) Service Example
2+
3+
This example shows how to use the `espp::GfpsService` class together with the
4+
`espp::BleGattServer` class to create and manage a BLE GATT server that provides
5+
support for Google Fast Pair Service (GFPS) using the
6+
[nearby/embedded](https://github.com/google/nearby) framework.
7+
8+
NOTE: this example uses a pre-registered device. If you want to register your
9+
own device(s), you must do that on the [nearby/devices
10+
dashboard](https://developers.google.com/nearby/devices).
11+
12+
## How to use example
13+
14+
### Hardware Required
15+
16+
This example should run on any ESP32s3 development board as it requires no
17+
peripheral connections.
18+
19+
### Build and Flash
20+
21+
Build the project and flash it to the board, then run monitor tool to view serial output:
22+
23+
```
24+
idf.py -p PORT flash monitor
25+
```
26+
27+
(Replace PORT with the name of the serial port to use.)
28+
29+
(To exit the serial monitor, type ``Ctrl-]``.)
30+
31+
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
32+
33+
## Example Output
34+
35+
![CleanShot 2024-02-29 at 09 51 07](https://github.com/esp-cpp/espp/assets/213467/dbd44440-56dd-4a61-8448-9a1e4543d73b)

Diff for: components/gfps_service/example/main/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
idf_component_register(SRC_DIRS "."
2+
INCLUDE_DIRS ".")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#include <chrono>
2+
#include <vector>
3+
4+
#include "ble_gatt_server.hpp"
5+
#include "gfps_service.hpp"
6+
7+
using namespace std::chrono_literals;
8+
9+
extern "C" void app_main(void) {
10+
espp::Logger logger({.tag = "Gfps Service Example", .level = espp::Logger::Verbosity::INFO});
11+
logger.info("Starting");
12+
13+
//! [gfps service example]
14+
15+
// NOTE: esp-nimble-cpp already depends on nvs_flash and initializes
16+
// nvs_flash in the NimBLEDevice::init(), so we don't have to do that
17+
// to store bonding info
18+
19+
// create the GATT server
20+
espp::BleGattServer ble_gatt_server;
21+
std::string device_name = "ESP++ GFPS Example";
22+
ble_gatt_server.set_log_level(espp::Logger::Verbosity::INFO);
23+
ble_gatt_server.set_callbacks({
24+
.connect_callback = [&](NimBLEConnInfo &conn_info) { logger.info("Device connected"); },
25+
.disconnect_callback = [&](NimBLEConnInfo &conn_info) { logger.info("Device disconnected"); },
26+
.authentication_complete_callback =
27+
[&](NimBLEConnInfo &conn_info) { logger.info("Device authenticated"); },
28+
});
29+
ble_gatt_server.init(device_name);
30+
ble_gatt_server.set_advertise_on_disconnect(true);
31+
32+
// let's create a GFPS service
33+
espp::GfpsService gfps_service;
34+
gfps_service.init(ble_gatt_server.server());
35+
36+
// now that we've made the input characteristic, we can start the service
37+
gfps_service.start();
38+
ble_gatt_server.start_services(); // starts the device info service and battery service
39+
// NOTE: we could also directly start them ourselves if we wanted to
40+
// control the order of starting the services
41+
// e.g.:
42+
// ble_gatt_server.battery_service().start();
43+
// ble_gatt_server.device_info_service().start();
44+
45+
// now start the gatt server
46+
ble_gatt_server.start();
47+
48+
// let's set some of the service data
49+
auto &battery_service = ble_gatt_server.battery_service();
50+
battery_service.set_battery_level(99);
51+
52+
auto &device_info_service = ble_gatt_server.device_info_service();
53+
uint8_t vendor_source = 0x02; // USB
54+
uint16_t vid = 0x045E; // Microsoft
55+
uint16_t pid = 0x02FD; // Xbox One Controller
56+
uint16_t product_version = 0x0100;
57+
device_info_service.set_pnp_id(vendor_source, vid, pid, product_version);
58+
device_info_service.set_manufacturer_name("ESP-CPP");
59+
// NOTE: this is NOT required to be the same as the GFPS SKU Name
60+
device_info_service.set_model_number("espp-gfps-01");
61+
device_info_service.set_serial_number("1234567890");
62+
device_info_service.set_software_version("1.0.0");
63+
device_info_service.set_firmware_version("1.0.0");
64+
device_info_service.set_hardware_version("1.0.0");
65+
66+
// now lets start advertising
67+
espp::BleGattServer::AdvertisingData adv_data = {
68+
.name = device_name,
69+
.appearance = 0x03C4, // Gamepad
70+
.services = {},
71+
.service_data =
72+
{// these are the service data that we want to advertise
73+
{gfps_service.uuid(), gfps_service.get_service_data()}},
74+
};
75+
espp::BleGattServer::AdvertisingParameters adv_params = {};
76+
ble_gatt_server.start_advertising(adv_data, adv_params);
77+
78+
// now lets update the battery level every second
79+
uint8_t battery_level = 99;
80+
while (true) {
81+
auto start = std::chrono::steady_clock::now();
82+
83+
// update the battery level
84+
battery_service.set_battery_level(battery_level);
85+
battery_level = (battery_level % 100) + 1;
86+
87+
// sleep
88+
std::this_thread::sleep_until(start + 1s);
89+
}
90+
//! [gfps service example]
91+
}

Diff for: components/gfps_service/example/partitions.csv

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Name, Type, SubType, Offset, Size
2+
nvs, data, nvs, 0x9000, 0x6000
3+
phy_init, data, phy, 0xf000, 0x1000
4+
factory, app, factory, 0x10000, 2M
5+
littlefs, data, spiffs, , 1M

Diff for: components/gfps_service/example/sdkconfig.defaults

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
CONFIG_IDF_TARGET="esp32s3"
2+
3+
# on the ESP32S3, which has native USB, we need to set the console so that the
4+
# CLI can be configured correctly:
5+
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
6+
7+
# Common ESP-related
8+
#
9+
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
10+
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
11+
12+
CONFIG_FREERTOS_HZ=1000
13+
14+
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
15+
16+
#
17+
# BT config
18+
#
19+
CONFIG_BT_ENABLED=y
20+
CONFIG_BT_BLUEDROID_ENABLED=n
21+
CONFIG_BT_NIMBLE_ENABLED=y
22+
CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y
23+
CONFIG_BT_NIMBLE_NVS_PERSIST=y
24+
CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=100
25+
CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
26+
27+
# NOTE: we can support extended advertising (longer advertisement packets) by
28+
# enabling the following:
29+
# CONFIG_BT_NIMBLE_EXT_ADV=y
30+
#
31+
# HOWEVER: we don't currently support this as the API to ble_gatt_server will
32+
# need to support this compile-time definition.
33+
34+
# Set the default Tx power level (P9 = +9dBm = the default)
35+
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y
36+
37+
# Support modem sleep (low power mode)
38+
# CONFIG_BT_CTRL_MODEM_SLEEP=y
39+
40+
# Set the ESP-NIMBLE-CPP Config
41+
CONFIG_NIMBLE_CPP_LOG_LEVEL_NONE=y

Diff for: components/gfps_service/include/gfps.hpp

+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#pragma once
2+
3+
#include <chrono>
4+
#include <cstdint>
5+
#include <functional>
6+
#include <vector>
7+
8+
#include <sdkconfig.h>
9+
10+
#include <esp_mac.h>
11+
#include <esp_random.h>
12+
#include <nvs_flash.h>
13+
14+
#include "nearby_fp_client.h"
15+
#include "nearby_fp_library.h"
16+
#include "nearby_platform_audio.h"
17+
#include "nearby_platform_battery.h"
18+
#include "nearby_platform_ble.h"
19+
#include "nearby_platform_bt.h"
20+
#include "nearby_platform_os.h"
21+
#include "nearby_platform_persistence.h"
22+
#include "nearby_platform_se.h"
23+
#include "nearby_platform_trace.h"
24+
25+
#include "freertos/FreeRTOS.h"
26+
#include "freertos/semphr.h"
27+
#include "freertos/task.h"
28+
29+
#include "logger.hpp"
30+
#include "task.hpp"
31+
#include "timer.hpp"
32+
33+
#if CONFIG_BT_NIMBLE_ENABLED
34+
#include "NimBLEDevice.h"
35+
#endif
36+
37+
namespace espp {
38+
namespace gfps {
39+
40+
/// Function which GFPS will call to send notifications to the remote device
41+
/// @param characteristic The characteristic to notify
42+
/// @param data The data to send
43+
/// @param length The length of the data
44+
typedef std::function<bool(nearby_fp_Characteristic, const uint8_t *, size_t)> notify_callback_t;
45+
46+
/// Configuration for the Google Fast Pair Service
47+
struct Config {
48+
notify_callback_t notify; ///< Callback to enable gfps to notify the remote device of changes
49+
};
50+
51+
/// Initialize the Google Fast Pair Service
52+
/// @param config The configuration for the service
53+
void init(const Config &config);
54+
55+
/// Deinitialize the Google Fast Pair Service
56+
void deinit();
57+
58+
/// Get the GFPS model ID
59+
uint32_t get_model_id();
60+
61+
/// Set the remote public key
62+
/// @param public_key The public key
63+
void set_remote_public_key(const std::vector<uint8_t> &public_key);
64+
65+
/// Set the remote public key
66+
/// @param public_key The public key
67+
/// @param length The length of the public key
68+
void set_remote_public_key(const uint8_t *public_key, size_t length);
69+
70+
/// Get the remote public key
71+
/// @return The remote public key
72+
std::vector<uint8_t> get_remote_public_key();
73+
74+
/// Get the underlying GFPS BLE interface
75+
const nearby_platform_BleInterface *get_ble_interface();
76+
77+
/// Get the underlying GFPS BT interface
78+
const nearby_platform_BtInterface *get_bt_interface();
79+
80+
#if CONFIG_BT_NIMBLE_ENABLED || defined(_DOXYGEN_)
81+
int ble_gap_event_handler(ble_gap_event *event, void *arg);
82+
#endif // CONFIG_BT_NIMBLE_ENABLED || defined(_DOXYGEN_)
83+
84+
} // namespace gfps
85+
} // namespace espp
86+
87+
#if CONFIG_BT_NIMBLE_ENABLED || defined(_DOXYGEN_)
88+
// for printing of BLE_GAP_EVENT_ using libfmt
89+
template <> struct fmt::formatter<ble_gap_event> {
90+
constexpr auto parse(format_parse_context &ctx) { return ctx.begin(); }
91+
92+
template <typename FormatContext> auto format(const ble_gap_event &event, FormatContext &ctx) {
93+
switch (event.type) {
94+
case BLE_GAP_EVENT_CONNECT:
95+
return fmt::format_to(ctx.out(), "BLE_GAP_EVENT_CONNECT");
96+
case BLE_GAP_EVENT_DISCONNECT:
97+
return fmt::format_to(ctx.out(), "BLE_GAP_EVENT_DISCONNECT");
98+
case BLE_GAP_EVENT_CONN_UPDATE:
99+
return fmt::format_to(ctx.out(), "BLE_GAP_EVENT_CONN_UPDATE");
100+
case BLE_GAP_EVENT_ENC_CHANGE:
101+
return fmt::format_to(ctx.out(), "BLE_GAP_EVENT_ENC_CHANGE");
102+
case BLE_GAP_EVENT_PASSKEY_ACTION:
103+
return fmt::format_to(ctx.out(), "BLE_GAP_EVENT_PASSKEY_ACTION");
104+
case BLE_GAP_EVENT_SUBSCRIBE:
105+
return fmt::format_to(ctx.out(), "BLE_GAP_EVENT_SUBSCRIBE");
106+
case BLE_GAP_EVENT_MTU:
107+
return fmt::format_to(ctx.out(), "BLE_GAP_EVENT_MTU");
108+
case BLE_GAP_EVENT_REPEAT_PAIRING:
109+
return fmt::format_to(ctx.out(), "BLE_GAP_EVENT_REPEAT_PAIRING");
110+
default:
111+
return fmt::format_to(ctx.out(), "BLE_GAP_EVENT_UNKNOWN ({})", (int)event.type);
112+
}
113+
}
114+
};
115+
#endif // CONFIG_BT_NIMBLE_ENABLED || defined(_DOXYGEN_)
116+
117+
// for pritning of nearby_fp_Characteristic using libfmt
118+
template <> struct fmt::formatter<nearby_fp_Characteristic> {
119+
constexpr auto parse(format_parse_context &ctx) { return ctx.begin(); }
120+
121+
template <typename FormatContext>
122+
auto format(const nearby_fp_Characteristic &characteristic, FormatContext &ctx) {
123+
switch (characteristic) {
124+
case kModelId:
125+
return fmt::format_to(ctx.out(), "kModelId");
126+
case kKeyBasedPairing:
127+
return fmt::format_to(ctx.out(), "kKeyBasedPairing");
128+
case kPasskey:
129+
return fmt::format_to(ctx.out(), "kPasskey");
130+
case kAccountKey:
131+
return fmt::format_to(ctx.out(), "kAccountKey");
132+
default:
133+
return fmt::format_to(ctx.out(), "nearby_fp_Characteristic_UNKNOWN ({})",
134+
(int)characteristic);
135+
}
136+
}
137+
};

0 commit comments

Comments
 (0)