OpenSourceSimWheelESP32
Open-source wireless steering wheel/button box for ESP32 boards
Loading...
Searching...
No Matches
NimBLEWrapper.hpp
Go to the documentation of this file.
1
12#pragma once
13
14//------------------------------------------------------------------------------
15// Conditional compilation
16//------------------------------------------------------------------------------
17
18#include <sdkconfig.h> // For conditional compilation
19#include "SimWheelInternals.hpp" // BatteryStatusChrData
20
21#if CONFIG_NIMBLE_ENABLED
22
23//------------------------------------------------------------------------------
24// Imports
25//------------------------------------------------------------------------------
26
27#include <cstdint>
28#include <string>
29#include <functional>
30#include <initializer_list>
31#include <type_traits>
32
33#include "host/ble_att.h" // ATT layer
34#include "host/ble_gatt.h" // GATT layer
35#include "host/ble_uuid.h" // UUIDs
36#include "host/ble_gap.h" // GAP layer
37#include "host/ble_gap.h" // GAP layer
38#include "os/os_mbuf.h" // Attribute buffers
39#include "esp32-hal-log.h" // Logging
40
41#include "HID_definitions.hpp"
42
43//------------------------------------------------------------------------------
44// Constants
45//------------------------------------------------------------------------------
46
47#define HID_REPORT_COUNT 10
48
49#define EMPTY_ble_gatt_cpfd { \
50 .format = 0, \
51 .exponent = 0, \
52 .unit = 0, \
53 .name_space = 0, \
54 .description = 0}
55
56#define EMPTY_ble_gatt_dsc_def { \
57 .uuid = nullptr, \
58 .att_flags = 0, \
59 .min_key_size = 0, \
60 .access_cb = nullptr, \
61 .arg = nullptr}
62
63#define EMPTY_ble_gatt_chr_def { \
64 .uuid = nullptr, \
65 .access_cb = nullptr, \
66 .arg = nullptr, \
67 .descriptors = nullptr, \
68 .flags = 0, \
69 .min_key_size = 0, \
70 .val_handle = nullptr, \
71 .cpfd = nullptr}
72
73#define EMPTY_ble_gatt_svc_def { \
74 .type = 0, \
75 .uuid = nullptr, \
76 .includes = nullptr, \
77 .characteristics = nullptr}
78
79//------------------------------------------------------------------------------
80// Classes
81//------------------------------------------------------------------------------
82
87struct ApiResult
88{
90 int code = 0;
92 constexpr ApiResult() noexcept {};
94 constexpr ApiResult(int value) noexcept : code{value} {};
96 constexpr ApiResult(const ApiResult &) noexcept = default;
98 constexpr ApiResult(ApiResult &&) noexcept = default;
99
106 constexpr operator bool() const noexcept { return (code == 0); }
107
113 constexpr explicit operator int() const noexcept { return code; }
114
120 constexpr ApiResult &operator=(const ApiResult &) noexcept = default;
121
127 constexpr ApiResult &operator=(ApiResult &&) noexcept = default;
128
135 constexpr ApiResult &operator=(int value) noexcept;
136
142 void abort_if(const char *txt) const;
143
149 void log_if(const char *txt) const noexcept;
150
158 int to_attr_rc(bool readOrWrite) const noexcept;
159}; // ApiResult
160
165struct BLEAdvertising
166{
167 friend struct BLEDevice;
168
170 using OnConnectionStatus = ::std::function<void(bool)>;
171
173 inline static OnConnectionStatus onConnectionStatus;
174
176 static void start();
177
179 static void stop() noexcept;
180
183 static bool connected() noexcept { return _connected; }
184
186 static void disconnect() noexcept;
187
188private:
190 inline static const ble_gap_adv_params adv_params{
191 .conn_mode = BLE_GAP_CONN_MODE_UND, // Undirected, connectable
192 .disc_mode = BLE_GAP_DISC_MODE_GEN, // General discoverable
193 .itvl_min = 0,
194 .itvl_max = 0,
195 .channel_map = 0,
196 .filter_policy = 0,
197 .high_duty_cycle = 0,
198 };
199
201 inline static bool _connected = false;
202
204 inline static int16_t _conn_handle = BLE_HS_CONN_HANDLE_NONE;
205
210 static int ble_gap_event_fn(ble_gap_event *event, void *arg);
211
213 static void init();
214}; // struct BLEAdvertising
215
220struct BLEDevice
221{
222 friend class BLEAdvertising;
223
230 static bool initialized() { return _initialized; };
231
237 static void init(const std::string &deviceName);
238
239private:
241 inline static uint8_t address_type = 0;
242
244 inline static bool ready = false;
245
247 inline static bool _initialized = false;
248
250 static void init_gatt_server();
251
254 static void onReset(int reason);
255
257 static void onSync();
258
261 static void host_task(void *param);
262}; // struct BLEDevice
263
265struct BLEReadCallback
266{
267 virtual int onRead(os_mbuf *buffer) { return 0; };
268};
269
271struct BLEWriteCallback
272{
273 virtual int onWrite(void *data, uint16_t data_size) { return 0; };
274};
275
282template <
283 typename T,
284 bool is_descriptor = false>
285struct BLEAccessor
286{
288 static constexpr bool can_read =
289 ::std::is_base_of<BLEReadCallback, T>::value;
291 static constexpr bool can_write =
292 ::std::is_base_of<BLEWriteCallback, T>::value;
293
294 static_assert(
295 can_read || can_write,
296 "BLE: Accessor does not read or write");
297
307 static int access_fn(
308 uint16_t conn_handle,
309 uint16_t attr_handle,
310 ble_gatt_access_ctxt *ctxt,
311 void *arg)
312 {
313 assert(arg);
314 log_d(
315 "BLEAccessor::access_fn(%d,%d,...), op=%d",
316 conn_handle,
317 attr_handle,
318 ctxt->op);
319 if constexpr (can_read)
320 {
321 if constexpr (is_descriptor)
322 {
323 if (ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC)
324 return static_cast<T *>(arg)->onRead(ctxt->om);
325 }
326 else
327 {
328 if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR)
329 return static_cast<T *>(arg)->onRead(ctxt->om);
330 }
331 log_e("Unexpected READ operation on attr_handle %d", attr_handle);
332 }
333 if constexpr (can_write)
334 {
335
336 if constexpr (is_descriptor)
337 {
338 if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_DSC)
339 return static_cast<T *>(arg)->onWrite(ctxt->om);
340 }
341 else
342 {
343 if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR)
344 return static_cast<T *>(arg)->onWrite(
345 ctxt->om->om_data,
346 ctxt->om->om_len);
347 }
348 log_e("Unexpected WRITE operation on attr_handle %d", attr_handle);
349 }
350 return BLE_ATT_ERR_UNLIKELY;
351 }
352};
353
355enum class HIDReportType : uint8_t
356{
357 input = 1,
358 output = 2,
359 feature = 3
360};
361
363struct BLEDesc2908 : BLEReadCallback
364{
365
367 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x2908);
368
374 ble_gatt_dsc_def definition();
375
383 void set(uint8_t report_id, HIDReportType report_type);
384
385 virtual int onRead(os_mbuf *buffer) override;
386
392 uint8_t report_id() { return _report_id; }
393
399 HIDReportType report_type() { return _report_type; }
400
401private:
403 HIDReportType _report_type = HIDReportType::feature;
404 uint8_t _report_id = 0;
405}; // BLEDesc2908
406
408struct BLECharacteristic
409{
410 static constexpr const uint16_t INVALID_HANDLE = 0xFFFF;
418 virtual ble_gatt_chr_def definition() = 0;
419
424 void notify() const;
425
435 void setSubscription_if(uint16_t requested_attr_handle, bool status);
436
437protected:
439 bool subscribed = false;
446 uint16_t attr_handle = INVALID_HANDLE;
447};
448
450struct BatteryLevelChr : BLECharacteristic, BLEReadCallback
451{
453 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x2A19);
454
455 virtual int onRead(os_mbuf *buffer) override;
456 virtual ble_gatt_chr_def definition() override;
457
463 void set(uint8_t new_value);
464
470 uint8_t get() const noexcept { return value; }
471
472private:
473 inline static ble_gatt_cpfd _chr_cpfd_def[]{
474 {
475 .format = 4, // uint8
476 .exponent = 0,
477 .unit = 0x27AD, // percentage
478 .name_space = 0x01, // Bluetooth SIG
479 .description = 0x106, // main namespace
480 },
481 EMPTY_ble_gatt_cpfd,
482 };
483 uint8_t value{100};
484 ble_gatt_dsc_def dsc_def[2]{};
485};
486
488struct BatteryStatusChr : BLECharacteristic, BLEReadCallback
489{
491 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x2BED);
492
493 virtual int onRead(os_mbuf *buffer) override;
494 virtual ble_gatt_chr_def definition() override;
495
501 void set(const BatteryStatusChrData &new_value);
502
503private:
505 BatteryStatusChrData value{};
506};
507
512struct BLEBatteryService
513{
515 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x180F);
516
518 inline static BatteryLevelChr batteryLevel{};
520 inline static BatteryStatusChr batteryStatus{};
521
528 static const ble_gatt_svc_def definition();
529
531 static void init();
532
541 static void onSubscriptionChange(
542 uint16_t attr_handle,
543 bool yesOrNo)
544 {
545 batteryLevel.setSubscription_if(attr_handle, yesOrNo);
546 batteryStatus.setSubscription_if(attr_handle, yesOrNo);
547 }
548
549private:
550 inline static ble_gatt_chr_def chr_set[3]{};
551};
552
554struct PnpInfoChr : BLECharacteristic, BLEReadCallback
555{
557 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x2A50);
558
560 uint16_t vid = BLE_VENDOR_ID;
562 uint16_t pid = BLE_PRODUCT_ID;
563
564 virtual int onRead(os_mbuf *buffer) override;
565 virtual ble_gatt_chr_def definition() override;
566};
567
569struct SerialNumberChr : BLECharacteristic, BLEReadCallback
570{
572 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x2A25);
573
574 virtual int onRead(os_mbuf *buffer) override;
575 virtual ble_gatt_chr_def definition() override;
576};
577
579struct ManufacturerChr : BLECharacteristic, BLEReadCallback
580{
582 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x2A29);
584 ::std::string name{};
585
586 virtual int onRead(os_mbuf *buffer) override;
587 virtual ble_gatt_chr_def definition() override;
588};
589
594struct BLEDeviceInfoService
595{
597 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x180A);
598
600 inline static PnpInfoChr pnpInfo;
601
603 inline static SerialNumberChr serialNumber;
604
606 inline static ManufacturerChr manufacturer;
607
609 static const ble_gatt_svc_def definition();
610
612 static void init();
613
614private:
615 inline static ble_gatt_chr_def chr_set[4]{};
616};
617
619struct HIDReportChr : BLECharacteristic
620{
621 friend class BLEHIDService;
622
624 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x2A4D);
625
626protected:
627 BLEDesc2908 desc2908;
628 ble_gatt_dsc_def desc_def[2]{};
629 size_t report_size = 0;
630};
631
633struct FeatureReportChr : HIDReportChr
634{
635 FeatureReportChr(uint8_t report_id, size_t size) noexcept;
636 FeatureReportChr(const FeatureReportChr &) noexcept = default;
637 FeatureReportChr(FeatureReportChr &&) noexcept = default;
638 FeatureReportChr &operator=(const FeatureReportChr &) noexcept = default;
639 FeatureReportChr &operator=(FeatureReportChr &&) noexcept = default;
640
641 virtual ble_gatt_chr_def definition() override;
642};
643
645struct OutputReportChr : HIDReportChr
646{
647 OutputReportChr(uint8_t report_id, size_t size) noexcept;
648 OutputReportChr(const OutputReportChr &) noexcept = default;
649 OutputReportChr(OutputReportChr &&) noexcept = default;
650 OutputReportChr &operator=(const OutputReportChr &) noexcept = default;
651 OutputReportChr &operator=(OutputReportChr &&) noexcept = default;
652
653 virtual ble_gatt_chr_def definition() override;
654};
655
657struct InputReportChr : HIDReportChr
658{
659 InputReportChr(uint8_t report_id, size_t size) noexcept;
660 InputReportChr(const InputReportChr &) noexcept = default;
661 InputReportChr(InputReportChr &&) noexcept = default;
662 InputReportChr &operator=(const InputReportChr &) noexcept = default;
663 InputReportChr &operator=(InputReportChr &&) noexcept = default;
664 virtual ble_gatt_chr_def definition() override;
665};
666
668struct HIDInfoChr : BLECharacteristic, BLEReadCallback
669{
671 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x2A4A);
672
673 virtual int onRead(os_mbuf *buffer) override;
674 virtual ble_gatt_chr_def definition() override;
675};
676
679struct HIDControlChr : BLECharacteristic, BLEWriteCallback
680{
682 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x2A4C);
683
684 virtual ble_gatt_chr_def definition() override;
685};
686
688struct HIDReportMapChr : BLECharacteristic, BLEReadCallback
689{
691 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x2A4B);
692
693 virtual int onRead(os_mbuf *buffer) override;
694 virtual ble_gatt_chr_def definition() override;
695};
696
697// TO-DO
698// - Consider return BLE_ATT_ERR_VALUE_NOT_ALLOWED on write attempts
699// as "boot mode" does not make sense in this device
700// - Check if this characteristic is mandatory or not
701
703struct HIDProtocolModeChr : BLECharacteristic, BLEReadCallback, BLEWriteCallback
704{
706 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x2A4E);
707
708 virtual int onRead(os_mbuf *buffer) override;
709 virtual int onWrite(void *data, uint16_t data_size) override;
710 virtual ble_gatt_chr_def definition() override;
711
712private:
714 uint8_t value = 0x01; // Non-boot mode
715};
716
721struct BLEHIDService
722{
723 friend class FeatureReportChr;
724 friend class OutputReportChr;
725 friend class InputReportChr;
726
728 inline static const ble_uuid16_t uuid = BLE_UUID16_INIT(0x1812);
729
731 using OnGetFeatureCallback =
732 // params(reportID,[out]data,data_size)
733 // return count of bytes written into "data"
734 ::std::function<uint16_t(uint8_t, uint8_t *, uint16_t)>;
736 using OnSetFeatureCallback =
737 // params(reportID,data,data_size)
738 ::std::function<void(uint8_t, const uint8_t *, uint16_t)>;
740 using OnOutputCallback = OnSetFeatureCallback;
742 using OnInputCallback = OnGetFeatureCallback;
743
745 inline static OnGetFeatureCallback onGetFeatureReport = nullptr;
747 inline static OnSetFeatureCallback onSetFeatureReport = nullptr;
749 inline static OnOutputCallback onWriteOutputReport = nullptr;
751 inline static OnInputCallback onReadInputReport = nullptr;
752
754 inline static HIDInfoChr info;
756 inline static HIDControlChr control;
758 inline static HIDReportMapChr report_map;
760 inline static HIDProtocolModeChr protocol_mode;
761
762 inline static InputReportChr input_report{
765 inline static FeatureReportChr capabilities_report{
768 inline static FeatureReportChr config_report{
771 inline static FeatureReportChr button_map_report{
774 inline static FeatureReportChr hardware_id_report{
777 inline static OutputReportChr powertrain_report{
780 inline static OutputReportChr ecu_report{
783 inline static OutputReportChr race_control_report{
786 inline static OutputReportChr gauges_report{
789 inline static OutputReportChr pixel_report{
792
798 static const ble_gatt_svc_def definition();
799
801 static void init();
802
811 static void onSubscriptionChange(
812 uint16_t attr_handle,
813 bool yesOrNo)
814 {
815 input_report.setSubscription_if(attr_handle, yesOrNo);
816 }
817
818private:
819 inline static ble_gatt_chr_def chr_set[4 + HID_REPORT_COUNT + 1]{};
820 static int report_access_fn(
821 uint16_t conn_handle,
822 uint16_t attr_handle,
823 ble_gatt_access_ctxt *ctxt,
824 void *arg);
825};
826
827#endif // CONFIG_NIMBLE_ENABLED
Key definitions of the sim wheel as a HID device. Independent from transport layer.
#define RID_OUTPUT_GAUGES
Gauges report ID.
#define RACE_CONTROL_REPORT_SIZE
Race control report size.
#define RID_FEATURE_CONFIG
Configuration report ID.
#define GAMEPAD_REPORT_SIZE
Input report size.
#define RID_OUTPUT_POWERTRAIN
Powertrain telemetry report ID.
#define BUTTONS_MAP_REPORT_SIZE
Input map report size.
#define PIXEL_REPORT_SIZE
Pixel control report size.
#define ECU_REPORT_SIZE
ECU telemetry report size.
#define RID_OUTPUT_RACE_CONTROL
Race control report ID.
#define RID_INPUT_GAMEPAD
Input report ID.
#define BLE_VENDOR_ID
Default BLE vendor ID.
#define HARDWARE_ID_REPORT_SIZE
Custom VID/PID report size.
#define CONFIG_REPORT_SIZE
Configuration report size.
#define POWERTRAIN_REPORT_SIZE
Powertrain telemetry report size.
#define RID_FEATURE_BUTTONS_MAP
Input map report ID.
#define RID_FEATURE_HARDWARE_ID
Custom VID/PID report ID.
#define BLE_PRODUCT_ID
Default BLE product ID.
#define GAUGES_REPORT_SIZE
Gauges report size.
#define RID_OUTPUT_ECU
ECU telemetry report ID.
#define CAPABILITIES_REPORT_SIZE
Capabilities report size.
#define RID_FEATURE_CAPABILITIES
Capabilities report ID.
#define RID_OUTPUT_PIXEL
Pixel control report ID.
System functionality not exposed to the end user.
void set(InputNumber neutral, InputNumberCombination combination={JOY_LSHIFT_PADDLE, JOY_RSHIFT_PADDLE})
Set a "virtual" button for the neutral gear.
void onReset(uint8_t *report)
Resets data for the input report.
Data format for the Battery Level Status characteristic (packed)