Compare commits
10 Commits
d374f824cd
...
cd8101428d
| Author | SHA1 | Date | |
|---|---|---|---|
| cd8101428d | |||
| a3196ef162 | |||
| e893ddded6 | |||
| 81846a870f | |||
| 05f4f117b0 | |||
| b3516b988a | |||
| 86af0d2373 | |||
| 3d9ce9168f | |||
| cd3400a9ba | |||
| c5778e6c7a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
build*/
|
build*/
|
||||||
|
external*/
|
||||||
|
|||||||
@@ -1,6 +1,34 @@
|
|||||||
cmake_minimum_required(VERSION 3.20.0)
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
|
||||||
|
if(EXISTS "E:/extra/modules/ip5305")
|
||||||
|
list(APPEND ZEPHYR_EXTRA_MODULES "E:/extra/modules/ip5305")
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
|
|
||||||
project(new_kbd)
|
project(new_kbd)
|
||||||
|
|
||||||
target_sources(app PRIVATE src/main.c)
|
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)
|
||||||
|
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/events)
|
||||||
|
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/configuration/atguigu_mini_keyboard_nrf52840)
|
||||||
|
|
||||||
|
target_compile_definitions(app PRIVATE
|
||||||
|
APP_HID_KEYMAP_DEF_PATH=\"hid_keymap_def.h\"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(app PRIVATE
|
||||||
|
src/main.c
|
||||||
|
src/events/battery_status_event.c
|
||||||
|
src/events/config_event.c
|
||||||
|
src/events/hid_protocol_event.c
|
||||||
|
src/events/hid_report_event.c
|
||||||
|
src/events/mode_event.c
|
||||||
|
src/events/usb_hid_event.c
|
||||||
|
src/modules/battery_module.c
|
||||||
|
src/modules/ble_adv_ctrl_module.c
|
||||||
|
src/modules/ble_bond_module.c
|
||||||
|
src/modules/keyboard_module.c
|
||||||
|
src/modules/mode_switch_module.c
|
||||||
|
src/modules/usb_hid_module.c
|
||||||
|
src/modules/ble_hid_module.c
|
||||||
|
)
|
||||||
|
|||||||
73
app.overlay
Normal file
73
app.overlay
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#include <zephyr/dt-bindings/gpio/gpio.h>
|
||||||
|
|
||||||
|
/ {
|
||||||
|
zephyr,user {
|
||||||
|
vbat-en-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
|
||||||
|
io-channels = <&adc 5>, <&adc 7>;
|
||||||
|
};
|
||||||
|
|
||||||
|
hid_dev_0: hid_dev_0 {
|
||||||
|
compatible = "zephyr,hid-device";
|
||||||
|
label = "HID_BOOT";
|
||||||
|
protocol-code = "keyboard";
|
||||||
|
in-report-size = <8>;
|
||||||
|
in-polling-period-us = <1000>;
|
||||||
|
out-report-size = <8>;
|
||||||
|
out-polling-period-us = <1000>;
|
||||||
|
};
|
||||||
|
|
||||||
|
hid_dev_1: hid_dev_1 {
|
||||||
|
compatible = "zephyr,hid-device";
|
||||||
|
label = "HID_NKRO";
|
||||||
|
protocol-code = "none";
|
||||||
|
in-report-size = <31>;
|
||||||
|
in-polling-period-us = <1000>;
|
||||||
|
out-report-size = <8>;
|
||||||
|
out-polling-period-us = <1000>;
|
||||||
|
};
|
||||||
|
|
||||||
|
raw_hid: hid_dev_2 {
|
||||||
|
compatible = "zephyr,hid-device";
|
||||||
|
label = "HID_RAW";
|
||||||
|
protocol-code = "none";
|
||||||
|
in-report-size = <64>;
|
||||||
|
in-polling-period-us = <1000>;
|
||||||
|
out-report-size = <64>;
|
||||||
|
out-polling-period-us = <1000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&gpio0 {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|
||||||
|
&gpio1 {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|
||||||
|
&gpiote {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|
||||||
|
&led_0 {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 使能 SAADC,mode_switch_module 使用 channel 7 采样模式拨码电压。 */
|
||||||
|
&adc {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
|
|
||||||
|
&i2c1 {
|
||||||
|
status = "okay";
|
||||||
|
|
||||||
|
ip5305: pmic@75 {
|
||||||
|
status = "okay";
|
||||||
|
/* 试验项:调整 IP5305 KEY 保活周期,观察 I2C 失败窗口是否随周期移动。 */
|
||||||
|
keepalive-interval-ms = <10000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&usbd {
|
||||||
|
status = "okay";
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* HID keymap for current new_kbd numeric keypad layout.
|
||||||
|
* 说明:
|
||||||
|
* - 本文件仿照 nrf_desktop 的 hid_keymap_def.h 组织方式;
|
||||||
|
* - 仅由 keyboard_module.c 包含一次(通过 APP_HID_KEYMAP_DEF_PATH);
|
||||||
|
* - 条目必须按 key_id 升序排列(按 KEY_ID(col, row) 计算后的数值顺序)。
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <caf/key_id.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 防止该定义文件被多处 include 导致重复符号。
|
||||||
|
* 约定仅由 keyboard_module.c 包含一次。
|
||||||
|
*/
|
||||||
|
const struct {} hid_keymap_def_include_once;
|
||||||
|
|
||||||
|
static const struct hid_keymap hid_keymap[] = {
|
||||||
|
/* col 0 */
|
||||||
|
{ KEY_ID(0, 1), 0x0053, REPORT_ID_KEYBOARD }, /* Num Lock */
|
||||||
|
{ KEY_ID(0, 2), 0x005F, REPORT_ID_KEYBOARD }, /* Keypad 7 */
|
||||||
|
{ KEY_ID(0, 3), 0x005C, REPORT_ID_KEYBOARD }, /* Keypad 4 */
|
||||||
|
{ KEY_ID(0, 4), 0x0059, REPORT_ID_KEYBOARD }, /* Keypad 1 */
|
||||||
|
{ KEY_ID(0, 5), 0x0062, REPORT_ID_KEYBOARD }, /* Keypad 0 */
|
||||||
|
|
||||||
|
/* col 1 */
|
||||||
|
{ KEY_ID(1, 1), 0x0054, REPORT_ID_KEYBOARD }, /* Keypad / */
|
||||||
|
{ KEY_ID(1, 2), 0x0060, REPORT_ID_KEYBOARD }, /* Keypad 8 */
|
||||||
|
{ KEY_ID(1, 3), 0x005D, REPORT_ID_KEYBOARD }, /* Keypad 5 */
|
||||||
|
{ KEY_ID(1, 4), 0x005A, REPORT_ID_KEYBOARD }, /* Keypad 2 */
|
||||||
|
{ KEY_ID(1, 5), 0x0063, REPORT_ID_KEYBOARD }, /* Keypad . */
|
||||||
|
|
||||||
|
/* col 2 */
|
||||||
|
{ KEY_ID(2, 1), 0x0055, REPORT_ID_KEYBOARD }, /* Keypad * */
|
||||||
|
{ KEY_ID(2, 2), 0x0061, REPORT_ID_KEYBOARD }, /* Keypad 9 */
|
||||||
|
{ KEY_ID(2, 3), 0x005E, REPORT_ID_KEYBOARD }, /* Keypad 6 */
|
||||||
|
{ KEY_ID(2, 4), 0x005B, REPORT_ID_KEYBOARD }, /* Keypad 3 */
|
||||||
|
|
||||||
|
/* col 3 */
|
||||||
|
{ KEY_ID(3, 0), 0x00E2, REPORT_ID_CONSUMER }, /* Mute */
|
||||||
|
{ KEY_ID(3, 1), 0x0056, REPORT_ID_KEYBOARD }, /* Keypad - */
|
||||||
|
{ KEY_ID(3, 3), 0x0057, REPORT_ID_KEYBOARD }, /* Keypad + */
|
||||||
|
{ KEY_ID(3, 5), 0x0058, REPORT_ID_KEYBOARD }, /* Keypad Enter */
|
||||||
|
};
|
||||||
34
inc/buttons_def.h
Normal file
34
inc/buttons_def.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* CAF buttons 矩阵引脚定义
|
||||||
|
*
|
||||||
|
* 设计说明:
|
||||||
|
* - 本文件被 CAF buttons 模块通过 CONFIG_CAF_BUTTONS_DEF_PATH 直接包含;
|
||||||
|
* - 行列引脚顺序必须与板级 DTS 中 my_keyboard 的 row-gpios/col-gpios 保持一致;
|
||||||
|
* - key_id 的行列编号完全基于这里的数组下标,不依赖 input-keymap 节点。
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <caf/gpio_pins.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 该符号用于保证配置文件只被链接一次:
|
||||||
|
* 若被重复包含到多个编译单元,会在链接阶段报重复定义,避免静默错配。
|
||||||
|
*/
|
||||||
|
const struct {} buttons_def_include_once;
|
||||||
|
|
||||||
|
/* 列引脚:对应 atguigu_mini_keyboard.dts 中 my_keyboard/col-gpios 顺序。 */
|
||||||
|
static const struct gpio_pin col[] = {
|
||||||
|
{ .port = 0, .pin = 5 },
|
||||||
|
{ .port = 0, .pin = 6 },
|
||||||
|
{ .port = 0, .pin = 26 },
|
||||||
|
{ .port = 0, .pin = 30 },
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 行引脚:对应 atguigu_mini_keyboard.dts 中 my_keyboard/row-gpios 顺序。 */
|
||||||
|
static const struct gpio_pin row[] = {
|
||||||
|
{ .port = 0, .pin = 15 },
|
||||||
|
{ .port = 0, .pin = 7 },
|
||||||
|
{ .port = 0, .pin = 12 },
|
||||||
|
{ .port = 0, .pin = 4 },
|
||||||
|
{ .port = 1, .pin = 9 },
|
||||||
|
{ .port = 0, .pin = 8 },
|
||||||
|
};
|
||||||
109
inc/hid_report_descriptor.h
Normal file
109
inc/hid_report_descriptor.h
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#ifndef HID_REPORT_DESCRIPTOR_H_
|
||||||
|
#define HID_REPORT_DESCRIPTOR_H_
|
||||||
|
|
||||||
|
#include <zephyr/usb/class/usbd_hid.h>
|
||||||
|
|
||||||
|
/* 与 HID Report Map 对齐的 Report ID。 */
|
||||||
|
enum {
|
||||||
|
REPORT_ID_KEYBOARD = 1,
|
||||||
|
REPORT_ID_CONSUMER = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HID_USAGE_PAGE() 只支持 1 字节 Usage Page。
|
||||||
|
* Vendor Defined Page(0xFF00) 需要 2 字节编码,因此在本地补一个 16 位版本,
|
||||||
|
* 避免在描述符里混用裸字节,后续维护时可以一眼看出字段语义。
|
||||||
|
*/
|
||||||
|
#define HID_USAGE_PAGE16(page_lsb, page_msb) \
|
||||||
|
HID_ITEM(HID_ITEM_TAG_USAGE_PAGE, HID_ITEM_TYPE_GLOBAL, 2), page_lsb, page_msb
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 键盘(NKRO) + Consumer 的复合 Report 描述符:
|
||||||
|
* - USB Report 接口和 BLE HIDS Report Map 统一使用这份定义,
|
||||||
|
* 避免两边手写常量后长期演进出现不一致。
|
||||||
|
*/
|
||||||
|
#define HID_DESC_KEYBOARD_NKRO_CONSUMER() \
|
||||||
|
{ \
|
||||||
|
/* Generic Desktop 页:声明这是一个 Keyboard Application 集合。 */ \
|
||||||
|
HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), \
|
||||||
|
HID_USAGE(HID_USAGE_GEN_DESKTOP_KEYBOARD), \
|
||||||
|
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
|
||||||
|
HID_REPORT_ID(REPORT_ID_KEYBOARD), \
|
||||||
|
\
|
||||||
|
/* Keyboard/Keypad 页:先定义 8bit Modifier,再定义 232bit NKRO 位图。 */ \
|
||||||
|
HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP_KEYPAD), \
|
||||||
|
HID_USAGE_MIN8(0xE0), \
|
||||||
|
HID_USAGE_MAX8(0xE7), \
|
||||||
|
HID_LOGICAL_MIN8(0), \
|
||||||
|
HID_LOGICAL_MAX8(1), \
|
||||||
|
HID_REPORT_SIZE(1), \
|
||||||
|
HID_REPORT_COUNT(8), \
|
||||||
|
HID_INPUT(0x02), \
|
||||||
|
HID_USAGE_MIN8(0x00), \
|
||||||
|
HID_USAGE_MAX8(0xE7), \
|
||||||
|
HID_LOGICAL_MIN8(0), \
|
||||||
|
HID_LOGICAL_MAX8(1), \
|
||||||
|
HID_REPORT_SIZE(1), \
|
||||||
|
HID_REPORT_COUNT(0xE7 + 1), \
|
||||||
|
HID_INPUT(0x02), \
|
||||||
|
\
|
||||||
|
/* Report 协议下键盘 LED 输出(NumLock/CapsLock/ScrollLock/Compose/Kana)。 */ \
|
||||||
|
HID_USAGE_PAGE(0x08U), \
|
||||||
|
HID_USAGE_MIN8(0x01), \
|
||||||
|
HID_USAGE_MAX8(0x05), \
|
||||||
|
HID_LOGICAL_MIN8(0), \
|
||||||
|
HID_LOGICAL_MAX8(1), \
|
||||||
|
HID_REPORT_SIZE(1), \
|
||||||
|
HID_REPORT_COUNT(5), \
|
||||||
|
HID_OUTPUT(0x02), \
|
||||||
|
/* 补齐到 1 字节:3bit padding,标记为常量。 */ \
|
||||||
|
HID_REPORT_SIZE(3), \
|
||||||
|
HID_REPORT_COUNT(1), \
|
||||||
|
HID_OUTPUT(0x01), \
|
||||||
|
HID_END_COLLECTION, \
|
||||||
|
\
|
||||||
|
/* Consumer 页:使用 16bit Usage 承载多媒体按键(音量/播放/亮度等)。 */ \
|
||||||
|
HID_USAGE_PAGE(0x0CU), \
|
||||||
|
HID_USAGE(0x01U), \
|
||||||
|
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
|
||||||
|
HID_REPORT_ID(REPORT_ID_CONSUMER), \
|
||||||
|
HID_LOGICAL_MIN8(0), \
|
||||||
|
HID_LOGICAL_MAX16(0xEA, 0x00), \
|
||||||
|
HID_USAGE_MIN16(0x00, 0x00), \
|
||||||
|
HID_USAGE_MAX16(0xEA, 0x00), \
|
||||||
|
HID_REPORT_SIZE(16), \
|
||||||
|
HID_REPORT_COUNT(1), \
|
||||||
|
HID_INPUT(0x00), \
|
||||||
|
HID_END_COLLECTION, \
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RAW HID 的固定 64 字节输入/输出描述符。
|
||||||
|
* 设计意图:
|
||||||
|
* - 采用 Vendor Defined(0xFF00) 页,避免与标准键盘/多媒体语义冲突;
|
||||||
|
* - IN/OUT 都固定 64 字节,便于固件与上位机用定长帧做双向透传;
|
||||||
|
* - 不使用 Report ID,接口只承载一个 RAW Report,减少主机端解析分支。
|
||||||
|
*/
|
||||||
|
#define HID_DESC_RAW_64() \
|
||||||
|
{ \
|
||||||
|
/* Vendor Defined 页(0xFF00):供厂商私有协议传输,不绑定标准 HID 语义。 */ \
|
||||||
|
HID_USAGE_PAGE16(0x00, 0xFF), \
|
||||||
|
HID_USAGE(0x01), \
|
||||||
|
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
|
||||||
|
HID_LOGICAL_MIN8(0), \
|
||||||
|
HID_LOGICAL_MAX16(0xFF, 0x00), \
|
||||||
|
HID_REPORT_SIZE(8), \
|
||||||
|
\
|
||||||
|
/* 输入页:定义 64 字节 Input 报文,Data|Var|Abs(0x02) 与原描述符一致。 */ \
|
||||||
|
HID_REPORT_COUNT(0x40), \
|
||||||
|
HID_USAGE(0x01), \
|
||||||
|
HID_INPUT(0x02), \
|
||||||
|
\
|
||||||
|
/* 输出页:定义 64 字节 Output 报文,与输入长度对齐,简化双向协议。 */ \
|
||||||
|
HID_REPORT_COUNT(0x40), \
|
||||||
|
HID_USAGE(0x01), \
|
||||||
|
HID_OUTPUT(0x02), \
|
||||||
|
HID_END_COLLECTION, \
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
17
inc/settings_loader_def.h
Normal file
17
inc/settings_loader_def.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Defines modules that must reach READY before CAF settings_loader
|
||||||
|
* calls settings_load().
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Enforce single inclusion in the final link unit. */
|
||||||
|
const struct {} settings_loader_def_include_once;
|
||||||
|
|
||||||
|
#include <caf/events/module_state_event.h>
|
||||||
|
|
||||||
|
static inline void get_req_modules(struct module_flags *mf)
|
||||||
|
{
|
||||||
|
module_flags_set_bit(mf, MODULE_IDX(main));
|
||||||
|
#ifdef CONFIG_CAF_BLE_STATE
|
||||||
|
module_flags_set_bit(mf, MODULE_IDX(ble_state));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
39
pm_static.yml
Normal file
39
pm_static.yml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
mcuboot:
|
||||||
|
address: 0x0
|
||||||
|
end_address: 0xc000
|
||||||
|
region: flash_primary
|
||||||
|
size: 0xc000
|
||||||
|
|
||||||
|
mcuboot_pad:
|
||||||
|
address: 0xc000
|
||||||
|
end_address: 0xc200
|
||||||
|
region: flash_primary
|
||||||
|
size: 0x200
|
||||||
|
|
||||||
|
app:
|
||||||
|
address: 0xc200
|
||||||
|
end_address: 0x82000
|
||||||
|
region: flash_primary
|
||||||
|
size: 0x75e00
|
||||||
|
|
||||||
|
mcuboot_primary:
|
||||||
|
address: 0xc000
|
||||||
|
end_address: 0x82000
|
||||||
|
orig_span: &id001
|
||||||
|
- mcuboot_pad
|
||||||
|
- app
|
||||||
|
region: flash_primary
|
||||||
|
size: 0x76000
|
||||||
|
span: *id001
|
||||||
|
|
||||||
|
mcuboot_secondary:
|
||||||
|
address: 0x82000
|
||||||
|
end_address: 0xf8000
|
||||||
|
region: flash_primary
|
||||||
|
size: 0x76000
|
||||||
|
|
||||||
|
settings_storage:
|
||||||
|
address: 0xf8000
|
||||||
|
end_address: 0x100000
|
||||||
|
region: flash_primary
|
||||||
|
size: 0x8000
|
||||||
79
prj.conf
79
prj.conf
@@ -1,3 +1,80 @@
|
|||||||
CONFIG_CAF=y
|
CONFIG_CAF=y
|
||||||
CONFIG_HEAP_MEM_POOL_SIZE=2048
|
CONFIG_HEAP_MEM_POOL_SIZE=2048
|
||||||
CONFIG_LOG=y
|
CONFIG_LOG=y
|
||||||
|
CONFIG_ASSERT=y
|
||||||
|
CONFIG_ASSERT_VERBOSE=y
|
||||||
|
CONFIG_RESET_ON_FATAL_ERROR=n
|
||||||
|
CONFIG_FAULT_DUMP=2
|
||||||
|
|
||||||
|
CONFIG_ZCBOR=y
|
||||||
|
CONFIG_BOOTLOADER_MCUBOOT=y
|
||||||
|
CONFIG_MCUMGR=y
|
||||||
|
CONFIG_MCUMGR_TRANSPORT_BT=y
|
||||||
|
CONFIG_MCUMGR_GRP_IMG=y
|
||||||
|
CONFIG_MCUMGR_GRP_OS=y
|
||||||
|
CONFIG_IMG_MANAGER=y
|
||||||
|
CONFIG_STREAM_FLASH=y
|
||||||
|
|
||||||
|
CONFIG_BT=y
|
||||||
|
CONFIG_BT_PERIPHERAL=y
|
||||||
|
CONFIG_BT_SMP=y
|
||||||
|
CONFIG_BT_DEVICE_NAME="new_kbd"
|
||||||
|
CONFIG_BT_DEVICE_APPEARANCE=961
|
||||||
|
CONFIG_BT_MAX_CONN=1
|
||||||
|
CONFIG_BT_MAX_PAIRED=4
|
||||||
|
CONFIG_BT_ID_MAX=4
|
||||||
|
CONFIG_SETTINGS=y
|
||||||
|
CONFIG_SETTINGS_NVS=y
|
||||||
|
CONFIG_NVS=y
|
||||||
|
CONFIG_FLASH=y
|
||||||
|
CONFIG_FLASH_MAP=y
|
||||||
|
CONFIG_BT_SETTINGS=y
|
||||||
|
|
||||||
|
CONFIG_CAF_BLE_STATE=y
|
||||||
|
CONFIG_CAF_BLE_ADV=y
|
||||||
|
CONFIG_CAF_MODULE_SUSPEND_EVENTS=y
|
||||||
|
CONFIG_CAF_SETTINGS_LOADER=y
|
||||||
|
CONFIG_BT_ADV_PROV_FLAGS=y
|
||||||
|
CONFIG_BT_ADV_PROV_GAP_APPEARANCE=y
|
||||||
|
CONFIG_BT_ADV_PROV_DEVICE_NAME=y
|
||||||
|
CONFIG_BT_ADV_PROV_SWIFT_PAIR=y
|
||||||
|
|
||||||
|
CONFIG_BT_HIDS=y
|
||||||
|
CONFIG_BT_CONN_CTX=y
|
||||||
|
CONFIG_BT_GATT_POOL=y
|
||||||
|
CONFIG_BT_GATT_CHRC_POOL_SIZE=16
|
||||||
|
CONFIG_BT_GATT_UUID16_POOL_SIZE=24
|
||||||
|
CONFIG_BT_HIDS_ATTR_MAX=32
|
||||||
|
CONFIG_BT_HIDS_INPUT_REP_MAX=2
|
||||||
|
CONFIG_BT_HIDS_OUTPUT_REP_MAX=1
|
||||||
|
CONFIG_BT_HIDS_FEATURE_REP_MAX=0
|
||||||
|
|
||||||
|
CONFIG_USB_DEVICE_STACK_NEXT=y
|
||||||
|
CONFIG_USBD_HID_SUPPORT=y
|
||||||
|
CONFIG_UDC_BUF_POOL_SIZE=8192
|
||||||
|
CONFIG_UDC_BUF_COUNT=32
|
||||||
|
CONFIG_USBD_MAX_UDC_MSG=20
|
||||||
|
CONFIG_USBD_MSG_SLAB_COUNT=16
|
||||||
|
|
||||||
|
CONFIG_LED=y
|
||||||
|
CONFIG_LED_GPIO=y
|
||||||
|
CONFIG_CAF_LEDS=y
|
||||||
|
CONFIG_CAF_LEDS_GPIO=y
|
||||||
|
CONFIG_CAF_LEDS_PM_EVENTS=y
|
||||||
|
|
||||||
|
CONFIG_CAF_POWER_MANAGER=y
|
||||||
|
CONFIG_CAF_POWER_MANAGER_TIMEOUT=300
|
||||||
|
CONFIG_CAF_POWER_MANAGER_ERROR_TIMEOUT=10
|
||||||
|
CONFIG_REBOOT=y
|
||||||
|
CONFIG_CAF_KEEP_ALIVE_EVENTS=y
|
||||||
|
|
||||||
|
CONFIG_CAF_BUTTONS=y
|
||||||
|
CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h"
|
||||||
|
CONFIG_CAF_BUTTONS_SCAN_INTERVAL=5
|
||||||
|
CONFIG_CAF_BUTTONS_DEBOUNCE_INTERVAL=10
|
||||||
|
|
||||||
|
CONFIG_ADC=y
|
||||||
|
CONFIG_I2C=y
|
||||||
|
CONFIG_IP5305=y
|
||||||
|
|
||||||
|
CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=4096
|
||||||
|
|||||||
34
src/events/battery_status_event.c
Normal file
34
src/events/battery_status_event.c
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#include "battery_status_event.h"
|
||||||
|
|
||||||
|
static void log_battery_status_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct battery_status_event *event = cast_battery_status_event(aeh);
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(aeh,
|
||||||
|
"charging=%u full=%u soc=%u",
|
||||||
|
event->charging,
|
||||||
|
event->full,
|
||||||
|
event->soc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void profile_battery_status_event(struct log_event_buf *buf,
|
||||||
|
const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct battery_status_event *event = cast_battery_status_event(aeh);
|
||||||
|
|
||||||
|
nrf_profiler_log_encode_uint8(buf, event->charging ? 1U : 0U);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, event->full ? 1U : 0U);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, event->soc);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_INFO_DEFINE(battery_status_event,
|
||||||
|
ENCODE(NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8),
|
||||||
|
ENCODE("charging", "full", "soc"),
|
||||||
|
profile_battery_status_event);
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(battery_status_event,
|
||||||
|
log_battery_status_event,
|
||||||
|
&battery_status_event_info,
|
||||||
|
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
20
src/events/battery_status_event.h
Normal file
20
src/events/battery_status_event.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef BATTERY_STATUS_EVENT_H
|
||||||
|
#define BATTERY_STATUS_EVENT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
|
||||||
|
struct battery_status_event
|
||||||
|
{
|
||||||
|
struct app_event_header header;
|
||||||
|
bool charging;
|
||||||
|
bool full;
|
||||||
|
uint8_t soc;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DECLARE(battery_status_event);
|
||||||
|
|
||||||
|
#endif
|
||||||
18
src/events/config_event.c
Normal file
18
src/events/config_event.c
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#include "config_event.h"
|
||||||
|
|
||||||
|
static void log_config_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct config_event *event = cast_config_event(aeh);
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(aeh, "status:%u %s rcpt:%02x id:%02x",
|
||||||
|
event->status,
|
||||||
|
event->is_request ? "req" : "rsp",
|
||||||
|
event->recipient,
|
||||||
|
event->event_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(config_event,
|
||||||
|
log_config_event,
|
||||||
|
NULL,
|
||||||
|
APP_EVENT_FLAGS_CREATE(
|
||||||
|
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
54
src/events/config_event.h
Normal file
54
src/events/config_event.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Lightweight config event used for local module configuration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NEW_KBD_CONFIG_EVENT_H__
|
||||||
|
#define NEW_KBD_CONFIG_EVENT_H__
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Keep the same local recipient as nRF Desktop config channel. */
|
||||||
|
#define CFG_CHAN_RECIPIENT_LOCAL 0x00
|
||||||
|
|
||||||
|
/* Event ID field layout (compatible with nRF Desktop config_event encoding). */
|
||||||
|
#define MOD_FIELD_POS 4
|
||||||
|
#define MOD_FIELD_SIZE 4
|
||||||
|
#define MOD_FIELD_MASK BIT_MASK(MOD_FIELD_SIZE)
|
||||||
|
#define MOD_FIELD_GET(id) (((id) >> MOD_FIELD_POS) & MOD_FIELD_MASK)
|
||||||
|
|
||||||
|
#define OPT_FIELD_POS 0
|
||||||
|
#define OPT_FIELD_SIZE 4
|
||||||
|
#define OPT_FIELD_MASK BIT_MASK(OPT_FIELD_SIZE)
|
||||||
|
#define OPT_FIELD_GET(id) (((id) >> OPT_FIELD_POS) & OPT_FIELD_MASK)
|
||||||
|
#define OPT_ID_GET(opt) ((opt) - 1U)
|
||||||
|
|
||||||
|
enum config_status {
|
||||||
|
CONFIG_STATUS_SET = 0,
|
||||||
|
CONFIG_STATUS_FETCH,
|
||||||
|
CONFIG_STATUS_SUCCESS,
|
||||||
|
CONFIG_STATUS_REJECT,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct config_event {
|
||||||
|
struct app_event_header header;
|
||||||
|
|
||||||
|
uint16_t transport_id;
|
||||||
|
bool is_request;
|
||||||
|
uint8_t event_id;
|
||||||
|
uint8_t recipient;
|
||||||
|
uint8_t status;
|
||||||
|
struct event_dyndata dyndata;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DYNDATA_DECLARE(config_event);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* NEW_KBD_CONFIG_EVENT_H__ */
|
||||||
42
src/events/hid_protocol_event.c
Normal file
42
src/events/hid_protocol_event.c
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#include "hid_protocol_event.h"
|
||||||
|
|
||||||
|
static const char *const hid_transport_name[] = {
|
||||||
|
[HID_TRANSPORT_BLE] = "BLE",
|
||||||
|
[HID_TRANSPORT_USB] = "USB",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *const hid_protocol_name[] = {
|
||||||
|
[HID_PROTO_BOOT] = "BOOT",
|
||||||
|
[HID_PROTO_REPORT] = "REPORT",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void log_hid_protocol_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct hid_protocol_event *event = cast_hid_protocol_event(aeh);
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(event->transport < ARRAY_SIZE(hid_transport_name));
|
||||||
|
__ASSERT_NO_MSG(event->protocol < ARRAY_SIZE(hid_protocol_name));
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(aeh, "transport=%s protocol=%s",
|
||||||
|
hid_transport_name[event->transport],
|
||||||
|
hid_protocol_name[event->protocol]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void profile_hid_protocol_event(struct log_event_buf *buf,
|
||||||
|
const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct hid_protocol_event *event = cast_hid_protocol_event(aeh);
|
||||||
|
|
||||||
|
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->transport);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_INFO_DEFINE(hid_protocol_event,
|
||||||
|
ENCODE(NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8),
|
||||||
|
ENCODE("transport", "protocol"),
|
||||||
|
profile_hid_protocol_event);
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(hid_protocol_event,
|
||||||
|
log_hid_protocol_event,
|
||||||
|
&hid_protocol_event_info,
|
||||||
|
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
30
src/events/hid_protocol_event.h
Normal file
30
src/events/hid_protocol_event.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef HID_PROTOCOL_EVENT_H__
|
||||||
|
#define HID_PROTOCOL_EVENT_H__
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
|
||||||
|
enum hid_transport_type {
|
||||||
|
HID_TRANSPORT_BLE = 0,
|
||||||
|
HID_TRANSPORT_USB,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum hid_protocol_type {
|
||||||
|
HID_PROTO_BOOT = 0,
|
||||||
|
HID_PROTO_REPORT,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HID 传输层在收到主机 set_protocol 请求后上报该事件,
|
||||||
|
* keyboard_module 会依据当前 active 传输的协议格式打包 hid_report_event。
|
||||||
|
*/
|
||||||
|
struct hid_protocol_event {
|
||||||
|
struct app_event_header header;
|
||||||
|
|
||||||
|
enum hid_transport_type transport;
|
||||||
|
enum hid_protocol_type protocol;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DECLARE(hid_protocol_event);
|
||||||
|
|
||||||
|
#endif /* HID_PROTOCOL_EVENT_H__ */
|
||||||
53
src/events/hid_report_event.c
Normal file
53
src/events/hid_report_event.c
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include "hid_report_event.h"
|
||||||
|
|
||||||
|
static const char *const hid_protocol_name[] = {
|
||||||
|
[HID_PROTO_BOOT] = "BOOT",
|
||||||
|
[HID_PROTO_REPORT] = "REPORT",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void log_hid_report_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct hid_report_event *event = cast_hid_report_event(aeh);
|
||||||
|
uint8_t report_id = 0x00;
|
||||||
|
uint16_t payload_len = event->dyndata.size;
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(event->protocol < ARRAY_SIZE(hid_protocol_name));
|
||||||
|
|
||||||
|
if (event->protocol == HID_PROTO_REPORT) {
|
||||||
|
__ASSERT_NO_MSG(event->dyndata.size >= 1U);
|
||||||
|
report_id = event->dyndata.data[0];
|
||||||
|
payload_len = event->dyndata.size - 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(aeh, "protocol=%s report_id=0x%02x payload_len=%u",
|
||||||
|
hid_protocol_name[event->protocol],
|
||||||
|
report_id,
|
||||||
|
payload_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void profile_hid_report_event(struct log_event_buf *buf,
|
||||||
|
const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct hid_report_event *event = cast_hid_report_event(aeh);
|
||||||
|
uint8_t report_id = 0x00;
|
||||||
|
|
||||||
|
if ((event->protocol == HID_PROTO_REPORT) && (event->dyndata.size >= 1U)) {
|
||||||
|
report_id = event->dyndata.data[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->protocol);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, report_id);
|
||||||
|
nrf_profiler_log_encode_uint16(buf, event->dyndata.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_INFO_DEFINE(hid_report_event,
|
||||||
|
ENCODE(NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U16),
|
||||||
|
ENCODE("protocol", "report_id", "len"),
|
||||||
|
profile_hid_report_event);
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(hid_report_event,
|
||||||
|
log_hid_report_event,
|
||||||
|
&hid_report_event_info,
|
||||||
|
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
24
src/events/hid_report_event.h
Normal file
24
src/events/hid_report_event.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef HID_REPORT_EVENT_H__
|
||||||
|
#define HID_REPORT_EVENT_H__
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
|
||||||
|
#include "hid_protocol_event.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HID 输入报告统一事件:
|
||||||
|
* - protocol 指示当前 dyndata 编码规则;
|
||||||
|
* - 当 protocol=HID_PROTO_REPORT:dyndata[0]=report_id,dyndata[1..]=payload;
|
||||||
|
* - 当 protocol=HID_PROTO_BOOT:dyndata 仅包含 boot payload(不含 report_id)。
|
||||||
|
*/
|
||||||
|
struct hid_report_event {
|
||||||
|
struct app_event_header header;
|
||||||
|
|
||||||
|
enum hid_protocol_type protocol;
|
||||||
|
struct event_dyndata dyndata;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DYNDATA_DECLARE(hid_report_event);
|
||||||
|
|
||||||
|
#endif /* HID_REPORT_EVENT_H__ */
|
||||||
34
src/events/mode_event.c
Normal file
34
src/events/mode_event.c
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#include "mode_event.h"
|
||||||
|
|
||||||
|
static const char *const mode_name[] = {
|
||||||
|
[MODE_TYPE_USB] = "USB",
|
||||||
|
[MODE_TYPE_BLE] = "BLE",
|
||||||
|
[MODE_TYPE_2G4] = "2.4G",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void log_mode_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct mode_event *event = cast_mode_event(aeh);
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(event->mode_type < MODE_TYPE_COUNT);
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(aeh, "mode=%s(%u)", mode_name[event->mode_type], event->mode_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void profile_mode_event(struct log_event_buf *buf,
|
||||||
|
const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct mode_event *event = cast_mode_event(aeh);
|
||||||
|
|
||||||
|
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->mode_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_INFO_DEFINE(mode_event,
|
||||||
|
ENCODE(NRF_PROFILER_ARG_U8),
|
||||||
|
ENCODE("mode"),
|
||||||
|
profile_mode_event);
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(mode_event,
|
||||||
|
log_mode_event,
|
||||||
|
&mode_event_info,
|
||||||
|
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
23
src/events/mode_event.h
Normal file
23
src/events/mode_event.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef MODE_EVENT_H
|
||||||
|
#define MODE_EVENT_H
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
MODE_TYPE_USB,
|
||||||
|
MODE_TYPE_BLE,
|
||||||
|
MODE_TYPE_2G4,
|
||||||
|
MODE_TYPE_COUNT,
|
||||||
|
} mode_type_t;
|
||||||
|
|
||||||
|
struct mode_event
|
||||||
|
{
|
||||||
|
struct app_event_header header;
|
||||||
|
mode_type_t mode_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DECLARE(mode_event);
|
||||||
|
|
||||||
|
#endif
|
||||||
46
src/events/usb_hid_event.c
Normal file
46
src/events/usb_hid_event.c
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#include "usb_hid_event.h"
|
||||||
|
|
||||||
|
static const char *const usb_hid_evt_type_name[] = {
|
||||||
|
[USB_HID_EVT_STATE_REPORT] = "STATE_REPORT",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *const usb_hid_usbd_state_name[] = {
|
||||||
|
[USB_HID_USBD_DISCONNECTED] = "DISCONNECTED",
|
||||||
|
[USB_HID_USBD_CONNECTED] = "CONNECTED",
|
||||||
|
[USB_HID_USBD_SUSPENDED] = "SUSPENDED",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void log_usb_hid_event(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct usb_hid_event *event = cast_usb_hid_event(aeh);
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(event->evt_type < ARRAY_SIZE(usb_hid_evt_type_name));
|
||||||
|
__ASSERT_NO_MSG(event->usbd_state < ARRAY_SIZE(usb_hid_usbd_state_name));
|
||||||
|
|
||||||
|
APP_EVENT_MANAGER_LOG(aeh, "type=%s en=%u usbd=%s",
|
||||||
|
usb_hid_evt_type_name[event->evt_type],
|
||||||
|
event->enable,
|
||||||
|
usb_hid_usbd_state_name[event->usbd_state]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void profile_usb_hid_event(struct log_event_buf *buf,
|
||||||
|
const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
const struct usb_hid_event *event = cast_usb_hid_event(aeh);
|
||||||
|
|
||||||
|
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->evt_type);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->enable);
|
||||||
|
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->usbd_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_INFO_DEFINE(usb_hid_event,
|
||||||
|
ENCODE(NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8,
|
||||||
|
NRF_PROFILER_ARG_U8),
|
||||||
|
ENCODE("evt_type", "enable", "usbd"),
|
||||||
|
profile_usb_hid_event);
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DEFINE(usb_hid_event,
|
||||||
|
log_usb_hid_event,
|
||||||
|
&usb_hid_event_info,
|
||||||
|
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||||
31
src/events/usb_hid_event.h
Normal file
31
src/events/usb_hid_event.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef USB_HID_EVENT_H__
|
||||||
|
#define USB_HID_EVENT_H__
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <app_event_manager_profiler_tracer.h>
|
||||||
|
|
||||||
|
enum usb_hid_event_type {
|
||||||
|
USB_HID_EVT_STATE_REPORT = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* USB 连接层状态(偏“链路可用性”) */
|
||||||
|
enum usb_hid_usbd_state {
|
||||||
|
USB_HID_USBD_DISCONNECTED = 0,
|
||||||
|
USB_HID_USBD_CONNECTED,
|
||||||
|
USB_HID_USBD_SUSPENDED,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct usb_hid_event {
|
||||||
|
struct app_event_header header;
|
||||||
|
|
||||||
|
enum usb_hid_event_type evt_type;
|
||||||
|
bool enable;
|
||||||
|
enum usb_hid_usbd_state usbd_state;
|
||||||
|
};
|
||||||
|
|
||||||
|
APP_EVENT_TYPE_DECLARE(usb_hid_event);
|
||||||
|
|
||||||
|
#endif /* USB_HID_EVENT_H__ */
|
||||||
351
src/modules/battery_module.c
Normal file
351
src/modules/battery_module.c
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/adc.h>
|
||||||
|
#include <zephyr/drivers/gpio.h>
|
||||||
|
#include <zephyr/drivers/power/ip5305.h>
|
||||||
|
#include <zephyr/sys/atomic.h>
|
||||||
|
#include <zephyr/sys/util.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <caf/events/power_event.h>
|
||||||
|
#include <caf/events/power_manager_event.h>
|
||||||
|
|
||||||
|
#define MODULE battery
|
||||||
|
#include <caf/events/module_state_event.h>
|
||||||
|
|
||||||
|
#include "battery_status_event.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(MODULE);
|
||||||
|
|
||||||
|
#define BATTERY_USER_NODE DT_PATH(zephyr_user)
|
||||||
|
#define BATTERY_ADC_IO_CH_IDX 1
|
||||||
|
#define BATTERY_SAMPLE_INTERVAL_MS 1000
|
||||||
|
#define BATTERY_VDIV_NUM 2
|
||||||
|
#define BATTERY_VDIV_DEN 1
|
||||||
|
#define BATTERY_MV_WINDOW_SIZE 10
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SOC 估算策略:
|
||||||
|
* 这里采用线性估算,默认把 3.30V 映射为 0%,4.20V 映射为 100%。
|
||||||
|
* 若后续实测曲线和电压分压比例不同,只需调整两个阈值即可。
|
||||||
|
*/
|
||||||
|
#define BATTERY_EMPTY_MV 3300
|
||||||
|
#define BATTERY_FULL_MV 4100
|
||||||
|
|
||||||
|
static const struct adc_dt_spec battery_adc =
|
||||||
|
ADC_DT_SPEC_GET_BY_IDX(BATTERY_USER_NODE, BATTERY_ADC_IO_CH_IDX);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 电池采样使能脚从 zephyr,user/vbat-en-gpios 读取,避免把引脚号硬编码在 C 里。
|
||||||
|
* 后续板级改脚位只需改 DTS,不需要改固件代码。
|
||||||
|
*/
|
||||||
|
static const struct gpio_dt_spec battery_en_gpio =
|
||||||
|
GPIO_DT_SPEC_GET(BATTERY_USER_NODE, vbat_en_gpios);
|
||||||
|
|
||||||
|
static const struct device *const ip5305_dev = DEVICE_DT_GET(DT_NODELABEL(ip5305));
|
||||||
|
|
||||||
|
struct battery_status
|
||||||
|
{
|
||||||
|
bool charging;
|
||||||
|
bool full;
|
||||||
|
uint8_t soc;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct k_work_delayable battery_sample_work;
|
||||||
|
static int16_t battery_adc_sample_buffer;
|
||||||
|
static atomic_t active;
|
||||||
|
static struct battery_status last_status;
|
||||||
|
static bool has_last_status;
|
||||||
|
static int32_t battery_mv_window[BATTERY_MV_WINDOW_SIZE];
|
||||||
|
static int64_t battery_mv_sum;
|
||||||
|
static size_t battery_mv_count;
|
||||||
|
static size_t battery_mv_index;
|
||||||
|
|
||||||
|
static void battery_module_resume(void);
|
||||||
|
|
||||||
|
static uint8_t soc_from_mv(int32_t mv)
|
||||||
|
{
|
||||||
|
if (mv <= BATTERY_EMPTY_MV)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mv >= BATTERY_FULL_MV)
|
||||||
|
{
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t soc = ((mv - BATTERY_EMPTY_MV) * 100) / (BATTERY_FULL_MV - BATTERY_EMPTY_MV);
|
||||||
|
return (uint8_t)CLAMP(soc, 0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int adc_sample_once_mv(int32_t *mv)
|
||||||
|
{
|
||||||
|
struct adc_sequence sequence = {0};
|
||||||
|
int err = adc_sequence_init_dt(&battery_adc, &sequence);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence.buffer = &battery_adc_sample_buffer;
|
||||||
|
sequence.buffer_size = sizeof(battery_adc_sample_buffer);
|
||||||
|
|
||||||
|
err = adc_read_dt(&battery_adc, &sequence);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
LOG_WRN("adc_read_dt failed (err=%d)", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
*mv = battery_adc_sample_buffer;
|
||||||
|
err = adc_raw_to_millivolts_dt(&battery_adc, mv);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
LOG_WRN("adc_raw_to_millivolts_dt failed (err=%d raw=%d)",
|
||||||
|
err, battery_adc_sample_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_battery_mv(int32_t *mv)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
int32_t sensed_mv;
|
||||||
|
|
||||||
|
err = adc_sample_once_mv(&sensed_mv);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 板级电池检测存在分压,ADC 读到的是分压后电压。
|
||||||
|
* 这里按分压比还原电池端真实电压,供 SOC 估算与事件上报使用。
|
||||||
|
*/
|
||||||
|
int32_t battery_mv = (sensed_mv * BATTERY_VDIV_NUM) / BATTERY_VDIV_DEN;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 使用固定窗口平均抑制采样抖动。
|
||||||
|
* 窗口未填满前按当前样本数求平均,填满后使用环形缓冲滚动更新。
|
||||||
|
*/
|
||||||
|
if (battery_mv_count < BATTERY_MV_WINDOW_SIZE)
|
||||||
|
{
|
||||||
|
battery_mv_window[battery_mv_index] = battery_mv;
|
||||||
|
battery_mv_sum += battery_mv;
|
||||||
|
battery_mv_count++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
battery_mv_sum -= battery_mv_window[battery_mv_index];
|
||||||
|
battery_mv_window[battery_mv_index] = battery_mv;
|
||||||
|
battery_mv_sum += battery_mv;
|
||||||
|
}
|
||||||
|
|
||||||
|
battery_mv_index = (battery_mv_index + 1U) % BATTERY_MV_WINDOW_SIZE;
|
||||||
|
*mv = (int32_t)(battery_mv_sum / (int64_t)battery_mv_count);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_battery_status(struct battery_status *status)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
int32_t mv;
|
||||||
|
|
||||||
|
err = ip5305_is_charging(ip5305_dev, &status->charging);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ip5305_is_charge_full(ip5305_dev, &status->full);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = read_battery_mv(&mv);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
LOG_WRN("read_battery_mv failed (err=%d)", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
status->soc = soc_from_mv(mv);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void publish_battery_status_event(const struct battery_status *status)
|
||||||
|
{
|
||||||
|
struct battery_status_event *event = new_battery_status_event();
|
||||||
|
|
||||||
|
event->charging = status->charging;
|
||||||
|
event->full = status->full;
|
||||||
|
event->soc = status->soc;
|
||||||
|
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void battery_sample_fn(struct k_work *work)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(work);
|
||||||
|
|
||||||
|
if (!atomic_get(&active))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct battery_status sampled;
|
||||||
|
int err = read_battery_status(&sampled);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
goto out_reschedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 仅在状态发生变化时上报,避免重复事件淹没总线。
|
||||||
|
* 变化条件:充电标志、满电标志、SOC 任意一个变化。
|
||||||
|
*/
|
||||||
|
if (!has_last_status ||
|
||||||
|
(sampled.charging != last_status.charging) ||
|
||||||
|
(sampled.full != last_status.full) ||
|
||||||
|
(sampled.soc != last_status.soc))
|
||||||
|
{
|
||||||
|
last_status = sampled;
|
||||||
|
has_last_status = true;
|
||||||
|
publish_battery_status_event(&sampled);
|
||||||
|
}
|
||||||
|
|
||||||
|
out_reschedule:
|
||||||
|
k_work_reschedule(&battery_sample_work, K_MSEC(BATTERY_SAMPLE_INTERVAL_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int battery_module_init(void)
|
||||||
|
{
|
||||||
|
if (!device_is_ready(ip5305_dev))
|
||||||
|
{
|
||||||
|
LOG_ERR("IP5305 device not ready");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!adc_is_ready_dt(&battery_adc))
|
||||||
|
{
|
||||||
|
LOG_ERR("Battery ADC device not ready");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_is_ready(battery_en_gpio.port))
|
||||||
|
{
|
||||||
|
LOG_ERR("Battery EN GPIO device not ready");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = gpio_pin_configure_dt(&battery_en_gpio, GPIO_OUTPUT_INACTIVE);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
LOG_ERR("Battery EN GPIO configure failed (err=%d)", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = adc_channel_setup_dt(&battery_adc);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
LOG_ERR("Battery ADC channel setup failed (err=%d)", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 按需求将最深电源级别限制在 SUSPENDED,禁止进入 OFF。
|
||||||
|
* 这样即使 CPU 进入挂起,IP5305 的硬件保活后端仍可持续输出保活脉冲。
|
||||||
|
*/
|
||||||
|
power_manager_restrict(MODULE_IDX(MODULE), POWER_MANAGER_LEVEL_SUSPENDED);
|
||||||
|
|
||||||
|
k_work_init_delayable(&battery_sample_work, battery_sample_fn);
|
||||||
|
has_last_status = false;
|
||||||
|
battery_mv_sum = 0;
|
||||||
|
battery_mv_count = 0;
|
||||||
|
battery_mv_index = 0;
|
||||||
|
atomic_set(&active, false);
|
||||||
|
|
||||||
|
atomic_set(&active, true);
|
||||||
|
(void)gpio_pin_set_dt(&battery_en_gpio, 1);
|
||||||
|
k_work_reschedule(&battery_sample_work, K_NO_WAIT);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void battery_module_suspend(void)
|
||||||
|
{
|
||||||
|
if (!atomic_get(&active))
|
||||||
|
{
|
||||||
|
/* 已经处于挂起态,避免重复上报 STANDBY 造成 power_down 循环。 */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_set(&active, false);
|
||||||
|
(void)k_work_cancel_delayable(&battery_sample_work);
|
||||||
|
(void)gpio_pin_set_dt(&battery_en_gpio, 0);
|
||||||
|
module_set_state(MODULE_STATE_STANDBY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void battery_module_resume(void)
|
||||||
|
{
|
||||||
|
if (atomic_get(&active))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_set(&active, true);
|
||||||
|
(void)gpio_pin_set_dt(&battery_en_gpio, 1);
|
||||||
|
k_work_reschedule(&battery_sample_work, K_NO_WAIT);
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool app_event_handler(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
if (is_module_state_event(aeh))
|
||||||
|
{
|
||||||
|
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||||
|
|
||||||
|
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY))
|
||||||
|
{
|
||||||
|
int err = battery_module_init();
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
module_set_state(MODULE_STATE_ERROR);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_power_down_event(aeh))
|
||||||
|
{
|
||||||
|
battery_module_suspend();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_wake_up_event(aeh))
|
||||||
|
{
|
||||||
|
battery_module_resume();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
||||||
85
src/modules/ble_adv_ctrl_module.c
Normal file
85
src/modules/ble_adv_ctrl_module.c
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#include <app_event_manager.h>
|
||||||
|
|
||||||
|
#define MODULE ble_adv_ctrl
|
||||||
|
#include <caf/events/module_state_event.h>
|
||||||
|
#include <caf/events/module_suspend_event.h>
|
||||||
|
|
||||||
|
#include "mode_event.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 该模块负责把“模式选择”转换成对 ble_adv 的挂起/恢复请求:
|
||||||
|
* - BLE 模式:恢复 ble_adv,允许广播。
|
||||||
|
* - USB/2.4G 模式:挂起 ble_adv,禁止广播。
|
||||||
|
*
|
||||||
|
* 说明:
|
||||||
|
* - 这里是控制层,不直接操作 bt_le_adv_start/stop;
|
||||||
|
* - 实际广播执行仍由 CAF 的 ble_adv 模块处理。
|
||||||
|
*/
|
||||||
|
static bool ble_adv_suspended = true;
|
||||||
|
|
||||||
|
static void send_ble_adv_ctrl_req(bool suspend)
|
||||||
|
{
|
||||||
|
if (suspend) {
|
||||||
|
struct module_suspend_req_event *event = new_module_suspend_req_event();
|
||||||
|
|
||||||
|
event->sink_module_id = MODULE_ID(ble_adv);
|
||||||
|
event->src_module_id = MODULE_ID(MODULE);
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
} else {
|
||||||
|
struct module_resume_req_event *event = new_module_resume_req_event();
|
||||||
|
|
||||||
|
event->sink_module_id = MODULE_ID(ble_adv);
|
||||||
|
event->src_module_id = MODULE_ID(MODULE);
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_mode_event(const struct mode_event *event)
|
||||||
|
{
|
||||||
|
bool new_suspend = (event->mode_type != MODE_TYPE_BLE);
|
||||||
|
|
||||||
|
if (new_suspend == ble_adv_suspended) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ble_adv_suspended = new_suspend;
|
||||||
|
send_ble_adv_ctrl_req(ble_adv_suspended);
|
||||||
|
|
||||||
|
LOG_INF("BLE advertising %s by mode %u",
|
||||||
|
ble_adv_suspended ? "suspended" : "resumed",
|
||||||
|
event->mode_type);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool app_event_handler(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
if (is_module_state_event(aeh)) {
|
||||||
|
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||||
|
|
||||||
|
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
||||||
|
/*
|
||||||
|
* 上电默认先请求挂起,避免在 mode_switch 首次采样前出现短暂误广播。
|
||||||
|
*/
|
||||||
|
send_ble_adv_ctrl_req(true);
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_mode_event(aeh)) {
|
||||||
|
return handle_mode_event(cast_mode_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||||
|
|
||||||
376
src/modules/ble_bond_module.c
Normal file
376
src/modules/ble_bond_module.c
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
#include <zephyr/bluetooth/bluetooth.h>
|
||||||
|
#include <zephyr/settings/settings.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
|
||||||
|
#define MODULE ble_bond
|
||||||
|
#include <caf/events/module_state_event.h>
|
||||||
|
#include <caf/events/ble_common_event.h>
|
||||||
|
#include <caf/events/power_event.h>
|
||||||
|
|
||||||
|
#include "config_event.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
/* Application-visible options carried by config_event. */
|
||||||
|
enum ble_bond_cfg_opt {
|
||||||
|
BLE_BOND_CFG_PEER_SELECT = 0,
|
||||||
|
BLE_BOND_CFG_PEER_ERASE,
|
||||||
|
BLE_BOND_CFG_PEER_ERASE_ALL,
|
||||||
|
BLE_BOND_CFG_OPT_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Module ID in config_event event_id[7:4]. Keep stable for host side tooling. */
|
||||||
|
#define BLE_BOND_CFG_MODULE_ID 0x01
|
||||||
|
|
||||||
|
#define PEER_ID_KEY "peer_id"
|
||||||
|
#define BT_LUT_KEY "bt_lut"
|
||||||
|
|
||||||
|
enum state {
|
||||||
|
STATE_DISABLED,
|
||||||
|
STATE_IDLE,
|
||||||
|
STATE_STANDBY,
|
||||||
|
};
|
||||||
|
|
||||||
|
BUILD_ASSERT(CONFIG_BT_ID_MAX >= 2, "Need at least one resettable identity");
|
||||||
|
|
||||||
|
#define APP_PEER_COUNT (CONFIG_BT_ID_MAX - 1)
|
||||||
|
|
||||||
|
static enum state state = STATE_DISABLED;
|
||||||
|
|
||||||
|
/* app peer id -> bt stack id mapping (default identity 0 is not used). */
|
||||||
|
static uint8_t bt_stack_id_lut[APP_PEER_COUNT];
|
||||||
|
static bool bt_stack_id_lut_valid;
|
||||||
|
|
||||||
|
static uint8_t cur_ble_peer_id;
|
||||||
|
static bool cur_peer_id_valid;
|
||||||
|
|
||||||
|
static const char *state_name(enum state s)
|
||||||
|
{
|
||||||
|
switch (s) {
|
||||||
|
case STATE_DISABLED:
|
||||||
|
return "DISABLED";
|
||||||
|
case STATE_IDLE:
|
||||||
|
return "IDLE";
|
||||||
|
case STATE_STANDBY:
|
||||||
|
return "STANDBY";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t get_bt_stack_peer_id(uint8_t app_id)
|
||||||
|
{
|
||||||
|
__ASSERT_NO_MSG(app_id < APP_PEER_COUNT);
|
||||||
|
return bt_stack_id_lut[app_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
static int store_peer_id(uint8_t peer_id)
|
||||||
|
{
|
||||||
|
char key[] = MODULE_NAME "/" PEER_ID_KEY;
|
||||||
|
|
||||||
|
return settings_save_one(key, &peer_id, sizeof(peer_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int store_bt_stack_id_lut(void)
|
||||||
|
{
|
||||||
|
char key[] = MODULE_NAME "/" BT_LUT_KEY;
|
||||||
|
|
||||||
|
return settings_save_one(key, bt_stack_id_lut, sizeof(bt_stack_id_lut));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void submit_peer_op_event(enum peer_operation op, uint8_t app_id)
|
||||||
|
{
|
||||||
|
struct ble_peer_operation_event *event = new_ble_peer_operation_event();
|
||||||
|
|
||||||
|
event->op = op;
|
||||||
|
event->bt_app_id = app_id;
|
||||||
|
event->bt_stack_id = get_bt_stack_peer_id(app_id);
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_bt_stack_id_lut(void)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(bt_stack_id_lut); i++) {
|
||||||
|
/* Keep id 0 (BT_ID_DEFAULT) untouched for safe reset/unpair flow. */
|
||||||
|
bt_stack_id_lut[i] = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool storage_data_is_valid(void)
|
||||||
|
{
|
||||||
|
if (!cur_peer_id_valid || !bt_stack_id_lut_valid) {
|
||||||
|
LOG_WRN("Stored data invalid: peer_valid=%d lut_valid=%d",
|
||||||
|
cur_peer_id_valid, bt_stack_id_lut_valid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cur_ble_peer_id >= APP_PEER_COUNT) {
|
||||||
|
LOG_WRN("Stored peer id out of range: peer_id=%u max=%u",
|
||||||
|
cur_ble_peer_id, APP_PEER_COUNT - 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(bt_stack_id_lut); i++) {
|
||||||
|
if ((bt_stack_id_lut[i] == BT_ID_DEFAULT) ||
|
||||||
|
(bt_stack_id_lut[i] >= CONFIG_BT_ID_MAX)) {
|
||||||
|
LOG_WRN("Stored LUT invalid at idx=%u value=%u",
|
||||||
|
(uint32_t)i, bt_stack_id_lut[i]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int settings_set(const char *key, size_t len_rd,
|
||||||
|
settings_read_cb read_cb, void *cb_arg)
|
||||||
|
{
|
||||||
|
ssize_t rc;
|
||||||
|
|
||||||
|
if (!strcmp(key, PEER_ID_KEY)) {
|
||||||
|
if (len_rd != sizeof(cur_ble_peer_id)) {
|
||||||
|
LOG_WRN("Settings '%s' size mismatch: got=%u expect=%u",
|
||||||
|
PEER_ID_KEY, (uint32_t)len_rd, sizeof(cur_ble_peer_id));
|
||||||
|
cur_peer_id_valid = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = read_cb(cb_arg, &cur_ble_peer_id, sizeof(cur_ble_peer_id));
|
||||||
|
cur_peer_id_valid = (rc == sizeof(cur_ble_peer_id));
|
||||||
|
if (!cur_peer_id_valid) {
|
||||||
|
LOG_WRN("Settings '%s' read failed: rc=%d", PEER_ID_KEY, (int)rc);
|
||||||
|
}
|
||||||
|
} else if (!strcmp(key, BT_LUT_KEY)) {
|
||||||
|
if (len_rd != sizeof(bt_stack_id_lut)) {
|
||||||
|
LOG_WRN("Settings '%s' size mismatch: got=%u expect=%u",
|
||||||
|
BT_LUT_KEY, (uint32_t)len_rd, sizeof(bt_stack_id_lut));
|
||||||
|
bt_stack_id_lut_valid = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = read_cb(cb_arg, bt_stack_id_lut, sizeof(bt_stack_id_lut));
|
||||||
|
bt_stack_id_lut_valid = (rc == sizeof(bt_stack_id_lut));
|
||||||
|
if (!bt_stack_id_lut_valid) {
|
||||||
|
LOG_WRN("Settings '%s' read failed: rc=%d", BT_LUT_KEY, (int)rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SETTINGS_STATIC_HANDLER_DEFINE(ble_bond, MODULE_NAME, NULL, settings_set, NULL, NULL);
|
||||||
|
|
||||||
|
static int load_identities(void)
|
||||||
|
{
|
||||||
|
bt_addr_le_t addrs[CONFIG_BT_ID_MAX];
|
||||||
|
size_t count = ARRAY_SIZE(addrs);
|
||||||
|
|
||||||
|
bt_id_get(addrs, &count);
|
||||||
|
LOG_INF("Identity count before ensure: %u / %u", (uint32_t)count, CONFIG_BT_ID_MAX);
|
||||||
|
|
||||||
|
for (; count < CONFIG_BT_ID_MAX; count++) {
|
||||||
|
int err = bt_id_create(NULL, NULL);
|
||||||
|
if (err < 0) {
|
||||||
|
LOG_ERR("Cannot create identity (err:%d)", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
LOG_INF("Created identity idx=%u", (uint32_t)count);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int erase_peer(uint8_t app_id)
|
||||||
|
{
|
||||||
|
uint8_t stack_id = get_bt_stack_peer_id(app_id);
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* Tell ble_adv to restart advertising session for this identity. */
|
||||||
|
submit_peer_op_event(PEER_OPERATION_ERASE_ADV, app_id);
|
||||||
|
|
||||||
|
err = bt_unpair(stack_id, NULL);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Cannot unpair id %u (err:%d)", stack_id, err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bt_id_reset(stack_id, NULL, NULL);
|
||||||
|
if (err < 0) {
|
||||||
|
LOG_ERR("Cannot reset id %u (err:%d)", stack_id, err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
submit_peer_op_event(PEER_OPERATION_ERASED, app_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int erase_all_peers(void)
|
||||||
|
{
|
||||||
|
for (uint8_t i = 0; i < APP_PEER_COUNT; i++) {
|
||||||
|
int err = erase_peer(i);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_config_event(const struct config_event *event)
|
||||||
|
{
|
||||||
|
if (!event->is_request || (event->recipient != CFG_CHAN_RECIPIENT_LOCAL)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MOD_FIELD_GET(event->event_id) != BLE_BOND_CFG_MODULE_ID) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct config_event *rsp = new_config_event(0);
|
||||||
|
rsp->transport_id = event->transport_id;
|
||||||
|
rsp->is_request = false;
|
||||||
|
rsp->event_id = event->event_id;
|
||||||
|
rsp->recipient = event->recipient;
|
||||||
|
rsp->status = CONFIG_STATUS_REJECT;
|
||||||
|
|
||||||
|
if (event->status == CONFIG_STATUS_SET) {
|
||||||
|
uint8_t opt_field = OPT_FIELD_GET(event->event_id);
|
||||||
|
uint8_t opt_id = (opt_field == 0) ? UINT8_MAX : OPT_ID_GET(opt_field);
|
||||||
|
|
||||||
|
switch (opt_id) {
|
||||||
|
case BLE_BOND_CFG_PEER_SELECT:
|
||||||
|
if (event->dyndata.size >= 1) {
|
||||||
|
uint8_t peer_id = event->dyndata.data[0];
|
||||||
|
if (peer_id < APP_PEER_COUNT) {
|
||||||
|
cur_ble_peer_id = peer_id;
|
||||||
|
if (!store_peer_id(cur_ble_peer_id)) {
|
||||||
|
submit_peer_op_event(PEER_OPERATION_SELECTED, cur_ble_peer_id);
|
||||||
|
rsp->status = CONFIG_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BLE_BOND_CFG_PEER_ERASE:
|
||||||
|
if (!erase_peer(cur_ble_peer_id)) {
|
||||||
|
rsp->status = CONFIG_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BLE_BOND_CFG_PEER_ERASE_ALL:
|
||||||
|
if (!erase_all_peers()) {
|
||||||
|
rsp->status = CONFIG_STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (event->status == CONFIG_STATUS_FETCH) {
|
||||||
|
uint8_t opt_field = OPT_FIELD_GET(event->event_id);
|
||||||
|
uint8_t opt_id = (opt_field == 0) ? UINT8_MAX : OPT_ID_GET(opt_field);
|
||||||
|
|
||||||
|
if (opt_id == BLE_BOND_CFG_PEER_SELECT) {
|
||||||
|
struct config_event *rsp_data = new_config_event(1);
|
||||||
|
rsp_data->transport_id = event->transport_id;
|
||||||
|
rsp_data->is_request = false;
|
||||||
|
rsp_data->event_id = event->event_id;
|
||||||
|
rsp_data->recipient = event->recipient;
|
||||||
|
rsp_data->status = CONFIG_STATUS_SUCCESS;
|
||||||
|
rsp_data->dyndata.data[0] = cur_ble_peer_id;
|
||||||
|
APP_EVENT_SUBMIT(rsp_data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_SUBMIT(rsp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int init_after_settings_loaded(void)
|
||||||
|
{
|
||||||
|
int err = load_identities();
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Identity initialization failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!storage_data_is_valid()) {
|
||||||
|
LOG_WRN("Stored BLE bond data invalid, reinitializing defaults");
|
||||||
|
cur_ble_peer_id = 0;
|
||||||
|
init_bt_stack_id_lut();
|
||||||
|
|
||||||
|
err = store_peer_id(cur_ble_peer_id);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to store peer_id=%u (err:%d)", cur_ble_peer_id, err);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store_bt_stack_id_lut();
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("Failed to store bt_stack_id_lut (err:%d)", err);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state = STATE_IDLE;
|
||||||
|
LOG_INF("ble_bond init done: state=%s peer_id=%u stack_id=%u",
|
||||||
|
state_name(state), cur_ble_peer_id, get_bt_stack_peer_id(cur_ble_peer_id));
|
||||||
|
submit_peer_op_event(PEER_OPERATION_SELECTED, cur_ble_peer_id);
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool app_event_handler(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
if (is_module_state_event(aeh)) {
|
||||||
|
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||||
|
|
||||||
|
if (check_state(event, MODULE_ID(settings_loader), MODULE_STATE_READY) &&
|
||||||
|
(state == STATE_DISABLED)) {
|
||||||
|
LOG_INF("settings_loader ready, starting ble_bond init");
|
||||||
|
int err = init_after_settings_loaded();
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("ble_bond init failed (err:%d), state=%s",
|
||||||
|
err, state_name(state));
|
||||||
|
module_set_state(MODULE_STATE_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_power_down_event(aeh)) {
|
||||||
|
if (state == STATE_IDLE) {
|
||||||
|
state = STATE_STANDBY;
|
||||||
|
module_set_state(MODULE_STATE_OFF);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_wake_up_event(aeh)) {
|
||||||
|
if (state == STATE_STANDBY) {
|
||||||
|
state = STATE_IDLE;
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
submit_peer_op_event(PEER_OPERATION_SELECTED, cur_ble_peer_id);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_config_event(aeh)) {
|
||||||
|
return handle_config_event(cast_config_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, power_down_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
||||||
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, config_event);
|
||||||
287
src/modules/ble_hid_module.c
Normal file
287
src/modules/ble_hid_module.c
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
#include <bluetooth/services/hids.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
|
||||||
|
#define MODULE ble_hid
|
||||||
|
#include <caf/events/module_state_event.h>
|
||||||
|
#include <caf/events/ble_common_event.h>
|
||||||
|
|
||||||
|
#include "hid_protocol_event.h"
|
||||||
|
#include "hid_report_event.h"
|
||||||
|
#include "hid_report_descriptor.h"
|
||||||
|
#include "mode_event.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
#define INPUT_REPORT_COUNT 2
|
||||||
|
#define OUTPUT_REPORT_COUNT 1
|
||||||
|
#define KEYBOARD_REPORT_LEN 30
|
||||||
|
#define CONSUMER_REPORT_LEN 2
|
||||||
|
#define KEYBOARD_LED_REPORT_LEN 1
|
||||||
|
#define BOOT_KEYBOARD_REPORT_LEN 8
|
||||||
|
|
||||||
|
BT_HIDS_DEF(hids_obj, INPUT_REPORT_COUNT, OUTPUT_REPORT_COUNT, 0);
|
||||||
|
|
||||||
|
static struct bt_conn *active_conn;
|
||||||
|
static enum bt_hids_pm current_pm = BT_HIDS_PM_REPORT;
|
||||||
|
static bool ble_mode_selected;
|
||||||
|
|
||||||
|
static enum hid_protocol_type pm_to_protocol(enum bt_hids_pm pm)
|
||||||
|
{
|
||||||
|
return (pm == BT_HIDS_PM_BOOT) ? HID_PROTO_BOOT : HID_PROTO_REPORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void publish_hid_protocol_event(enum hid_protocol_type protocol)
|
||||||
|
{
|
||||||
|
struct hid_protocol_event *event = new_hid_protocol_event();
|
||||||
|
|
||||||
|
event->transport = HID_TRANSPORT_BLE;
|
||||||
|
event->protocol = protocol;
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(conn);
|
||||||
|
|
||||||
|
switch (evt) {
|
||||||
|
case BT_HIDS_PM_EVT_BOOT_MODE_ENTERED:
|
||||||
|
current_pm = BT_HIDS_PM_BOOT;
|
||||||
|
LOG_INF("HIDS protocol: boot");
|
||||||
|
if (active_conn) {
|
||||||
|
publish_hid_protocol_event(HID_PROTO_BOOT);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED:
|
||||||
|
current_pm = BT_HIDS_PM_REPORT;
|
||||||
|
LOG_INF("HIDS protocol: report");
|
||||||
|
if (active_conn) {
|
||||||
|
publish_hid_protocol_event(HID_PROTO_REPORT);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void report_notify_handler(enum bt_hids_notify_evt evt)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void boot_keyboard_notif_handler(enum bt_hids_notify_evt evt)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void boot_keyboard_output_report_handler(struct bt_hids_rep *rep,
|
||||||
|
struct bt_conn *conn,
|
||||||
|
bool write)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(conn);
|
||||||
|
|
||||||
|
if (!write || !rep || (rep->size == 0) || !rep->data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("Boot KB out report 0x%02x", rep->data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void keyboard_output_report_handler(struct bt_hids_rep *rep,
|
||||||
|
struct bt_conn *conn,
|
||||||
|
bool write)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(conn);
|
||||||
|
|
||||||
|
if (!write || !rep || !rep->data || (rep->size < KEYBOARD_LED_REPORT_LEN)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("Report KB out report 0x%02x", rep->data[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hids_service_init(void)
|
||||||
|
{
|
||||||
|
static const uint8_t report_map[] = HID_DESC_KEYBOARD_NKRO_CONSUMER();
|
||||||
|
struct bt_hids_init_param init_param = { 0 };
|
||||||
|
struct bt_hids_inp_rep *input_report = &init_param.inp_rep_group_init.reports[0];
|
||||||
|
struct bt_hids_outp_feat_rep *output_report = &init_param.outp_rep_group_init.reports[0];
|
||||||
|
|
||||||
|
init_param.info.bcd_hid = 0x0101;
|
||||||
|
init_param.info.b_country_code = 0x00;
|
||||||
|
init_param.info.flags = BT_HIDS_REMOTE_WAKE | BT_HIDS_NORMALLY_CONNECTABLE;
|
||||||
|
|
||||||
|
init_param.rep_map.data = report_map;
|
||||||
|
init_param.rep_map.size = sizeof(report_map);
|
||||||
|
|
||||||
|
input_report[0].id = REPORT_ID_KEYBOARD;
|
||||||
|
input_report[0].size = KEYBOARD_REPORT_LEN;
|
||||||
|
input_report[0].handler = report_notify_handler;
|
||||||
|
|
||||||
|
input_report[1].id = REPORT_ID_CONSUMER;
|
||||||
|
input_report[1].size = CONSUMER_REPORT_LEN;
|
||||||
|
input_report[1].handler = report_notify_handler;
|
||||||
|
|
||||||
|
output_report[0].id = REPORT_ID_KEYBOARD;
|
||||||
|
output_report[0].size = KEYBOARD_LED_REPORT_LEN;
|
||||||
|
output_report[0].handler = keyboard_output_report_handler;
|
||||||
|
|
||||||
|
init_param.inp_rep_group_init.cnt = INPUT_REPORT_COUNT;
|
||||||
|
init_param.outp_rep_group_init.cnt = OUTPUT_REPORT_COUNT;
|
||||||
|
init_param.pm_evt_handler = pm_evt_handler;
|
||||||
|
init_param.is_kb = true;
|
||||||
|
init_param.boot_kb_notif_handler = boot_keyboard_notif_handler;
|
||||||
|
init_param.boot_kb_outp_rep_handler = boot_keyboard_output_report_handler;
|
||||||
|
|
||||||
|
return bt_hids_init(&hids_obj, &init_param);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_ble_peer_event(const struct ble_peer_event *event)
|
||||||
|
{
|
||||||
|
switch (event->state) {
|
||||||
|
case PEER_STATE_CONNECTED:
|
||||||
|
__ASSERT_NO_MSG(active_conn == NULL);
|
||||||
|
active_conn = event->id;
|
||||||
|
if (bt_hids_connected(&hids_obj, active_conn)) {
|
||||||
|
LOG_WRN("bt_hids_connected failed");
|
||||||
|
}
|
||||||
|
/* 连接建立后按当前协议主动同步一次,避免 keyboard_module 等待下一次 set_protocol。 */
|
||||||
|
publish_hid_protocol_event(pm_to_protocol(current_pm));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PEER_STATE_DISCONNECTED:
|
||||||
|
if (active_conn == event->id) {
|
||||||
|
if (bt_hids_disconnected(&hids_obj, active_conn)) {
|
||||||
|
LOG_WRN("bt_hids_disconnected failed");
|
||||||
|
}
|
||||||
|
active_conn = NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_hid_report_event(const struct hid_report_event *event)
|
||||||
|
{
|
||||||
|
if (!ble_mode_selected || !active_conn) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t report_id;
|
||||||
|
const uint8_t *payload;
|
||||||
|
size_t payload_len;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* keyboard_module 已经按照 protocol 打包好了完整 payload。
|
||||||
|
* BLE 模块这里只做协议一致性检查与发送。
|
||||||
|
*/
|
||||||
|
if ((current_pm == BT_HIDS_PM_BOOT) && (event->protocol != HID_PROTO_BOOT)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((current_pm == BT_HIDS_PM_REPORT) && (event->protocol != HID_PROTO_REPORT)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->protocol == HID_PROTO_BOOT) {
|
||||||
|
report_id = REPORT_ID_KEYBOARD;
|
||||||
|
payload = event->dyndata.data;
|
||||||
|
payload_len = event->dyndata.size;
|
||||||
|
} else {
|
||||||
|
if (event->dyndata.size < 1U) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
report_id = event->dyndata.data[0];
|
||||||
|
payload = &event->dyndata.data[1];
|
||||||
|
payload_len = event->dyndata.size - 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
if (event->protocol == HID_PROTO_BOOT) {
|
||||||
|
if (report_id != REPORT_ID_KEYBOARD) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (payload_len != BOOT_KEYBOARD_REPORT_LEN) {
|
||||||
|
LOG_WRN("Invalid boot keyboard payload len=%u", payload_len);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bt_hids_boot_kb_inp_rep_send(&hids_obj, active_conn,
|
||||||
|
payload, payload_len, NULL);
|
||||||
|
} else {
|
||||||
|
uint8_t rep_index;
|
||||||
|
|
||||||
|
if (report_id == REPORT_ID_KEYBOARD) {
|
||||||
|
rep_index = 0U;
|
||||||
|
} else if (report_id == REPORT_ID_CONSUMER) {
|
||||||
|
rep_index = 1U;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload_len > UINT8_MAX) {
|
||||||
|
LOG_WRN("Payload too large=%u", payload_len);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bt_hids_inp_rep_send(&hids_obj, active_conn, rep_index,
|
||||||
|
payload, (uint8_t)payload_len, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
LOG_WRN("BLE HID send failed report=0x%02x err=%d", report_id, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool app_event_handler(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
if (is_module_state_event(aeh)) {
|
||||||
|
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||||
|
|
||||||
|
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
||||||
|
static bool initialized;
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(!initialized);
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
if (hids_service_init()) {
|
||||||
|
LOG_ERR("Cannot initialize HIDS service");
|
||||||
|
module_set_state(MODULE_STATE_ERROR);
|
||||||
|
} else {
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_ble_peer_event(aeh)) {
|
||||||
|
handle_ble_peer_event(cast_ble_peer_event(aeh));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_mode_event(aeh)) {
|
||||||
|
const struct mode_event *event = cast_mode_event(aeh);
|
||||||
|
ble_mode_selected = (event->mode_type == MODE_TYPE_BLE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_hid_report_event(aeh)) {
|
||||||
|
return handle_hid_report_event(cast_hid_report_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||||
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event);
|
||||||
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, hid_report_event);
|
||||||
368
src/modules/keyboard_module.c
Normal file
368
src/modules/keyboard_module.c
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
|
||||||
|
#define MODULE keyboard
|
||||||
|
#include <caf/events/module_state_event.h>
|
||||||
|
|
||||||
|
#include <caf/events/button_event.h>
|
||||||
|
#include <caf/key_id.h>
|
||||||
|
|
||||||
|
#include "hid_report_descriptor.h"
|
||||||
|
#include "hid_protocol_event.h"
|
||||||
|
#include "hid_report_event.h"
|
||||||
|
#include "mode_event.h"
|
||||||
|
|
||||||
|
#include <zephyr/sys/util.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 参考 nrf_desktop 的表驱动设计:
|
||||||
|
* - key_id -> (usage_id, report_id) 映射定义在外部 hid_keymap_def.h;
|
||||||
|
* - keyboard_module 内部完成映射表校验与查询,不再依赖独立 hid_keymap 模块。
|
||||||
|
*/
|
||||||
|
struct hid_keymap {
|
||||||
|
uint16_t key_id;
|
||||||
|
uint16_t usage_id;
|
||||||
|
uint8_t report_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include APP_HID_KEYMAP_DEF_PATH
|
||||||
|
|
||||||
|
static bool hid_keymap_initialized;
|
||||||
|
|
||||||
|
/* 比较函数:供 bsearch 按 key_id 升序查找映射项。 */
|
||||||
|
static int hid_keymap_compare(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
const struct hid_keymap *pa = a;
|
||||||
|
const struct hid_keymap *pb = b;
|
||||||
|
|
||||||
|
return ((int)pa->key_id - (int)pb->key_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 初始化并校验 hid_keymap:
|
||||||
|
* - 仅在 CONFIG_ASSERT 打开时执行校验,避免 release 构建引入额外开销;
|
||||||
|
* - 校验 key_id 严格升序,确保二分查找行为正确;
|
||||||
|
* - 校验 report_id 只落在当前模块支持的 Keyboard/Consumer 两类。
|
||||||
|
*/
|
||||||
|
static void hid_keymap_init_local(void)
|
||||||
|
{
|
||||||
|
if (!IS_ENABLED(CONFIG_ASSERT) || hid_keymap_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(hid_keymap); i++) {
|
||||||
|
if (i > 0U) {
|
||||||
|
__ASSERT(hid_keymap[i - 1].key_id < hid_keymap[i].key_id,
|
||||||
|
"hid_keymap must be sorted by key_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT((hid_keymap[i].report_id == REPORT_ID_KEYBOARD) ||
|
||||||
|
(hid_keymap[i].report_id == REPORT_ID_CONSUMER),
|
||||||
|
"hid_keymap uses unsupported report_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
hid_keymap_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 查询指定 key_id 的 HID 映射,查不到返回 NULL。 */
|
||||||
|
static const struct hid_keymap *hid_keymap_get_local(uint16_t key_id)
|
||||||
|
{
|
||||||
|
if (ARRAY_SIZE(hid_keymap) == 0U) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct hid_keymap key = {
|
||||||
|
.key_id = key_id,
|
||||||
|
.usage_id = 0U,
|
||||||
|
.report_id = 0U,
|
||||||
|
};
|
||||||
|
|
||||||
|
return bsearch(&key,
|
||||||
|
hid_keymap,
|
||||||
|
ARRAY_SIZE(hid_keymap),
|
||||||
|
sizeof(hid_keymap[0]),
|
||||||
|
hid_keymap_compare);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Report 协议键盘 payload: modifier(1) + usage bitset(0..0xE7 => 29B)。 */
|
||||||
|
#define KEYBOARD_USAGE_MAX 0x00E7
|
||||||
|
#define KEYBOARD_BITMAP_SIZE DIV_ROUND_UP(KEYBOARD_USAGE_MAX + 1, 8)
|
||||||
|
#define KEYBOARD_REPORT_PAYLOAD (1 + KEYBOARD_BITMAP_SIZE)
|
||||||
|
|
||||||
|
/* Boot 协议键盘 payload: modifier(1) + reserved(1) + 6 keys。 */
|
||||||
|
#define BOOT_KEYBOARD_PAYLOAD 8
|
||||||
|
|
||||||
|
/* Consumer payload 固定 16-bit usage。 */
|
||||||
|
#define CONSUMER_PAYLOAD 2
|
||||||
|
|
||||||
|
struct keyboard_state {
|
||||||
|
uint8_t modifier_bm;
|
||||||
|
uint8_t usage_bm[KEYBOARD_BITMAP_SIZE];
|
||||||
|
enum hid_protocol_type ble_protocol;
|
||||||
|
enum hid_protocol_type usb_protocol;
|
||||||
|
mode_type_t current_mode;
|
||||||
|
uint16_t consumer_usage;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct keyboard_state ks = {
|
||||||
|
.ble_protocol = HID_PROTO_REPORT,
|
||||||
|
.usb_protocol = HID_PROTO_REPORT,
|
||||||
|
.current_mode = MODE_TYPE_COUNT,
|
||||||
|
.consumer_usage = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* 依据当前 mode 选择生效的 HID 协议来源(BLE 或 USB)。 */
|
||||||
|
static enum hid_protocol_type active_protocol_get(void)
|
||||||
|
{
|
||||||
|
return (ks.current_mode == MODE_TYPE_USB) ? ks.usb_protocol : ks.ble_protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 查询某 usage 位在当前键盘位图里是否处于按下状态。 */
|
||||||
|
static bool usage_pressed(uint16_t usage)
|
||||||
|
{
|
||||||
|
if (usage > KEYBOARD_USAGE_MAX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ks.usage_bm[usage / 8] & BIT(usage % 8)) != 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 更新键盘 usage 位图与 modifier 状态。
|
||||||
|
* 返回 true 表示状态有变化,需要向传输层同步新报告。
|
||||||
|
*/
|
||||||
|
static bool keyboard_usage_update(uint16_t usage_id, bool pressed)
|
||||||
|
{
|
||||||
|
if (usage_id > KEYBOARD_USAGE_MAX) {
|
||||||
|
LOG_WRN("Unsupported usage_id=0x%04x", usage_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t idx = usage_id / 8;
|
||||||
|
uint8_t mask = BIT(usage_id % 8);
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
if (pressed) {
|
||||||
|
if ((ks.usage_bm[idx] & mask) == 0U) {
|
||||||
|
ks.usage_bm[idx] |= mask;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((ks.usage_bm[idx] & mask) != 0U) {
|
||||||
|
ks.usage_bm[idx] &= (uint8_t)~mask;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* modifier(E0~E7) 额外维护一份 bitmask,便于 Boot/Report 复用。 */
|
||||||
|
if ((usage_id >= 0x00E0) && (usage_id <= 0x00E7)) {
|
||||||
|
uint8_t mod_mask = BIT(usage_id - 0x00E0);
|
||||||
|
if (pressed) {
|
||||||
|
ks.modifier_bm |= mod_mask;
|
||||||
|
} else {
|
||||||
|
ks.modifier_bm &= (uint8_t)~mod_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 提交 HID 报告事件:
|
||||||
|
* - Report 协议编码为 [report_id | payload]
|
||||||
|
* - Boot 协议编码为 [payload](不含 report_id)
|
||||||
|
*/
|
||||||
|
static void submit_hid_report(enum hid_protocol_type protocol,
|
||||||
|
uint8_t report_id,
|
||||||
|
const uint8_t *payload,
|
||||||
|
size_t payload_len)
|
||||||
|
{
|
||||||
|
size_t report_len = (protocol == HID_PROTO_REPORT) ? (payload_len + 1U) : payload_len;
|
||||||
|
struct hid_report_event *event = new_hid_report_event(report_len);
|
||||||
|
|
||||||
|
event->protocol = protocol;
|
||||||
|
if (protocol == HID_PROTO_REPORT) {
|
||||||
|
event->dyndata.data[0] = report_id;
|
||||||
|
memcpy(&event->dyndata.data[1], payload, payload_len);
|
||||||
|
} else {
|
||||||
|
memcpy(event->dyndata.data, payload, payload_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 组包并提交键盘报告:
|
||||||
|
* - Report 协议发送 NKRO payload;
|
||||||
|
* - Boot 协议降级为 6KRO 固定 8 字节格式。
|
||||||
|
*/
|
||||||
|
static void submit_keyboard_report_payload(enum hid_protocol_type protocol)
|
||||||
|
{
|
||||||
|
if (protocol == HID_PROTO_REPORT) {
|
||||||
|
uint8_t payload[KEYBOARD_REPORT_PAYLOAD];
|
||||||
|
|
||||||
|
payload[0] = ks.modifier_bm;
|
||||||
|
memcpy(&payload[1], ks.usage_bm, sizeof(ks.usage_bm));
|
||||||
|
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_KEYBOARD,
|
||||||
|
payload, sizeof(payload));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Boot 协议只支持 6KRO。
|
||||||
|
* 从 usage 位图中按升序提取最多 6 个普通键,modifier 走独立字节。
|
||||||
|
*/
|
||||||
|
uint8_t payload[BOOT_KEYBOARD_PAYLOAD] = { 0 };
|
||||||
|
size_t key_pos = 2;
|
||||||
|
|
||||||
|
payload[0] = ks.modifier_bm;
|
||||||
|
|
||||||
|
for (uint16_t usage = 0x04; usage <= 0x65; usage++) {
|
||||||
|
if (!usage_pressed(usage)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload[key_pos++] = (uint8_t)usage;
|
||||||
|
if (key_pos >= ARRAY_SIZE(payload)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submit_hid_report(HID_PROTO_BOOT, REPORT_ID_KEYBOARD, payload, sizeof(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 组包并提交 consumer 报告(16-bit usage)。 */
|
||||||
|
static void submit_consumer_report_payload(void)
|
||||||
|
{
|
||||||
|
uint8_t payload[CONSUMER_PAYLOAD];
|
||||||
|
|
||||||
|
payload[0] = ks.consumer_usage & 0xFF;
|
||||||
|
payload[1] = (ks.consumer_usage >> 8) & 0xFF;
|
||||||
|
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_CONSUMER, payload, sizeof(payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 处理键盘类 usage:
|
||||||
|
* - 仅在按键状态实际变化时提交报告,避免无效重复上报。
|
||||||
|
*/
|
||||||
|
static bool handle_keyboard_usage_event(const struct hid_keymap *map, bool pressed)
|
||||||
|
{
|
||||||
|
if (!keyboard_usage_update(map->usage_id, pressed))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
submit_keyboard_report_payload(active_protocol_get());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 处理 consumer 类 usage:
|
||||||
|
* - Boot 协议不发送 consumer 报告;
|
||||||
|
* - 按下时上报 usage,抬起时上报 0 清状态。
|
||||||
|
*/
|
||||||
|
static bool handle_consumer_usage_event(const struct hid_keymap *map, bool pressed)
|
||||||
|
{
|
||||||
|
if (active_protocol_get() == HID_PROTO_BOOT)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (pressed) {
|
||||||
|
if (ks.consumer_usage == map->usage_id)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ks.consumer_usage = map->usage_id;
|
||||||
|
submit_consumer_report_payload();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ks.consumer_usage != map->usage_id)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ks.consumer_usage = 0U;
|
||||||
|
submit_consumer_report_payload();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 处理 button_event:
|
||||||
|
* - 先查 key_id 映射;
|
||||||
|
* - 再按 report_id 分派到键盘/consumer 分支。
|
||||||
|
*/
|
||||||
|
static bool handle_button_event(const struct button_event *event)
|
||||||
|
{
|
||||||
|
const struct hid_keymap *map = hid_keymap_get_local(event->key_id);
|
||||||
|
if (!map) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map->report_id == REPORT_ID_KEYBOARD)
|
||||||
|
return handle_keyboard_usage_event(map, event->pressed);
|
||||||
|
|
||||||
|
if (map->report_id == REPORT_ID_CONSUMER)
|
||||||
|
return handle_consumer_usage_event(map, event->pressed);
|
||||||
|
|
||||||
|
LOG_WRN("Unsupported report_id=%u key_id=0x%04x", map->report_id, event->key_id);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 同步 BLE/USB 传输层上报的当前协议。 */
|
||||||
|
static bool handle_hid_protocol_event(const struct hid_protocol_event *event)
|
||||||
|
{
|
||||||
|
if (event->transport == HID_TRANSPORT_BLE) {
|
||||||
|
ks.ble_protocol = event->protocol;
|
||||||
|
} else if (event->transport == HID_TRANSPORT_USB) {
|
||||||
|
ks.usb_protocol = event->protocol;
|
||||||
|
} else {
|
||||||
|
__ASSERT_NO_MSG(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 更新当前激活模式,决定协议来源取 BLE 还是 USB。 */
|
||||||
|
static bool handle_mode_event(const struct mode_event *event)
|
||||||
|
{
|
||||||
|
ks.current_mode = event->mode_type;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模块总事件分发入口。 */
|
||||||
|
static bool app_event_handler(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
if (is_button_event(aeh)) {
|
||||||
|
return handle_button_event(cast_button_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_hid_protocol_event(aeh)) {
|
||||||
|
return handle_hid_protocol_event(cast_hid_protocol_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_mode_event(aeh)) {
|
||||||
|
return handle_mode_event(cast_mode_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_module_state_event(aeh)) {
|
||||||
|
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||||
|
|
||||||
|
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
||||||
|
/* 主模块 ready 后做一次 keymap 结构校验。 */
|
||||||
|
hid_keymap_init_local();
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, button_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, hid_protocol_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
240
src/modules/mode_switch_module.c
Normal file
240
src/modules/mode_switch_module.c
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/drivers/adc.h>
|
||||||
|
#include <zephyr/drivers/gpio.h>
|
||||||
|
#include <zephyr/sys/atomic.h>
|
||||||
|
#include <zephyr/sys/util.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <caf/events/power_event.h>
|
||||||
|
#include <caf/events/keep_alive_event.h>
|
||||||
|
|
||||||
|
#define MODULE mode_switch
|
||||||
|
#include <caf/events/module_state_event.h>
|
||||||
|
|
||||||
|
#include "mode_event.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(MODULE);
|
||||||
|
|
||||||
|
#define MODE_USER_NODE DT_PATH(zephyr_user)
|
||||||
|
#define MODE_ADC_IO_CH_IDX 0
|
||||||
|
#define MODE_SAMPLE_INTERVAL_MS 50
|
||||||
|
|
||||||
|
static const struct adc_dt_spec mode_adc =
|
||||||
|
ADC_DT_SPEC_GET_BY_IDX(MODE_USER_NODE, MODE_ADC_IO_CH_IDX);
|
||||||
|
|
||||||
|
static struct k_work_delayable mode_sample_work;
|
||||||
|
static int16_t adc_sample_buffer;
|
||||||
|
|
||||||
|
static atomic_t active;
|
||||||
|
static mode_type_t current_mode;
|
||||||
|
static uint8_t mode_stable_status;
|
||||||
|
|
||||||
|
static int init_adc(void)
|
||||||
|
{
|
||||||
|
if (!adc_is_ready_dt(&mode_adc))
|
||||||
|
{
|
||||||
|
LOG_ERR("ADC device not ready");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = adc_channel_setup_dt(&mode_adc);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
LOG_ERR("ADC channel setup failed (err=%d)", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static mode_type_t classify_mode_from_mv(int32_t mv)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 使用“距离最近中心点”的分类方式,避免阈值边界附近抖动时出现模式跳变。
|
||||||
|
* 三个中心点直接对应硬件设计目标电压:USB=0mV、2.4G=1650mV、BLE=3300mV。
|
||||||
|
*/
|
||||||
|
static const int32_t centers[MODE_TYPE_COUNT] = {
|
||||||
|
[MODE_TYPE_USB] = 0,
|
||||||
|
[MODE_TYPE_BLE] = 3300,
|
||||||
|
[MODE_TYPE_2G4] = 1650,
|
||||||
|
};
|
||||||
|
|
||||||
|
int32_t best_diff = INT32_MAX;
|
||||||
|
mode_type_t best_mode = MODE_TYPE_USB;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < MODE_TYPE_COUNT; i++)
|
||||||
|
{
|
||||||
|
int32_t diff = abs(mv - centers[i]);
|
||||||
|
|
||||||
|
if (diff < best_diff)
|
||||||
|
{
|
||||||
|
best_diff = diff;
|
||||||
|
best_mode = (mode_type_t)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_mode(mode_type_t *mode)
|
||||||
|
{
|
||||||
|
struct adc_sequence sequence = {0};
|
||||||
|
int err = adc_sequence_init_dt(&mode_adc, &sequence);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence.buffer = &adc_sample_buffer;
|
||||||
|
sequence.buffer_size = sizeof(adc_sample_buffer);
|
||||||
|
|
||||||
|
err = adc_read_dt(&mode_adc, &sequence);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t v = adc_sample_buffer;
|
||||||
|
err = adc_raw_to_millivolts_dt(&mode_adc, &v);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
*mode = classify_mode_from_mv(v);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void publish_mode_event(mode_type_t mode)
|
||||||
|
{
|
||||||
|
if (current_mode == mode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
current_mode = mode;
|
||||||
|
struct mode_event *event = new_mode_event();
|
||||||
|
|
||||||
|
event->mode_type = mode;
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
/*
|
||||||
|
* 模式切换是明确的人机交互动作。这里同步上报 keep_alive_event,
|
||||||
|
* 让 power manager 重置休眠倒计时,避免用户刚切换模式就进入省电流程。
|
||||||
|
*/
|
||||||
|
keep_alive();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mode_sample_fn(struct k_work *work)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(work);
|
||||||
|
|
||||||
|
if (!atomic_get(&active))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mode_type_t sampled_mode;
|
||||||
|
int err = read_mode(&sampled_mode);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
LOG_ERR("ADC read failed (err=%d)", err);
|
||||||
|
module_set_state(MODULE_STATE_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mode_stable_status = mode_stable_status << 2;
|
||||||
|
mode_stable_status |= (sampled_mode & 0x3);
|
||||||
|
|
||||||
|
switch (mode_stable_status)
|
||||||
|
{
|
||||||
|
case 0b00000000:
|
||||||
|
publish_mode_event(MODE_TYPE_USB);
|
||||||
|
break;
|
||||||
|
case 0b01010101:
|
||||||
|
publish_mode_event(MODE_TYPE_BLE);
|
||||||
|
break;
|
||||||
|
case 0b10101010:
|
||||||
|
publish_mode_event(MODE_TYPE_2G4);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_work_reschedule(&mode_sample_work, K_MSEC(MODE_SAMPLE_INTERVAL_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mode_switch_suspend(void)
|
||||||
|
{
|
||||||
|
if (!atomic_get(&active))
|
||||||
|
return;
|
||||||
|
|
||||||
|
atomic_set(&active, false);
|
||||||
|
(void)k_work_cancel_delayable(&mode_sample_work);
|
||||||
|
module_set_state(MODULE_STATE_STANDBY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mode_switch_resume(void)
|
||||||
|
{
|
||||||
|
if (atomic_get(&active))
|
||||||
|
return;
|
||||||
|
|
||||||
|
atomic_set(&active, true);
|
||||||
|
k_work_reschedule(&mode_sample_work, K_NO_WAIT);
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_mode_switch(void)
|
||||||
|
{
|
||||||
|
if (atomic_get(&active))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (init_adc())
|
||||||
|
{
|
||||||
|
module_set_state(MODULE_STATE_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mode_stable_status = 0xFF;
|
||||||
|
current_mode = MODE_TYPE_COUNT;
|
||||||
|
atomic_set(&active, false);
|
||||||
|
k_work_init_delayable(&mode_sample_work, mode_sample_fn);
|
||||||
|
|
||||||
|
mode_switch_resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool app_event_handler(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
if (is_module_state_event(aeh))
|
||||||
|
{
|
||||||
|
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||||
|
|
||||||
|
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY))
|
||||||
|
{
|
||||||
|
init_mode_switch();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_power_down_event(aeh))
|
||||||
|
{
|
||||||
|
mode_switch_suspend();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_wake_up_event(aeh))
|
||||||
|
{
|
||||||
|
mode_switch_resume();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
||||||
656
src/modules/usb_hid_module.c
Normal file
656
src/modules/usb_hid_module.c
Normal file
@@ -0,0 +1,656 @@
|
|||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/usb/class/usbd_hid.h>
|
||||||
|
#include <zephyr/usb/usbd.h>
|
||||||
|
|
||||||
|
#include <app_event_manager.h>
|
||||||
|
#include <caf/events/power_event.h>
|
||||||
|
|
||||||
|
#define MODULE usb_hid
|
||||||
|
#include <caf/events/module_state_event.h>
|
||||||
|
|
||||||
|
#include "hid_report_descriptor.h"
|
||||||
|
#include "hid_protocol_event.h"
|
||||||
|
#include "hid_report_event.h"
|
||||||
|
#include "mode_event.h"
|
||||||
|
#include "usb_hid_event.h"
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||||
|
|
||||||
|
#define APP_USB_VID 0x1209
|
||||||
|
#define APP_USB_PID 0x0001
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 模块目标:
|
||||||
|
* 1) 模块内聚控制 USB HID 栈生命周期(初始化/启用/禁用),不依赖外部 usb_hid_event 控制。
|
||||||
|
* 2) 对外统一上报 usb_hid_event,供 LED/上层状态机消费。
|
||||||
|
* 3) 仅响应 mode_event(USB/BLE/2.4G)和 power_event(休眠/唤醒)。
|
||||||
|
*
|
||||||
|
* 约束:
|
||||||
|
* - 启动时只做 USB 设备初始化,不自动 enable USB 栈;
|
||||||
|
* - 只有当 mode 切到 USB 且系统非休眠时才 enable;
|
||||||
|
* - BLE 逻辑保持不变,不在本模块中触碰。
|
||||||
|
*/
|
||||||
|
struct usb_hid_ctx {
|
||||||
|
const struct device *boot_dev;
|
||||||
|
const struct device *nkro_dev;
|
||||||
|
const struct device *raw_dev;
|
||||||
|
|
||||||
|
bool stack_initialized;
|
||||||
|
bool stack_enabled;
|
||||||
|
bool stack_error;
|
||||||
|
|
||||||
|
bool usb_mode_selected;
|
||||||
|
bool pm_suspended;
|
||||||
|
|
||||||
|
bool boot_iface_ready;
|
||||||
|
bool nkro_iface_ready;
|
||||||
|
bool raw_iface_ready;
|
||||||
|
bool boot_in_flight;
|
||||||
|
bool nkro_in_flight;
|
||||||
|
|
||||||
|
enum usb_hid_usbd_state usbd_state;
|
||||||
|
enum hid_protocol_type current_protocol;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct usb_hid_ctx g_usb_hid = {
|
||||||
|
.usbd_state = USB_HID_USBD_DISCONNECTED,
|
||||||
|
.current_protocol = HID_PROTO_REPORT,
|
||||||
|
};
|
||||||
|
|
||||||
|
USBD_DEVICE_DEFINE(new_kbd_usbd,
|
||||||
|
DEVICE_DT_GET(DT_NODELABEL(usbd)),
|
||||||
|
APP_USB_VID, APP_USB_PID);
|
||||||
|
USBD_DESC_LANG_DEFINE(new_kbd_lang);
|
||||||
|
USBD_DESC_MANUFACTURER_DEFINE(new_kbd_mfr, "new_kbd");
|
||||||
|
USBD_DESC_PRODUCT_DEFINE(new_kbd_product, "new_kbd composite HID");
|
||||||
|
USBD_DESC_CONFIG_DEFINE(new_kbd_fs_cfg_desc, "FS Configuration");
|
||||||
|
USBD_CONFIGURATION_DEFINE(new_kbd_fs_config, 0, 100, &new_kbd_fs_cfg_desc);
|
||||||
|
|
||||||
|
static const uint8_t boot_report_desc[] = HID_KEYBOARD_REPORT_DESC();
|
||||||
|
static const uint8_t nkro_report_desc[] = HID_DESC_KEYBOARD_NKRO_CONSUMER();
|
||||||
|
static const uint8_t raw_report_desc[] = HID_DESC_RAW_64();
|
||||||
|
|
||||||
|
static void publish_usb_hid_state(void)
|
||||||
|
{
|
||||||
|
struct usb_hid_event *event = new_usb_hid_event();
|
||||||
|
|
||||||
|
event->evt_type = USB_HID_EVT_STATE_REPORT;
|
||||||
|
event->enable = g_usb_hid.stack_enabled;
|
||||||
|
event->usbd_state = g_usb_hid.usbd_state;
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recompute_hid_state(void)
|
||||||
|
{
|
||||||
|
/* 兼容现有调用点:对外仅发布 enable + usbd 状态。 */
|
||||||
|
publish_usb_hid_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_usbd_state(enum usb_hid_usbd_state state)
|
||||||
|
{
|
||||||
|
if (g_usb_hid.usbd_state != state) {
|
||||||
|
g_usb_hid.usbd_state = state;
|
||||||
|
publish_usb_hid_state();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hid_stub_get_report(const struct device *dev,
|
||||||
|
uint8_t type, uint8_t id,
|
||||||
|
uint16_t len, uint8_t *buf)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(dev);
|
||||||
|
ARG_UNUSED(type);
|
||||||
|
ARG_UNUSED(id);
|
||||||
|
ARG_UNUSED(len);
|
||||||
|
ARG_UNUSED(buf);
|
||||||
|
return -ENOTSUP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hid_stub_set_report(const struct device *dev,
|
||||||
|
uint8_t type, uint8_t id,
|
||||||
|
uint16_t len, const uint8_t *buf)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(dev);
|
||||||
|
ARG_UNUSED(type);
|
||||||
|
ARG_UNUSED(id);
|
||||||
|
ARG_UNUSED(len);
|
||||||
|
ARG_UNUSED(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hid_stub_set_idle(const struct device *dev, uint8_t id, uint32_t duration)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(dev);
|
||||||
|
ARG_UNUSED(id);
|
||||||
|
ARG_UNUSED(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t hid_stub_get_idle(const struct device *dev, uint8_t id)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(dev);
|
||||||
|
ARG_UNUSED(id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hid_stub_set_protocol(const struct device *dev, uint8_t proto)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(dev);
|
||||||
|
|
||||||
|
enum hid_protocol_type new_protocol =
|
||||||
|
(proto == HID_PROTOCOL_BOOT) ? HID_PROTO_BOOT : HID_PROTO_REPORT;
|
||||||
|
|
||||||
|
if (g_usb_hid.current_protocol == new_protocol) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_usb_hid.current_protocol = new_protocol;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 按需求:USB HID 在连接后收到 set_protocol 时上报 hid_protocol_event。
|
||||||
|
* 这里额外检查接口 ready,避免在未枚举完成阶段上报无意义协议切换。
|
||||||
|
*/
|
||||||
|
if (g_usb_hid.boot_iface_ready || g_usb_hid.nkro_iface_ready) {
|
||||||
|
struct hid_protocol_event *event = new_hid_protocol_event();
|
||||||
|
|
||||||
|
event->transport = HID_TRANSPORT_USB;
|
||||||
|
event->protocol = new_protocol;
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hid_stub_input_done(const struct device *dev, const uint8_t *report)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(report);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 发送完成回调:
|
||||||
|
* - 仅在这里清除“在途发送”标志,确保“上一包未完成则丢弃新包”的策略可闭环;
|
||||||
|
* - 若收到未知 dev 的回调,仅记录告警,避免静默状态错乱。
|
||||||
|
*/
|
||||||
|
if (dev == g_usb_hid.boot_dev) {
|
||||||
|
g_usb_hid.boot_in_flight = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev == g_usb_hid.nkro_dev) {
|
||||||
|
g_usb_hid.nkro_in_flight = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_WRN("input_done from unknown HID dev: %p", (void *)dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hid_stub_output_report(const struct device *dev, uint16_t len, const uint8_t *buf)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(dev);
|
||||||
|
ARG_UNUSED(len);
|
||||||
|
ARG_UNUSED(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hid_iface_ready_cb(const struct device *dev, bool ready)
|
||||||
|
{
|
||||||
|
if (dev == g_usb_hid.boot_dev) {
|
||||||
|
g_usb_hid.boot_iface_ready = ready;
|
||||||
|
if (!ready) {
|
||||||
|
g_usb_hid.boot_in_flight = false;
|
||||||
|
}
|
||||||
|
} else if (dev == g_usb_hid.nkro_dev) {
|
||||||
|
g_usb_hid.nkro_iface_ready = ready;
|
||||||
|
if (!ready) {
|
||||||
|
g_usb_hid.nkro_in_flight = false;
|
||||||
|
}
|
||||||
|
} else if (dev == g_usb_hid.raw_dev) {
|
||||||
|
g_usb_hid.raw_iface_ready = ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ready) {
|
||||||
|
set_usbd_state(USB_HID_USBD_CONNECTED);
|
||||||
|
/* 连接可用后同步一次当前协议,让 keyboard_module 与传输侧编码一致。 */
|
||||||
|
struct hid_protocol_event *event = new_hid_protocol_event();
|
||||||
|
|
||||||
|
event->transport = HID_TRANSPORT_USB;
|
||||||
|
event->protocol = g_usb_hid.current_protocol;
|
||||||
|
APP_EVENT_SUBMIT(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
recompute_hid_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct hid_device_ops boot_hid_ops = {
|
||||||
|
.iface_ready = hid_iface_ready_cb,
|
||||||
|
.get_report = hid_stub_get_report,
|
||||||
|
.set_report = hid_stub_set_report,
|
||||||
|
.set_idle = hid_stub_set_idle,
|
||||||
|
.get_idle = hid_stub_get_idle,
|
||||||
|
.set_protocol = hid_stub_set_protocol,
|
||||||
|
.input_report_done = hid_stub_input_done,
|
||||||
|
.output_report = hid_stub_output_report,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hid_device_ops report_hid_ops = {
|
||||||
|
.iface_ready = hid_iface_ready_cb,
|
||||||
|
.get_report = hid_stub_get_report,
|
||||||
|
.set_report = hid_stub_set_report,
|
||||||
|
.set_idle = hid_stub_set_idle,
|
||||||
|
.get_idle = hid_stub_get_idle,
|
||||||
|
.set_protocol = hid_stub_set_protocol,
|
||||||
|
.input_report_done = hid_stub_input_done,
|
||||||
|
.output_report = hid_stub_output_report,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct hid_device_ops raw_hid_ops = {
|
||||||
|
.iface_ready = hid_iface_ready_cb,
|
||||||
|
.get_report = hid_stub_get_report,
|
||||||
|
.set_report = hid_stub_set_report,
|
||||||
|
.set_idle = hid_stub_set_idle,
|
||||||
|
.get_idle = hid_stub_get_idle,
|
||||||
|
.input_report_done = hid_stub_input_done,
|
||||||
|
.output_report = hid_stub_output_report,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void usbd_msg_cb(struct usbd_context *const usbd_ctx,
|
||||||
|
const struct usbd_msg *const msg)
|
||||||
|
{
|
||||||
|
switch (msg->type) {
|
||||||
|
case USBD_MSG_VBUS_READY:
|
||||||
|
set_usbd_state(USB_HID_USBD_CONNECTED);
|
||||||
|
/*
|
||||||
|
* 只有在 USB 模式下才允许拉起 USB 栈。
|
||||||
|
* 这样即使插着线,只要用户切到 BLE/2.4G,也不会强制进入 USB HID。
|
||||||
|
*/
|
||||||
|
if (usbd_can_detect_vbus(usbd_ctx) && g_usb_hid.stack_enabled) {
|
||||||
|
(void)usbd_enable(usbd_ctx);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case USBD_MSG_VBUS_REMOVED:
|
||||||
|
set_usbd_state(USB_HID_USBD_DISCONNECTED);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case USBD_MSG_SUSPEND:
|
||||||
|
set_usbd_state(USB_HID_USBD_SUSPENDED);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case USBD_MSG_RESUME:
|
||||||
|
set_usbd_state(USB_HID_USBD_CONNECTED);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case USBD_MSG_CONFIGURATION:
|
||||||
|
set_usbd_state(USB_HID_USBD_CONNECTED);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case USBD_MSG_UDC_ERROR:
|
||||||
|
case USBD_MSG_STACK_ERROR:
|
||||||
|
LOG_ERR("USBD stack error message: %d", msg->type);
|
||||||
|
g_usb_hid.stack_error = true;
|
||||||
|
recompute_hid_state();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool usb_hid_devices_ready(void)
|
||||||
|
{
|
||||||
|
if (!device_is_ready(g_usb_hid.boot_dev)) {
|
||||||
|
LOG_ERR("HID boot device is not ready");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_is_ready(g_usb_hid.nkro_dev)) {
|
||||||
|
LOG_ERR("HID nkro device is not ready");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_is_ready(g_usb_hid.raw_dev)) {
|
||||||
|
LOG_ERR("HID raw device is not ready");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_is_ready(DEVICE_DT_GET(DT_NODELABEL(usbd)))) {
|
||||||
|
LOG_ERR("USBD device is not ready");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb_hid_register_hid_devices(void)
|
||||||
|
{
|
||||||
|
int err = hid_device_register(g_usb_hid.boot_dev,
|
||||||
|
boot_report_desc, sizeof(boot_report_desc),
|
||||||
|
&boot_hid_ops);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("hid_device_register(boot) failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = hid_device_register(g_usb_hid.nkro_dev,
|
||||||
|
nkro_report_desc, sizeof(nkro_report_desc),
|
||||||
|
&report_hid_ops);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("hid_device_register(nkro) failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = hid_device_register(g_usb_hid.raw_dev,
|
||||||
|
raw_report_desc, sizeof(raw_report_desc),
|
||||||
|
&raw_hid_ops);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("hid_device_register(raw) failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb_hid_configure_usbd(void)
|
||||||
|
{
|
||||||
|
int err = usbd_add_descriptor(&new_kbd_usbd, &new_kbd_lang);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("usbd_add_descriptor(lang) failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usbd_add_descriptor(&new_kbd_usbd, &new_kbd_mfr);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("usbd_add_descriptor(mfr) failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usbd_add_descriptor(&new_kbd_usbd, &new_kbd_product);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("usbd_add_descriptor(product) failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usbd_add_configuration(&new_kbd_usbd, USBD_SPEED_FS, &new_kbd_fs_config);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("usbd_add_configuration failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usbd_register_all_classes(&new_kbd_usbd, USBD_SPEED_FS, 1, NULL);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("usbd_register_all_classes failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb_hid_init_usbd_stack(void)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
usbd_device_set_code_triple(&new_kbd_usbd, USBD_SPEED_FS, 0, 0, 0);
|
||||||
|
|
||||||
|
err = usbd_msg_register_cb(&new_kbd_usbd, usbd_msg_cb);
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("usbd_msg_register_cb failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usbd_init(&new_kbd_usbd);
|
||||||
|
if (err && (err != -EALREADY)) {
|
||||||
|
LOG_ERR("usbd_init failed: %d", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb_hid_stack_init(void)
|
||||||
|
{
|
||||||
|
if (g_usb_hid.stack_initialized) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_usb_hid.boot_dev = DEVICE_DT_GET(DT_NODELABEL(hid_dev_0));
|
||||||
|
g_usb_hid.nkro_dev = DEVICE_DT_GET(DT_NODELABEL(hid_dev_1));
|
||||||
|
g_usb_hid.raw_dev = DEVICE_DT_GET(DT_NODELABEL(raw_hid));
|
||||||
|
|
||||||
|
if (!usb_hid_devices_ready()) {
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = usb_hid_register_hid_devices();
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usb_hid_configure_usbd();
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = usb_hid_init_usbd_stack();
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_usb_hid.stack_initialized = true;
|
||||||
|
recompute_hid_state();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usb_hid_set_enabled(bool enable)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!g_usb_hid.stack_initialized) {
|
||||||
|
err = usb_hid_stack_init();
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_usb_hid.stack_enabled == enable) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_usb_hid.stack_enabled = enable;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
err = usbd_enable(&new_kbd_usbd);
|
||||||
|
} else {
|
||||||
|
err = usbd_disable(&new_kbd_usbd);
|
||||||
|
g_usb_hid.boot_iface_ready = false;
|
||||||
|
g_usb_hid.nkro_iface_ready = false;
|
||||||
|
g_usb_hid.raw_iface_ready = false;
|
||||||
|
g_usb_hid.boot_in_flight = false;
|
||||||
|
g_usb_hid.nkro_in_flight = false;
|
||||||
|
set_usbd_state(USB_HID_USBD_DISCONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err && (err != -EALREADY)) {
|
||||||
|
LOG_ERR("usbd_%s failed: %d", enable ? "enable" : "disable", err);
|
||||||
|
g_usb_hid.stack_error = true;
|
||||||
|
recompute_hid_state();
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
recompute_hid_state();
|
||||||
|
publish_usb_hid_state();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void refresh_usb_state_by_policy(void)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* 控制策略:
|
||||||
|
* - USB 模式 + 非休眠:启用 USB HID。
|
||||||
|
* - 其他情况:关闭 USB HID(不销毁初始化结果,后续可快速恢复)。
|
||||||
|
*/
|
||||||
|
bool should_enable = g_usb_hid.usb_mode_selected && !g_usb_hid.pm_suspended;
|
||||||
|
int err = usb_hid_set_enabled(should_enable);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("usb_hid_set_enabled(%d) failed: %d", should_enable, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_module_state_event(const struct module_state_event *event)
|
||||||
|
{
|
||||||
|
if (!check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = usb_hid_stack_init();
|
||||||
|
if (err) {
|
||||||
|
LOG_ERR("USB HID stack init failed: %d", err);
|
||||||
|
g_usb_hid.stack_error = true;
|
||||||
|
module_set_state(MODULE_STATE_ERROR);
|
||||||
|
recompute_hid_state();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
publish_usb_hid_state();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_mode_event(const struct mode_event *event)
|
||||||
|
{
|
||||||
|
g_usb_hid.usb_mode_selected = (event->mode_type == MODE_TYPE_USB);
|
||||||
|
refresh_usb_state_by_policy();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_power_down_event(void)
|
||||||
|
{
|
||||||
|
if (g_usb_hid.pm_suspended) {
|
||||||
|
/* 避免重复上报 STANDBY 导致 power_manager 在 SUSPENDING 期间反复迭代。 */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_usb_hid.pm_suspended = true;
|
||||||
|
refresh_usb_state_by_policy();
|
||||||
|
module_set_state(MODULE_STATE_STANDBY);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_wake_up_event(void)
|
||||||
|
{
|
||||||
|
if (!g_usb_hid.pm_suspended) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_usb_hid.pm_suspended = false;
|
||||||
|
refresh_usb_state_by_policy();
|
||||||
|
module_set_state(MODULE_STATE_READY);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool handle_hid_report_event(const struct hid_report_event *event)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* USB 侧仅在 active 条件满足时发送:
|
||||||
|
* - 当前 mode 为 USB;
|
||||||
|
* - USB HID 栈已启用且对应接口 ready。
|
||||||
|
*/
|
||||||
|
if (!g_usb_hid.usb_mode_selected || !g_usb_hid.stack_enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t report_id;
|
||||||
|
|
||||||
|
if (event->protocol != g_usb_hid.current_protocol) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->protocol == HID_PROTO_BOOT) {
|
||||||
|
const uint8_t *payload = event->dyndata.data;
|
||||||
|
size_t payload_len = event->dyndata.size;
|
||||||
|
|
||||||
|
report_id = REPORT_ID_KEYBOARD;
|
||||||
|
|
||||||
|
if (!g_usb_hid.boot_iface_ready || !g_usb_hid.boot_dev) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (report_id != REPORT_ID_KEYBOARD) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (g_usb_hid.boot_in_flight) {
|
||||||
|
LOG_WRN("Drop boot report: previous report not sent");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = hid_device_submit_report(g_usb_hid.boot_dev,
|
||||||
|
payload_len,
|
||||||
|
payload);
|
||||||
|
if (err) {
|
||||||
|
LOG_WRN("USB boot report send failed err=%d", err);
|
||||||
|
} else {
|
||||||
|
g_usb_hid.boot_in_flight = true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->dyndata.size < 1U) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
report_id = event->dyndata.data[0];
|
||||||
|
|
||||||
|
if (!g_usb_hid.nkro_iface_ready || !g_usb_hid.nkro_dev) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((report_id != REPORT_ID_KEYBOARD) && (report_id != REPORT_ID_CONSUMER)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (g_usb_hid.nkro_in_flight) {
|
||||||
|
LOG_WRN("Drop report id=0x%02x: previous report not sent", report_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Report 协议下 dyndata 是 [report_id|payload],可直接透传。 */
|
||||||
|
int err = hid_device_submit_report(g_usb_hid.nkro_dev, event->dyndata.size, event->dyndata.data);
|
||||||
|
if (err) {
|
||||||
|
LOG_WRN("USB report send failed id=0x%02x err=%d", report_id, err);
|
||||||
|
} else {
|
||||||
|
g_usb_hid.nkro_in_flight = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool app_event_handler(const struct app_event_header *aeh)
|
||||||
|
{
|
||||||
|
if (is_module_state_event(aeh)) {
|
||||||
|
return handle_module_state_event(cast_module_state_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_mode_event(aeh)) {
|
||||||
|
return handle_mode_event(cast_mode_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_power_down_event(aeh)) {
|
||||||
|
return handle_power_down_event();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_wake_up_event(aeh)) {
|
||||||
|
return handle_wake_up_event();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_hid_report_event(aeh)) {
|
||||||
|
return handle_hid_report_event(cast_hid_report_event(aeh));
|
||||||
|
}
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
|
||||||
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
||||||
|
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|
||||||
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_report_event);
|
||||||
1
sysbuild.conf
Normal file
1
sysbuild.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
SB_CONFIG_BOOTLOADER_MCUBOOT=y
|
||||||
Reference in New Issue
Block a user