Skip to content

Commit 080f51d

Browse files
committed
rework HID subsystem so each application may support parallel sessions
Signed-off-by: Benedek Kupper <kupper.benedek@gmail.com>
1 parent a5c35de commit 080f51d

69 files changed

Lines changed: 3099 additions & 2273 deletions

Some content is hidden

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

.clang-format

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,5 @@ FixNamespaceComments: true
3636
AllowShortFunctionsOnASingleLine: Inline
3737
AllowShortLambdasOnASingleLine: Inline
3838
ShortNamespaceLines: 1
39+
InsertNewlineAtEOF: true
40+
KeepEmptyLinesAtEOF: true

CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,19 @@ set(CMAKE_CXX_STANDARD 20)
99
set(CMAKE_CXX_STANDARD_REQUIRED ON)
1010
set(CMAKE_CXX_EXTENSIONS ON)
1111

12+
# TODO: list all kconfig options that aren't dependent on SDK
1213
set(CONFIG_C2USB ON)
14+
set(CONFIG_C2USB_I2C_HID ON)
15+
set(CONFIG_C2USB_HID_BOOT_PROTOCOL ON)
16+
1317
add_subdirectory(${PROJECT_NAME})
1418

19+
target_compile_definitions(${PROJECT_NAME} PUBLIC
20+
CONFIG_C2USB=1
21+
CONFIG_C2USB_I2C_HID=1
22+
CONFIG_C2USB_HID_BOOT_PROTOCOL=1
23+
)
24+
1525
option(C2USB_TESTS "c2usb: enable unit testing" ${PROJECT_IS_TOP_LEVEL})
1626
if(C2USB_TESTS)
1727
enable_testing()

c2usb/CMakeLists.txt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ set(WORKSPACE_TOPDIR "${CMAKE_CURRENT_LIST_DIR}/../..")
99

1010
# ports
1111
if(ZEPHYR_BASE)
12+
add_subdirectory(bluetooth)
1213
add_subdirectory(port/zephyr)
1314
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive" PARENT_SCOPE) # https://github.com/zephyrproject-rtos/zephyr/pull/69490
1415

@@ -37,7 +38,7 @@ CPMAddPackage("gh:Neargye/magic_enum@0.9.3")
3738
include(../cmake/get_cpm.cmake)
3839
if(ZEPHYR_HID_RP_MODULE_DIR OR MCUX_HID_RP_MODULE_DIR)
3940
else()
40-
CPMAddPackage("gh:IntergatedCircuits/hid-rp#be90bc4cee05b2a329c0024157b7668e31eb00d4")
41+
CPMAddPackage("gh:IntergatedCircuits/hid-rp#85d86f7fa37d3a846c3197b7b599cf7bc0c21b04")
4142
endif()
4243

4344
if(ZEPHYR_ETL_MODULE_DIR OR MCUX_ETL_MODULE_DIR)
@@ -59,7 +60,7 @@ target_include_directories(c2usb
5960

6061
target_sources(c2usb
6162
PRIVATE
62-
i2c/hid/device.cpp
63+
hid/application.cpp
6364
usb/df/class/cdc.cpp
6465
usb/df/class/cdc_acm.cpp
6566
usb/df/class/hid.cpp
@@ -72,5 +73,11 @@ target_sources(c2usb
7273
usb/df/message.cpp
7374
usb/standard/descriptors.cpp
7475
)
76+
if(CONFIG_C2USB_I2C_HID)
77+
target_sources(c2usb
78+
PRIVATE
79+
i2c/hid/device.cpp
80+
)
81+
endif()
7582

7683
add_subdirectory(examples)

c2usb/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rsource "hid/Kconfig"
2+
rsource "i2c/Kconfig"

c2usb/bluetooth/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
target_sources_ifdef(CONFIG_C2USB_HOGP c2usb
2+
PRIVATE
3+
hid_over_gatt.cpp
4+
)

c2usb/bluetooth/Kconfig

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
menuconfig C2USB_HOGP
2+
bool "c2usb HID over GATT"
3+
select C2USB
4+
depends on BT
5+
6+
if C2USB_HOGP
7+
8+
module = C2USB_HOGP
9+
module-str = c2usb HID over GATT
10+
source "subsys/logging/Kconfig.template.log_config"
11+
12+
config C2USB_HOGP_POWER_EVENT
13+
bool "Enable HOGP control point power event callback"
14+
15+
config C2USB_HOGP_BT_DISCONN_CB
16+
bool "HOGP registers its own Bluetooth disconnection callback through BT_CONN_CB_DEFINE"
17+
default y
18+
help
19+
If enabled, HOGP registers its own Bluetooth disconnection callback through BT_CONN_CB_DEFINE.
20+
If disabled, the application must register its own Bluetooth disconnection callback and call
21+
bluetooth::hid_over_gatt::service::disconnect_callback() from it.
22+
23+
endif # C2USB_HOGP

c2usb/bluetooth/advertise.hpp

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
#pragma once
3+
#include <array>
4+
#include <chrono>
5+
#include <span>
6+
#include <string_view>
7+
#include <type_traits>
8+
#if __has_include("zephyr/bluetooth/bluetooth.h")
9+
#include <zephyr/bluetooth/bluetooth.h>
10+
#else
11+
#error "Unsupported platform"
12+
#endif
13+
#include "bluetooth/uuid.hpp"
14+
#include "c2usb.hpp"
15+
16+
namespace bluetooth
17+
{
18+
19+
// aka scan window, 0.625 ms per count
20+
template <typename T = uint16_t>
21+
using baseband_slot = std::chrono::duration<T, std::ratio<625, 1'000'000>>;
22+
23+
// 1.25 ms per count
24+
template <typename T = uint16_t>
25+
using connection_interval = std::chrono::duration<T, std::ratio<1250, 1'000'000>>;
26+
27+
template <uint8_t TYPE, typename T = std::nullptr_t>
28+
struct ad_struct : public ::bt_data
29+
{
30+
explicit ad_struct(const T& arg)
31+
requires(std::is_integral_v<T> and
32+
((std::endian::native == std::endian::little) or (std::is_same_v<T, uint8_t>)))
33+
{
34+
this->type = TYPE;
35+
this->data_len = sizeof(T);
36+
this->data = reinterpret_cast<const uint8_t*>(&arg);
37+
}
38+
39+
explicit ad_struct(std::initializer_list<T> args)
40+
requires(std::is_integral_v<T> and
41+
((std::endian::native == std::endian::little) or (std::is_same_v<T, uint8_t>)))
42+
{
43+
this->type = TYPE;
44+
this->data_len = sizeof(T) * args.size();
45+
this->data = reinterpret_cast<const uint8_t*>(args.begin());
46+
}
47+
48+
explicit ad_struct(std::string_view str)
49+
{
50+
this->type = TYPE;
51+
this->data_len = str.size();
52+
this->data = reinterpret_cast<const uint8_t*>(str.data());
53+
}
54+
55+
explicit ad_struct(const uuid& arg)
56+
requires((TYPE == BT_DATA_UUID16_SOME) or (TYPE == BT_DATA_UUID16_ALL) or
57+
(TYPE == BT_DATA_UUID32_SOME) or (TYPE == BT_DATA_UUID32_ALL) or
58+
(TYPE == BT_DATA_UUID128_SOME) or (TYPE == BT_DATA_UUID128_ALL))
59+
{
60+
this->type = TYPE;
61+
if constexpr ((TYPE == BT_DATA_UUID16_SOME) or (TYPE == BT_DATA_UUID16_ALL))
62+
{
63+
if (arg.type == BT_UUID_TYPE_16)
64+
{
65+
this->data = reinterpret_cast<const uint8_t*>(&BT_UUID_16(&arg)->val);
66+
this->data_len = sizeof(BT_UUID_16(&arg)->val);
67+
}
68+
}
69+
else if constexpr ((TYPE == BT_DATA_UUID32_SOME) or (TYPE == BT_DATA_UUID32_ALL))
70+
{
71+
if (arg.type == BT_UUID_TYPE_32)
72+
{
73+
this->data = reinterpret_cast<const uint8_t*>(&BT_UUID_32(&arg)->val);
74+
this->data_len = sizeof(BT_UUID_32(&arg)->val);
75+
}
76+
}
77+
else if constexpr ((TYPE == BT_DATA_UUID128_SOME) or (TYPE == BT_DATA_UUID128_ALL))
78+
{
79+
if (arg.type == BT_UUID_TYPE_128)
80+
{
81+
this->data = (BT_UUID_128(&arg)->val);
82+
this->data_len = sizeof(BT_UUID_128(&arg)->val);
83+
}
84+
}
85+
this->data_len = 0;
86+
}
87+
};
88+
89+
template <std::size_t N>
90+
constexpr auto to_adv_data(::bt_data (&data)[N])
91+
{
92+
return std::to_array(data);
93+
}
94+
template <std::size_t N>
95+
constexpr auto to_adv_data(::bt_data (&&data)[N])
96+
{
97+
return std::to_array(data);
98+
}
99+
100+
class advertisement : public ::bt_le_adv_param
101+
{
102+
public:
103+
using options = ::bt_le_adv_opt;
104+
105+
// Directed connectable
106+
constexpr static advertisement directed(const ::bt_addr_le_t& peer_addr,
107+
options opts = options::BT_LE_ADV_OPT_NONE)
108+
{
109+
return advertisement(BT_LE_ADV_OPT_CONN | opts, 0, 0, &peer_addr);
110+
}
111+
constexpr static advertisement directed_low_duty(const ::bt_addr_le_t& peer_addr,
112+
options opts = options::BT_LE_ADV_OPT_NONE)
113+
{
114+
return advertisement(BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY | opts,
115+
BT_GAP_ADV_FAST_INT_MIN_2, BT_GAP_ADV_FAST_INT_MAX_2, &peer_addr);
116+
}
117+
118+
// Scannable and connectable
119+
template <typename T, typename MinRatio, typename MaxRatio>
120+
constexpr static advertisement
121+
connectable(std::chrono::duration<T, MinRatio> interval_min =
122+
baseband_slot<uint16_t>(BT_GAP_ADV_FAST_INT_MIN_1),
123+
std::chrono::duration<T, MaxRatio> interval_max =
124+
baseband_slot<uint16_t>(BT_GAP_ADV_FAST_INT_MAX_1))
125+
{
126+
return advertisement(BT_LE_ADV_OPT_CONN,
127+
std::chrono::round<baseband_slot<uint16_t>>(interval_min).count(),
128+
std::chrono::round<baseband_slot<uint16_t>>(interval_max).count());
129+
}
130+
131+
// Non-connectable and scannable
132+
template <typename T, typename MinRatio, typename MaxRatio>
133+
constexpr static advertisement scannable(std::chrono::duration<T, MinRatio> interval_min =
134+
baseband_slot<uint16_t>(BT_GAP_ADV_FAST_INT_MIN_1),
135+
std::chrono::duration<T, MaxRatio> interval_max =
136+
baseband_slot<uint16_t>(BT_GAP_ADV_FAST_INT_MAX_1))
137+
{
138+
return advertisement(BT_LE_ADV_OPT_SCANNABLE,
139+
std::chrono::round<baseband_slot<uint16_t>>(interval_min).count(),
140+
std::chrono::round<baseband_slot<uint16_t>>(interval_max).count());
141+
}
142+
143+
// Non-connectable and non-scannable
144+
template <typename T, typename MinRatio, typename MaxRatio>
145+
constexpr static advertisement
146+
non_connectable(std::chrono::duration<T, MinRatio> interval_min =
147+
baseband_slot<uint16_t>(BT_GAP_ADV_FAST_INT_MIN_1),
148+
std::chrono::duration<T, MaxRatio> interval_max =
149+
baseband_slot<uint16_t>(BT_GAP_ADV_FAST_INT_MAX_1),
150+
options opts = options::BT_LE_ADV_OPT_NONE)
151+
{
152+
return advertisement(opts,
153+
std::chrono::round<baseband_slot<uint16_t>>(interval_min).count(),
154+
std::chrono::round<baseband_slot<uint16_t>>(interval_max).count());
155+
}
156+
157+
template <typename T, typename MinRatio, typename MaxRatio>
158+
constexpr advertisement(options opts, std::chrono::duration<T, MinRatio> interval_min,
159+
std::chrono::duration<T, MaxRatio> interval_max)
160+
: advertisement(opts, std::chrono::round<baseband_slot<uint16_t>>(interval_min).count(),
161+
std::chrono::round<baseband_slot<uint16_t>>(interval_max).count())
162+
{}
163+
164+
c2usb::result start(const std::span<const ::bt_data>& ad = {},
165+
const std::span<const ::bt_data>& sd = {}) const
166+
{
167+
return c2usb::result(::bt_le_adv_start(this, ad.data(), ad.size(), sd.data(), sd.size()));
168+
}
169+
170+
static c2usb::result update_data(const std::span<const ::bt_data>& ad = {},
171+
const std::span<const ::bt_data>& sd = {})
172+
{
173+
return c2usb::result(::bt_le_adv_update_data(ad.data(), ad.size(), sd.data(), sd.size()));
174+
}
175+
176+
static c2usb::result stop() { return c2usb::result(::bt_le_adv_stop()); }
177+
178+
private:
179+
constexpr advertisement(uint8_t options, uint16_t interval_min, uint16_t interval_max,
180+
const ::bt_addr_le_t* peer_addr = nullptr)
181+
: bt_le_adv_param{.id = BT_ID_DEFAULT,
182+
.sid = 0,
183+
.secondary_max_skip = 0,
184+
.options = options,
185+
.interval_min = interval_min,
186+
.interval_max = interval_max,
187+
.peer = peer_addr}
188+
{}
189+
};
190+
191+
} // namespace bluetooth

c2usb/bluetooth/base.hpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
#pragma once
3+
#if __has_include("zephyr/bluetooth/bluetooth.h")
4+
#include <zephyr/bluetooth/addr.h>
5+
#include <zephyr/bluetooth/bluetooth.h>
6+
#include <zephyr/bluetooth/conn.h>
7+
#else
8+
#error "Unsupported platform"
9+
#endif
10+
#include "c2usb.hpp"
11+
12+
namespace bluetooth
13+
{
14+
enum struct security : uint8_t
15+
{
16+
L1_NONE = BT_SECURITY_L1,
17+
L2_UNAUTH_ENC = BT_SECURITY_L2, // Encryption with unauthenticated pairing
18+
L3_AUTH_ENC = BT_SECURITY_L3, // Authenticated pairing with encryption
19+
L4_LE_SC = BT_SECURITY_L4 // Authenticated LE Secure Connections pairing with encryption
20+
};
21+
22+
/// @brief Creates a string out of a Bluetooth LE address.
23+
struct le_address_str : public std::array<char, BT_ADDR_LE_STR_LEN>
24+
{
25+
le_address_str(const ::bt_conn* conn)
26+
{
27+
bt_addr_le_to_str(bt_conn_get_dst(conn), data(), size());
28+
}
29+
};
30+
} // namespace bluetooth

0 commit comments

Comments
 (0)