Compare commits

...

37 Commits

Author SHA1 Message Date
88ee4da089 docs: 添加Blinky键盘2.4G端详细设计文档
- 定义了键盘外设固件内实现2.4G端的设计方案
- 描述了基于专用local identity的dongle peer设计方案
- 详细说明了身份规划和模块职责分工
- 包含了行为模型、状态事件设计和UI设计
- 提供了分阶段实施建议和风险评估
2026-05-06 18:36:39 +08:00
adaad6cc34 feat(ble): 添加 Swift Pair 模块支持
- 在 CMakeLists.txt 中添加 swift_pair_module.c 源文件
- 将 BLINKY_BLE_BOND_MULTI 配置项重命名为 BLINKY_BLE_BOND_MULTI_INTERNAL 并移除帮助文本
- 在 prj.conf 中启用 CONFIG_BT_ADV_PROV_SWIFT_PAIR 配置选项
- 新增 swift_pair_module.c 实现 Swift Pair 功能,包括:
  - 监听蓝牙配对多设备事件和蓝牙对等操作事件
  - 根据选中的身份标识控制 Swift Pair 载荷的启用/禁用
  - 当设备被选中或擦除时更新 Swift Pair 状态
2026-04-25 18:09:53 +08:00
d27df7f3bf feat(display): 添加蓝牙连接状态显示功能
- 引入ble_common_event头文件支持蓝牙事件处理
- 在UI模型中添加蓝牙连接状态字段,包括隐藏、搜索中、已连接三种状态
- 实现蓝牙事件处理器,响应蓝牙对等设备搜索和连接事件
- 当切换到非BLE模式时自动隐藏蓝牙连接状态
- 在主界面UI中添加蓝牙连接状态显示组件(包装器、旋转动画、蓝牙图标)
- 根据蓝牙连接状态动态更新UI显示:搜索时显示旋转动画,连接时显示蓝色蓝牙图标,其他情况隐藏
- 订阅蓝牙对等设备相关事件以实时更新连接状态
2026-04-25 17:49:22 +08:00
07fe70becd feat(ble): 更新蓝牙配对多模块显示名称逻辑
- 移除DEFAULT_DISPLAY_NAME_BONDED宏定义
- 添加display_name_set_addr函数用于根据设备地址设置显示名称
- 修改display_name_set_default函数逻辑,当有已占用槽位且存在最后连接的对等设备地址时,
  使用该地址作为显示名称
- 在slot_update_from_bonds函数中添加逻辑,当显示名称为空或为默认空名称时,使用对等
  设备地址更新显示名称
- 在处理BLE对等事件时,使用对等设备地址直接设置显示名称

fix(ui): 调整UI设置项文本对齐和截断方式

- 将设置项值的文本对齐方式从右对齐改为左对齐
- 将标签长文本模式从裁剪(CLIP)改为滚动(SCROLL),以便更好地显示长设备名称
2026-04-25 15:57:42 +08:00
54c5f76c84 feat: 添加蓝牙多槽位绑定支持模块
- 新增 ble_bond_multi_module.c 实现多槽位蓝牙绑定管理功能
- 添加 ble_bond_multi_event 事件系统支持槽位状态广播
- 在 CMakeLists.txt 中注册新模块和事件源文件
- 更新 Kconfig 配置添加 BLINKY_BLE_BOND_MULTI 选项
- 修改 prj.conf 配置支持 4 个配对设备和 5 个身份标识
- 关闭默认 CAF ble_bond 模块使用自定义实现
- 更新 ui_settings_controller.h 接口支持槽位元数据设置
- 在 display_module.c 中添加事件订阅刷新UI显示
- 编写详细的设计文档 ble_multi_slot_design.md
2026-04-25 15:40:49 +08:00
3971d7c4b2 feat(proto): 添加设备通信协议v1修订版及统一帧格式
- 新增docs/device_communication_protocol_v1.md文档,定义V1修订版协议
- CDC和BLE GATT均改为直接传输业务消息,去掉外层协议封装
- BLE改为使用NUS(Nordic UART Service)替代原有GATT服务
- 统一键盘位图为29字节格式,FunctionKeyEvent改为上报全键盘位图
- 顶层消息增加msg_id和reply_to字段用于请求响应匹配
- Ack和Error合并为统一Response消息类型
- CDC和NUS均增加统一外层帧格式:magic(2) + len(1) + protobuf
- 添加Proto frame常量定义及长度验证逻辑
- 更新proto文件定义,包含DeviceMessage统一信封和ResponseCode枚举

- 重构hid_flowctrl_module.c中的上下文访问方式,统一使用ctx前缀
2026-04-24 10:54:14 +08:00
48968e7880 feat(ui): 重构设置界面为页面控制器架构
- 添加新的UI页面基础架构(ui_page.h),包含页面操作接口
- 创建设置页面控制器(ui_settings_controller.h/.c)来管理页面导航
- 实现具体的设置页面类型:根页面、BLE页面、主题页面
- 修改display_module.c以使用新的页面系统替代旧的状态机
- 移除过时的settings_ui.h头文件和相关状态结构
- 更新事件处理逻辑以使用页面指针而非状态数据传递
- 修改主界面实现以适配统一的页面接口标准
2026-04-23 18:46:55 +08:00
fbdc5426be feat: 添加设置模块和相关UI功能
- 新增settings_module.c实现设置菜单逻辑,包括蓝牙配对槽位管理和主题颜色选择
- 添加settings_mode_event.h/.c和settings_view_event.h/.c事件定义用于设置模式切换
- 创建settings_ui.h定义设置界面状态结构体和页面枚举
- 修改display_module.c集成设置UI显示逻辑,支持主界面和设置界面切换
- 在keyboard_core_module.c中添加设置活动状态检查,避免设置模式下键盘输入冲突
- 更新CMakeLists.txt包含新的源文件:settings_module.c、ui_settings.c及新事件文件
- 修改prj.conf调整LVGL内存池大小从16KB到32KB以支持更复杂UI渲染
- 移除BLE配对擦除相关配置选项并增加长按检测时间到1500毫秒
- 更新ui_main.c添加可见性控制函数用于界面切换
2026-04-23 15:12:29 +08:00
6a03df1b39 feat(events): 添加传输策略事件支持
添加了新的 transport_policy_event 事件类型,用于管理 HID
传输策略和蓝牙配置文件策略。该事件包含源模式、HID 传输策
略和蓝牙配置文件策略三个枚举值,并提供了相应的提交函数
和日志记录功能。

BREAKING CHANGE: 将原有的 mode_switch_event 替换为更具体的
transport_policy_event,相关模块需要更新以使用新的事件类
型。
2026-04-23 09:48:06 +08:00
a11f4c0110 feat(usb_cdc): 添加USB CDC ACM控制台和日志后端支持
添加zephyr,console和zephyr,log-uart设备树配置,启用UART控制台和日志功能,
同时增加第二个CDC ACM UART实例用于控制台输出。在prj.conf中启用相关配置项。

refactor(protocol): 优化协议会话状态管理并增强日志记录

引入proto_session_set函数统一管理协议会话状态转换,添加详细的日志输出来跟踪
状态变化、消息处理和传输事件,提高系统的可调试性。

feat(usb_device): 增强USB设备模块状态跟踪和日志输出

为USB栈、总线和公共状态添加命名函数,实现详细的状态转换日志记录,包括USB
连接、断开、使能等关键事件的日志输出,便于系统调试和问题排查。
2026-04-21 16:40:37 +08:00
0a1357c62d refactor(module_lifecycle): 重构模块生命周期管理实现
移除了LC_SUSPENDED状态并简化了生命周期状态转换逻辑,
将状态名称查询功能统一到module_lifecycle_name函数中,
删除了不再需要的状态检查和路径验证函数,
同时更新了相关的配置文件和模块调用以适配新的API。
2026-04-21 14:34:08 +08:00
e0817a7b44 feat(module_lifecycle): 完善模块生命周期管理功能
- 添加了模块生命周期状态转换验证函数,包括目标状态允许性检查和路径允许性检查
- 实现了模块操作验证功能,确保必要的回调函数存在
- 更新了状态报告逻辑,增加了模式检查以防止无效状态转换
- 修改了生命周期转换流程,区分运行启动和停止操作
- 优化了错误处理机制,返回适当的错误码

refactor(ble_modules): 简化BLE模块生命周期配置

- 将多个BLE模块(lifecycle_cfg)的模式从ML_MODE_POWER改为ML_MODE_NONE
- 移除了power_event相关依赖和事件订阅
- 更新了BLE BAS模块的状态转换逻辑
- 简化了BLE HID/NUS/Protocol模块的电源管理相关代码

fix(mode_switch): 修复唤醒后模式恢复功能

- 在唤醒事件处理中添加了最后模式的恢复逻辑
- 确保设备唤醒后能够重新提交之前的模式切换事件
2026-04-18 14:17:23 +08:00
caf8d5acc6 feat(mode_switch): 添加无效模式枚举值并更新事件处理
添加 MODE_SWITCH_INVALID 枚举值到 mode_switch_mode 中,
用于表示无效的模式状态。同时更新相关的事件处理逻辑,
确保在模式切换时能够正确处理无效模式的情况。

refactor(mode_policy): 简化模块上下文结构并优化模式策略

将模块上下文中的 ble_adv_suspended 和 usb_enabled 标志位
替换为 active_mode 枚举值,简化了数据结构。重新设计了
模式策略应用逻辑,使其更清晰易懂,并改进了BLE和USB设备
的启用/暂停控制流程。

refactor(usb_device): 重构USB设备生命周期管理

移除了 USB_STACK_DISABLED 状态,简化了USB栈的状态管理。
改进了USB设备的启动和停止逻辑,更好地与模块生命周期
集成。现在USB状态事件仅在模块初始化后提交,避免了
不必要的事件发布。

feat(logging): 增加详细的状态转换日志记录

为BLE NUS模块添加了业务状态转换的日志记录功能,
增加了详细的错误和警告日志,包括生命周期状态、
业务状态和连接信息,便于调试和问题排查。

refactor(mode_switch): 优化模式检测和报告机制

修改了模式切换采样的判断逻辑,移除了force_report和
mode_valid标志位,改用last_mode状态来决定是否提交
模式切换事件,使代码更简洁且易于理解。
2026-04-18 11:27:48 +08:00
ceebaaa600 feat: 添加模块生命周期管理框架并重构现有模块
添加了模块生命周期管理头文件 module_lifecycle.h,定义了完整的生命周期状态机,
包括初始化、运行、停止、挂起和错误状态。同时将电池模块、BLE BAS模块、BLE HID
模块和BLE NUS模块重构为使用新的生命周期框架进行状态管理。

提升日志缓冲区大小以支持更详细的调试信息记录。
2026-04-17 19:12:57 +08:00
8bfb8b540c feat(bt): 添加蓝牙外设首选超时配置
新增 CONFIG_BT_PERIPHERAL_PREF_TIMEOUT 配置项,
设置为 400 以优化蓝牙连接超时参数
2026-04-17 16:17:13 +08:00
0cbb16052d feat(protocol): 添加传输状态事件管理协议会话状态
添加了新的 proto_transport_state_event 事件类型来跟踪协议传输连接状态,
包括链接断开和就绪状态。为 BLE NUS 和 USB CDC 模块实现了状态机管理,
替换原有的简单布尔标志,提供更精确的连接状态跟踪。

- 添加 proto_transport_state_event 事件定义和实现
- 为 BLE NUS 模块引入业务状态机管理
- 为 USB CDC 模块引入业务状态机管理
- 实现协议模块中的会话状态管理
- 移除 protocol_module_reset_transport_state 函数
- 更新 CMakeLists.txt 包含新事件源文件
2026-04-17 11:55:03 +08:00
2ca02325c1 feat(usb_hid_keyboard): 支持键盘LED报告处理
- 添加KBD_LED_REPORT_WITH_ID_SIZE宏定义以支持带ID的LED报告
- 实现keyboard_handle_led_report函数来处理不同长度的LED报告
- 在keyboard_set_report中添加对HID_REPORT_TYPE_OUTPUT类型的支持
- 优化keyboard_output_report函数以复用LED报告处理逻辑
- 移除datetime_event中的INIT_LOG_ENABLE标志

支持处理长度为1字节或2字节(包含report ID)的键盘LED报告,
并提供适当的错误日志记录功能。
2026-04-15 17:54:21 +08:00
bd57b00080 feat(hid): 实现HID通道状态管理和多通道支持
- 将原来的HID传输状态事件替换为新的HID通道状态事件,支持USB键盘、USB消费者和BLE共享通道
- 添加usb_hid_consumer_module.c模块来处理USB消费者HID报告
- 添加usb_hid_keyboard_module.c模块来处理USB键盘HID报告
- 修改CMakeLists.txt以包含新的HID模块源文件
- 更新事件系统中的transport字段为channel字段,并相应修改所有相关处理逻辑
- 在hid_flowctrl_module.c中实现多通道并发处理机制
- 删除过时的hid_transport_state_event相关代码
- 添加新的hid_channel_state_event用于报告各通道就绪状态
2026-04-15 15:47:14 +08:00
6125f04102 feat(usb): 简化USB状态管理并引入模式策略模块
- 修改usb_state_event结构,将复杂的flag操作简化为单一的state枚举值
- 新增usb_function_hook机制用于USB功能预初始化
- 将ble_adv_ctrl_module重命名为mode_policy_module以更好地反映其功能
- 在mode_policy_module中添加USB设备启用/暂停控制逻辑
- 添加对电源事件的处理支持休眠/唤醒功能
- 更新CMakeLists.txt添加必要的链接器脚本和源文件
- 移除不再需要的ble_adv_ctrl_module并添加新的mode_policy_module
2026-04-15 15:13:44 +08:00
0a905d280d refactor(ui): 移除屏幕背景渐变样式
移除了UI主界面屏幕对象的背景渐变颜色和垂直渐变方向样式设置,
统一使用纯色背景以简化界面设计并提升渲染性能。
2026-04-15 10:59:03 +08:00
79af0eb025 feat(events): 添加功能位图状态事件并移除旧的按键功能事件
- 添加新的 function_bitmap_state_event 事件类型用于跟踪功能键位图状态
- 移除已废弃的 key_function_event 事件及其相关文件
- 更新 CMakeLists.txt 中的源文件列表以包含新事件文件
- 修改协议定义文件 device_comm.options 和 device_comm.proto
  以使用位图方式传输功能键状态而不是单独的按键事件
- 更新键盘核心模块中的位图处理逻辑,添加 usage_to_bitmap_pos
 辅助函数来正确定位修饰键和普通按键的位置
- 修改报告构建逻辑以正确处理新的位图布局
- 更新协议模块以处理新的功能位图状态事件和 LED 状态事件
- 实现协议模块中的 ACK、错误响应和 LED 状态编码功能
2026-04-15 10:52:01 +08:00
bc42a4dd63 feat(ble): 添加BLE NUS模块替换原有的BLE串口功能
- 将ble_serial_module替换为ble_nus_module以使用标准的BLE NUS服务
- 移除不再使用的cdc_wrapper_module和相关事件处理
- 更新协议传输层抽象,支持USB CDC和BLE NUS两种传输方式
- 创建统一的proto_rx_event和proto_tx_event替代专用的串行通信事件
- 添加proto_common.h定义传输类型枚举
- 修改protocol_module接口以支持多传输方式
- 在prj.conf中启用CONFIG_BT_ZEPHYR_NUS配置选项
2026-04-15 10:34:12 +08:00
c4b205b8a1 feat(usb): 引入统一的USB状态事件系统
重构USB事件管理,将原有的多个专用事件(usb_device_state_event、
usb_function_ready_event、usb_prepare_event)合并为统一的
usb_state_event。新的事件系统采用位标志方式管理USB状态,
提供更灵活的状态跟踪机制。

BREAKING CHANGE: 移除了旧的USB相关事件类型,需要更新依赖这些
事件的模块代码。
2026-04-15 09:30:40 +08:00
78a6dc212d feat(events): 添加事件提交函数到各个头文件中
为多个事件头文件添加了静态内联提交函数,包括:
- bat_state_event: 添加submit_bat_state_event函数
- ble_serial_rx_event: 添加submit_ble_serial_rx_event函数
- ble_serial_tx_event: 添加submit_ble_serial_tx_event函数
- cdc_proto_tx_event: 添加submit_cdc_proto_tx_event函数
- datetime_event: 添加submit_datetime_event函数
- encoder_event: 添加submit_encoder_event函数
- function_bitmap_update_event: 添加submit_function_bitmap_update_event函数
- hid_led_event: 添加submit_hid_led_event函数
- hid_report_sent_event: 添加submit_hid_report_sent_event函数
- hid_transport_state_event: 添加submit_hid_transport_state_event函数
- hid_tx_report_event: 添加submit_hid_tx_report_event函数
- key_function_event: 添加submit_key_function_event函数
- keyboard_hid_report_event: 添加submit_keyboard_hid_report_event函数
- led_strip_en_event: 添加submit_led_strip_en_event函数
- mode_switch_event: 添加submit_mode_switch_event函数
- set_protocol_event: 添加submit_set_protocol_event函数
- theme_rgb_update_event: 添加submit_theme_rgb_update_event函数
- time_sync_event: 添加submit_time_sync_event函数
- usb_cdc_rx_event: 添加submit_usb_cdc_rx_event函数
- usb_cdc_tx_event: 添加submit_usb_cdc_tx_event函数
- usb_device_state_event: 添加submit_usb_device_state_event函数
- usb_function_ready_event: 添加submit_usb_function_ready_event函数
- usb_prepare_event: 添加submit_usb_prepare_event函数

这些函数提供了一致的事件提交接口,简化了事件创建和提交过程。
2026-04-14 16:42:04 +08:00
c342a8d3f0 feat(protocol): 添加时间同步和主题颜色协议支持
- 添加CDC_PROTO_TYPE_LED_STATE、CDC_PROTO_TYPE_TIME_SYNC和
  CDC_PROTO_TYPE_THEME_RGB协议类型定义
- 在protobuf中定义LedState、TimeSync和ThemeRgb消息结构
- 更新CdcPacketBody消息以包含新的协议类型
- 增加协议能力标志位以支持新功能
2026-04-13 16:43:17 +08:00
23e23f63a7 feat(led): 添加LED按键淡入淡出效果并重构LED条模块
添加了完整的LED效果系统架构,包括:
- 新增主题颜色定义文件theme_color.h
- 实现key fade LED效果算法,支持按键触发的渐变效果
- 创建LED效果注册机制和通用接口
- 配置17个LED像素与按键映射关系
- 将原有简单的周期性效果替换为基于按键事件的动态效果

CMakeLists.txt中添加了新的源文件路径和实现文件。

BREAKING CHANGE: LED效果从固定的周期性变化改为响应按键事件的动态效果。
2026-04-13 15:56:45 +08:00
ff8f0d81d7 feat: 添加LED灯带模块支持
- 在CMakeLists.txt中添加led_strip_module.c源文件和led_strip_en_event.c事件文件
- 在设备树配置中添加SPI1接口的WS2812灯带引脚控制配置
- 在板级配置文件中添加LED灯带设备节点和别名定义
- 新增led_strip_en_event事件头文件和实现,用于控制灯带使能状态
- 配置prj.conf启用LED Strip和WS2812 SPI驱动
- 实现完整的LED灯带模块功能,包括:
  - 初始化和电源管理
  - RGB色彩效果渲染
  - 通过GPIO控制灯带供电
  - 响应应用事件进行启停控制
2026-04-13 15:23:46 +08:00
968fb626bb feat(nvs): 增加应用事件管理器最大事件计数配置
新增 CONFIG_APP_EVENT_MANAGER_MAX_EVENT_CNT 配置项,
设置最大事件计数为64,以支持更多的应用事件管理需求。
2026-04-13 14:10:15 +08:00
30bc314698 feat(protocol): 添加键盘协议功能支持
添加了完整的键盘协议功能,包括:
- 新增多个事件类型:cdc_proto_tx_event、function_bitmap_update_event、key_function_event
- 在CMakeLists.txt中注册新的事件源文件
- 扩展keyboard_core.h定义键盘协议相关宏
- 增强protocol_module.h定义协议消息类型常量
- 更新protobuf定义device_comm.proto添加Bitmap、FunctionKeyEvent、Ack、Error消息
- 实现CDC协议包装器模块处理协议消息传输
- 改进键盘核心模块实现按键功能映射和位图管理
- 添加协议模块处理Hello、Bitmap、FunctionKeyEvent等协议消息
- 实现USB设备状态管理和错误响应机制
2026-04-13 11:55:59 +08:00
d86f0d6b78 Merge branch 'dev' 2026-04-13 10:11:18 +08:00
15307dfde5 feat: 添加蓝牙串口模块支持
- 添加 ble_serial_module.c 实现蓝牙串口功能
- 添加 ble_serial_rx_event 和 ble_serial_tx_event 事件定义及实现
- 在 CMakeLists.txt 中注册新的源文件和事件
- 配置蓝牙 L2CAP MTU 和缓冲区大小参数
- 修改 usb_cdc_test_module 支持通过蓝牙发送测试消息
- 实现蓝牙连接状态管理及数据收发功能
2026-04-13 10:10:46 +08:00
227158870a feat: 添加CDC协议通信模块支持
- 集成nanopb库用于protobuf序列化
- 创建cdc_wrapper_module.c实现帧解析功能
- 实现protocol_module.c处理协议编解码
- 定义device_comm.proto通信协议
- 修改CMakeLists.txt添加protobuf源文件
- 更新配置启用NANOPB支持
- 移除usb_cdc_module中基于行的处理逻辑
2026-04-11 18:21:18 +08:00
39c6a1fe84 feat(usb_device_module): 添加电源管理限制功能
- 引入power_manager_event.h头文件
- 实现update_power_manager_restriction函数来控制电源管理级别
- 在VBUS就绪时设置为ALIVE级别,在VUSB移除时设置为SUSPENDED级别
- 模块初始化时默认设置为SUSPENDED级别
2026-04-11 17:57:00 +08:00
33fb416cfa feat(usb): 添加USB CDC功能模块支持
- 在CMakeLists.txt中添加usb_cdc_module、usb_cdc_test_module和
  usb_device_module源文件
- 添加usb_cdc_rx_event、usb_cdc_tx_event、usb_device_state_event、
  usb_function_ready_event和usb_prepare_event事件定义
- 实现USB CDC串口通信功能,包括接收和发送数据处理
- 添加USB设备状态管理,支持连接、断开、激活等状态变化
- 配置设备树中的USB端点数量以支持CDC ACM功能
- 创建USB设备模块用于管理USB堆栈初始化和状态监控
- 添加USB功能就绪事件以协调不同USB功能的准备状态
2026-04-11 17:15:11 +08:00
c40fc709d5 feat(display): 添加显示模块功能支持电池状态和模式切换
- 配置文件中启用USB CDC ACM类、UART相关配置和LVGL显示库
- 添加对bat_state_event、hid_led_event和mode_switch_event事件的订阅
- 实现UI模型结构体ui_main_model用于管理显示状态
- 添加refresh_ui函数用于刷新UI界面
- 集成电池电量显示、充电状态指示和模式切换状态更新

fix(ui): 重构主UI界面添加动态数据更新功能

- 重写ui_main.c实现完整的UI组件创建和刷新逻辑
- 添加状态栏芯片显示USB、BLE、NumLock、CapsLock状态
- 实现电池图标、电量百分比和充电状态的动态更新
- 添加日期时间显示区域和整体UI刷新功能
- 创建ui_main_model数据结构管理UI状态数据

chore(config): 更新项目配置启用串口和显示相关功能

- 启用串口和UART中断驱动配置
- 添加USB CDC ACM类和HID支持
- 增加LVGL工作队列栈大小到16KB
- 添加蒙特赛拉特32号字体支持
2026-04-11 16:40:54 +08:00
2f6126da96 feat(display): 集成LVGL图形库并重构显示模块
- 将display_test_module重命名为display_module
- 集成LVGL配置到prj.conf中,包括颜色深度、双缓冲等设置
- 添加UI主界面实现(ui_main.c),包含标题和副标题显示
- 实现背光控制功能替代原有的测试图案绘制
- 调整LCD配置参数(mdac从0x70改为0xA0)
- 修改日志级别从DEBUG降至ERROR以优化性能
- 在CMakeLists.txt中添加UI模块源文件引用
2026-04-11 14:28:34 +08:00
76adb3584c feat(board): 添加显示屏和PWM背光支持
- 在CMakeLists.txt中添加display_test_module.c源文件
- 在设备树配置中添加SPI3和PWM0引脚控制定义
- 配置MIPI DBI显示屏驱动,支持ST7789V控制器
- 添加PWM LED背光控制功能
- 启用GPIO复位功能并添加点击检测器配置
- 实现显示测试模块,支持彩色测试图案渲染
2026-04-11 13:41:35 +08:00
102 changed files with 11063 additions and 1801 deletions

View File

@@ -4,31 +4,74 @@ cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(blinky) project(blinky)
list(APPEND CMAKE_MODULE_PATH ${ZEPHYR_BASE}/modules/nanopb)
include(nanopb)
zephyr_linker_sources(SECTIONS src/usb_function_hook.ld)
zephyr_include_directories( zephyr_include_directories(
inc inc
inc/events inc/events
src
) )
add_subdirectory(drivers) add_subdirectory(drivers)
zephyr_nanopb_sources(app
proto/device_comm.proto
)
target_sources(app PRIVATE target_sources(app PRIVATE
src/main.c src/main.c
src/battery_module.c src/battery_module.c
src/ble_adv_ctrl_module.c src/ble_bond_multi_module.c
src/ble_adv_uuid16.c src/ble_adv_uuid16.c
src/ble_bas_module.c src/ble_bas_module.c
src/ble_hid_module.c src/ble_hid_module.c
src/ble_nus_module.c
src/display_module.c
src/encoder_module.c src/encoder_module.c
src/hid_flowctrl_module.c src/hid_flowctrl_module.c
src/keyboard_core_module.c src/keyboard_core_module.c
src/usb_hid_module.c src/led_effect/led_effect_registry.c
src/led_effect/effects/led_effect_key_fade.c
src/led_strip_module.c
src/mode_policy_module.c
src/time_sync_module.c
src/settings_module.c
src/swift_pair_module.c
src/ui/ui_main.c
src/ui/ui_settings.c
src/ui/ui_settings_controller.c
src/ui/ui_settings_root.c
src/ui/ui_settings_ble.c
src/ui/ui_settings_theme.c
src/protocol_module.c
src/usb_cdc_module.c
src/usb_device_module.c
src/usb_hid_consumer_module.c
src/usb_hid_keyboard_module.c
src/events/bat_state_event.c src/events/bat_state_event.c
src/events/ble_bond_multi_event.c
src/events/datetime_event.c
src/events/encoder_event.c src/events/encoder_event.c
src/events/function_bitmap_state_event.c
src/events/function_bitmap_update_event.c
src/events/hid_channel_state_event.c
src/events/hid_led_event.c src/events/hid_led_event.c
src/events/hid_report_sent_event.c src/events/hid_report_sent_event.c
src/events/hid_transport_state_event.c
src/events/hid_tx_report_event.c src/events/hid_tx_report_event.c
src/events/led_strip_en_event.c
src/mode_switch_module.c src/mode_switch_module.c
src/events/keyboard_hid_report_event.c src/events/keyboard_hid_report_event.c
src/events/mode_switch_event.c src/events/mode_switch_event.c
src/events/proto_rx_event.c
src/events/proto_transport_state_event.c
src/events/proto_tx_event.c
src/events/set_protocol_event.c src/events/set_protocol_event.c
src/events/settings_mode_event.c
src/events/settings_view_event.c
src/events/theme_rgb_update_event.c
src/events/time_sync_event.c
src/events/transport_policy_event.c
src/events/usb_control_event.c
src/events/usb_state_event.c
) )

13
Kconfig
View File

@@ -2,6 +2,19 @@ mainmenu "blinky"
source "Kconfig.zephyr" source "Kconfig.zephyr"
menu "Application"
config BLINKY_BLE_BOND_MULTI_INTERNAL
bool
depends on BT_BONDABLE
depends on BT_SETTINGS
depends on CAF_SETTINGS_LOADER
depends on CAF_BLE_COMMON_EVENTS
select CAF_BLE_BOND_SUPPORTED
default y
endmenu
menu "Application Drivers" menu "Application Drivers"
rsource "drivers/Kconfig" rsource "drivers/Kconfig"
endmenu endmenu

View File

@@ -30,4 +30,47 @@
bias-pull-up; bias-pull-up;
}; };
}; };
spi3_default: spi3_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 13)>,
<NRF_PSEL(SPIM_MOSI, 0, 28)>;
};
};
spi3_sleep: spi3_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 13)>,
<NRF_PSEL(SPIM_MOSI, 0, 28)>;
low-power-enable;
};
};
spi1_ws2812_default: spi1_ws2812_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 4)>,
<NRF_PSEL(SPIM_MOSI, 0, 20)>;
};
};
spi1_ws2812_sleep: spi1_ws2812_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 4)>,
<NRF_PSEL(SPIM_MOSI, 0, 20)>;
low-power-enable;
};
};
pwm0_default: pwm0_default {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 1, 11)>;
};
};
pwm0_sleep: pwm0_sleep {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 1, 11)>;
low-power-enable;
};
};
}; };

View File

@@ -2,6 +2,9 @@
#include <nordic/nrf52840_qiaa.dtsi> #include <nordic/nrf52840_qiaa.dtsi>
#include "mini_keyboard-pinctrl.dtsi" #include "mini_keyboard-pinctrl.dtsi"
#include <zephyr/dt-bindings/adc/adc.h> #include <zephyr/dt-bindings/adc/adc.h>
#include <zephyr/dt-bindings/led/led.h>
#include <zephyr/dt-bindings/mipi_dbi/mipi_dbi.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
/ { / {
model = "Mini keyboard"; model = "Mini keyboard";
@@ -11,19 +14,30 @@
zephyr,sram = &sram0; zephyr,sram = &sram0;
zephyr,flash = &flash0; zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition; zephyr,code-partition = &slot0_partition;
zephyr,display = &screen_lcd;
zephyr,led-strip = &led_strip;
zephyr,console = &cdc_acm_uart1;
zephyr,log-uart = &log_uarts;
}; };
aliases { aliases {
led0 = &myled0; led0 = &myled0;
led-strip = &led_strip;
qdec0 = &qdec; qdec0 = &qdec;
backlight = &backlight;
}; };
log_uarts: log_uarts {
compatible = "zephyr,log-uart";
uarts = <&cdc_acm_uart1>;
};
hid_kbd: hid_kbd { hid_kbd: hid_kbd {
compatible = "zephyr,hid-device"; compatible = "zephyr,hid-device";
label = "HID_KBD"; label = "HID_KBD";
protocol-code = "keyboard"; protocol-code = "keyboard";
in-report-size = <29>; in-report-size = <29>;
out-report-size = <1>; out-report-size = <29>;
in-polling-period-us = <1000>; in-polling-period-us = <1000>;
out-polling-period-us = <1000>; out-polling-period-us = <1000>;
}; };
@@ -36,12 +50,57 @@
in-polling-period-us = <1000>; in-polling-period-us = <1000>;
}; };
leds { leds {
compatible = "gpio-leds"; compatible = "gpio-leds";
myled0: led_0 { myled0: led_0 {
gpios = <&gpio1 2 GPIO_ACTIVE_LOW>; gpios = <&gpio1 2 GPIO_ACTIVE_LOW>;
}; };
}; };
pwm_leds {
compatible = "pwm-leds";
status = "okay";
backlight: pwm_led_0 {
pwms = <&pwm0 0 PWM_MSEC(10) PWM_POLARITY_INVERTED>;
};
};
mipi_dbi_screen: mipi_dbi_screen {
compatible = "zephyr,mipi-dbi-spi";
spi-dev = <&spi3>;
dc-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
write-only;
#address-cells = <1>;
#size-cells = <0>;
screen_lcd: st7789v@0 {
compatible = "sitronix,st7789v";
status = "okay";
reg = <0>;
mipi-max-frequency = <32000000>;
width = <320>;
height = <172>;
x-offset = <0>;
y-offset = <34>;
vcom = <0x34>;
gctrl = <0x00>;
mdac = <0xA0>;
gamma = <0x01>;
colmod = <0x05>;
lcm = <0x2c>;
porch-param = [ 0c 0c 00 33 33 ];
cmd2en-param = [ 5a 69 02 01 ];
pwctrl1-param = [ a4 a1 ];
pvgam-param = [ f0 04 08 0a 0a 05 25 33 3c 24 0e 0f 27 2f ];
nvgam-param = [ f0 02 06 06 04 22 25 32 3b 3a 15 17 2d 37 ];
ram-param = [ 00 f0 ];
rgb-param = [ cd 08 14 ];
ready-time-ms = <120>;
mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";
};
};
vbatt: vbatt { vbatt: vbatt {
compatible = "voltage-divider"; compatible = "voltage-divider";
@@ -90,6 +149,7 @@
&uicr { &uicr {
nfct-pins-as-gpios; nfct-pins-as-gpios;
gpio-as-nreset;
}; };
&adc { &adc {
@@ -148,6 +208,43 @@
status = "okay"; status = "okay";
}; };
&spi3 {
status = "okay";
pinctrl-0 = <&spi3_default>;
pinctrl-1 = <&spi3_sleep>;
pinctrl-names = "default", "sleep";
cs-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
};
&spi1 {
status = "okay";
pinctrl-0 = <&spi1_ws2812_default>;
pinctrl-1 = <&spi1_ws2812_sleep>;
pinctrl-names = "default", "sleep";
led_strip: ws2812@0 {
compatible = "worldsemi,ws2812-spi";
reg = <0>;
spi-max-frequency = <6400000>;
chain-length = <17>;
color-mapping = <LED_COLOR_ID_GREEN
LED_COLOR_ID_RED
LED_COLOR_ID_BLUE>;
spi-one-frame = <0x70>;
spi-zero-frame = <0x40>;
bits-per-symbol = <8>;
reset-delay = <8>;
supply-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>;
};
};
&pwm0 {
status = "okay";
pinctrl-0 = <&pwm0_default>;
pinctrl-1 = <&pwm0_sleep>;
pinctrl-names = "default", "sleep";
};
&qdec { &qdec {
status = "okay"; status = "okay";
pinctrl-0 = <&encoder_default>; pinctrl-0 = <&encoder_default>;
@@ -160,9 +257,19 @@
&usbd { &usbd {
status = "okay"; status = "okay";
num-bidir-endpoints = <0>; num-bidir-endpoints = <1>;
num-in-endpoints = <2>; num-in-endpoints = <7>;
num-out-endpoints = <1>; num-out-endpoints = <7>;
num-isoin-endpoints = <0>; num-isoin-endpoints = <0>;
num-isoout-endpoints = <0>; num-isoout-endpoints = <0>;
cdc_acm_uart0: cdc_acm_uart0 {
compatible = "zephyr,cdc-acm-uart";
label = "CDC_ACM_0";
};
cdc_acm_uart1: cdc_acm_uart1 {
compatible = "zephyr,cdc-acm-uart";
label = "CDC_ACM_1";
};
}; };

View File

@@ -0,0 +1,582 @@
# Blinky 多槽蓝牙设计方案
## 1. 目标与约束
`C:\projects\blinky` 增加多槽蓝牙能力,当前阶段只实现 BLE多 dongle 方案先预留结构,不实现完整业务流程。
本方案已经按当前讨论结论收敛为以下硬约束:
- 支持 3 个 BLE 槽位 `Slot 1~3`
- 每个槽位固定对应一个 Bluetooth local identity
- 槽位切换本质上是切换 `bt_identity_id`
- 不引入 `app_slot_id`
- 不使用临时 identity
- 不做 erase advertising
- 不做擦除确认
- 不做擦除回滚
- 擦除当前槽位时直接删除该 identity 上的 bond
- settings 只持久化 `current_slot` 和各槽位 `meta`
- `slot_meta` 中必须保留 `display_name`,用于 UI 显示
- `24G / dongle` 采用方案 A但本阶段只预留专用槽位
## 2. 本地参考基线
本方案主要参考本地 NCS 3.2.3 中以下实现和文档:
- `c:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\ble_bond.rst`
- `c:\ncs\v3.2.3\nrf\applications\nrf_desktop\src\modules\ble_bond.c`
- `c:\ncs\v3.2.3\nrf\applications\nrf_desktop\src\modules\Kconfig.ble_bond`
- `c:\ncs\v3.2.3\nrf\include\caf\events\ble_common_event.h`
- `c:\ncs\v3.2.3\nrf\subsys\caf\modules\ble_adv.c`
- `c:\ncs\v3.2.3\nrf\subsys\caf\modules\Kconfig.ble_state`
- `c:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_state.rst`
参考方式如下:
- 继承 `nrf_desktop` 的总体方向:应用自定义 bond 管理模块,而不是 CAF 默认 `ble_bond`
- 继续通过 `ble_peer_operation_event` 驱动 CAF `ble_adv`
- 保留 dongle 专用 identity 的规划方式
明确不采用 `nrf_desktop` 的部分:
- 不使用 `app_slot_id <-> bt_identity_id` 的二级映射
- 不使用临时 identity
- 不采用 `ERASE_ADV`
- 不采用“新配对成功后再替换旧槽位”的回滚保护机制
## 3. 对现有项目的判断
`blinky` 当前已经具备多槽蓝牙需要的大部分基础能力:
- 已启用 `CONFIG_CAF_BLE_STATE`
- 已启用 `CONFIG_CAF_BLE_ADV`
- 已启用 `CONFIG_BT_SETTINGS`
- HID 和 NUS 都已基于 `ble_peer_event` 跟踪当前连接
- UI 已经预留 3 个蓝牙槽位和擦除当前槽位入口
- 模式策略层已经区分:
- `MODE_SWITCH_BLE -> BLE_PROFILE_POLICY_GENERAL`
- `MODE_SWITCH_24G -> BLE_PROFILE_POLICY_DONGLE`
相关文件:
- `C:\projects\blinky\src\ble_hid_module.c`
- `C:\projects\blinky\src\ble_nus_module.c`
- `C:\projects\blinky\src\ui\ui_settings_ble.c`
- `C:\projects\blinky\src\ui\ui_settings_controller.c`
- `C:\projects\blinky\src\mode_policy_module.c`
- `C:\projects\blinky\prj.conf`
当前限制也很明确:
- `CONFIG_BT_MAX_PAIRED=1`,全设备只能保存 1 个 bond
- 当前是单槽逻辑
- 还在使用 CAF 默认 `CONFIG_CAF_BLE_BOND=y`
- UI 的槽位状态仍是内存假数据
## 4. 配置语义结论
基于本地 Kconfig 和 CAF 文档,关键配置的含义如下:
- `CONFIG_BT_MAX_PAIRED`
说明:整个设备允许保存的 bond 总数
- `CONFIG_CAF_BLE_STATE_MAX_LOCAL_ID_BONDS`
说明:每个 Bluetooth local identity 允许的 bond 数上限
- `CONFIG_BT_ID_MAX`
说明:设备允许使用的 local identity 总数
如果目标是:
- 3 个 BLE 槽位
- 每槽 1 个 bond
- 预留 1 个 dongle 槽位
那么推荐配置应为:
```conf
CONFIG_BT_MAX_CONN=1
CONFIG_BT_MAX_PAIRED=4
CONFIG_CAF_BLE_STATE_MAX_LOCAL_ID_BONDS=1
CONFIG_BT_ID_MAX=5
```
语义如下:
- `BT_MAX_CONN=1`
说明:同时只保持 1 条 BLE 连接
- `BT_MAX_PAIRED=4`
说明:全设备最多保存 4 个 bond
- `CAF_BLE_STATE_MAX_LOCAL_ID_BONDS=1`
说明:每个 identity 只允许绑定 1 个主机
- `BT_ID_MAX=5`
说明:可用 identity 为 `0..4`
配合本方案的 identity 规划:
- `0`:默认 identity不参与槽位
- `1`Slot 1
- `2`Slot 2
- `3`Slot 3
- `4`Dongle Slot 预留
## 5. 总体架构
## 5.1 设计结论
建议采用:
- 保留 `CAF_BLE_STATE`
- 保留 `CAF_BLE_ADV`
- 关闭 CAF 默认 `BLE_BOND`
- 新增应用自定义模块 `ble_bond_multi_module`
即:
- `CONFIG_CAF_BLE_BOND=n`
- 新增 `CONFIG_BLINKY_BLE_BOND_MULTI`
- 自定义模块 `select CAF_BLE_BOND_SUPPORTED`
- 由自定义模块向 CAF 提交 `ble_peer_operation_event`
原因:
- CAF 默认 `ble_bond` 不适合固定多 identity 槽位管理
- 你的需求已经明确不需要 `nrf_desktop` 那种复杂映射和回滚流程
- 自定义模块更适合和现有 UI、模式策略、settings 同步
## 5.2 核心模型
本方案直接把“槽位”和“identity”绑定
- `Slot 1` = `bt_identity_id 1`
- `Slot 2` = `bt_identity_id 2`
- `Slot 3` = `bt_identity_id 3`
- `Dongle Slot` = `bt_identity_id 4`
所以:
- `current_slot` 实际上就是当前使用的 `bt_identity_id`
- UI 显示“Slot 1/2/3”时后端直接映射到固定 identity
- 不再存在可变的 `id_lut`
这个设计的优点:
- 逻辑简单
- settings 简单
- 调试简单
- 后续接入 dongle 不需要迁移已有 BLE 槽位
## 6. identity 规划
建议固定如下:
| identity | 角色 | 当前阶段 |
| --- | --- | --- |
| `0` | 默认 identity | 不使用 |
| `1` | BLE Slot 1 | 使用 |
| `2` | BLE Slot 2 | 使用 |
| `3` | BLE Slot 3 | 使用 |
| `4` | Dongle Slot | 预留 |
设计原则:
- 不使用 `BT_ID_DEFAULT`
原因:默认 identity 的 reset/unpair 行为不适合作为固定槽位基线
- 3 个 BLE 槽位 identity 固定不变
- `identity 4` 只给 `BLE_PROFILE_POLICY_DONGLE`
## 7. 模块设计
## 7.1 新增模块和文件
建议新增:
- `src/ble_bond_multi_module.c`
- `src/events/ble_bond_multi_event.c`
- `inc/events/ble_bond_multi_event.h`
不建议新增:
- `inc/ble_bond_multi.h`
原因:
- 当前设计是事件驱动模块
- 没有必要向外暴露一组直接调用 API
- 共享内容更适合放在事件头文件里
只有在后续出现明确的跨模块直接调用需求时,才需要再补 `inc/ble_bond_multi.h`
## 7.2 模块职责边界
### `ble_bond_multi_module`
负责:
- 管理当前活动 `bt_identity_id`
- 启动时恢复 `current_slot`
- 维护各 identity 槽位的 `slot_meta`
- 处理“切换当前槽位”
- 处理“直接擦除当前槽位”
- 监听连接、加密、断链事件,更新 `slot_meta`
- 向 CAF 提交 `ble_peer_operation_event`
- 向 UI / 显示层广播槽位状态
不负责:
- 发 HID/NUS 数据
- 改 GATT 服务
- 绘制 UI
### `ble_hid_module` / `ble_nus_module`
保持现有思路,不理解“多槽逻辑”,只跟随当前活动连接:
- 连接上谁,就服务谁
- 切槽或擦除造成断链时,内部复位连接状态
### `mode_policy_module`
负责决定当前是:
- `BLE_PROFILE_POLICY_GENERAL`
- `BLE_PROFILE_POLICY_DONGLE`
但不负责槽位 bond 细节。
## 8. 状态机设计
第一阶段建议极简化,只保留:
- `DISABLED`
- `IDLE`
- `SWITCHING`
- `ERASING`
明确不引入:
- `SELECT_SLOT`
- `ERASE_PENDING`
- `ERASE_ADV`
- `TEMP_IDENTITY`
- `ROLLBACK`
## 8.1 切槽流程
1. 用户在 UI 选择 `Slot N`
2. 模块判断:
- 如果 `N == current_slot`,直接返回
- 否则进入 `SWITCHING`
3. 提交 `PEER_OPERATION_SELECTED`
4. CAF `ble_adv` 切换当前 advertising identity
5. 当前连接断开
6. 设备以新 identity 重新广播
7. 目标主机重新连接
8. 保存新的 `current_slot`
9. 返回 `IDLE`
这里采用“直接切换”,不做预选确认。
## 8.2 擦除当前槽位流程
1. 用户选择“Erase Bond”
2. 模块进入 `ERASING`
3. 对当前 `bt_identity_id` 执行:
- `bt_unpair(identity, BT_ADDR_LE_ANY)`
4. 清除该槽位 `slot_meta`
5. 重新以当前 identity 进入可配对广播
6. 返回 `IDLE`
这里采用产品级简化语义:
- 不确认
- 不保护旧配对
- 不回滚
- 擦除后旧主机立即失效
## 9. Settings 设计
## 9.1 持久化范围
只持久化:
- `current_slot`
- 各槽位 `slot_meta`
不持久化:
- `id_lut`
- 临时 identity 状态
- 擦除中间态
## 9.2 settings namespace
建议采用:
- `ble_multi/current_slot`
- `ble_multi/meta/1`
- `ble_multi/meta/2`
- `ble_multi/meta/3`
- `ble_multi/meta/4`
其中:
- `1/2/3` 是 BLE 槽位
- `4` 是 dongle 预留槽位
## 9.3 slot_meta 结构
`slot_meta` 建议固定包含以下字段:
- `occupied`
- `last_peer_addr`
- `display_name`
字段说明:
- `occupied`
说明:该槽位当前是否有有效 bond
- `last_peer_addr`
说明:最近一次绑定主机的 BLE 地址,用于识别和调试
- `display_name`
说明UI 显示名,不做简化,作为正式字段保留
这里 `display_name` 是明确保留项,不是可选优化项。
设计目的就是让 UI 能直接显示:
- `MacBook Pro`
- `iPad Mini`
- `Windows-PC`
而不是只能显示 `Bonded` / `Empty`
## 9.4 display_name 来源
需要注意一点:
- Zephyr bond 本身不会自动提供“友好主机名”
因此 `display_name` 的来源需要在实现阶段明确,一般有两个方向:
1. 配对后由应用层通过协议或自定义方式写入
2. 若当前阶段无法拿到真实主机名,则先允许 UI/上位机为该槽位写入名称
也就是说:
- `display_name` 必须作为正式持久化字段存在
- 但其填充机制可以分阶段实现
第一阶段如果没有自动名称来源,建议默认值如下:
- 有 bond 但无名称:`"Bonded Device"`
- 无 bond空串或 `"Empty"`
## 10. UI 设计
现有 UI 结构已经适合复用,不建议重做。
现有文件:
- `C:\projects\blinky\src\ui\ui_settings_ble.c`
- `C:\projects\blinky\src\ui\ui_settings_controller.c`
建议交互规则如下:
- 进入 BLE 设置页后显示 `Slot 1~3`
- 每个槽位 value 直接显示 `display_name`
- 若该槽位无 bond则显示 `Empty`
- 当前槽位显示为 `Slot N`
- 擦除项显示为 `Erase Slot N`
操作方式:
- 选中某个槽位后直接切换
- 选中擦除项后直接擦除
- 不加确认页
UI 需要的数据来源:
- `current_slot`
- `slot_meta[1]`
- `slot_meta[2]`
- `slot_meta[3]`
所以建议 UI 不再维护本地假数据,而改为订阅 `ble_bond_multi_event` 或读取统一状态缓存。
## 11. 与模式切换的兼容设计
## 11.1 BLE 档
`MODE_SWITCH_BLE` 下:
- 只允许使用 `identity 1/2/3`
- UI 允许切槽和擦除
- `current_slot` 必须属于 `1..3`
## 11.2 24G / dongle 档
采用方案 A
- `MODE_SWITCH_24G` 绑定专用 `identity 4`
- `identity 4` 不与普通 BLE 槽位混用
- 第一阶段只预留,不实现完整 dongle bond 生命周期
这样做的好处:
- 保持 `mode_policy_module.c` 现有设计语义
- 普通 BLE 槽位和 dongle 配对状态不会互相污染
- 后续接入 dongle 时无需改动 BLE 三槽数据模型
## 12. 事件设计
建议新增应用事件:
- `ble_bond_multi_event`
放在:
- `inc/events/ble_bond_multi_event.h`
- `src/events/ble_bond_multi_event.c`
字段建议:
- `current_slot`
- `active_identity_id`
- `slot_occupied_bitmap`
- `slot_meta_summary`
- `op`
至少要能支持:
- UI 刷新槽位名称
- 显示层提示当前槽位
- 设置层同步当前 active slot
继续复用 CAF 事件:
- `ble_peer_event`
- `ble_peer_operation_event`
第一阶段不需要依赖:
- `ble_peer_search_event`
## 13. 对现有代码的影响评估
## 13.1 必改
- `prj.conf`
- `CMakeLists.txt`
- 新增 `src/ble_bond_multi_module.c`
- 新增 `inc/events/ble_bond_multi_event.h`
- 新增 `src/events/ble_bond_multi_event.c`
- `ui_settings_controller.c`
## 13.2 小改
- `mode_policy_module.c`
说明:把 `BLE_PROFILE_POLICY_GENERAL` 约束到 `identity 1/2/3`,把 `BLE_PROFILE_POLICY_DONGLE` 预留到 `identity 4`
- `display_module.c`
说明:如果要显示当前槽位或名称,需要订阅新事件
## 13.3 原则上不应大改
- `ble_hid_module.c`
- `ble_nus_module.c`
- `keyboard_core_module.c`
这些模块只应该感知当前连接状态,不承担槽位管理职责。
## 14. 实施分阶段建议
## 阶段 1固定 identity 多槽骨架
- 替换 CAF 默认 `ble_bond`
- 新增 `ble_bond_multi_module`
- 固定 `identity 1/2/3` 为 BLE 槽位
- 预留 `identity 4` 为 dongle 槽位
- 恢复和保存 `current_slot`
- 打通直接切槽
交付标准:
- `Slot 1~3` 可以切换
- 每个槽位 bond 独立
- 重启后当前槽位可恢复
## 阶段 2slot_meta 与 UI 打通
- 维护 `occupied`
- 维护 `last_peer_addr`
- 正式持久化 `display_name`
- UI 改为显示真实槽位数据
交付标准:
- 屏幕上显示真实槽位名
- `display_name` 可稳定持久化
## 阶段 3直接擦除与 dongle 预留对齐
- 实现当前槽位直接 `unpair`
- 擦除后刷新 `slot_meta`
- 擦除后重新进入可配对状态
- 明确 `identity 4` 的模式入口
交付标准:
- 擦除后旧主机不能再连接
- 当前槽位可重新配对
- `24G` 路径已有干净预留点
## 15. 风险与注意事项
## 15.1 直接擦除的用户体验
本方案放弃回滚能力。
一旦擦除:
- 旧 bond 立即失效
- 用户必须重新配对
这是当前明确接受的产品行为。
## 15.2 display_name 获取机制
`display_name` 虽然是正式字段,但自动来源未必现成。
实现阶段必须明确:
- 名称是自动采集
- 还是通过 UI / 上位机写入
这不是字段是否保留的问题,而是字段填充路径的问题。
## 15.3 `BT_ID_MAX=5` 可用性
必须在目标板上验证:
- identity 创建正常
- 切换 advertising identity 正常
- settings 恢复正常
## 15.4 默认 identity 不参与槽位
本方案建议 `BT_ID_DEFAULT` 不参与槽位管理,避免后续行为不一致。
## 16. 当前版本结论
按目前所有已确认信息,最终设计结论如下:
1. 只做 BLE 三槽dongle 只预留专用槽位
2. 三个 BLE 槽位直接固定到 `bt_identity_id 1/2/3`
3. `bt_identity_id 4` 预留给 dongle
4. 不使用 `app_slot_id`
5. 不使用临时 identity
6. 不做 erase advertising
7. 不做擦除确认和回滚
8. `slot_meta` 固定包含:
- `occupied`
- `last_peer_addr`
- `display_name`
9. `display_name` 作为正式持久化字段保留,用于 UI 显示
10. 不新增 `inc/ble_bond_multi.h`,共享接口放到事件头文件
如果你认可这个版本,下一步就可以按这份方案进入实现阶段。

View File

@@ -0,0 +1,302 @@
# 下位机通信协议 v1 修订版
本版基于原 `device_communication_protocol_v1.md` 修订,变更点如下:
- `CDC` 去掉外层协议,直接传业务消息
- `BLE GATT` 改为使用 `NUS (Nordic UART Service)`
- `Bitmap` 统一为 29 字节键盘位图格式
- `FunctionKeyEvent` 不再上报单键按下/释放,改为上报全键盘位图,格式与 `Bitmap` 一致
- 顶层消息增加 `msg_id` / `reply_to`
- `Ack``Error` 合并为统一 `Response`
- `CDC``NUS` 均增加统一外层帧:`magic(2) + len(1) + protobuf`
## 1. 总体原则
- 标准键盘输入继续走 `HID / BLE HID`,不变。
- 私有通信继续保留两条通道:
- `USB`:走 `CDC`
- `BLE`:走 `NUS`
- 两条通道统一承载同一套 `protobuf` 业务消息。
- `CDC``NUS` 均使用完全相同的外层帧格式,帧内承载 `protobuf` 业务消息。
- 当前版本不做应用层分片。
说明:
- 顶层 `protobuf` 消息名改为 `DeviceMessage`,表示独立于具体传输通道的统一业务信封。
- 当前版本使用统一外层帧,`CDC``NUS` 都按完整帧进行发送与解析。
## 2. 共享业务消息
统一的 `protobuf` 业务消息为 `DeviceMessage`,内部 `oneof body` 可取以下类型:
| 消息 | 方向 | 说明 |
| --- | --- | --- |
| `HelloReq` | Host -> Device | 握手请求 |
| `HelloRsp` | Device -> Host | 握手响应 |
| `Bitmap` | Host -> Device | 下发功能键位图配置 |
| `FunctionKeyEvent` | Device -> Host | 上报当前全键盘状态位图 |
| `LedState` | Device -> Host | 上报键盘 LED 状态 |
| `TimeSync` | Host -> Device | 下发时间同步 |
| `ThemeRgb` | Host -> Device | 下发主题颜色 |
| `Response` | Device -> Host | 控制命令统一响应 |
顶层公共字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `msg_id` | `uint32` | 当前消息 ID`0` 表示未使用 |
| `reply_to` | `uint32` | 当前消息响应的请求 ID`0` 表示不是响应 |
规则:
- Host 发起的请求消息应填写唯一 `msg_id`
- `HelloRsp``Response` 应填写 `reply_to = 原请求.msg_id`
- `FunctionKeyEvent``LedState` 等异步上报消息应设置 `reply_to = 0`
- 当前版本建议每条链路同一时刻仅保留 `1` 条 in-flight 控制请求。
## 3. CDC 协议
CDC 在线路上的真实字节格式如下:
```text
magic(2B) + len(1B) + protobuf(DeviceMessage)
```
约束:
- `magic` 固定为 `0xAA55`,按小端发送,即线路字节序为 `0x55 0xAA`
- `len` 为后续 `protobuf(DeviceMessage)` 的字节长度,范围 `0..64`
- Host -> Device 与 Device -> Host 都发送完整帧。
- 当前版本要求单条业务消息一次完整发送,不做应用层分片与重组。
## 4. NUS 协议
BLE 私有通信改为使用 `NUS (Nordic UART Service)`
NUS 线路上的真实字节格式如下:
```text
magic(2B) + len(1B) + protobuf(DeviceMessage)
```
方向定义:
- Host -> Device`RX` 特征写入完整帧
- Device -> Host通过 `TX Notify` 上报完整帧
## 5. NUS 服务定义
Service UUID
```text
6E400001-B5A3-F393-E0A9-E50E24DCCA9E
```
Characteristic 定义:
| 名称 | UUID | 属性 | 用途 |
| --- | --- | --- | --- |
| `RX` | `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` | `Write`, `Write Without Response` | Host -> Device |
| `TX` | `6E400003-B5A3-F393-E0A9-E50E24DCCA9E` | `Notify` | Device -> Host |
约束:
- 连接后主机必须先订阅 `TX`
- 当前版本不做应用层分片。
- 因为增加了 `magic + len` 外层帧,建议协商 `ATT_MTU >= 64`,保证完整帧可一次发送。
## 6. 外层帧定义
外层帧字段如下:
| 字段 | 大小 | 说明 |
| --- | --- | --- |
| `magic` | `2` 字节 | 固定 `0xAA55`,线路字节序 `55 AA` |
| `len` | `1` 字节 | `protobuf(DeviceMessage)` 长度,最大 `64` |
| `payload` | `len` 字节 | `protobuf(DeviceMessage)` |
## 7. protobuf 字段定义
以下是业务字段语义。
### HelloReq
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `protocol_version` | `uint32` | 当前固定为 `1` |
### HelloRsp
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `protocol_version` | `uint32` | 当前固定为 `1` |
| `vendor_id` | `uint32` | 当前建议 `0x1209` |
| `product_id` | `uint32` | 当前建议 `0x0001` |
| `firmware_major` | `uint32` | 固件主版本 |
| `firmware_minor` | `uint32` | 固件次版本 |
| `capability_flags` | `uint32` | 能力位 |
### Bitmap
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `usage_bitmap` | `bytes` | 固定 29 字节,表示功能键配置位图 |
语义:
- `Bitmap.usage_bitmap` 长度固定为 `29` 字节。
-`0` 字节表示 `0xE0``0xE7` 这 8 个控制键。
-`1` 到第 `28` 字节表示 `0x00``0xDF`
- 位值为 `1` 表示该 usage 被配置为功能键。
- 位值为 `0` 表示该 usage 不是功能键,继续按普通键处理。
位映射规则:
-`0` 字节:
- `bit0 -> 0xE0`
- `bit1 -> 0xE1`
- ...
- `bit7 -> 0xE7`
-`1` 字节:
- `bit0 -> 0x00`
- `bit1 -> 0x01`
- ...
- `bit7 -> 0x07`
-`2` 字节:
- `bit0 -> 0x08`
- ...
- `bit7 -> 0x0F`
- 依此类推。
-`28` 字节:
- `bit0 -> 0xD8`
- ...
- `bit7 -> 0xDF`
### FunctionKeyEvent
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `usage_bitmap` | `bytes` | 固定 29 字节,表示当前全键盘状态位图 |
语义:
- `FunctionKeyEvent` 不再使用 `usage + action` 的单键事件格式。
- `FunctionKeyEvent.usage_bitmap` 长度固定为 `29` 字节。
- 位图编码方式与 `Bitmap.usage_bitmap` 完全一致。
- 位值为 `1` 表示该 usage 当前处于按下状态。
- 位值为 `0` 表示该 usage 当前处于释放状态。
- 该消息表示一次完整键盘状态快照,而不是单个按键变化。
### LedState
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `led_mask` | `uint32` | NumLock/CapsLock 等位掩码 |
### TimeSync
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `version` | `uint32` | 当前固定 `1` |
| `flags` | `uint32` | 标志位 |
| `timezone_min` | `sint32` | 时区偏移,单位分钟 |
| `utc_ms` | `fixed64` | UTC 毫秒时间戳 |
| `accuracy_ms` | `fixed32` | 精度,毫秒 |
### ThemeRgb
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `red` | `uint32` | `0..255` |
| `green` | `uint32` | `0..255` |
| `blue` | `uint32` | `0..255` |
### Response
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `error_code` | `enum ResponseCode` | 响应结果;`OK` 表示成功,其余表示失败原因 |
## 8. 枚举定义
### ResponseCode
| 值 | 含义 |
| ---: | --- |
| `0` | `OK` |
| `1` | `UNKNOWN_TYPE` |
| `2` | `INVALID_LENGTH` |
| `3` | `INVALID_PARAM` |
| `4` | `NOT_READY` |
说明:
-`KeyAction` 枚举已不再使用,因为 `FunctionKeyEvent` 改为完整位图上报。
## 9. CapabilityFlags 定义
`HelloRsp.capability_flags` 先按以下位定义:
| Bit | 含义 |
| ---: | --- |
| `0` | 支持功能位图配置 |
| `1` | 支持时间同步 |
| `2` | 支持主题切换 |
| `3` | 支持 LED 状态上报 |
| `4` | 支持全键盘位图事件上报 |
当前建议最小返回值:
- 若仅先做握手:可先返回 `0`
-`Bitmap` 可用:打开 `bit0`
-`FunctionKeyEvent` 全键盘位图上报可用:打开 `bit4`
## 10. 握手流程
### CDC 握手
1. Host 打开串口
2. Host 发送 `magic + len + protobuf(DeviceMessage{hello_req})`
3. Device 解析外层帧与 `protobuf(DeviceMessage)`
4. Device 取出 `hello_req`
5. Device 返回 `magic + len + protobuf(DeviceMessage{hello_rsp})`
### NUS 握手
1. Host 连接 BLE
2. Host 发现 `NUS Service`
3. Host 订阅 `TX Notify`
4. Host 向 `RX` 写入完整帧
5. Device 解析外层帧与 `protobuf(DeviceMessage)`
6. Device 通过 `TX Notify` 发回完整帧
说明:
- 文中 `protobuf(HelloReq)` / `protobuf(HelloRsp)` 指业务上发送对应的 `DeviceMessage` 消息,其 `oneof body` 分别为 `hello_req` / `hello_rsp`
## 11. 设备行为规则
- 上电默认所有按键都是普通键。
- 普通键的标准输入行为继续走 `HID / BLE HID`,不变。
- 收到 `Bitmap` 后,位图中标记为功能键的按键按功能键逻辑处理。
- `FunctionKeyEvent` 上报的是当前全键盘状态快照,编码格式与 `Bitmap` 一致。
- `LedState` 在 LED 状态变化时上报。
- `TimeSync` 收到后更新时间管理模块。
- `ThemeRgb` 收到后更新主题模块。
- 控制类请求处理完成后统一返回 `Response`
- 不支持的消息类型也应返回 `Response { error_code = UNKNOWN_TYPE }`,不得静默丢弃。
## 12. Response 规则
- `HelloReq` 不返回 `Response`,而是直接返回 `HelloRsp`,且 `reply_to = HelloReq.msg_id`
- `Bitmap``TimeSync``ThemeRgb` 处理完成后应返回 `Response`
- `Response.error_code = OK` 表示成功。
- 不支持的消息类型返回 `Response { error_code = UNKNOWN_TYPE }`
- 长度错误、参数错误、模块未就绪时分别返回 `INVALID_LENGTH``INVALID_PARAM``NOT_READY`
示例:
- 收到 `Bitmap` 成功处理
返回 `Response { error_code = OK }`
- 收到 `ThemeRgb` 参数非法
返回 `Response { error_code = INVALID_PARAM }`

View File

@@ -0,0 +1,476 @@
# Blinky 键盘 2.4G 端详细设计稿
## 1. 背景
当前 `C:\projects\blinky` 已经完成了外设侧多槽 BLE 基础能力:
- `Slot 1~3` 对应普通 BLE 主机
- `identity 4` 预留为 `Dongle Slot`
- `mode_policy_module` 已经区分:
- `MODE_SWITCH_BLE -> BLE_PROFILE_POLICY_GENERAL`
- `MODE_SWITCH_24G -> BLE_PROFILE_POLICY_DONGLE`
但目前 `identity 4` 还只是“预留位”,没有真正落成完整的 dongle peer 行为。
本设计稿的目标,是定义如何在**键盘外设固件内**实现“2.4G 端”,参考 `nrf_desktop` 的外围设备侧 dongle peer 设计。
这里的“2.4G 端”在第一阶段的实现语义不是 ESB 或私有无线协议,而是:
- 键盘继续作为 BLE Peripheral
- 通过专用 local identity 与自家 dongle 建立专用连接
- 在产品层把该模式呈现为“2.4G”
这和 `nrf_desktop``dongle peer` 设计方向一致。
## 2. 设计目标
本方案的目标如下:
- 在当前键盘固件内实现专用 `dongle peer`
- `MODE_SWITCH_24G` 时固定使用专用 `identity 4`
- `MODE_SWITCH_BLE` 时继续使用普通 BLE 槽位 `identity 1/2/3`
- `dongle peer` 与普通 BLE 槽位完全隔离
- `dongle peer` 有独立 bond 和独立状态显示
- Swift Pair 对普通 BLE 开启,对 `dongle peer` 默认关闭
- 后续为自家 dongle 识别准备一个最小 `dev_descr` 风格自定义 GATT 服务
本设计稿不包含“dongle central 固件”的实现,只定义键盘外设侧。
## 3. 参考基线
本设计主要参考本地 `nrf_desktop` 的外围设备侧设计:
- `c:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\ble_bond.rst`
- `c:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\dev_descr.rst`
- `c:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\qos.rst`
- `c:\ncs\v3.2.3\nrf\applications\nrf_desktop\src\modules\dev_descr.c`
- `c:\ncs\v3.2.3\nrf\applications\nrf_desktop\src\modules\qos.c`
参考结论:
- `dongle peer` 的核心不是新的无线协议,而是**专用 Bluetooth local identity**
- 外设与 dongle 的专用配对要和普通 BLE peer 隔离
- `dev_descr` 是未来 dongle 识别外设的关键入口
- `QoS` 是增强项,不是第一阶段前置条件
## 4. 当前工程现状
当前工程已经具备实现 `dongle peer` 的关键基础:
- `ble_bond_multi_module` 已支持固定 identity 多槽
- `identity 1/2/3` 已用于普通 BLE 槽位
- `identity 4` 已预留为 `Dongle Slot`
- `mode_policy_module` 已输出 `BLE_PROFILE_POLICY_GENERAL / DONGLE`
- Swift Pair 已默认开启,并且在 `identity 4` 上默认关闭
相关文件:
- `C:\projects\blinky\src\ble_bond_multi_module.c`
- `C:\projects\blinky\src\mode_policy_module.c`
- `C:\projects\blinky\inc\events\transport_policy_event.h`
- `C:\projects\blinky\src\swift_pair_module.c`
当前缺失点:
- `MODE_SWITCH_24G` 没有真正强制切到 `identity 4`
- `dongle peer` 没有独立 UI 状态
- 没有 `dev_descr` 风格自定义服务
- 没有独立的 dongle 管理接口
## 5. 总体设计
## 5.1 核心设计结论
采用 `nrf_desktop` 风格的“专用 dongle peer”设计
- 保持当前键盘为 BLE Peripheral
- 使用 `identity 4` 作为专用 dongle identity
-`MODE_SWITCH_24G` 作为选择 `dongle peer` 的产品模式
- 普通 BLE 槽位 `1/2/3``dongle peer` 独立管理
也就是说:
- `2.4G` 是产品语义
- 第一阶段实现仍然是 BLE Peripheral + 专用 identity
## 5.2 identity 规划
identity 规划继续固定如下:
| Identity | 角色 | 用途 |
| --- | --- | --- |
| `0` | 默认 identity | 不使用 |
| `1` | BLE Slot 1 | 普通 BLE |
| `2` | BLE Slot 2 | 普通 BLE |
| `3` | BLE Slot 3 | 普通 BLE |
| `4` | Dongle Slot | 专用 2.4G / dongle peer |
设计原则:
- `identity 4` 不参与普通槽位轮换
- `identity 4` 不参与普通 BLE 设置页
- `identity 4` 有独立 bond 和独立状态
## 6. 模块职责分工
## 6.1 `mode_policy_module`
职责保持不变:
- 根据拨杆位置输出 transport policy
当前定义:
- `MODE_SWITCH_BLE -> BLE_PROFILE_POLICY_GENERAL`
- `MODE_SWITCH_24G -> BLE_PROFILE_POLICY_DONGLE`
它不直接管理 bond只输出“当前应该走哪种 BLE profile”。
## 6.2 `ble_bond_multi_module`
这是本方案第一阶段的主修改模块。
需要扩展职责:
- 支持根据 `BLE_PROFILE_POLICY_DONGLE` 固定选择 `identity 4`
- 普通 BLE 与 `dongle peer` 分开管理
- 提供 `dongle peer` 独立状态输出
- 提供 `dongle peer` 独立擦除操作
仍然保留已有职责:
- 普通 BLE 三槽切换
- 普通 BLE 三槽擦除
- slot meta 持久化
## 6.3 `swift_pair_module`
职责保持:
- 普通 BLE 槽位启用 Swift Pair
- `dongle peer` 默认关闭 Swift Pair payload
这和 `nrf_desktop` 的思路一致。
## 6.4 新增 `dev_descr_module`
建议新增一个最小版 `dev_descr` 风格服务。
目标:
- 给未来的 dongle 提供外设识别信息
- 不依赖设备名做唯一识别
第一阶段最小字段:
- `caps`
- `hwid`
后续可扩展:
- `role`
- `product type`
- `firmware revision`
## 6.5 `qos_module`
第一阶段不做。
理由:
- `nrf_desktop` 已明确说明自家 dongle 不依赖外围设备 QoS
- 当前先做 `dongle peer` identity 和识别就足够
## 7. 行为模型
## 7.1 普通 BLE 模式
条件:
- `MODE_SWITCH_BLE`
- `BLE_PROFILE_POLICY_GENERAL`
行为:
- 当前 active slot 只能为 `1/2/3`
- UI 显示普通 BLE 三槽
- 允许普通 BLE 切槽和擦除
- Swift Pair 默认开启
## 7.2 2.4G / Dongle 模式
条件:
- `MODE_SWITCH_24G`
- `BLE_PROFILE_POLICY_DONGLE`
行为:
- 当前 active identity 固定为 `4`
- 当前 bond 只属于 `identity 4`
- 不允许普通 BLE 三槽逻辑影响 `identity 4`
- Swift Pair 默认关闭
- UI 应显示:
- `Dongle Searching`
- `Dongle Paired`
- `Dongle Connected`
- `Dongle Empty`
## 7.3 模式切换语义
### BLE -> 24G
1. `mode_policy_module` 发布 `BLE_PROFILE_POLICY_DONGLE`
2. `ble_bond_multi_module` 切 active identity 到 `4`
3. 提交 `PEER_OPERATION_SELECTED`
4. CAF `ble_adv` 切到 `identity 4`
5. 当前连接断开
6. 使用 `identity 4` 广播
7. dongle 重连
### 24G -> BLE
1. `mode_policy_module` 发布 `BLE_PROFILE_POLICY_GENERAL`
2. `ble_bond_multi_module` 恢复普通 BLE 当前槽位 `1/2/3`
3. 提交 `PEER_OPERATION_SELECTED`
4. CAF `ble_adv` 切回对应 identity
5. 当前连接断开
6. 使用普通 BLE 槽位广播
## 8. 状态与事件设计
## 8.1 当前已有事件
可继续复用:
- `transport_policy_event`
- `ble_peer_event`
- `ble_peer_operation_event`
- `ble_bond_multi_event`
## 8.2 建议扩展 `ble_bond_multi_event`
建议增加字段或语义:
- `active_profile`
- `GENERAL`
- `DONGLE`
- `dongle_slot_meta`
- `occupied`
- `last_peer_addr`
- `display_name`
- `current_identity`
如果不想改现有事件结构,也可以新增一个独立事件:
- `ble_dongle_state_event`
推荐字段:
- `state`
- `EMPTY`
- `SEARCHING`
- `PAIRED`
- `CONNECTED`
- `identity_id`
- `display_name`
## 8.3 UI 状态来源
主界面与设置页可以通过以下信息判断:
- 当前 `mode`
- 当前 `active_identity_id`
- `identity 4` 是否有 bond
- 当前是否有连接且该连接属于 `identity 4`
## 9. `dev_descr` 设计
## 9.1 目标
最小化模仿 `nrf_desktop dev_descr`
- 为 dongle 提供一个稳定可读的识别服务
- 避免仅靠设备名判断
## 9.2 服务内容
建议服务包含两个 characteristic
### Characteristic 1: `caps`
建议字节布局:
- bit0: `supports_llpm`
- bit1: `supports_dongle_peer`
- bit2: `is_keyboard`
第一阶段只要能读出:
- 这个设备支持 dongle peer
- 这是 keyboard 类型
### Characteristic 2: `hwid`
固定长度硬件 ID
- 用于以后让 dongle 唯一识别这把键盘
- 也可用于配置通道映射
## 9.3 权限建议
建议与 `nrf_desktop` 保持一致:
- `BT_GATT_PERM_READ_ENCRYPT`
原因:
- 避免未加密链路上暴露识别信息
## 9.4 依赖
需要:
- `CONFIG_HWINFO`
- 固件内已有或新增 `hwid_get()` 辅助逻辑
## 10. Settings 设计
继续沿用现有 namespace
- `ble_multi/current_slot`
- `ble_multi/meta/1`
- `ble_multi/meta/2`
- `ble_multi/meta/3`
- `ble_multi/meta/4`
其中 `meta/4` 现在正式用于 dongle peer。
建议 `meta/4` 字段仍保持:
- `occupied`
- `last_peer_addr`
- `display_name`
显示策略:
- 有真实名字则显示名字
- 否则显示 MAC 地址
- 无 bond 显示 `Empty`
## 11. UI 设计
## 11.1 主界面
当前主界面可新增或扩展显示:
- 当前模式 `USB / BLE / 24G`
- 如果 `24G` 模式:
- 右上角额外状态位显示:
- searching
- connected
现有 BLE LINK 状态位可以复用,但语义建议细化:
- BLE 模式下:表示普通 BLE 当前槽状态
- 24G 模式下:表示 dongle peer 状态
## 11.2 设置页
建议新增二级入口:
- `Bluetooth`
- Slot 1
- Slot 2
- Slot 3
- Erase Current
- `Dongle`
- Status
- Erase Dongle Bond
第一阶段如果不想扩菜单,至少要做到:
- 主界面显示 dongle peer 状态
- 设置页不把 `identity 4` 混入 Slot 1~3
## 12. 分阶段实施建议
## 阶段 1Dongle Slot 真正接线
内容:
- `ble_bond_multi_module` 支持 `BLE_PROFILE_POLICY_DONGLE`
- `MODE_SWITCH_24G` 时固定切到 `identity 4`
- `identity 4` 独立 bond 和独立状态
- Swift Pair 在 `identity 4` 默认关闭
交付标准:
- 24G 模式和 BLE 模式切换时 identity 正确切换
- `identity 4``1/2/3` 完全隔离
## 阶段 2UI / 状态管理
内容:
- 增加 dongle 状态显示
- 增加独立擦除 dongle bond 入口
- 不把 dongle 混入普通 BLE 三槽 UI
交付标准:
- 用户可以明确区分:
- 普通 BLE 槽位
- 2.4G / dongle peer
## 阶段 3最小 `dev_descr`
内容:
- 实现最小自定义 GATT service
- 提供 `caps + hwid`
交付标准:
- 未来 dongle 可稳定识别这把键盘
## 阶段 4后续增强非当前范围
可选项:
- QoS
- 更强的 dongle 配对约束
- 生产预绑定
- 更明确的 dongle selector/恢复流程
## 13. 风险与边界
## 13.1 “2.4G” 实际仍然是 BLE
第一阶段产品语义是 2.4G,链路实现仍是 BLE。
这没有问题,但必须在代码和文档中明确,避免后续和真正私有 2.4G 链路混淆。
## 13.2 `dev_descr` 与未来 dongle 要对齐
如果未来 dongle 端也参考 `nrf_desktop central`,那这里的 UUID 和字段应该尽量稳定,不要后面再频繁改。
## 13.3 `identity 4` 与普通 UI 必须隔离
一旦 `identity 4` 被误混入普通三槽,就会造成用户理解混乱:
- 不知道哪个是普通蓝牙
- 哪个是 2.4G
这是 UI 设计上最需要避免的问题。
## 14. 结论
本方案建议把当前键盘的“2.4G 端”实现为:
- `BLE Peripheral + 专用 Dongle identity`
- `MODE_SWITCH_24G` 时固定走 `identity 4`
- 与普通 BLE 三槽完全隔离
- 第一阶段先打通 identity / bond / UI 状态
- 第二阶段补最小 `dev_descr`
- `QoS` 暂缓
这条路径与 `nrf_desktop` 外设侧的 dongle peer 设计一致,也与当前工程已有的 `transport_policy_event` 语义完全匹配。

15
inc/click_detector_def.h Normal file
View File

@@ -0,0 +1,15 @@
/*
* This configuration file is included only once from the CAF click detector
* module and defines the keys that should produce click events.
*/
#include <caf/click_detector.h>
const struct {} click_detector_def_include_once;
static const struct click_detector_config click_detector_config[] = {
{
.key_id = 0x180,
.consume_button_event = true,
},
};

View File

@@ -20,6 +20,16 @@ struct bat_state_event {
APP_EVENT_TYPE_DECLARE(bat_state_event); APP_EVENT_TYPE_DECLARE(bat_state_event);
static inline void submit_bat_state_event(uint8_t soc, bool charging, bool full)
{
struct bat_state_event *event = new_bat_state_event();
event->soc = soc;
event->charging = charging;
event->full = full;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -0,0 +1,46 @@
#ifndef BLINKY_BLE_BOND_MULTI_EVENT_H_
#define BLINKY_BLE_BOND_MULTI_EVENT_H_
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include <zephyr/bluetooth/addr.h>
#ifdef __cplusplus
extern "C" {
#endif
#define BLE_BOND_MULTI_DISPLAY_NAME_MAX_LEN 32U
#define BLE_BOND_MULTI_BLE_SLOT_COUNT 3U
#define BLE_BOND_MULTI_DONGLE_SLOT_ID 4U
enum ble_bond_multi_op {
BLE_BOND_MULTI_OP_NONE = 0,
BLE_BOND_MULTI_OP_REFRESH,
BLE_BOND_MULTI_OP_SWITCH,
BLE_BOND_MULTI_OP_ERASE,
};
struct ble_bond_multi_slot_meta {
bool occupied;
bt_addr_le_t last_peer_addr;
char display_name[BLE_BOND_MULTI_DISPLAY_NAME_MAX_LEN];
};
struct ble_bond_multi_event {
struct app_event_header header;
uint8_t current_slot;
uint8_t active_identity_id;
enum ble_bond_multi_op op;
uint8_t slot_occupied_bitmap;
struct ble_bond_multi_slot_meta slots[BLE_BOND_MULTI_BLE_SLOT_COUNT];
};
APP_EVENT_TYPE_DECLARE(ble_bond_multi_event);
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_BLE_BOND_MULTI_EVENT_H_ */

View File

@@ -0,0 +1,45 @@
#ifndef BLINKY_DATETIME_EVENT_H_
#define BLINKY_DATETIME_EVENT_H_
#include <errno.h>
#include <string.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#ifdef __cplusplus
extern "C" {
#endif
#define DATETIME_EVENT_DATE_TEXT_LEN 16
#define DATETIME_EVENT_TIME_TEXT_LEN 16
struct datetime_event {
struct app_event_header header;
char date_text[DATETIME_EVENT_DATE_TEXT_LEN];
char time_text[DATETIME_EVENT_TIME_TEXT_LEN];
};
APP_EVENT_TYPE_DECLARE(datetime_event);
static inline int submit_datetime_event(const char *date_text, const char *time_text)
{
struct datetime_event *event = new_datetime_event();
if ((date_text == NULL) || (time_text == NULL)) {
return -EINVAL;
}
strncpy(event->date_text, date_text, sizeof(event->date_text));
event->date_text[sizeof(event->date_text) - 1] = '\0';
strncpy(event->time_text, time_text, sizeof(event->time_text));
event->time_text[sizeof(event->time_text) - 1] = '\0';
APP_EVENT_SUBMIT(event);
return 0;
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_DATETIME_EVENT_H_ */

View File

@@ -15,6 +15,14 @@ struct encoder_event {
APP_EVENT_TYPE_DECLARE(encoder_event); APP_EVENT_TYPE_DECLARE(encoder_event);
static inline void submit_encoder_event(int8_t detents)
{
struct encoder_event *event = new_encoder_event();
event->detents = detents;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -0,0 +1,41 @@
#ifndef BLINKY_FUNCTION_BITMAP_STATE_EVENT_H_
#define BLINKY_FUNCTION_BITMAP_STATE_EVENT_H_
#include <errno.h>
#include <string.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "keyboard_core.h"
#ifdef __cplusplus
extern "C" {
#endif
struct function_bitmap_state_event {
struct app_event_header header;
uint8_t bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES];
};
APP_EVENT_TYPE_DECLARE(function_bitmap_state_event);
static inline int submit_function_bitmap_state_event(const uint8_t *bitmap)
{
struct function_bitmap_state_event *event;
if (bitmap == NULL) {
return -EINVAL;
}
event = new_function_bitmap_state_event();
memcpy(event->bitmap, bitmap, sizeof(event->bitmap));
APP_EVENT_SUBMIT(event);
return 0;
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_FUNCTION_BITMAP_STATE_EVENT_H_ */

View File

@@ -0,0 +1,41 @@
#ifndef BLINKY_FUNCTION_BITMAP_UPDATE_EVENT_H_
#define BLINKY_FUNCTION_BITMAP_UPDATE_EVENT_H_
#include <errno.h>
#include <string.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "keyboard_core.h"
#ifdef __cplusplus
extern "C" {
#endif
struct function_bitmap_update_event {
struct app_event_header header;
uint8_t bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES];
};
APP_EVENT_TYPE_DECLARE(function_bitmap_update_event);
static inline int submit_function_bitmap_update_event(const uint8_t *bitmap)
{
struct function_bitmap_update_event *event;
if (bitmap == NULL) {
return -EINVAL;
}
event = new_function_bitmap_update_event();
memcpy(event->bitmap, bitmap, sizeof(event->bitmap));
APP_EVENT_SUBMIT(event);
return 0;
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_FUNCTION_BITMAP_UPDATE_EVENT_H_ */

View File

@@ -0,0 +1,38 @@
#ifndef BLINKY_HID_CHANNEL_STATE_EVENT_H_
#define BLINKY_HID_CHANNEL_STATE_EVENT_H_
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "keyboard_core.h"
#ifdef __cplusplus
extern "C" {
#endif
struct hid_channel_state_event {
struct app_event_header header;
enum hid_send_channel channel;
uint8_t report_ready_bm;
enum keyboard_protocol_mode protocol_mode;
};
APP_EVENT_TYPE_DECLARE(hid_channel_state_event);
static inline void submit_hid_channel_state_event(
enum hid_send_channel channel, uint8_t report_ready_bm,
enum keyboard_protocol_mode protocol_mode)
{
struct hid_channel_state_event *event = new_hid_channel_state_event();
event->channel = channel;
event->report_ready_bm = report_ready_bm;
event->protocol_mode = protocol_mode;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_HID_CHANNEL_STATE_EVENT_H_ */

View File

@@ -18,6 +18,15 @@ struct hid_led_event {
APP_EVENT_TYPE_DECLARE(hid_led_event); APP_EVENT_TYPE_DECLARE(hid_led_event);
static inline void submit_hid_led_event(enum hid_transport transport, uint8_t led_bm)
{
struct hid_led_event *event = new_hid_led_event();
event->transport = transport;
event->led_bm = led_bm;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -12,7 +12,7 @@ extern "C" {
struct hid_report_sent_event { struct hid_report_sent_event {
struct app_event_header header; struct app_event_header header;
enum hid_transport transport; enum hid_send_channel channel;
enum keyboard_report_type report_type; enum keyboard_report_type report_type;
uint16_t sequence; uint16_t sequence;
bool error; bool error;
@@ -20,6 +20,19 @@ struct hid_report_sent_event {
APP_EVENT_TYPE_DECLARE(hid_report_sent_event); APP_EVENT_TYPE_DECLARE(hid_report_sent_event);
static inline void submit_hid_report_sent_event(enum hid_send_channel channel,
enum keyboard_report_type report_type,
uint16_t sequence, bool error)
{
struct hid_report_sent_event *event = new_hid_report_sent_event();
event->channel = channel;
event->report_type = report_type;
event->sequence = sequence;
event->error = error;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -1,28 +0,0 @@
#ifndef BLINKY_HID_TRANSPORT_STATE_EVENT_H_
#define BLINKY_HID_TRANSPORT_STATE_EVENT_H_
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "keyboard_core.h"
#ifdef __cplusplus
extern "C" {
#endif
struct hid_transport_state_event {
struct app_event_header header;
enum hid_transport transport;
bool ready;
bool keys_ready;
bool consumer_ready;
enum keyboard_protocol_mode protocol_mode;
};
APP_EVENT_TYPE_DECLARE(hid_transport_state_event);
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_HID_TRANSPORT_STATE_EVENT_H_ */

View File

@@ -1,6 +1,10 @@
#ifndef BLINKY_HID_TX_REPORT_EVENT_H_ #ifndef BLINKY_HID_TX_REPORT_EVENT_H_
#define BLINKY_HID_TX_REPORT_EVENT_H_ #define BLINKY_HID_TX_REPORT_EVENT_H_
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <app_event_manager.h> #include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h> #include <app_event_manager_profiler_tracer.h>
@@ -12,7 +16,7 @@ extern "C" {
struct hid_tx_report_event { struct hid_tx_report_event {
struct app_event_header header; struct app_event_header header;
enum hid_transport transport; enum hid_send_channel channel;
enum keyboard_report_type report_type; enum keyboard_report_type report_type;
enum keyboard_protocol_mode protocol_mode; enum keyboard_protocol_mode protocol_mode;
uint16_t sequence; uint16_t sequence;
@@ -21,6 +25,31 @@ struct hid_tx_report_event {
APP_EVENT_TYPE_DYNDATA_DECLARE(hid_tx_report_event); APP_EVENT_TYPE_DYNDATA_DECLARE(hid_tx_report_event);
static inline int submit_hid_tx_report_event(enum hid_send_channel channel,
enum keyboard_report_type report_type,
enum keyboard_protocol_mode protocol_mode,
uint16_t sequence,
const uint8_t *data, size_t size)
{
struct hid_tx_report_event *event;
if ((data == NULL) && (size > 0U)) {
return -EINVAL;
}
event = new_hid_tx_report_event(size);
event->channel = channel;
event->report_type = report_type;
event->protocol_mode = protocol_mode;
event->sequence = sequence;
if (size > 0U) {
memcpy(event->dyndata.data, data, size);
}
APP_EVENT_SUBMIT(event);
return 0;
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -1,6 +1,10 @@
#ifndef BLINKY_KEYBOARD_HID_REPORT_EVENT_H_ #ifndef BLINKY_KEYBOARD_HID_REPORT_EVENT_H_
#define BLINKY_KEYBOARD_HID_REPORT_EVENT_H_ #define BLINKY_KEYBOARD_HID_REPORT_EVENT_H_
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <app_event_manager.h> #include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h> #include <app_event_manager_profiler_tracer.h>
@@ -22,6 +26,30 @@ struct keyboard_hid_report_event {
APP_EVENT_TYPE_DYNDATA_DECLARE(keyboard_hid_report_event); APP_EVENT_TYPE_DYNDATA_DECLARE(keyboard_hid_report_event);
static inline int submit_keyboard_hid_report_event(
enum mode_switch_mode mode, enum keyboard_report_type report_type,
enum keyboard_protocol_mode protocol_mode,
enum hid_queue_policy queue_policy, const uint8_t *data, size_t size)
{
struct keyboard_hid_report_event *event;
if ((data == NULL) && (size > 0U)) {
return -EINVAL;
}
event = new_keyboard_hid_report_event(size);
event->mode = mode;
event->report_type = report_type;
event->protocol_mode = protocol_mode;
event->queue_policy = queue_policy;
if (size > 0U) {
memcpy(event->dyndata.data, data, size);
}
APP_EVENT_SUBMIT(event);
return 0;
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -0,0 +1,32 @@
#ifndef BLINKY_LED_STRIP_EN_EVENT_H_
#define BLINKY_LED_STRIP_EN_EVENT_H_
#include <stdbool.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#ifdef __cplusplus
extern "C" {
#endif
struct led_strip_en_event {
struct app_event_header header;
bool enabled;
};
APP_EVENT_TYPE_DECLARE(led_strip_en_event);
static inline void submit_led_strip_en_event(bool enabled)
{
struct led_strip_en_event *event = new_led_strip_en_event();
event->enabled = enabled;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_LED_STRIP_EN_EVENT_H_ */

View File

@@ -9,6 +9,7 @@ extern "C" {
#endif #endif
enum mode_switch_mode { enum mode_switch_mode {
MODE_SWITCH_INVALID,
MODE_SWITCH_USB, MODE_SWITCH_USB,
MODE_SWITCH_24G, MODE_SWITCH_24G,
MODE_SWITCH_BLE, MODE_SWITCH_BLE,
@@ -22,6 +23,16 @@ struct mode_switch_event {
APP_EVENT_TYPE_DECLARE(mode_switch_event); APP_EVENT_TYPE_DECLARE(mode_switch_event);
static inline void submit_mode_switch_event(enum mode_switch_mode mode,
uint16_t voltage_mv)
{
struct mode_switch_event *event = new_mode_switch_event();
event->mode = mode;
event->voltage_mv = voltage_mv;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

28
inc/events/proto_common.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef BLINKY_PROTO_COMMON_H_
#define BLINKY_PROTO_COMMON_H_
#ifdef __cplusplus
extern "C" {
#endif
enum proto_transport {
PROTO_TRANSPORT_USB_CDC = 0,
PROTO_TRANSPORT_BLE_NUS,
PROTO_TRANSPORT_COUNT,
};
enum proto_transport_link_state {
PROTO_TRANSPORT_LINK_DOWN = 0,
PROTO_TRANSPORT_LINK_READY,
};
#define PROTO_FRAME_MAGIC 0xAA55U
#define PROTO_FRAME_HEADER_SIZE 3U
#define PROTO_MAX_PAYLOAD_LEN 64U
#define PROTO_MAX_FRAME_LEN (PROTO_FRAME_HEADER_SIZE + PROTO_MAX_PAYLOAD_LEN)
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_PROTO_COMMON_H_ */

View File

@@ -0,0 +1,51 @@
#ifndef BLINKY_PROTO_RX_EVENT_H_
#define BLINKY_PROTO_RX_EVENT_H_
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "proto_common.h"
#ifdef __cplusplus
extern "C" {
#endif
struct proto_rx_event {
struct app_event_header header;
enum proto_transport transport;
struct event_dyndata dyndata;
};
APP_EVENT_TYPE_DYNDATA_DECLARE(proto_rx_event);
static inline int submit_proto_rx_event(enum proto_transport transport,
const uint8_t *data, size_t len)
{
struct proto_rx_event *event;
if ((transport >= PROTO_TRANSPORT_COUNT) ||
((data == NULL) && (len > 0U)) ||
(len > PROTO_MAX_FRAME_LEN)) {
return -EINVAL;
}
event = new_proto_rx_event(len);
event->transport = transport;
if (len > 0U) {
memcpy(event->dyndata.data, data, len);
}
APP_EVENT_SUBMIT(event);
return 0;
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_PROTO_RX_EVENT_H_ */

View File

@@ -0,0 +1,36 @@
#ifndef BLINKY_PROTO_TRANSPORT_STATE_EVENT_H_
#define BLINKY_PROTO_TRANSPORT_STATE_EVENT_H_
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "proto_common.h"
#ifdef __cplusplus
extern "C" {
#endif
struct proto_transport_state_event {
struct app_event_header header;
enum proto_transport transport;
enum proto_transport_link_state state;
};
APP_EVENT_TYPE_DECLARE(proto_transport_state_event);
static inline void submit_proto_transport_state_event(
enum proto_transport transport, enum proto_transport_link_state state)
{
struct proto_transport_state_event *event =
new_proto_transport_state_event();
event->transport = transport;
event->state = state;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_PROTO_TRANSPORT_STATE_EVENT_H_ */

View File

@@ -0,0 +1,51 @@
#ifndef BLINKY_PROTO_TX_EVENT_H_
#define BLINKY_PROTO_TX_EVENT_H_
#include <errno.h>
#include <stddef.h>
#include <string.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "proto_common.h"
#ifdef __cplusplus
extern "C" {
#endif
struct proto_tx_event {
struct app_event_header header;
enum proto_transport transport;
struct event_dyndata dyndata;
};
APP_EVENT_TYPE_DYNDATA_DECLARE(proto_tx_event);
static inline int submit_proto_tx_event(enum proto_transport transport,
const uint8_t *data, size_t len)
{
struct proto_tx_event *event;
if ((transport >= PROTO_TRANSPORT_COUNT) ||
((data == NULL) && (len > 0U)) ||
(len > PROTO_MAX_FRAME_LEN)) {
return -EINVAL;
}
event = new_proto_tx_event(len);
event->transport = transport;
if (len > 0U) {
memcpy(event->dyndata.data, data, len);
}
APP_EVENT_SUBMIT(event);
return 0;
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_PROTO_TX_EVENT_H_ */

View File

@@ -18,6 +18,16 @@ struct set_protocol_event {
APP_EVENT_TYPE_DECLARE(set_protocol_event); APP_EVENT_TYPE_DECLARE(set_protocol_event);
static inline void submit_set_protocol_event(enum hid_transport transport,
enum keyboard_protocol_mode protocol_mode)
{
struct set_protocol_event *event = new_set_protocol_event();
event->transport = transport;
event->protocol_mode = protocol_mode;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -0,0 +1,30 @@
#ifndef BLINKY_SETTINGS_MODE_EVENT_H_
#define BLINKY_SETTINGS_MODE_EVENT_H_
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#ifdef __cplusplus
extern "C" {
#endif
struct settings_mode_event {
struct app_event_header header;
bool active;
};
APP_EVENT_TYPE_DECLARE(settings_mode_event);
static inline void submit_settings_mode_event(bool active)
{
struct settings_mode_event *event = new_settings_mode_event();
event->active = active;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_SETTINGS_MODE_EVENT_H_ */

View File

@@ -0,0 +1,35 @@
#ifndef BLINKY_SETTINGS_VIEW_EVENT_H_
#define BLINKY_SETTINGS_VIEW_EVENT_H_
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "ui/ui_settings_page.h"
#ifdef __cplusplus
extern "C" {
#endif
struct settings_view_event {
struct app_event_header header;
struct ui_settings_page *page;
bool animate;
};
APP_EVENT_TYPE_DECLARE(settings_view_event);
static inline void submit_settings_view_event(struct ui_settings_page *page,
bool animate)
{
struct settings_view_event *event = new_settings_view_event();
event->page = page;
event->animate = animate;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_SETTINGS_VIEW_EVENT_H_ */

View File

@@ -0,0 +1,32 @@
#ifndef BLINKY_THEME_RGB_UPDATE_EVENT_H_
#define BLINKY_THEME_RGB_UPDATE_EVENT_H_
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "theme_color.h"
#ifdef __cplusplus
extern "C" {
#endif
struct theme_rgb_update_event {
struct app_event_header header;
struct theme_rgb theme;
};
APP_EVENT_TYPE_DECLARE(theme_rgb_update_event);
static inline void submit_theme_rgb_update_event(struct theme_rgb theme)
{
struct theme_rgb_update_event *event = new_theme_rgb_update_event();
event->theme = theme;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_THEME_RGB_UPDATE_EVENT_H_ */

View File

@@ -0,0 +1,40 @@
#ifndef BLINKY_TIME_SYNC_EVENT_H_
#define BLINKY_TIME_SYNC_EVENT_H_
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#ifdef __cplusplus
extern "C" {
#endif
struct time_sync_event {
struct app_event_header header;
uint32_t version;
uint32_t flags;
int32_t timezone_min;
uint64_t utc_ms;
uint32_t accuracy_ms;
};
APP_EVENT_TYPE_DECLARE(time_sync_event);
static inline void submit_time_sync_event(uint32_t version, uint32_t flags,
int32_t timezone_min, uint64_t utc_ms,
uint32_t accuracy_ms)
{
struct time_sync_event *event = new_time_sync_event();
event->version = version;
event->flags = flags;
event->timezone_min = timezone_min;
event->utc_ms = utc_ms;
event->accuracy_ms = accuracy_ms;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_TIME_SYNC_EVENT_H_ */

View File

@@ -0,0 +1,42 @@
#ifndef BLINKY_TRANSPORT_POLICY_EVENT_H_
#define BLINKY_TRANSPORT_POLICY_EVENT_H_
#include <app_event_manager.h>
#include "mode_switch_event.h"
enum hid_transport_policy {
HID_TRANSPORT_POLICY_NONE = 0,
HID_TRANSPORT_POLICY_USB,
HID_TRANSPORT_POLICY_BLE,
};
enum ble_profile_policy {
BLE_PROFILE_POLICY_NONE = 0,
BLE_PROFILE_POLICY_GENERAL,
BLE_PROFILE_POLICY_DONGLE,
};
struct transport_policy_event {
struct app_event_header header;
enum mode_switch_mode source_mode;
enum hid_transport_policy hid_transport;
enum ble_profile_policy ble_profile;
};
APP_EVENT_TYPE_DECLARE(transport_policy_event);
static inline void submit_transport_policy_event(
enum mode_switch_mode source_mode,
enum hid_transport_policy hid_transport,
enum ble_profile_policy ble_profile)
{
struct transport_policy_event *event = new_transport_policy_event();
event->source_mode = source_mode;
event->hid_transport = hid_transport;
event->ble_profile = ble_profile;
APP_EVENT_SUBMIT(event);
}
#endif /* BLINKY_TRANSPORT_POLICY_EVENT_H_ */

View File

@@ -0,0 +1,71 @@
#ifndef BLINKY_USB_CONTROL_EVENT_H_
#define BLINKY_USB_CONTROL_EVENT_H_
#include <stdbool.h>
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include <zephyr/device.h>
#ifdef __cplusplus
extern "C" {
#endif
enum usb_control_event_type {
USB_CONTROL_EVENT_CDC_LINE_STATE = 0,
USB_CONTROL_EVENT_CDC_LINE_CODING,
};
struct usb_control_event {
struct app_event_header header;
enum usb_control_event_type type;
const struct device *dev;
union {
struct {
bool dtr;
} cdc_line_state;
struct {
uint32_t baudrate;
uint8_t data_bits;
uint8_t stop_bits;
uint8_t parity;
uint8_t flow_ctrl;
} cdc_line_coding;
} data;
};
APP_EVENT_TYPE_DECLARE(usb_control_event);
static inline void submit_usb_control_cdc_line_state_event(
const struct device *dev, bool dtr)
{
struct usb_control_event *event = new_usb_control_event();
event->type = USB_CONTROL_EVENT_CDC_LINE_STATE;
event->dev = dev;
event->data.cdc_line_state.dtr = dtr;
APP_EVENT_SUBMIT(event);
}
static inline void submit_usb_control_cdc_line_coding_event(
const struct device *dev, uint32_t baudrate, uint8_t data_bits,
uint8_t stop_bits, uint8_t parity, uint8_t flow_ctrl)
{
struct usb_control_event *event = new_usb_control_event();
event->type = USB_CONTROL_EVENT_CDC_LINE_CODING;
event->dev = dev;
event->data.cdc_line_coding.baudrate = baudrate;
event->data.cdc_line_coding.data_bits = data_bits;
event->data.cdc_line_coding.stop_bits = stop_bits;
event->data.cdc_line_coding.parity = parity;
event->data.cdc_line_coding.flow_ctrl = flow_ctrl;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_USB_CONTROL_EVENT_H_ */

View File

@@ -0,0 +1,41 @@
#ifndef BLINKY_USB_STATE_EVENT_H_
#define BLINKY_USB_STATE_EVENT_H_
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include <zephyr/sys/util.h>
#ifdef __cplusplus
extern "C" {
#endif
enum usb_state {
USB_STATE_DISABLED = 0,
USB_STATE_DISCONNECTED,
USB_STATE_POWERED,
USB_STATE_ACTIVE,
USB_STATE_SUSPENDED,
};
struct usb_state_event {
struct app_event_header header;
enum usb_state state;
};
APP_EVENT_TYPE_DECLARE(usb_state_event);
static inline void submit_usb_state(enum usb_state state)
{
struct usb_state_event *event = new_usb_state_event();
event->state = state;
APP_EVENT_SUBMIT(event);
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_USB_STATE_EVENT_H_ */

View File

@@ -8,6 +8,8 @@ extern "C" {
#endif #endif
#define KEYBOARD_BOOT_REPORT_SIZE 8U #define KEYBOARD_BOOT_REPORT_SIZE 8U
#define KEYBOARD_PROTOCOL_USAGE_MAX 0xE7U
#define KEYBOARD_PROTOCOL_BITMAP_BYTES ((KEYBOARD_PROTOCOL_USAGE_MAX + 8U) / 8U)
#define KEYBOARD_NKRO_USAGE_MAX 0xDFU #define KEYBOARD_NKRO_USAGE_MAX 0xDFU
#define KEYBOARD_NKRO_BITMAP_BYTES ((KEYBOARD_NKRO_USAGE_MAX + 8U) / 8U) #define KEYBOARD_NKRO_BITMAP_BYTES ((KEYBOARD_NKRO_USAGE_MAX + 8U) / 8U)
#define KEYBOARD_NKRO_REPORT_SIZE (1U + KEYBOARD_NKRO_BITMAP_BYTES) #define KEYBOARD_NKRO_REPORT_SIZE (1U + KEYBOARD_NKRO_BITMAP_BYTES)
@@ -34,6 +36,13 @@ enum hid_transport {
HID_TRANSPORT_COUNT, HID_TRANSPORT_COUNT,
}; };
enum hid_send_channel {
HID_SEND_CH_USB_KEYS,
HID_SEND_CH_USB_CONSUMER,
HID_SEND_CH_BLE_SHARED,
HID_SEND_CH_COUNT,
};
enum keyboard_consumer_control { enum keyboard_consumer_control {
KEYBOARD_CONSUMER_CTRL_MUTE, KEYBOARD_CONSUMER_CTRL_MUTE,
KEYBOARD_CONSUMER_CTRL_VOLUME_UP, KEYBOARD_CONSUMER_CTRL_VOLUME_UP,

207
inc/module_lifecycle.h Normal file
View File

@@ -0,0 +1,207 @@
#ifndef MODULE_LIFECYCLE_H_
#define MODULE_LIFECYCLE_H_
#include <errno.h>
#include <stdbool.h>
#include <caf/events/module_state_event.h>
enum module_lifecycle {
LC_UNINIT,
LC_STOPPED,
LC_RUNNING,
LC_ERROR,
};
enum module_lifecycle_mode {
ML_MODE_NONE,
ML_MODE_POWER,
ML_MODE_SUSPEND,
};
struct module_lifecycle_ops {
int (*do_init)(void);
int (*do_start)(void);
int (*do_stop)(void);
};
struct module_lifecycle_cfg {
enum module_lifecycle_mode mode;
enum module_state stopped_state;
};
struct module_lifecycle_ctx {
enum module_lifecycle state;
const struct module_lifecycle_cfg *cfg;
const struct module_lifecycle_ops *ops;
};
static inline bool module_lifecycle_is_running(
const struct module_lifecycle_ctx *ctx)
{
return ctx->state == LC_RUNNING;
}
static inline bool module_lifecycle_is_initialized(
const struct module_lifecycle_ctx *ctx)
{
return (ctx->state != LC_UNINIT) && (ctx->state != LC_ERROR);
}
static inline const char *module_lifecycle_name(enum module_lifecycle state)
{
switch (state) {
case LC_UNINIT:
return "UNINIT";
case LC_STOPPED:
return "STOPPED";
case LC_RUNNING:
return "RUNNING";
case LC_ERROR:
return "ERROR";
default:
return "?";
}
}
static inline int module_lifecycle_validate_ops(
const struct module_lifecycle_ctx *ctx)
{
if ((ctx == NULL) || (ctx->cfg == NULL) || (ctx->ops == NULL) ||
(ctx->ops->do_init == NULL)) {
return -EINVAL;
}
switch (ctx->cfg->mode) {
case ML_MODE_NONE:
return 0;
case ML_MODE_POWER:
case ML_MODE_SUSPEND:
return (ctx->ops->do_start != NULL) && (ctx->ops->do_stop != NULL) ?
0 :
-EINVAL;
default:
return -EINVAL;
}
}
static inline int module_lifecycle_report_state(
struct module_lifecycle_ctx *ctx,
enum module_lifecycle state)
{
switch (state) {
case LC_RUNNING:
module_set_state(MODULE_STATE_READY);
return 0;
case LC_STOPPED:
switch (ctx->cfg->mode) {
case ML_MODE_POWER:
module_set_state(ctx->cfg->stopped_state);
return 0;
case ML_MODE_SUSPEND:
module_set_state(MODULE_STATE_SUSPENDED);
return 0;
case ML_MODE_NONE:
return 0;
default:
return -EINVAL;
}
case LC_ERROR:
module_set_state(MODULE_STATE_ERROR);
return 0;
case LC_UNINIT:
default:
return -EINVAL;
}
}
static inline int module_lifecycle_fail(struct module_lifecycle_ctx *ctx, int err)
{
ctx->state = LC_ERROR;
(void)module_lifecycle_report_state(ctx, LC_ERROR);
return err ? err : -EIO;
}
static inline int module_set_lifecycle(struct module_lifecycle_ctx *ctx,
enum module_lifecycle target)
{
int err;
err = module_lifecycle_validate_ops(ctx);
if (err) {
return err;
}
if (ctx->state == LC_ERROR) {
return -EFAULT;
}
if (ctx->state == target) {
return 0;
}
if (target == LC_ERROR) {
return module_lifecycle_fail(ctx, -EIO);
}
if ((target != LC_STOPPED) && (target != LC_RUNNING)) {
return -EPERM;
}
switch (ctx->state) {
case LC_UNINIT:
err = ctx->ops->do_init();
if (err) {
return module_lifecycle_fail(ctx, err);
}
ctx->state = LC_STOPPED;
err = module_lifecycle_report_state(ctx, LC_STOPPED);
if (err) {
return module_lifecycle_fail(ctx, err);
}
return module_set_lifecycle(ctx, target);
case LC_STOPPED:
if (target != LC_RUNNING) {
return -EPERM;
}
err = ctx->ops->do_start ? ctx->ops->do_start() : 0;
if (err) {
return module_lifecycle_fail(ctx, err);
}
ctx->state = LC_RUNNING;
return module_lifecycle_report_state(ctx, LC_RUNNING);
case LC_RUNNING:
if (target != LC_STOPPED) {
return -EPERM;
}
err = ctx->ops->do_stop ? ctx->ops->do_stop() : 0;
if (err) {
return module_lifecycle_fail(ctx, err);
}
ctx->state = LC_STOPPED;
return module_lifecycle_report_state(ctx, LC_STOPPED);
case LC_ERROR:
default:
return -EPERM;
}
}
#endif /* MODULE_LIFECYCLE_H_ */

25
inc/protocol_module.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef BLINKY_PROTOCOL_MODULE_H_
#define BLINKY_PROTOCOL_MODULE_H_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "proto_common.h"
#ifdef __cplusplus
extern "C" {
#endif
int protocol_module_process_message(enum proto_transport transport,
const uint8_t *req_payload,
size_t req_payload_len,
uint8_t *rsp_payload,
size_t rsp_payload_buf_size,
size_t *rsp_payload_len);
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_PROTOCOL_MODULE_H_ */

24
inc/theme_color.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef BLINKY_THEME_COLOR_H_
#define BLINKY_THEME_COLOR_H_
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define BLINKY_THEME_DEFAULT_R 0x4CU
#define BLINKY_THEME_DEFAULT_G 0x9EU
#define BLINKY_THEME_DEFAULT_B 0xF5U
struct theme_rgb {
uint8_t r;
uint8_t g;
uint8_t b;
};
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_THEME_COLOR_H_ */

66
inc/ui/ui_page.h Normal file
View File

@@ -0,0 +1,66 @@
#ifndef BLINKY_UI_PAGE_H_
#define BLINKY_UI_PAGE_H_
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
struct ui_page;
struct ui_page_ops {
void (*init)(struct ui_page *page);
void (*deinit)(struct ui_page *page);
void (*refresh)(struct ui_page *page);
};
struct ui_page {
const struct ui_page_ops *ops;
struct ui_page *parent;
bool initialized;
};
static inline void ui_page_init(struct ui_page *page)
{
if ((page == NULL) || (page->ops == NULL)) {
return;
}
if (page->initialized) {
return;
}
if (page->ops->init != NULL) {
page->ops->init(page);
}
page->initialized = true;
}
static inline void ui_page_deinit(struct ui_page *page)
{
if ((page == NULL) || !page->initialized || (page->ops == NULL)) {
return;
}
if (page->ops->deinit != NULL) {
page->ops->deinit(page);
}
page->initialized = false;
}
static inline void ui_page_refresh(struct ui_page *page)
{
if ((page == NULL) || !page->initialized ||
(page->ops == NULL) || (page->ops->refresh == NULL)) {
return;
}
page->ops->refresh(page);
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_UI_PAGE_H_ */

View File

@@ -0,0 +1,43 @@
#ifndef BLINKY_UI_SETTINGS_CONTROLLER_H_
#define BLINKY_UI_SETTINGS_CONTROLLER_H_
#include <stdbool.h>
#include <stdint.h>
#include "theme_color.h"
#include "ui_page.h"
#include "ui_settings_page.h"
#ifdef __cplusplus
extern "C" {
#endif
struct ble_bond_multi_slot_meta;
void ui_settings_controller_open(void);
void ui_settings_controller_close(void);
bool ui_settings_controller_back(void);
void ui_settings_controller_select(void);
void ui_settings_controller_move(int8_t delta);
void ui_settings_controller_refresh(bool animate);
bool ui_settings_controller_is_active(void);
void ui_settings_controller_switch_to(struct ui_settings_page *page,
struct ui_page *parent);
const char *ui_settings_ble_current_label(void);
const char *ui_settings_ble_slot_label(uint8_t slot);
void ui_settings_ble_select_slot(uint8_t slot);
void ui_settings_ble_erase_current(void);
void ui_settings_ble_set_current_slot(uint8_t slot);
void ui_settings_ble_set_slot_meta(uint8_t slot,
const struct ble_bond_multi_slot_meta *meta);
void ui_settings_theme_set_current(struct theme_rgb theme);
const char *ui_settings_theme_current_name(void);
uint8_t ui_settings_theme_current_index(void);
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_UI_SETTINGS_CONTROLLER_H_ */

56
inc/ui/ui_settings_page.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef BLINKY_UI_SETTINGS_PAGE_H_
#define BLINKY_UI_SETTINGS_PAGE_H_
#include <stdint.h>
#include <lvgl.h>
#include "ui_page.h"
#ifdef __cplusplus
extern "C" {
#endif
struct ui_settings_page;
struct ui_settings_item;
typedef void (*ui_settings_item_draw_fn)(const struct ui_settings_item *item,
lv_obj_t *container);
struct ui_settings_item {
const char *icon;
const char *title;
const char *value;
ui_settings_item_draw_fn draw;
void *user_data;
};
struct ui_settings_page_ops {
struct ui_page_ops base;
uint8_t (*get_count)(struct ui_settings_page *page);
void (*get_item)(struct ui_settings_page *page, uint8_t index,
struct ui_settings_item *item);
void (*on_enter)(struct ui_settings_page *page);
void (*on_select)(struct ui_settings_page *page, uint8_t index);
void (*on_back)(struct ui_settings_page *page);
};
struct ui_settings_page {
struct ui_page base;
const struct ui_settings_page_ops *ops;
const char *title;
const char *hint;
uint8_t selected;
};
static inline struct ui_settings_page *ui_page_to_settings(struct ui_page *page)
{
return (struct ui_settings_page *)page;
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_UI_SETTINGS_PAGE_H_ */

25
inc/usb_function_hook.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef BLINKY_USB_FUNCTION_HOOK_H_
#define BLINKY_USB_FUNCTION_HOOK_H_
#include <zephyr/sys/iterable_sections.h>
#ifdef __cplusplus
extern "C" {
#endif
struct usb_function_hook {
const char *name;
int (*pre_stack_init)(void);
};
#define USB_FUNCTION_HOOK_DEFINE(_name, _pre_stack_init) \
const STRUCT_SECTION_ITERABLE(usb_function_hook, _name) = { \
.name = STRINGIFY(_name), \
.pre_stack_init = (_pre_stack_init), \
}
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_USB_FUNCTION_HOOK_H_ */

View File

@@ -1,8 +1,14 @@
CONFIG_CAF=y CONFIG_CAF=y
CONFIG_CAF_BUTTONS=y CONFIG_CAF_BUTTONS=y
CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h" CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h"
CONFIG_CAF_CLICK_DETECTOR=y
CONFIG_CAF_CLICK_DETECTOR_DEF_PATH="click_detector_def.h"
CONFIG_CAF_CLICK_DETECTOR_LONG_CLICK_MSEC=1500
CONFIG_GPIO=y CONFIG_GPIO=y
CONFIG_I2C=y CONFIG_I2C=y
CONFIG_LED=y
CONFIG_PWM=y
CONFIG_SPI=y
CONFIG_NRFX_RTC2=y CONFIG_NRFX_RTC2=y
CONFIG_NRFX_GPPI=y CONFIG_NRFX_GPPI=y
CONFIG_NRFX_QDEC=y CONFIG_NRFX_QDEC=y
@@ -10,6 +16,9 @@ CONFIG_PINCTRL_DYNAMIC=y
CONFIG_REBOOT=y CONFIG_REBOOT=y
CONFIG_SENSOR=y CONFIG_SENSOR=y
CONFIG_ADC=y CONFIG_ADC=y
CONFIG_DISPLAY=y
CONFIG_DISPLAY_LOG_LEVEL_ERR=y
CONFIG_MIPI_DBI_LOG_LEVEL_ERR=y
CONFIG_SETTINGS=y CONFIG_SETTINGS=y
CONFIG_SETTINGS_NVS=y CONFIG_SETTINGS_NVS=y
CONFIG_FLASH=y CONFIG_FLASH=y
@@ -18,21 +27,46 @@ CONFIG_FLASH_MAP=y
CONFIG_NVS=y CONFIG_NVS=y
CONFIG_HEAP_MEM_POOL_SIZE=4096 CONFIG_HEAP_MEM_POOL_SIZE=4096
CONFIG_LOG=y CONFIG_LOG=y
CONFIG_LOG_BUFFER_SIZE=16384
CONFIG_LOG_BACKEND_UART=y
CONFIG_LOG_BACKEND_SHOW_COLOR=n
CONFIG_LOG_BACKEND_RTT_OUTPUT_BUFFER_SIZE=16384
CONFIG_ASSERT=y CONFIG_ASSERT=y
CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=16384
CONFIG_APP_EVENT_MANAGER_MAX_EVENT_CNT=64
CONFIG_LED_STRIP=y
CONFIG_WS2812_STRIP_SPI=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_STDOUT_CONSOLE=y
CONFIG_PRINTK=y
# USB HID next stack # USB HID next stack
CONFIG_USB_DEVICE_STACK_NEXT=y CONFIG_USB_DEVICE_STACK_NEXT=y
CONFIG_SERIAL=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=y
CONFIG_UART_USE_RUNTIME_CONFIGURE=y
CONFIG_NANOPB=y
CONFIG_USBD_HID_SUPPORT=y CONFIG_USBD_HID_SUPPORT=y
CONFIG_USBD_CDC_ACM_CLASS=y
CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n
# BLE # BLE
CONFIG_BT=y CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y CONFIG_BT_PERIPHERAL=y
CONFIG_BT_ZEPHYR_NUS=y
CONFIG_BT_SMP=y CONFIG_BT_SMP=y
CONFIG_BT_BONDABLE=y CONFIG_BT_BONDABLE=y
CONFIG_BT_SETTINGS=y CONFIG_BT_SETTINGS=y
CONFIG_BT_MAX_CONN=1 CONFIG_BT_MAX_CONN=1
CONFIG_BT_MAX_PAIRED=1 CONFIG_BT_MAX_PAIRED=4
CONFIG_BT_ID_MAX=5
CONFIG_BT_ATT_TX_COUNT=5 CONFIG_BT_ATT_TX_COUNT=5
CONFIG_BT_L2CAP_TX_MTU=65
CONFIG_BT_BUF_ACL_RX_SIZE=69
CONFIG_BT_BUF_ACL_TX_SIZE=69
CONFIG_BT_PERIPHERAL_PREF_TIMEOUT=400
CONFIG_BT_CONN_CTX=y CONFIG_BT_CONN_CTX=y
CONFIG_BT_DEVICE_NAME="WH Mini Keyboard" CONFIG_BT_DEVICE_NAME="WH Mini Keyboard"
CONFIG_BT_DEVICE_APPEARANCE=961 CONFIG_BT_DEVICE_APPEARANCE=961
@@ -77,9 +111,30 @@ CONFIG_CAF_BLE_ADV_SUSPEND_ON_READY=y
CONFIG_CAF_BLE_ADV_FAST_ADV=y CONFIG_CAF_BLE_ADV_FAST_ADV=y
CONFIG_CAF_BLE_ADV_FILTER_ACCEPT_LIST=y CONFIG_CAF_BLE_ADV_FILTER_ACCEPT_LIST=y
CONFIG_CAF_BLE_ADV_MODULE_SUSPEND_EVENTS=y CONFIG_CAF_BLE_ADV_MODULE_SUSPEND_EVENTS=y
CONFIG_CAF_BLE_BOND=y CONFIG_CAF_BLE_BOND=n
CONFIG_CAF_MODULE_SUSPEND_EVENTS=y CONFIG_CAF_MODULE_SUSPEND_EVENTS=y
CONFIG_BT_ADV_PROV_FLAGS=y CONFIG_BT_ADV_PROV_FLAGS=y
CONFIG_BT_ADV_PROV_GAP_APPEARANCE=y CONFIG_BT_ADV_PROV_GAP_APPEARANCE=y
CONFIG_BT_ADV_PROV_DEVICE_NAME=y CONFIG_BT_ADV_PROV_DEVICE_NAME=y
CONFIG_BT_ADV_PROV_DEVICE_NAME_SD=y CONFIG_BT_ADV_PROV_DEVICE_NAME_SD=y
CONFIG_BT_ADV_PROV_SWIFT_PAIR=y
# LVGL
CONFIG_LVGL=y
CONFIG_LV_Z_AUTO_INIT=n
CONFIG_LV_Z_RUN_LVGL_ON_WORKQUEUE=y
CONFIG_LV_Z_LVGL_WORKQUEUE_STACK_SIZE=16384
CONFIG_LV_Z_LVGL_MUTEX=y
CONFIG_LV_COLOR_DEPTH_16=y
CONFIG_LV_COLOR_16_SWAP=y
CONFIG_LV_Z_BITS_PER_PIXEL=16
CONFIG_LV_Z_VDB_SIZE=25
CONFIG_LV_Z_DOUBLE_VDB=y
CONFIG_LV_Z_MEM_POOL_SIZE=32768
CONFIG_LV_USE_LABEL=y
CONFIG_LV_FONT_MONTSERRAT_14=y
CONFIG_LV_FONT_MONTSERRAT_32=y
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_USE_SEGGER_RTT=y
CONFIG_SPEED_OPTIMIZATIONS=y

View File

@@ -0,0 +1,2 @@
Bitmap.usage_bitmap max_size:29
FunctionKeyEvent.usage_bitmap max_size:29

68
proto/device_comm.proto Normal file
View File

@@ -0,0 +1,68 @@
syntax = "proto3";
enum ResponseCode {
RESPONSE_CODE_OK = 0;
RESPONSE_CODE_UNKNOWN_TYPE = 1;
RESPONSE_CODE_INVALID_LENGTH = 2;
RESPONSE_CODE_INVALID_PARAM = 3;
RESPONSE_CODE_NOT_READY = 4;
}
message HelloReq {
uint32 protocol_version = 1;
}
message HelloRsp {
uint32 protocol_version = 1;
uint32 vendor_id = 2;
uint32 product_id = 3;
uint32 firmware_major = 4;
uint32 firmware_minor = 5;
uint32 capability_flags = 6;
}
message Bitmap {
bytes usage_bitmap = 1;
}
message FunctionKeyEvent {
bytes usage_bitmap = 1;
}
message LedState {
uint32 led_mask = 1;
}
message TimeSync {
uint32 version = 1;
uint32 flags = 2;
sint32 timezone_min = 3;
fixed64 utc_ms = 4;
fixed32 accuracy_ms = 5;
}
message ThemeRgb {
uint32 red = 1;
uint32 green = 2;
uint32 blue = 3;
}
message Response {
ResponseCode error_code = 1;
}
message DeviceMessage {
uint32 msg_id = 10;
uint32 reply_to = 11;
oneof body {
HelloReq hello_req = 1;
HelloRsp hello_rsp = 2;
Bitmap bitmap = 3;
FunctionKeyEvent function_key_event = 4;
LedState led_state = 5;
TimeSync time_sync = 6;
ThemeRgb theme_rgb = 7;
Response response = 8;
}
}

View File

@@ -18,6 +18,7 @@
#include <zephyr/pm/device.h> #include <zephyr/pm/device.h>
#include "bat_state_event.h" #include "bat_state_event.h"
#include "module_lifecycle.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
@@ -32,17 +33,43 @@ BUILD_ASSERT(DT_NODE_HAS_STATUS(VBATT_NODE, okay),
BUILD_ASSERT(DT_NODE_HAS_STATUS(IP5306_NODE, okay), BUILD_ASSERT(DT_NODE_HAS_STATUS(IP5306_NODE, okay),
"Missing ip5306 node in devicetree"); "Missing ip5306 node in devicetree");
static const struct device *const vbatt_dev = DEVICE_DT_GET(VBATT_NODE); struct battery_module_ctx {
static const struct device *const ip5306_dev = DEVICE_DT_GET(IP5306_NODE); struct module_lifecycle_ctx lc;
static struct k_work_delayable battery_sample_work; const struct device *vbatt_dev;
static struct { const struct device *ip5306_dev;
bool valid; struct k_work_delayable battery_sample_work;
uint8_t soc; struct {
bool charging; bool valid;
bool full; uint8_t soc;
} last_bat_state; bool charging;
static bool initialized; bool full;
static bool running; } last_bat_state;
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_POWER,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct battery_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.vbatt_dev = DEVICE_DT_GET(VBATT_NODE),
.ip5306_dev = DEVICE_DT_GET(IP5306_NODE),
};
static int sensor_value_to_mv(const struct sensor_value *value) static int sensor_value_to_mv(const struct sensor_value *value)
{ {
@@ -53,7 +80,7 @@ static int measurement_enable(bool enable)
{ {
enum pm_device_action action = enable ? PM_DEVICE_ACTION_RESUME enum pm_device_action action = enable ? PM_DEVICE_ACTION_RESUME
: PM_DEVICE_ACTION_SUSPEND; : PM_DEVICE_ACTION_SUSPEND;
int err = pm_device_action_run(vbatt_dev, action); int err = pm_device_action_run(ctx.vbatt_dev, action);
if (err && (err != -EALREADY) && (err != -ENOTSUP)) { if (err && (err != -EALREADY) && (err != -ENOTSUP)) {
LOG_ERR("Cannot %s vbatt sensor (%d)", enable ? "resume" : "suspend", err); LOG_ERR("Cannot %s vbatt sensor (%d)", enable ? "resume" : "suspend", err);
@@ -81,30 +108,6 @@ static uint8_t battery_soc_from_mv(int voltage_mv)
return (uint8_t)(bucket * 10); return (uint8_t)(bucket * 10);
} }
static void submit_bat_state_event(uint8_t soc, bool charging, bool full)
{
struct bat_state_event *event;
if (last_bat_state.valid &&
(last_bat_state.soc == soc) &&
(last_bat_state.charging == charging) &&
(last_bat_state.full == full)) {
return;
}
last_bat_state.valid = true;
last_bat_state.soc = soc;
last_bat_state.charging = charging;
last_bat_state.full = full;
event = new_bat_state_event();
event->soc = soc;
event->charging = charging;
event->full = full;
APP_EVENT_SUBMIT(event);
}
static void battery_sample_fn(struct k_work *work) static void battery_sample_fn(struct k_work *work)
{ {
struct ip5306_status pmic_status; struct ip5306_status pmic_status;
@@ -114,69 +117,78 @@ static void battery_sample_fn(struct k_work *work)
ARG_UNUSED(work); ARG_UNUSED(work);
if (!running) { if (!module_lifecycle_is_running(&ctx.lc)) {
return; return;
} }
err = sensor_sample_fetch(vbatt_dev); err = sensor_sample_fetch(ctx.vbatt_dev);
if (err) { if (err) {
LOG_WRN("Battery sample fetch failed (%d)", err); LOG_WRN("Battery sample fetch failed (%d)", err);
goto reschedule; goto reschedule;
} }
err = sensor_channel_get(vbatt_dev, SENSOR_CHAN_VOLTAGE, &voltage); err = sensor_channel_get(ctx.vbatt_dev, SENSOR_CHAN_VOLTAGE, &voltage);
if (err) { if (err) {
LOG_WRN("Battery channel get failed (%d)", err); LOG_WRN("Battery channel get failed (%d)", err);
goto reschedule; goto reschedule;
} }
err = ip5306_get_status(ip5306_dev, &pmic_status); err = ip5306_get_status(ctx.ip5306_dev, &pmic_status);
if (err) { if (err) {
LOG_WRN("IP5306 status read failed (%d)", err); LOG_WRN("IP5306 status read failed (%d)", err);
goto reschedule; goto reschedule;
} }
voltage_mv = sensor_value_to_mv(&voltage); voltage_mv = sensor_value_to_mv(&voltage);
submit_bat_state_event(battery_soc_from_mv(voltage_mv), uint8_t soc = battery_soc_from_mv(voltage_mv);
pmic_status.charging,
pmic_status.full); if (!ctx.last_bat_state.valid ||
(ctx.last_bat_state.soc != soc) ||
(ctx.last_bat_state.charging != pmic_status.charging) ||
(ctx.last_bat_state.full != pmic_status.full)) {
ctx.last_bat_state.valid = true;
ctx.last_bat_state.soc = soc;
ctx.last_bat_state.charging = pmic_status.charging;
ctx.last_bat_state.full = pmic_status.full;
submit_bat_state_event(soc, pmic_status.charging, pmic_status.full);
}
reschedule: reschedule:
if (running) { if (module_lifecycle_is_running(&ctx.lc)) {
k_work_reschedule(&battery_sample_work, BATTERY_SAMPLE_INTERVAL); k_work_reschedule(&ctx.battery_sample_work, BATTERY_SAMPLE_INTERVAL);
} }
} }
static int module_init(void) static int do_init(void)
{ {
if (!device_is_ready(vbatt_dev)) { if (!device_is_ready(ctx.vbatt_dev)) {
LOG_ERR("vbatt device not ready"); LOG_ERR("vbatt device not ready");
return -ENODEV; return -ENODEV;
} }
if (!device_is_ready(ip5306_dev)) { if (!device_is_ready(ctx.ip5306_dev)) {
LOG_ERR("ip5306 device not ready"); LOG_ERR("ip5306 device not ready");
return -ENODEV; return -ENODEV;
} }
int err = ip5306_init(ip5306_dev); int err = ip5306_init(ctx.ip5306_dev);
if (err) { if (err) {
LOG_ERR("ip5306 init failed (%d)", err); LOG_ERR("ip5306 init failed (%d)", err);
return err; return err;
} }
k_work_init_delayable(&battery_sample_work, battery_sample_fn); k_work_init_delayable(&ctx.battery_sample_work, battery_sample_fn);
memset(&last_bat_state, 0, sizeof(last_bat_state)); memset(&ctx.last_bat_state, 0, sizeof(ctx.last_bat_state));
power_manager_restrict(MODULE_IDX(MODULE), POWER_MANAGER_LEVEL_SUSPENDED); power_manager_restrict(MODULE_IDX(MODULE), POWER_MANAGER_LEVEL_SUSPENDED);
return 0; return 0;
} }
static int module_start(void) static int do_start(void)
{ {
int err; int err;
if (running) { if (module_lifecycle_is_running(&ctx.lc)) {
return 0; return 0;
} }
@@ -185,21 +197,21 @@ static int module_start(void)
return err; return err;
} }
running = true; k_work_reschedule(&ctx.battery_sample_work, K_NO_WAIT);
k_work_reschedule(&battery_sample_work, K_NO_WAIT);
return 0; return 0;
} }
static void module_pause(void) static int do_stop(void)
{ {
if (!running) { if (!module_lifecycle_is_running(&ctx.lc)) {
return; return 0;
} }
(void)k_work_cancel_delayable(&battery_sample_work); (void)k_work_cancel_delayable(&ctx.battery_sample_work);
(void)measurement_enable(false); (void)measurement_enable(false);
running = false;
return 0;
} }
static bool app_event_handler(const struct app_event_header *aeh) static bool app_event_handler(const struct app_event_header *aeh)
@@ -208,47 +220,23 @@ static bool app_event_handler(const struct app_event_header *aeh)
const struct module_state_event *event = cast_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)) { if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
int err; (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
if (!initialized) {
err = module_init();
if (err) {
module_set_state(MODULE_STATE_ERROR);
return false;
}
initialized = true;
}
err = module_start();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
} }
return false; return false;
} }
if (is_power_down_event(aeh)) { if (is_power_down_event(aeh)) {
if (initialized) { if (module_lifecycle_is_initialized(&ctx.lc)) {
module_pause(); (void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
module_set_state(MODULE_STATE_STANDBY);
} }
return false; return false;
} }
if (is_wake_up_event(aeh)) { if (is_wake_up_event(aeh)) {
if (initialized) { if (module_lifecycle_is_initialized(&ctx.lc)) {
int err = module_start(); (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
} }
return false; return false;

View File

@@ -1,115 +0,0 @@
#include <stdbool.h>
#include <app_event_manager.h>
#define MODULE ble_adv_ctrl_module
#include <caf/events/module_state_event.h>
#include <caf/events/module_suspend_event.h>
#include <zephyr/logging/log.h>
#include "mode_switch_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
static bool initialized;
static bool running;
static bool ble_adv_suspended = true;
static void broadcast_ble_adv_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 int module_init(void)
{
ble_adv_suspended = true;
return 0;
}
static int module_start(void)
{
if (running) {
return 0;
}
running = true;
return 0;
}
static void module_pause(void)
{
running = false;
}
static bool handle_mode_switch_event(const struct mode_switch_event *event)
{
bool should_suspend;
if (!running) {
return false;
}
should_suspend = (event->mode != MODE_SWITCH_BLE);
if (should_suspend != ble_adv_suspended) {
ble_adv_suspended = should_suspend;
broadcast_ble_adv_req(should_suspend);
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_mode_switch_event(aeh)) {
return handle_mode_switch_event(cast_mode_switch_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)) {
int err;
if (!initialized) {
err = module_init();
if (err) {
module_set_state(MODULE_STATE_ERROR);
return false;
}
initialized = true;
}
err = module_start();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, mode_switch_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);

View File

@@ -11,34 +11,54 @@
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
#include "bat_state_event.h" #include "bat_state_event.h"
#include "module_lifecycle.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
static uint8_t current_soc = 100U; struct ble_bas_module_ctx {
static bool initialized; struct module_lifecycle_ctx lc;
static bool running; uint8_t current_soc;
static bool ble_ready; bool ble_ready;
};
static int module_init(void) static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_NONE,
.stopped_state = MODULE_STATE_OFF,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct ble_bas_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.current_soc = 100U,
};
static int do_init(void)
{ {
return 0; return 0;
} }
static int module_start(void) static int bas_update_level(void)
{ {
int err; int err = bt_bas_set_battery_level(ctx.current_soc);
if (running) { if ((err == -EAGAIN) || (err == -ENOTCONN)) {
LOG_INF("BAS notify deferred (%d)", err);
return 0; return 0;
} }
running = true;
if (!ble_ready) {
return 0;
}
err = bt_bas_set_battery_level(current_soc);
if (err) { if (err) {
LOG_WRN("bt_bas_set_battery_level failed (%d)", err); LOG_WRN("bt_bas_set_battery_level failed (%d)", err);
return err; return err;
@@ -47,21 +67,30 @@ static int module_start(void)
return 0; return 0;
} }
static void module_pause(void) static int do_start(void)
{ {
running = false; if (module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
if (!ctx.ble_ready) {
return 0;
}
return bas_update_level();
}
static int do_stop(void)
{
return 0;
} }
static bool handle_bat_state_event(const struct bat_state_event *event) static bool handle_bat_state_event(const struct bat_state_event *event)
{ {
current_soc = event->soc; ctx.current_soc = event->soc;
if (running && ble_ready) { if (module_lifecycle_is_running(&ctx.lc) && ctx.ble_ready) {
int err = bt_bas_set_battery_level(current_soc); (void)bas_update_level();
if (err) {
LOG_WRN("bt_bas_set_battery_level failed (%d)", err);
}
} }
return false; return false;
@@ -75,38 +104,18 @@ static bool app_event_handler(const struct app_event_header *aeh)
if (is_module_state_event(aeh)) { if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh); const struct module_state_event *event = cast_module_state_event(aeh);
int err;
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
if (!initialized) { (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
err = module_init();
if (err) {
module_set_state(MODULE_STATE_ERROR);
return false;
}
initialized = true;
}
err = module_start();
if (err) {
module_set_state(MODULE_STATE_ERROR);
}
return false; return false;
} }
if (check_state(event, MODULE_ID(ble_state), MODULE_STATE_READY)) { if (check_state(event, MODULE_ID(ble_state), MODULE_STATE_READY)) {
ble_ready = true; ctx.ble_ready = true;
if (running) { if (module_lifecycle_is_running(&ctx.lc)) {
err = bt_bas_set_battery_level(current_soc); (void)bas_update_level();
if (err) {
LOG_WRN("bt_bas_set_battery_level failed (%d)", err);
}
} }
module_set_state(MODULE_STATE_READY);
return false; return false;
} }

555
src/ble_bond_multi_module.c Normal file
View File

@@ -0,0 +1,555 @@
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.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 <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/settings/settings.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include "ble_bond_multi_event.h"
#include "module_lifecycle.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define BLE_SLOT_MIN 1U
#define BLE_SLOT_MAX BLE_BOND_MULTI_BLE_SLOT_COUNT
#define IDENTITY_DONGLE BLE_BOND_MULTI_DONGLE_SLOT_ID
#define SETTINGS_KEY_CURRENT_SLOT "current_slot"
#define SETTINGS_KEY_META_PREFIX "meta/"
#define DEFAULT_DISPLAY_NAME_EMPTY "Empty"
struct ble_bond_multi_slot_meta_storage {
uint8_t occupied;
bt_addr_le_t last_peer_addr;
char display_name[BLE_BOND_MULTI_DISPLAY_NAME_MAX_LEN];
};
struct ble_bond_multi_ctx {
struct module_lifecycle_ctx lc;
uint8_t current_slot;
uint8_t pending_slot;
bool current_slot_valid;
bool identities_ready;
struct ble_bond_multi_slot_meta slot_meta[CONFIG_BT_ID_MAX];
struct bt_conn *active_conn;
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static int identity_ensure_exists(uint8_t identity);
static void slot_bond_cnt_cb(const struct bt_bond_info *info, void *user_data);
int ble_bond_multi_select_slot(uint8_t slot);
int ble_bond_multi_erase_current_slot(void);
const struct ble_bond_multi_slot_meta *ble_bond_multi_get_slot_meta(uint8_t slot);
uint8_t ble_bond_multi_get_current_slot(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_NONE,
.stopped_state = MODULE_STATE_OFF,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct ble_bond_multi_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.current_slot = BLE_SLOT_MIN,
.pending_slot = BLE_SLOT_MIN,
};
BUILD_ASSERT(CONFIG_BT_ID_MAX > IDENTITY_DONGLE,
"BT_ID_MAX must include BLE slots and dongle slot");
BUILD_ASSERT(CONFIG_BT_MAX_PAIRED >= 4,
"BT_MAX_PAIRED must allow three BLE slots and dongle slot");
static bool is_ble_slot(uint8_t slot)
{
return (slot >= BLE_SLOT_MIN) && (slot <= BLE_SLOT_MAX);
}
static void display_name_set_addr(uint8_t slot, const bt_addr_le_t *addr)
{
struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot];
if ((addr == NULL) || !bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) {
strncpy(meta->display_name, DEFAULT_DISPLAY_NAME_EMPTY,
sizeof(meta->display_name));
meta->display_name[sizeof(meta->display_name) - 1U] = '\0';
return;
}
bt_addr_le_to_str(addr, meta->display_name, sizeof(meta->display_name));
meta->display_name[sizeof(meta->display_name) - 1U] = '\0';
}
static void display_name_set_default(uint8_t slot)
{
struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot];
const char *name = DEFAULT_DISPLAY_NAME_EMPTY;
if (meta->occupied &&
bt_addr_le_cmp(&meta->last_peer_addr, BT_ADDR_LE_ANY)) {
display_name_set_addr(slot, &meta->last_peer_addr);
return;
}
strncpy(meta->display_name, name, sizeof(meta->display_name));
meta->display_name[sizeof(meta->display_name) - 1U] = '\0';
}
static void slot_meta_clear(uint8_t slot)
{
struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot];
memset(meta, 0, sizeof(*meta));
bt_addr_le_copy(&meta->last_peer_addr, BT_ADDR_LE_ANY);
display_name_set_default(slot);
}
static void slot_meta_ensure_name(uint8_t slot)
{
if (ctx.slot_meta[slot].display_name[0] == '\0') {
display_name_set_default(slot);
}
}
static void slot_meta_from_storage(uint8_t slot,
const struct ble_bond_multi_slot_meta_storage *storage)
{
struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot];
meta->occupied = (storage->occupied != 0U);
bt_addr_le_copy(&meta->last_peer_addr, &storage->last_peer_addr);
memcpy(meta->display_name, storage->display_name, sizeof(meta->display_name));
meta->display_name[sizeof(meta->display_name) - 1U] = '\0';
slot_meta_ensure_name(slot);
}
static void slot_meta_to_storage(uint8_t slot,
struct ble_bond_multi_slot_meta_storage *storage)
{
const struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot];
memset(storage, 0, sizeof(*storage));
storage->occupied = meta->occupied ? 1U : 0U;
bt_addr_le_copy(&storage->last_peer_addr, &meta->last_peer_addr);
memcpy(storage->display_name, meta->display_name,
sizeof(storage->display_name));
}
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, SETTINGS_KEY_CURRENT_SLOT)) {
uint8_t stored_slot;
if (len_rd != sizeof(stored_slot)) {
return 0;
}
rc = read_cb(cb_arg, &stored_slot, sizeof(stored_slot));
if (rc == sizeof(stored_slot) && is_ble_slot(stored_slot)) {
ctx.current_slot = stored_slot;
ctx.pending_slot = stored_slot;
ctx.current_slot_valid = true;
}
return 0;
}
if (!strncmp(key, SETTINGS_KEY_META_PREFIX,
sizeof(SETTINGS_KEY_META_PREFIX) - 1)) {
const char *slot_str = key + (sizeof(SETTINGS_KEY_META_PREFIX) - 1);
long slot = strtol(slot_str, NULL, 10);
struct ble_bond_multi_slot_meta_storage storage;
if ((slot < BLE_SLOT_MIN) || (slot > IDENTITY_DONGLE) ||
(len_rd != sizeof(storage))) {
return 0;
}
rc = read_cb(cb_arg, &storage, sizeof(storage));
if (rc == sizeof(storage)) {
slot_meta_from_storage((uint8_t)slot, &storage);
}
return 0;
}
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(ble_bond_multi, "ble_multi", NULL, settings_set,
NULL, NULL);
static int settings_save_current_slot(void)
{
int err;
err = settings_save_one("ble_multi/" SETTINGS_KEY_CURRENT_SLOT,
&ctx.current_slot,
sizeof(ctx.current_slot));
if (err) {
LOG_ERR("Save current slot failed (%d)", err);
}
return err;
}
static int settings_save_slot_meta(uint8_t slot)
{
int err;
char key[24];
struct ble_bond_multi_slot_meta_storage storage;
slot_meta_to_storage(slot, &storage);
snprintk(key, sizeof(key), "ble_multi/" SETTINGS_KEY_META_PREFIX "%u", slot);
err = settings_save_one(key, &storage, sizeof(storage));
if (err) {
LOG_ERR("Save slot %u meta failed (%d)", slot, err);
}
return err;
}
static void active_conn_clear(void)
{
ctx.active_conn = NULL;
}
static void bond_addr_get_cb(const struct bt_bond_info *info, void *user_data)
{
bt_addr_le_t *addr = user_data;
if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) {
bt_addr_le_copy(addr, &info->addr);
}
}
static void publish_state(enum ble_bond_multi_op op)
{
struct ble_bond_multi_event *event = new_ble_bond_multi_event();
uint8_t occ = 0U;
event->current_slot = ctx.current_slot;
event->active_identity_id = ctx.current_slot;
event->op = op;
for (uint8_t slot = BLE_SLOT_MIN; slot <= BLE_SLOT_MAX; slot++) {
if (ctx.slot_meta[slot].occupied) {
occ |= BIT(slot - BLE_SLOT_MIN);
}
event->slots[slot - BLE_SLOT_MIN] = ctx.slot_meta[slot];
}
event->slot_occupied_bitmap = occ;
APP_EVENT_SUBMIT(event);
}
static int identity_ensure_exists(uint8_t identity)
{
size_t count = CONFIG_BT_ID_MAX;
bt_addr_le_t addrs[CONFIG_BT_ID_MAX];
(void)bt_id_get(addrs, &count);
while (count <= identity) {
int id = bt_id_create(NULL, NULL);
if (id < 0) {
LOG_ERR("bt_id_create failed (%d)", id);
return id;
}
count++;
}
return 0;
}
static bool slot_has_bond(uint8_t slot)
{
size_t cnt = 0;
bt_foreach_bond(slot, slot_bond_cnt_cb, &cnt);
return cnt > 0U;
}
static void slot_bond_cnt_cb(const struct bt_bond_info *info, void *user_data)
{
size_t *count = user_data;
ARG_UNUSED(info);
(*count)++;
}
static void slot_update_from_bonds(uint8_t slot)
{
struct ble_bond_multi_slot_meta *meta = &ctx.slot_meta[slot];
meta->occupied = slot_has_bond(slot);
if (!meta->occupied) {
bt_addr_le_copy(&meta->last_peer_addr, BT_ADDR_LE_ANY);
} else if (!bt_addr_le_cmp(&meta->last_peer_addr, BT_ADDR_LE_ANY)) {
bt_foreach_bond(slot, bond_addr_get_cb, &meta->last_peer_addr);
}
if (meta->occupied &&
((meta->display_name[0] == '\0') ||
!strcmp(meta->display_name, DEFAULT_DISPLAY_NAME_EMPTY))) {
display_name_set_addr(slot, &meta->last_peer_addr);
}
slot_meta_ensure_name(slot);
}
static void all_slots_refresh_from_bonds(void)
{
for (uint8_t slot = BLE_SLOT_MIN; slot <= IDENTITY_DONGLE; slot++) {
slot_update_from_bonds(slot);
}
}
static void submit_selected_event(uint8_t identity)
{
struct ble_peer_operation_event *event = new_ble_peer_operation_event();
event->op = PEER_OPERATION_SELECTED;
event->bt_app_id = identity;
event->bt_stack_id = identity;
APP_EVENT_SUBMIT(event);
}
static void submit_erased_event(uint8_t identity)
{
struct ble_peer_operation_event *event = new_ble_peer_operation_event();
event->op = PEER_OPERATION_ERASED;
event->bt_app_id = identity;
event->bt_stack_id = identity;
APP_EVENT_SUBMIT(event);
}
static int switch_slot(uint8_t slot)
{
if (!is_ble_slot(slot) || (slot == ctx.current_slot)) {
return 0;
}
ctx.pending_slot = slot;
ctx.current_slot = slot;
submit_selected_event(slot);
return settings_save_current_slot();
}
static int erase_slot(uint8_t slot)
{
int err;
err = bt_unpair(slot, BT_ADDR_LE_ANY);
if (err) {
LOG_ERR("bt_unpair slot %u failed (%d)", slot, err);
return err;
}
slot_meta_clear(slot);
err = settings_save_slot_meta(slot);
if (err) {
return err;
}
submit_erased_event(slot);
submit_selected_event(slot);
return 0;
}
static int do_init(void)
{
for (uint8_t slot = BLE_SLOT_MIN; slot <= IDENTITY_DONGLE; slot++) {
slot_meta_clear(slot);
}
if (!ctx.current_slot_valid) {
ctx.current_slot = BLE_SLOT_MIN;
ctx.pending_slot = BLE_SLOT_MIN;
}
active_conn_clear();
return 0;
}
static int do_start(void)
{
int err;
for (uint8_t identity = BLE_SLOT_MIN; identity <= IDENTITY_DONGLE;
identity++) {
err = identity_ensure_exists(identity);
if (err) {
return err;
}
}
ctx.identities_ready = true;
all_slots_refresh_from_bonds();
for (uint8_t slot = BLE_SLOT_MIN; slot <= IDENTITY_DONGLE; slot++) {
(void)settings_save_slot_meta(slot);
}
submit_selected_event(ctx.current_slot);
publish_state(BLE_BOND_MULTI_OP_REFRESH);
return 0;
}
static int do_stop(void)
{
active_conn_clear();
return 0;
}
static bool handle_ble_peer_event(const struct ble_peer_event *event)
{
switch (event->state) {
case PEER_STATE_CONNECTED:
ctx.active_conn = event->id;
return false;
case PEER_STATE_SECURED:
if (ctx.active_conn != event->id) {
return false;
}
ctx.slot_meta[ctx.current_slot].occupied = true;
bt_addr_le_copy(&ctx.slot_meta[ctx.current_slot].last_peer_addr,
bt_conn_get_dst(ctx.active_conn));
if ((ctx.slot_meta[ctx.current_slot].display_name[0] == '\0') ||
!strcmp(ctx.slot_meta[ctx.current_slot].display_name,
DEFAULT_DISPLAY_NAME_EMPTY)) {
display_name_set_addr(ctx.current_slot,
&ctx.slot_meta[ctx.current_slot]
.last_peer_addr);
}
(void)settings_save_slot_meta(ctx.current_slot);
publish_state(BLE_BOND_MULTI_OP_REFRESH);
return false;
case PEER_STATE_DISCONNECTED:
case PEER_STATE_CONN_FAILED:
if (ctx.active_conn == event->id) {
active_conn_clear();
}
return false;
default:
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(settings_loader), MODULE_STATE_READY)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
return false;
}
return false;
}
if (is_ble_peer_event(aeh)) {
return handle_ble_peer_event(cast_ble_peer_event(aeh));
}
if (is_ble_bond_multi_event(aeh)) {
return false;
}
return false;
}
int ble_bond_multi_select_slot(uint8_t slot)
{
int err;
if (!module_lifecycle_is_running(&ctx.lc)) {
return -EAGAIN;
}
if (!ctx.identities_ready) {
return -EAGAIN;
}
err = switch_slot(slot);
if (!err) {
publish_state(BLE_BOND_MULTI_OP_SWITCH);
}
return err;
}
int ble_bond_multi_erase_current_slot(void)
{
int err;
if (!module_lifecycle_is_running(&ctx.lc)) {
return -EAGAIN;
}
if (!ctx.identities_ready) {
return -EAGAIN;
}
err = erase_slot(ctx.current_slot);
if (!err) {
publish_state(BLE_BOND_MULTI_OP_ERASE);
}
return err;
}
const struct ble_bond_multi_slot_meta *ble_bond_multi_get_slot_meta(uint8_t slot)
{
if (!is_ble_slot(slot)) {
return NULL;
}
return &ctx.slot_meta[slot];
}
uint8_t ble_bond_multi_get_current_slot(void)
{
return ctx.current_slot;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);

View File

@@ -7,17 +7,17 @@
#define MODULE ble_hid_module #define MODULE ble_hid_module
#include <caf/events/module_state_event.h> #include <caf/events/module_state_event.h>
#include <caf/events/power_event.h>
#include <caf/events/ble_common_event.h> #include <caf/events/ble_common_event.h>
#include <bluetooth/services/hids.h> #include <bluetooth/services/hids.h>
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
#include "hid_led_event.h" #include "hid_led_event.h"
#include "hid_channel_state_event.h"
#include "hid_report_sent_event.h" #include "hid_report_sent_event.h"
#include "hid_transport_state_event.h"
#include "hid_tx_report_event.h" #include "hid_tx_report_event.h"
#include "keyboard_core.h" #include "keyboard_core.h"
#include "module_lifecycle.h"
#include "set_protocol_event.h" #include "set_protocol_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
@@ -40,15 +40,40 @@ BT_HIDS_DEF(hids_obj,
KEYBOARD_CONSUMER_REPORT_SIZE, KEYBOARD_CONSUMER_REPORT_SIZE,
BLE_HID_KEYS_LED_REPORT_SIZE); BLE_HID_KEYS_LED_REPORT_SIZE);
static struct bt_conn *active_conn; struct ble_hid_module_ctx {
static struct in_flight_report in_flight; struct module_lifecycle_ctx lc;
static enum keyboard_protocol_mode protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT; struct bt_conn *active_conn;
static bool initialized; struct in_flight_report in_flight;
static bool running; enum keyboard_protocol_mode protocol_mode;
static bool secured; bool secured;
static bool keyboard_report_notify_enabled; bool keyboard_report_notify_enabled;
static bool consumer_report_notify_enabled; bool consumer_report_notify_enabled;
static bool boot_keyboard_notify_enabled; bool boot_keyboard_notify_enabled;
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_NONE,
.stopped_state = MODULE_STATE_OFF,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct ble_hid_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT,
};
static const uint8_t hid_report_desc[] = { static const uint8_t hid_report_desc[] = {
0x05, 0x01, /* Usage Page (Generic Desktop) */ 0x05, 0x01, /* Usage Page (Generic Desktop) */
@@ -98,54 +123,27 @@ static const uint8_t hid_report_desc[] = {
0xC0 /* End Collection */ 0xC0 /* End Collection */
}; };
static void submit_set_protocol_event(void) static void submit_ble_transport_state_event(void)
{ {
struct set_protocol_event *event = new_set_protocol_event(); bool ready = module_lifecycle_is_running(&ctx.lc) &&
ctx.secured && (ctx.active_conn != NULL);
uint8_t report_ready_bm = 0U;
event->transport = HID_TRANSPORT_BLE; if (ready && ((ctx.protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) ?
event->protocol_mode = protocol_mode; ctx.boot_keyboard_notify_enabled :
APP_EVENT_SUBMIT(event); ctx.keyboard_report_notify_enabled)) {
} report_ready_bm |= BIT(KEYBOARD_REPORT_TYPE_KEYS);
}
static void submit_hid_led_event(uint8_t led_bm) if (ready && (ctx.protocol_mode == KEYBOARD_PROTOCOL_MODE_REPORT) &&
{ ctx.consumer_report_notify_enabled) {
struct hid_led_event *event = new_hid_led_event(); report_ready_bm |= BIT(KEYBOARD_REPORT_TYPE_CONSUMER);
}
event->transport = HID_TRANSPORT_BLE; submit_hid_channel_state_event(
event->led_bm = led_bm; HID_SEND_CH_BLE_SHARED,
APP_EVENT_SUBMIT(event); report_ready_bm,
} ctx.protocol_mode);
static void submit_transport_state_event(void)
{
struct hid_transport_state_event *event = new_hid_transport_state_event();
bool ready = running && secured && (active_conn != NULL);
event->transport = HID_TRANSPORT_BLE;
event->ready = ready;
event->protocol_mode = protocol_mode;
event->keys_ready = ready &&
((protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) ?
boot_keyboard_notify_enabled :
keyboard_report_notify_enabled);
event->consumer_ready = ready &&
(protocol_mode == KEYBOARD_PROTOCOL_MODE_REPORT) &&
consumer_report_notify_enabled;
APP_EVENT_SUBMIT(event);
}
static void submit_hid_report_sent_event(enum keyboard_report_type report_type,
uint16_t sequence, bool error)
{
struct hid_report_sent_event *event = new_hid_report_sent_event();
event->transport = HID_TRANSPORT_BLE;
event->report_type = report_type;
event->sequence = sequence;
event->error = error;
APP_EVENT_SUBMIT(event);
} }
static void input_report_notify_handler(uint8_t report_id, enum bt_hids_notify_evt evt) static void input_report_notify_handler(uint8_t report_id, enum bt_hids_notify_evt evt)
@@ -153,18 +151,18 @@ static void input_report_notify_handler(uint8_t report_id, enum bt_hids_notify_e
bool enabled = (evt == BT_HIDS_CCCD_EVT_NOTIFY_ENABLED); bool enabled = (evt == BT_HIDS_CCCD_EVT_NOTIFY_ENABLED);
if (report_id == BLE_HID_KEYS_REPORT_ID) { if (report_id == BLE_HID_KEYS_REPORT_ID) {
keyboard_report_notify_enabled = enabled; ctx.keyboard_report_notify_enabled = enabled;
} else if (report_id == BLE_HID_CONSUMER_REPORT_ID) { } else if (report_id == BLE_HID_CONSUMER_REPORT_ID) {
consumer_report_notify_enabled = enabled; ctx.consumer_report_notify_enabled = enabled;
} }
submit_transport_state_event(); submit_ble_transport_state_event();
} }
static void boot_keyboard_notify_handler(enum bt_hids_notify_evt evt) static void boot_keyboard_notify_handler(enum bt_hids_notify_evt evt)
{ {
boot_keyboard_notify_enabled = (evt == BT_HIDS_CCCD_EVT_NOTIFY_ENABLED); ctx.boot_keyboard_notify_enabled = (evt == BT_HIDS_CCCD_EVT_NOTIFY_ENABLED);
submit_transport_state_event(); submit_ble_transport_state_event();
} }
static void hid_report_complete_cb(struct bt_conn *conn, void *user_data) static void hid_report_complete_cb(struct bt_conn *conn, void *user_data)
@@ -172,12 +170,14 @@ static void hid_report_complete_cb(struct bt_conn *conn, void *user_data)
ARG_UNUSED(conn); ARG_UNUSED(conn);
ARG_UNUSED(user_data); ARG_UNUSED(user_data);
if (!in_flight.active) { if (!ctx.in_flight.active) {
return; return;
} }
submit_hid_report_sent_event(in_flight.report_type, in_flight.sequence, false); submit_hid_report_sent_event(HID_SEND_CH_BLE_SHARED,
in_flight.active = false; ctx.in_flight.report_type,
ctx.in_flight.sequence, false);
ctx.in_flight.active = false;
} }
static void keyboard_led_report_common(struct bt_hids_rep *rep, bool write) static void keyboard_led_report_common(struct bt_hids_rep *rep, bool write)
@@ -186,7 +186,7 @@ static void keyboard_led_report_common(struct bt_hids_rep *rep, bool write)
return; return;
} }
submit_hid_led_event(rep->data[0]); submit_hid_led_event(HID_TRANSPORT_BLE, rep->data[0]);
} }
static void keyboard_led_report_handler(struct bt_hids_rep *rep, static void keyboard_led_report_handler(struct bt_hids_rep *rep,
@@ -213,22 +213,22 @@ static void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn)
switch (evt) { switch (evt) {
case BT_HIDS_PM_EVT_BOOT_MODE_ENTERED: case BT_HIDS_PM_EVT_BOOT_MODE_ENTERED:
protocol_mode = KEYBOARD_PROTOCOL_MODE_BOOT; ctx.protocol_mode = KEYBOARD_PROTOCOL_MODE_BOOT;
break; break;
case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED: case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED:
protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT; ctx.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT;
break; break;
default: default:
return; return;
} }
submit_set_protocol_event(); submit_set_protocol_event(HID_TRANSPORT_BLE, ctx.protocol_mode);
submit_transport_state_event(); submit_ble_transport_state_event();
} }
static int module_init(void) static int do_init(void)
{ {
struct bt_hids_init_param hids_init_param = { 0 }; struct bt_hids_init_param hids_init_param = { 0 };
struct bt_hids_inp_rep *input_report; struct bt_hids_inp_rep *input_report;
@@ -265,38 +265,38 @@ static int module_init(void)
return bt_hids_init(&hids_obj, &hids_init_param); return bt_hids_init(&hids_obj, &hids_init_param);
} }
static int module_start(void) static int do_start(void)
{ {
if (running) { if (module_lifecycle_is_running(&ctx.lc)) {
return 0; return 0;
} }
running = true; submit_ble_transport_state_event();
submit_transport_state_event();
return 0; return 0;
} }
static void module_pause(void) static int do_stop(void)
{ {
if (!running) { if (!module_lifecycle_is_running(&ctx.lc)) {
return; return 0;
} }
in_flight.active = false; ctx.in_flight.active = false;
running = false; submit_ble_transport_state_event();
submit_transport_state_event();
return 0;
} }
static void reset_connection_state(void) static void reset_connection_state(void)
{ {
active_conn = NULL; ctx.active_conn = NULL;
secured = false; ctx.secured = false;
keyboard_report_notify_enabled = false; ctx.keyboard_report_notify_enabled = false;
consumer_report_notify_enabled = false; ctx.consumer_report_notify_enabled = false;
boot_keyboard_notify_enabled = false; ctx.boot_keyboard_notify_enabled = false;
protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT; ctx.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT;
in_flight.active = false; ctx.in_flight.active = false;
} }
static bool handle_ble_peer_event(const struct ble_peer_event *event) static bool handle_ble_peer_event(const struct ble_peer_event *event)
@@ -305,31 +305,31 @@ static bool handle_ble_peer_event(const struct ble_peer_event *event)
switch (event->state) { switch (event->state) {
case PEER_STATE_CONNECTED: case PEER_STATE_CONNECTED:
if (active_conn != NULL) { if (ctx.active_conn != NULL) {
return false; return false;
} }
active_conn = event->id; ctx.active_conn = event->id;
protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT; ctx.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT;
submit_set_protocol_event(); submit_set_protocol_event(HID_TRANSPORT_BLE, ctx.protocol_mode);
err = bt_hids_connected(&hids_obj, event->id); err = bt_hids_connected(&hids_obj, event->id);
if (err) { if (err) {
LOG_ERR("bt_hids_connected failed (%d)", err); LOG_ERR("bt_hids_connected failed (%d)", err);
} }
submit_transport_state_event(); submit_ble_transport_state_event();
return false; return false;
case PEER_STATE_SECURED: case PEER_STATE_SECURED:
if (active_conn != event->id) { if (ctx.active_conn != event->id) {
return false; return false;
} }
secured = true; ctx.secured = true;
submit_transport_state_event(); submit_ble_transport_state_event();
return false; return false;
case PEER_STATE_DISCONNECTED: case PEER_STATE_DISCONNECTED:
if (active_conn != event->id) { if (ctx.active_conn != event->id) {
return false; return false;
} }
@@ -339,7 +339,7 @@ static bool handle_ble_peer_event(const struct ble_peer_event *event)
} }
reset_connection_state(); reset_connection_state();
submit_transport_state_event(); submit_ble_transport_state_event();
return false; return false;
default: default:
@@ -351,33 +351,35 @@ static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event)
{ {
int err; int err;
if (!running || (event->transport != HID_TRANSPORT_BLE) || in_flight.active) { if (!module_lifecycle_is_running(&ctx.lc) ||
(event->channel != HID_SEND_CH_BLE_SHARED) ||
ctx.in_flight.active) {
return false; return false;
} }
if ((active_conn == NULL) || !secured) { if ((ctx.active_conn == NULL) || !ctx.secured) {
return false; return false;
} }
if (event->report_type == KEYBOARD_REPORT_TYPE_KEYS) { if (event->report_type == KEYBOARD_REPORT_TYPE_KEYS) {
if (event->protocol_mode != protocol_mode) { if (event->protocol_mode != ctx.protocol_mode) {
LOG_WRN("Drop BLE keys report due to protocol mismatch"); LOG_WRN("Drop BLE keys report due to protocol mismatch");
return false; return false;
} }
in_flight.active = true; ctx.in_flight.active = true;
in_flight.report_type = event->report_type; ctx.in_flight.report_type = event->report_type;
in_flight.sequence = event->sequence; ctx.in_flight.sequence = event->sequence;
if (protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) { if (ctx.protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) {
err = bt_hids_boot_kb_inp_rep_send(&hids_obj, err = bt_hids_boot_kb_inp_rep_send(&hids_obj,
active_conn, ctx.active_conn,
event->dyndata.data, event->dyndata.data,
(uint8_t)event->dyndata.size, (uint8_t)event->dyndata.size,
hid_report_complete_cb); hid_report_complete_cb);
} else { } else {
err = bt_hids_inp_rep_send(&hids_obj, err = bt_hids_inp_rep_send(&hids_obj,
active_conn, ctx.active_conn,
BLE_HID_KEYS_REPORT_IDX, BLE_HID_KEYS_REPORT_IDX,
event->dyndata.data, event->dyndata.data,
(uint8_t)event->dyndata.size, (uint8_t)event->dyndata.size,
@@ -385,9 +387,10 @@ static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event)
} }
if (err) { if (err) {
in_flight.active = false; ctx.in_flight.active = false;
LOG_WRN("BLE keyboard report submit failed (%d)", err); LOG_WRN("BLE keyboard report submit failed (%d)", err);
submit_hid_report_sent_event(KEYBOARD_REPORT_TYPE_KEYS, submit_hid_report_sent_event(HID_SEND_CH_BLE_SHARED,
KEYBOARD_REPORT_TYPE_KEYS,
event->sequence, true); event->sequence, true);
} }
@@ -395,25 +398,26 @@ static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event)
} }
if (event->report_type == KEYBOARD_REPORT_TYPE_CONSUMER) { if (event->report_type == KEYBOARD_REPORT_TYPE_CONSUMER) {
if (protocol_mode != KEYBOARD_PROTOCOL_MODE_REPORT) { if (ctx.protocol_mode != KEYBOARD_PROTOCOL_MODE_REPORT) {
LOG_WRN("Drop BLE consumer report in boot mode"); LOG_WRN("Drop BLE consumer report in boot mode");
return false; return false;
} }
in_flight.active = true; ctx.in_flight.active = true;
in_flight.report_type = event->report_type; ctx.in_flight.report_type = event->report_type;
in_flight.sequence = event->sequence; ctx.in_flight.sequence = event->sequence;
err = bt_hids_inp_rep_send(&hids_obj, err = bt_hids_inp_rep_send(&hids_obj,
active_conn, ctx.active_conn,
BLE_HID_CONSUMER_REPORT_IDX, BLE_HID_CONSUMER_REPORT_IDX,
event->dyndata.data, event->dyndata.data,
(uint8_t)event->dyndata.size, (uint8_t)event->dyndata.size,
hid_report_complete_cb); hid_report_complete_cb);
if (err) { if (err) {
in_flight.active = false; ctx.in_flight.active = false;
LOG_WRN("BLE consumer report submit failed (%d)", err); LOG_WRN("BLE consumer report submit failed (%d)", err);
submit_hid_report_sent_event(KEYBOARD_REPORT_TYPE_CONSUMER, submit_hid_report_sent_event(HID_SEND_CH_BLE_SHARED,
KEYBOARD_REPORT_TYPE_CONSUMER,
event->sequence, true); event->sequence, true);
} }
} }
@@ -433,48 +437,9 @@ static bool app_event_handler(const struct app_event_header *aeh)
if (is_module_state_event(aeh)) { if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh); const struct module_state_event *event = cast_module_state_event(aeh);
int err;
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
if (!initialized) { (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
err = module_init();
if (err) {
module_set_state(MODULE_STATE_ERROR);
return false;
}
initialized = true;
}
err = module_start();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
}
return false;
}
if (is_power_down_event(aeh)) {
if (initialized) {
module_pause();
module_set_state(MODULE_STATE_STANDBY);
}
return false;
}
if (is_wake_up_event(aeh)) {
if (initialized) {
int err = module_start();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
} }
return false; return false;
@@ -487,5 +452,3 @@ APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_tx_report_event); APP_EVENT_SUBSCRIBE(MODULE, hid_tx_report_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

324
src/ble_nus_module.c Normal file
View File

@@ -0,0 +1,324 @@
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <app_event_manager.h>
#define MODULE ble_nus_module
#include <caf/events/module_state_event.h>
#include <caf/events/ble_common_event.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/services/nus.h>
#include <zephyr/logging/log.h>
#include "module_lifecycle.h"
#include "proto_rx_event.h"
#include "proto_transport_state_event.h"
#include "proto_tx_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
enum ble_nus_business_state {
BLE_NUS_STACK_OFFLINE = 0,
BLE_NUS_IDLE,
BLE_NUS_WAIT_NOTIFY,
BLE_NUS_SESSION_READY,
};
struct ble_nus_ctx {
struct module_lifecycle_ctx lc;
enum ble_nus_business_state business;
struct bt_conn *active_conn;
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_NONE,
.stopped_state = MODULE_STATE_OFF,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct ble_nus_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.business = BLE_NUS_STACK_OFFLINE,
.active_conn = NULL,
};
static const char *business_state_name(enum ble_nus_business_state state)
{
switch (state) {
case BLE_NUS_STACK_OFFLINE:
return "STACK_OFFLINE";
case BLE_NUS_IDLE:
return "IDLE";
case BLE_NUS_WAIT_NOTIFY:
return "WAIT_NOTIFY";
case BLE_NUS_SESSION_READY:
return "SESSION_READY";
default:
return "?";
}
}
static const char *link_state_name(enum proto_transport_link_state state)
{
switch (state) {
case PROTO_TRANSPORT_LINK_DOWN:
return "DOWN";
case PROTO_TRANSPORT_LINK_READY:
return "READY";
default:
return "?";
}
}
static bool lifecycle_is_ready(void)
{
return module_lifecycle_is_running(&ctx.lc);
}
static enum proto_transport_link_state transport_link_state_get(void)
{
return (lifecycle_is_ready() &&
(ctx.business == BLE_NUS_SESSION_READY)) ?
PROTO_TRANSPORT_LINK_READY :
PROTO_TRANSPORT_LINK_DOWN;
}
static void state_reconcile(enum module_lifecycle old_lifecycle,
enum ble_nus_business_state old_business)
{
enum proto_transport_link_state old_link =
((old_lifecycle == LC_RUNNING) &&
(old_business == BLE_NUS_SESSION_READY)) ?
PROTO_TRANSPORT_LINK_READY :
PROTO_TRANSPORT_LINK_DOWN;
enum proto_transport_link_state new_link = transport_link_state_get();
if (old_link != new_link) {
submit_proto_transport_state_event(PROTO_TRANSPORT_BLE_NUS,
new_link);
}
}
static void business_state_set(enum ble_nus_business_state new_state)
{
enum module_lifecycle old_lifecycle = ctx.lc.state;
enum ble_nus_business_state old_business = ctx.business;
if (ctx.business == new_state) {
return;
}
LOG_INF("BLE NUS business %s -> %s",
business_state_name(ctx.business),
business_state_name(new_state));
ctx.business = new_state;
state_reconcile(old_lifecycle, old_business);
}
static void business_state_set_stack_ready(void)
{
if (ctx.business == BLE_NUS_STACK_OFFLINE) {
business_state_set(BLE_NUS_IDLE);
}
}
static void business_state_set_from_notify(bool enabled)
{
if (ctx.business == BLE_NUS_STACK_OFFLINE) {
return;
}
if (ctx.active_conn == NULL) {
return;
}
business_state_set(enabled ? BLE_NUS_SESSION_READY :
BLE_NUS_WAIT_NOTIFY);
}
static void notif_enabled(bool enabled, void *ctx_ptr)
{
ARG_UNUSED(ctx_ptr);
LOG_INF("BLE NUS TX notify %s", enabled ? "enabled" : "disabled");
business_state_set_from_notify(enabled);
}
static void received(struct bt_conn *conn, const void *data, uint16_t len,
void *ctx_ptr)
{
ARG_UNUSED(ctx_ptr);
if (!lifecycle_is_ready() || (ctx.business == BLE_NUS_STACK_OFFLINE) ||
(conn != ctx.active_conn)) {
LOG_WRN("BLE NUS drop RX len:%u lc:%s business:%s active_conn:%p conn:%p link:%s",
len, module_lifecycle_name(ctx.lc.state),
business_state_name(ctx.business),
(void *)ctx.active_conn, (void *)conn,
link_state_name(transport_link_state_get()));
return;
}
if ((len < PROTO_FRAME_HEADER_SIZE) || (len > PROTO_MAX_FRAME_LEN)) {
LOG_WRN("BLE NUS drop invalid framed RX len:%u", len);
return;
}
(void)submit_proto_rx_event(PROTO_TRANSPORT_BLE_NUS, data, len);
}
static struct bt_nus_cb nus_listener = {
.notif_enabled = notif_enabled,
.received = received,
};
static void reset_connection_state(void)
{
ctx.active_conn = NULL;
if (ctx.business != BLE_NUS_STACK_OFFLINE) {
business_state_set(BLE_NUS_IDLE);
}
}
static int do_init(void)
{
int err;
err = bt_nus_cb_register(&nus_listener, NULL);
if (err) {
LOG_ERR("bt_nus_cb_register failed (%d)", err);
return err;
}
ctx.business = BLE_NUS_STACK_OFFLINE;
ctx.active_conn = NULL;
return 0;
}
static int do_start(void)
{
return 0;
}
static int do_stop(void)
{
return 0;
}
static int apply_lifecycle(enum module_lifecycle target)
{
enum module_lifecycle old_lifecycle = ctx.lc.state;
enum ble_nus_business_state old_business = ctx.business;
int err = module_set_lifecycle(&ctx.lc, target);
if (!err) {
state_reconcile(old_lifecycle, old_business);
} else {
LOG_WRN("BLE NUS lifecycle change failed target:%s err:%d",
module_lifecycle_name(target), err);
}
return err;
}
static bool handle_ble_peer_event(const struct ble_peer_event *event)
{
switch (event->state) {
case PEER_STATE_CONNECTED:
if (ctx.active_conn != NULL) {
return false;
}
ctx.active_conn = event->id;
if (ctx.business != BLE_NUS_STACK_OFFLINE) {
business_state_set(BLE_NUS_WAIT_NOTIFY);
}
return false;
case PEER_STATE_DISCONNECTED:
if (ctx.active_conn != event->id) {
return false;
}
reset_connection_state();
return false;
default:
return false;
}
}
static bool handle_proto_tx_event(const struct proto_tx_event *event)
{
int err;
if (event->transport != PROTO_TRANSPORT_BLE_NUS) {
return false;
}
if ((transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) ||
(ctx.active_conn == NULL)) {
return false;
}
err = bt_nus_send(ctx.active_conn, event->dyndata.data,
(uint16_t)event->dyndata.size);
if (err) {
LOG_WRN("bt_nus_send failed (%d)", err);
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_proto_tx_event(aeh)) {
return handle_proto_tx_event(cast_proto_tx_event(aeh));
}
if (is_ble_peer_event(aeh)) {
return handle_ble_peer_event(cast_ble_peer_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)) {
(void)apply_lifecycle(LC_RUNNING);
return false;
}
if (check_state(event, MODULE_ID(ble_state), MODULE_STATE_READY)) {
business_state_set_stack_ready();
return false;
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, proto_tx_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);

380
src/display_module.c Normal file
View File

@@ -0,0 +1,380 @@
#include <errno.h>
#include <stdbool.h>
#include <app_event_manager.h>
#define MODULE display_module
#include <caf/events/module_state_event.h>
#include <caf/events/ble_common_event.h>
#include <caf/events/power_event.h>
#include <lvgl_zephyr.h>
#include <zephyr/device.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/led.h>
#include <zephyr/logging/log.h>
#include "bat_state_event.h"
#include "ble_bond_multi_event.h"
#include "datetime_event.h"
#include "hid_led_event.h"
#include "module_lifecycle.h"
#include "mode_switch_event.h"
#include "settings_mode_event.h"
#include "settings_view_event.h"
#include "theme_rgb_update_event.h"
#include "theme_color.h"
#include "ui/ui_page.h"
#include "ui/ui_main.h"
#include "ui/ui_settings.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
BUILD_ASSERT(DT_HAS_CHOSEN(zephyr_display), "Missing zephyr,display chosen node");
BUILD_ASSERT(DT_NODE_HAS_STATUS(DT_ALIAS(backlight), okay),
"Missing backlight alias");
struct display_module_ctx {
struct module_lifecycle_ctx lc;
const struct device *display_dev;
const struct device *backlight_dev;
uint32_t backlight_idx;
struct ui_main_model ui_model;
struct ui_settings_page *settings_page;
bool settings_active;
bool lvgl_initialized;
char date_text[DATETIME_EVENT_DATE_TEXT_LEN];
char time_text[DATETIME_EVENT_TIME_TEXT_LEN];
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_POWER,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct display_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)),
.backlight_dev = DEVICE_DT_GET(DT_PARENT(DT_ALIAS(backlight))),
.backlight_idx = DT_NODE_CHILD_IDX(DT_ALIAS(backlight)),
.ui_model = {
.theme_color = LV_COLOR_MAKE(BLINKY_THEME_DEFAULT_R,
BLINKY_THEME_DEFAULT_G,
BLINKY_THEME_DEFAULT_B),
.inactive_border_color = LV_COLOR_MAKE(0x3A, 0x44, 0x52),
.mode = MODE_SWITCH_BLE,
.ble_link_state = UI_BLE_LINK_HIDDEN,
},
.date_text = "1970/01/01",
.time_text = "00:00:00",
};
static struct ui_page *main_page(void)
{
return ui_main_page_get(&ctx.ui_model, ctx.date_text, ctx.time_text);
}
static int backlight_set(bool on)
{
if (on) {
return led_on(ctx.backlight_dev, ctx.backlight_idx);
}
return led_off(ctx.backlight_dev, ctx.backlight_idx);
}
static int do_init(void)
{
int err;
LOG_INF("Display init on %s", ctx.display_dev->name);
if (!device_is_ready(ctx.display_dev)) {
LOG_ERR("Display device %s not ready", ctx.display_dev->name);
return -ENODEV;
}
if (!device_is_ready(ctx.backlight_dev)) {
LOG_ERR("Backlight device %s not ready", ctx.backlight_dev->name);
return -ENODEV;
}
err = backlight_set(false);
if (err) {
LOG_ERR("Backlight off failed (%d)", err);
return err;
}
return 0;
}
static int do_start(void)
{
int err;
if (module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
if (!ctx.lvgl_initialized) {
err = lvgl_init();
if (err) {
LOG_ERR("lvgl_init failed (%d)", err);
return err;
}
ctx.lvgl_initialized = true;
lvgl_lock();
ui_page_init(main_page());
lvgl_unlock();
}
lvgl_lock();
if (!ctx.settings_active) {
ui_page_init(main_page());
}
lvgl_unlock();
err = backlight_set(true);
if (err) {
LOG_ERR("Backlight enable failed (%d)", err);
return err;
}
err = display_blanking_off(ctx.display_dev);
if (err) {
LOG_ERR("display_blanking_off failed (%d)", err);
(void)backlight_set(false);
return err;
}
LOG_INF("LVGL display started");
return 0;
}
static int do_stop(void)
{
if (!module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
(void)display_blanking_on(ctx.display_dev);
(void)backlight_set(false);
LOG_INF("LVGL display paused");
return 0;
}
static void refresh_ui(void)
{
if (!ctx.lvgl_initialized) {
return;
}
lvgl_lock();
if (ctx.settings_active && (ctx.settings_page != NULL)) {
ui_settings_show(ctx.settings_page, true);
} else {
ui_page_refresh(main_page());
}
lvgl_unlock();
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_bat_state_event(aeh)) {
const struct bat_state_event *event = cast_bat_state_event(aeh);
ctx.ui_model.battery_level = event->soc;
ctx.ui_model.charging = event->charging;
ctx.ui_model.full = event->full;
refresh_ui();
return false;
}
if (is_mode_switch_event(aeh)) {
const struct mode_switch_event *event = cast_mode_switch_event(aeh);
ctx.ui_model.mode = event->mode;
if (ctx.ui_model.mode != MODE_SWITCH_BLE) {
ctx.ui_model.ble_link_state = UI_BLE_LINK_HIDDEN;
}
refresh_ui();
return false;
}
if (is_ble_bond_multi_event(aeh)) {
refresh_ui();
return false;
}
if (is_ble_peer_search_event(aeh)) {
const struct ble_peer_search_event *event =
cast_ble_peer_search_event(aeh);
if (ctx.ui_model.mode == MODE_SWITCH_BLE) {
ctx.ui_model.ble_link_state = event->active ?
UI_BLE_LINK_SEARCHING : UI_BLE_LINK_HIDDEN;
refresh_ui();
}
return false;
}
if (is_ble_peer_event(aeh)) {
const struct ble_peer_event *event = cast_ble_peer_event(aeh);
if (ctx.ui_model.mode != MODE_SWITCH_BLE) {
return false;
}
switch (event->state) {
case PEER_STATE_CONNECTED:
case PEER_STATE_SECURED:
ctx.ui_model.ble_link_state = UI_BLE_LINK_CONNECTED;
break;
case PEER_STATE_DISCONNECTED:
case PEER_STATE_CONN_FAILED:
ctx.ui_model.ble_link_state = UI_BLE_LINK_SEARCHING;
break;
default:
return false;
}
refresh_ui();
return false;
}
if (is_hid_led_event(aeh)) {
const struct hid_led_event *event = cast_hid_led_event(aeh);
ctx.ui_model.led_mask = event->led_bm;
refresh_ui();
return false;
}
if (is_theme_rgb_update_event(aeh)) {
const struct theme_rgb_update_event *event =
cast_theme_rgb_update_event(aeh);
ctx.ui_model.theme_color = (lv_color_t)LV_COLOR_MAKE(event->theme.r,
event->theme.g,
event->theme.b);
refresh_ui();
return false;
}
if (is_settings_mode_event(aeh)) {
const struct settings_mode_event *event =
cast_settings_mode_event(aeh);
ctx.settings_active = event->active;
if (!ctx.settings_active) {
ctx.settings_page = NULL;
}
if (!ctx.lvgl_initialized) {
return false;
}
lvgl_lock();
if (ctx.settings_active) {
ui_page_deinit(main_page());
ui_settings_init();
if (ctx.settings_page != NULL) {
ui_settings_show(ctx.settings_page, false);
}
} else {
ui_settings_deinit();
ui_page_init(main_page());
ui_page_refresh(main_page());
}
lvgl_unlock();
return false;
}
if (is_settings_view_event(aeh)) {
const struct settings_view_event *event =
cast_settings_view_event(aeh);
ctx.settings_page = event->page;
if (ctx.settings_active && ctx.lvgl_initialized) {
lvgl_lock();
ui_settings_show(ctx.settings_page, event->animate);
lvgl_unlock();
}
return false;
}
if (is_datetime_event(aeh)) {
const struct datetime_event *event = cast_datetime_event(aeh);
strncpy(ctx.date_text, event->date_text, sizeof(ctx.date_text));
ctx.date_text[sizeof(ctx.date_text) - 1] = '\0';
strncpy(ctx.time_text, event->time_text, sizeof(ctx.time_text));
ctx.time_text[sizeof(ctx.time_text) - 1] = '\0';
refresh_ui();
return false;
}
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)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
if (is_power_down_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
}
return false;
}
if (is_wake_up_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, bat_state_event);
APP_EVENT_SUBSCRIBE(MODULE, ble_bond_multi_event);
APP_EVENT_SUBSCRIBE(MODULE, ble_peer_event);
APP_EVENT_SUBSCRIBE(MODULE, ble_peer_search_event);
APP_EVENT_SUBSCRIBE(MODULE, datetime_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_led_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_switch_event);
APP_EVENT_SUBSCRIBE(MODULE, settings_mode_event);
APP_EVENT_SUBSCRIBE(MODULE, settings_view_event);
APP_EVENT_SUBSCRIBE(MODULE, theme_rgb_update_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

View File

@@ -13,6 +13,7 @@
#include <zephyr/pm/device.h> #include <zephyr/pm/device.h>
#include "encoder_event.h" #include "encoder_event.h"
#include "module_lifecycle.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
@@ -22,40 +23,64 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
BUILD_ASSERT(DT_NODE_EXISTS(ENCODER_QDEC_NODE), "Missing qdec0 alias"); BUILD_ASSERT(DT_NODE_EXISTS(ENCODER_QDEC_NODE), "Missing qdec0 alias");
static const struct device *const qdec_dev = DEVICE_DT_GET(ENCODER_QDEC_NODE); struct encoder_module_ctx {
struct module_lifecycle_ctx lc;
static struct k_work encoder_report_work; const struct device *qdec_dev;
static struct sensor_trigger encoder_trigger = { struct k_work encoder_report_work;
.type = SENSOR_TRIG_DATA_READY, struct sensor_trigger encoder_trigger;
.chan = SENSOR_CHAN_ROTATION, int64_t angle_remainder_udeg;
}; };
static bool initialized; static int do_init(void);
static bool running; static int do_start(void);
static int64_t angle_remainder_udeg; static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_POWER,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct encoder_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.qdec_dev = DEVICE_DT_GET(ENCODER_QDEC_NODE),
.encoder_trigger = {
.type = SENSOR_TRIG_DATA_READY,
.chan = SENSOR_CHAN_ROTATION,
},
};
static int64_t sensor_value_to_udeg(const struct sensor_value *value) static int64_t sensor_value_to_udeg(const struct sensor_value *value)
{ {
return ((int64_t)value->val1 * 1000000LL) + value->val2; return ((int64_t)value->val1 * 1000000LL) + value->val2;
} }
static void submit_detents(int32_t detents) static void submit_detents_batched(int32_t detents)
{ {
while (detents != 0) { while (detents != 0) {
struct encoder_event *event = new_encoder_event(); int8_t event_detents;
if (detents > INT8_MAX) { if (detents > INT8_MAX) {
event->detents = INT8_MAX; event_detents = INT8_MAX;
detents -= INT8_MAX; detents -= INT8_MAX;
} else if (detents < INT8_MIN) { } else if (detents < INT8_MIN) {
event->detents = INT8_MIN; event_detents = INT8_MIN;
detents -= INT8_MIN; detents -= INT8_MIN;
} else { } else {
event->detents = (int8_t)detents; event_detents = (int8_t)detents;
detents = 0; detents = 0;
} }
APP_EVENT_SUBMIT(event); submit_encoder_event(event_detents);
} }
} }
@@ -68,28 +93,29 @@ static void encoder_report_work_handler(struct k_work *work)
ARG_UNUSED(work); ARG_UNUSED(work);
if (!running) { if (!module_lifecycle_is_running(&ctx.lc)) {
return; return;
} }
err = sensor_sample_fetch(qdec_dev); err = sensor_sample_fetch(ctx.qdec_dev);
if (err) { if (err) {
LOG_WRN("QDEC sample fetch failed (%d)", err); LOG_WRN("QDEC sample fetch failed (%d)", err);
return; return;
} }
err = sensor_channel_get(qdec_dev, SENSOR_CHAN_ROTATION, &rotation); err = sensor_channel_get(ctx.qdec_dev, SENSOR_CHAN_ROTATION, &rotation);
if (err) { if (err) {
LOG_WRN("QDEC channel get failed (%d)", err); LOG_WRN("QDEC channel get failed (%d)", err);
return; return;
} }
total_udeg = angle_remainder_udeg + sensor_value_to_udeg(&rotation); total_udeg = ctx.angle_remainder_udeg + sensor_value_to_udeg(&rotation);
detents = (int32_t)(total_udeg / ENCODER_DETENT_UDEG); detents = (int32_t)(total_udeg / ENCODER_DETENT_UDEG);
angle_remainder_udeg = total_udeg - ((int64_t)detents * ENCODER_DETENT_UDEG); ctx.angle_remainder_udeg =
total_udeg - ((int64_t)detents * ENCODER_DETENT_UDEG);
if (detents != 0) { if (detents != 0) {
submit_detents(detents); submit_detents_batched(detents);
} }
} }
@@ -99,26 +125,27 @@ static void encoder_trigger_handler(const struct device *dev,
ARG_UNUSED(dev); ARG_UNUSED(dev);
ARG_UNUSED(trigger); ARG_UNUSED(trigger);
if (!running) { if (!module_lifecycle_is_running(&ctx.lc)) {
return; return;
} }
k_work_submit(&encoder_report_work); k_work_submit(&ctx.encoder_report_work);
} }
static int module_init(void) static int do_init(void)
{ {
int err; int err;
if (!device_is_ready(qdec_dev)) { if (!device_is_ready(ctx.qdec_dev)) {
LOG_ERR("QDEC device not ready"); LOG_ERR("QDEC device not ready");
return -ENODEV; return -ENODEV;
} }
k_work_init(&encoder_report_work, encoder_report_work_handler); k_work_init(&ctx.encoder_report_work, encoder_report_work_handler);
angle_remainder_udeg = 0; ctx.angle_remainder_udeg = 0;
err = sensor_trigger_set(qdec_dev, &encoder_trigger, encoder_trigger_handler); err = sensor_trigger_set(ctx.qdec_dev, &ctx.encoder_trigger,
encoder_trigger_handler);
if (err) { if (err) {
LOG_ERR("Cannot set QDEC trigger (%d)", err); LOG_ERR("Cannot set QDEC trigger (%d)", err);
return err; return err;
@@ -127,41 +154,41 @@ static int module_init(void)
return 0; return 0;
} }
static int module_start(void) static int do_start(void)
{ {
int err; int err;
if (running) { if (module_lifecycle_is_running(&ctx.lc)) {
return 0; return 0;
} }
err = pm_device_action_run(qdec_dev, PM_DEVICE_ACTION_RESUME); err = pm_device_action_run(ctx.qdec_dev, PM_DEVICE_ACTION_RESUME);
if (err && (err != -EALREADY) && (err != -ENOTSUP)) { if (err && (err != -EALREADY) && (err != -ENOTSUP)) {
LOG_ERR("Cannot resume QDEC device (%d)", err); LOG_ERR("Cannot resume QDEC device (%d)", err);
return err; return err;
} }
angle_remainder_udeg = 0; ctx.angle_remainder_udeg = 0;
running = true;
return 0; return 0;
} }
static void module_pause(void) static int do_stop(void)
{ {
int err; int err;
if (!running) { if (!module_lifecycle_is_running(&ctx.lc)) {
return; return 0;
} }
err = pm_device_action_run(qdec_dev, PM_DEVICE_ACTION_SUSPEND); err = pm_device_action_run(ctx.qdec_dev, PM_DEVICE_ACTION_SUSPEND);
if (err && (err != -EALREADY) && (err != -ENOTSUP)) { if (err && (err != -EALREADY) && (err != -ENOTSUP)) {
LOG_WRN("Cannot suspend QDEC device (%d)", err); LOG_WRN("Cannot suspend QDEC device (%d)", err);
} }
angle_remainder_udeg = 0; ctx.angle_remainder_udeg = 0;
running = false;
return 0;
} }
static bool app_event_handler(const struct app_event_header *aeh) static bool app_event_handler(const struct app_event_header *aeh)
@@ -170,47 +197,23 @@ static bool app_event_handler(const struct app_event_header *aeh)
const struct module_state_event *event = cast_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)) { if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
int err; (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
if (!initialized) {
err = module_init();
if (err) {
module_set_state(MODULE_STATE_ERROR);
return false;
}
initialized = true;
}
err = module_start();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
} }
return false; return false;
} }
if (is_power_down_event(aeh)) { if (is_power_down_event(aeh)) {
if (initialized) { if (module_lifecycle_is_initialized(&ctx.lc)) {
module_pause(); (void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
module_set_state(MODULE_STATE_STANDBY);
} }
return false; return false;
} }
if (is_wake_up_event(aeh)) { if (is_wake_up_event(aeh)) {
if (initialized) { if (module_lifecycle_is_initialized(&ctx.lc)) {
int err = module_start(); (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
} }
return false; return false;

View File

@@ -0,0 +1,38 @@
#include "ble_bond_multi_event.h"
static void log_ble_bond_multi_event(const struct app_event_header *aeh)
{
const struct ble_bond_multi_event *event =
cast_ble_bond_multi_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "slot:%u identity:%u op:%u occ:0x%02x",
event->current_slot, event->active_identity_id,
event->op, event->slot_occupied_bitmap);
}
static void profile_ble_bond_multi_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct ble_bond_multi_event *event =
cast_ble_bond_multi_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->current_slot);
nrf_profiler_log_encode_uint8(buf, event->active_identity_id);
nrf_profiler_log_encode_uint8(buf, event->op);
nrf_profiler_log_encode_uint8(buf, event->slot_occupied_bitmap);
}
APP_EVENT_INFO_DEFINE(ble_bond_multi_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("current_slot", "active_identity_id", "op",
"slot_occupied_bitmap"),
profile_ble_bond_multi_event);
APP_EVENT_TYPE_DEFINE(ble_bond_multi_event,
log_ble_bond_multi_event,
&ble_bond_multi_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,26 @@
#include "datetime_event.h"
static void log_datetime_event(const struct app_event_header *aeh)
{
const struct datetime_event *event = cast_datetime_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "date:%s time:%s",
event->date_text, event->time_text);
}
static void profile_datetime_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
ARG_UNUSED(buf);
ARG_UNUSED(aeh);
}
APP_EVENT_INFO_DEFINE(datetime_event,
ENCODE(),
ENCODE(),
profile_datetime_event);
APP_EVENT_TYPE_DEFINE(datetime_event,
log_datetime_event,
&datetime_event_info,
APP_EVENT_FLAGS_CREATE());

View File

@@ -0,0 +1,27 @@
#include "function_bitmap_state_event.h"
static void log_function_bitmap_state_event(const struct app_event_header *aeh)
{
const struct function_bitmap_state_event *event =
cast_function_bitmap_state_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "bitmap_len:%zu", sizeof(event->bitmap));
}
static void profile_function_bitmap_state_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
ARG_UNUSED(buf);
ARG_UNUSED(aeh);
}
APP_EVENT_INFO_DEFINE(function_bitmap_state_event,
ENCODE(),
ENCODE(),
profile_function_bitmap_state_event);
APP_EVENT_TYPE_DEFINE(function_bitmap_state_event,
log_function_bitmap_state_event,
&function_bitmap_state_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,24 @@
#include "function_bitmap_update_event.h"
static void log_function_bitmap_update_event(const struct app_event_header *aeh)
{
APP_EVENT_MANAGER_LOG(aeh, "bitmap updated");
}
static void profile_function_bitmap_update_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
ARG_UNUSED(buf);
ARG_UNUSED(aeh);
}
APP_EVENT_INFO_DEFINE(function_bitmap_update_event,
ENCODE(),
ENCODE(),
profile_function_bitmap_update_event);
APP_EVENT_TYPE_DEFINE(function_bitmap_update_event,
log_function_bitmap_update_event,
&function_bitmap_update_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,51 @@
#include "hid_channel_state_event.h"
static const char *channel_name(enum hid_send_channel channel)
{
switch (channel) {
case HID_SEND_CH_USB_KEYS:
return "usb_keys";
case HID_SEND_CH_USB_CONSUMER:
return "usb_consumer";
case HID_SEND_CH_BLE_SHARED:
return "ble_shared";
default:
return "?";
}
}
static void log_hid_channel_state_event(const struct app_event_header *aeh)
{
const struct hid_channel_state_event *event =
cast_hid_channel_state_event(aeh);
APP_EVENT_MANAGER_LOG(aeh,
"channel:%s ready_bm:0x%02x protocol:%d",
channel_name(event->channel),
event->report_ready_bm,
event->protocol_mode);
}
static void profile_hid_channel_state_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_channel_state_event *event =
cast_hid_channel_state_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->channel);
nrf_profiler_log_encode_uint8(buf, event->report_ready_bm);
nrf_profiler_log_encode_uint8(buf, event->protocol_mode);
}
APP_EVENT_INFO_DEFINE(hid_channel_state_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("channel", "ready_bm", "protocol_mode"),
profile_hid_channel_state_event);
APP_EVENT_TYPE_DEFINE(hid_channel_state_event,
log_hid_channel_state_event,
&hid_channel_state_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -1,12 +1,14 @@
#include "hid_report_sent_event.h" #include "hid_report_sent_event.h"
static const char *transport_name(enum hid_transport transport) static const char *channel_name(enum hid_send_channel channel)
{ {
switch (transport) { switch (channel) {
case HID_TRANSPORT_USB: case HID_SEND_CH_USB_KEYS:
return "USB"; return "usb_keys";
case HID_TRANSPORT_BLE: case HID_SEND_CH_USB_CONSUMER:
return "BLE"; return "usb_consumer";
case HID_SEND_CH_BLE_SHARED:
return "ble_shared";
default: default:
return "?"; return "?";
} }
@@ -28,8 +30,8 @@ static void log_hid_report_sent_event(const struct app_event_header *aeh)
{ {
const struct hid_report_sent_event *event = cast_hid_report_sent_event(aeh); const struct hid_report_sent_event *event = cast_hid_report_sent_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "transport:%s type:%s seq:%u error:%u", APP_EVENT_MANAGER_LOG(aeh, "channel:%s type:%s seq:%u error:%u",
transport_name(event->transport), channel_name(event->channel),
report_type_name(event->report_type), report_type_name(event->report_type),
event->sequence, event->sequence,
event->error); event->error);
@@ -40,7 +42,7 @@ static void profile_hid_report_sent_event(struct log_event_buf *buf,
{ {
const struct hid_report_sent_event *event = cast_hid_report_sent_event(aeh); const struct hid_report_sent_event *event = cast_hid_report_sent_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->transport); nrf_profiler_log_encode_uint8(buf, event->channel);
nrf_profiler_log_encode_uint8(buf, event->report_type); nrf_profiler_log_encode_uint8(buf, event->report_type);
nrf_profiler_log_encode_uint16(buf, event->sequence); nrf_profiler_log_encode_uint16(buf, event->sequence);
nrf_profiler_log_encode_uint8(buf, event->error); nrf_profiler_log_encode_uint8(buf, event->error);
@@ -51,7 +53,7 @@ APP_EVENT_INFO_DEFINE(hid_report_sent_event,
NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U16, NRF_PROFILER_ARG_U16,
NRF_PROFILER_ARG_U8), NRF_PROFILER_ARG_U8),
ENCODE("transport", "report_type", "sequence", "error"), ENCODE("channel", "report_type", "sequence", "error"),
profile_hid_report_sent_event); profile_hid_report_sent_event);
APP_EVENT_TYPE_DEFINE(hid_report_sent_event, APP_EVENT_TYPE_DEFINE(hid_report_sent_event,

View File

@@ -1,68 +0,0 @@
#include "hid_transport_state_event.h"
static const char *transport_name(enum hid_transport transport)
{
switch (transport) {
case HID_TRANSPORT_USB:
return "USB";
case HID_TRANSPORT_BLE:
return "BLE";
default:
return "?";
}
}
static const char *protocol_mode_name(enum keyboard_protocol_mode protocol_mode)
{
switch (protocol_mode) {
case KEYBOARD_PROTOCOL_MODE_BOOT:
return "boot";
case KEYBOARD_PROTOCOL_MODE_REPORT:
return "report";
default:
return "?";
}
}
static void log_hid_transport_state_event(const struct app_event_header *aeh)
{
const struct hid_transport_state_event *event =
cast_hid_transport_state_event(aeh);
APP_EVENT_MANAGER_LOG(aeh,
"transport:%s ready:%u keys_ready:%u consumer_ready:%u protocol:%s",
transport_name(event->transport),
event->ready,
event->keys_ready,
event->consumer_ready,
protocol_mode_name(event->protocol_mode));
}
static void profile_hid_transport_state_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_transport_state_event *event =
cast_hid_transport_state_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->transport);
nrf_profiler_log_encode_uint8(buf, event->ready);
nrf_profiler_log_encode_uint8(buf, event->keys_ready);
nrf_profiler_log_encode_uint8(buf, event->consumer_ready);
nrf_profiler_log_encode_uint8(buf, event->protocol_mode);
}
APP_EVENT_INFO_DEFINE(hid_transport_state_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("transport", "ready", "keys_ready", "consumer_ready",
"protocol_mode"),
profile_hid_transport_state_event);
APP_EVENT_TYPE_DEFINE(hid_transport_state_event,
log_hid_transport_state_event,
&hid_transport_state_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -4,13 +4,15 @@
#define HID_TX_REPORT_EVENT_LOG_BUF_LEN 192 #define HID_TX_REPORT_EVENT_LOG_BUF_LEN 192
static const char *transport_name(enum hid_transport transport) static const char *channel_name(enum hid_send_channel channel)
{ {
switch (transport) { switch (channel) {
case HID_TRANSPORT_USB: case HID_SEND_CH_USB_KEYS:
return "USB"; return "usb_keys";
case HID_TRANSPORT_BLE: case HID_SEND_CH_USB_CONSUMER:
return "BLE"; return "usb_consumer";
case HID_SEND_CH_BLE_SHARED:
return "ble_shared";
default: default:
return "?"; return "?";
} }
@@ -47,8 +49,8 @@ static void log_hid_tx_report_event(const struct app_event_header *aeh)
int pos; int pos;
pos = snprintf(log_buf, sizeof(log_buf), pos = snprintf(log_buf, sizeof(log_buf),
"transport:%s type:%s protocol:%s seq:%u len:%zu", "channel:%s type:%s protocol:%s seq:%u len:%zu",
transport_name(event->transport), channel_name(event->channel),
report_type_name(event->report_type), report_type_name(event->report_type),
protocol_mode_name(event->protocol_mode), protocol_mode_name(event->protocol_mode),
event->sequence, event->sequence,
@@ -84,7 +86,7 @@ static void profile_hid_tx_report_event(struct log_event_buf *buf,
{ {
const struct hid_tx_report_event *event = cast_hid_tx_report_event(aeh); const struct hid_tx_report_event *event = cast_hid_tx_report_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->transport); nrf_profiler_log_encode_uint8(buf, event->channel);
nrf_profiler_log_encode_uint8(buf, event->report_type); nrf_profiler_log_encode_uint8(buf, event->report_type);
nrf_profiler_log_encode_uint8(buf, event->protocol_mode); nrf_profiler_log_encode_uint8(buf, event->protocol_mode);
nrf_profiler_log_encode_uint16(buf, event->sequence); nrf_profiler_log_encode_uint16(buf, event->sequence);
@@ -97,7 +99,7 @@ APP_EVENT_INFO_DEFINE(hid_tx_report_event,
NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U16, NRF_PROFILER_ARG_U16,
NRF_PROFILER_ARG_U8), NRF_PROFILER_ARG_U8),
ENCODE("transport", "report_type", "protocol_mode", "sequence", "len"), ENCODE("channel", "report_type", "protocol_mode", "sequence", "len"),
profile_hid_tx_report_event); profile_hid_tx_report_event);
APP_EVENT_TYPE_DEFINE(hid_tx_report_event, APP_EVENT_TYPE_DEFINE(hid_tx_report_event,

View File

@@ -7,6 +7,8 @@
static const char *mode_name(enum mode_switch_mode mode) static const char *mode_name(enum mode_switch_mode mode)
{ {
switch (mode) { switch (mode) {
case MODE_SWITCH_INVALID:
return "INVALID";
case MODE_SWITCH_USB: case MODE_SWITCH_USB:
return "USB"; return "USB";
case MODE_SWITCH_24G: case MODE_SWITCH_24G:

View File

@@ -0,0 +1,27 @@
#include "led_strip_en_event.h"
static void log_led_strip_en_event(const struct app_event_header *aeh)
{
const struct led_strip_en_event *event = cast_led_strip_en_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "enabled:%u", event->enabled);
}
static void profile_led_strip_en_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct led_strip_en_event *event = cast_led_strip_en_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->enabled);
}
APP_EVENT_INFO_DEFINE(led_strip_en_event,
ENCODE(NRF_PROFILER_ARG_U8),
ENCODE("enabled"),
profile_led_strip_en_event);
APP_EVENT_TYPE_DEFINE(led_strip_en_event,
log_led_strip_en_event,
&led_strip_en_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -5,6 +5,8 @@
static const char *mode_name(enum mode_switch_mode mode) static const char *mode_name(enum mode_switch_mode mode)
{ {
switch (mode) { switch (mode) {
case MODE_SWITCH_INVALID:
return "INVALID";
case MODE_SWITCH_USB: case MODE_SWITCH_USB:
return "USB"; return "USB";
case MODE_SWITCH_24G: case MODE_SWITCH_24G:

View File

@@ -0,0 +1,42 @@
#include "proto_rx_event.h"
static const char *transport_name(enum proto_transport transport)
{
switch (transport) {
case PROTO_TRANSPORT_USB_CDC:
return "usb_cdc";
case PROTO_TRANSPORT_BLE_NUS:
return "ble_nus";
default:
return "?";
}
}
static void log_proto_rx_event(const struct app_event_header *aeh)
{
const struct proto_rx_event *event = cast_proto_rx_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "transport:%s len:%zu",
transport_name(event->transport),
event->dyndata.size);
}
static void profile_proto_rx_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct proto_rx_event *event = cast_proto_rx_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->transport);
nrf_profiler_log_encode_uint16(buf, (uint16_t)event->dyndata.size);
}
APP_EVENT_INFO_DEFINE(proto_rx_event,
ENCODE(NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U16),
ENCODE("transport", "len"),
profile_proto_rx_event);
APP_EVENT_TYPE_DEFINE(proto_rx_event,
log_proto_rx_event,
&proto_rx_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,56 @@
#include "proto_transport_state_event.h"
static const char *transport_name(enum proto_transport transport)
{
switch (transport) {
case PROTO_TRANSPORT_USB_CDC:
return "usb_cdc";
case PROTO_TRANSPORT_BLE_NUS:
return "ble_nus";
default:
return "?";
}
}
static const char *state_name(enum proto_transport_link_state state)
{
switch (state) {
case PROTO_TRANSPORT_LINK_DOWN:
return "down";
case PROTO_TRANSPORT_LINK_READY:
return "ready";
default:
return "?";
}
}
static void log_proto_transport_state_event(const struct app_event_header *aeh)
{
const struct proto_transport_state_event *event =
cast_proto_transport_state_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "transport:%s state:%s",
transport_name(event->transport),
state_name(event->state));
}
static void profile_proto_transport_state_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct proto_transport_state_event *event =
cast_proto_transport_state_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->transport);
nrf_profiler_log_encode_uint8(buf, event->state);
}
APP_EVENT_INFO_DEFINE(proto_transport_state_event,
ENCODE(NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8),
ENCODE("transport", "state"),
profile_proto_transport_state_event);
APP_EVENT_TYPE_DEFINE(proto_transport_state_event,
log_proto_transport_state_event,
&proto_transport_state_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,42 @@
#include "proto_tx_event.h"
static const char *transport_name(enum proto_transport transport)
{
switch (transport) {
case PROTO_TRANSPORT_USB_CDC:
return "usb_cdc";
case PROTO_TRANSPORT_BLE_NUS:
return "ble_nus";
default:
return "?";
}
}
static void log_proto_tx_event(const struct app_event_header *aeh)
{
const struct proto_tx_event *event = cast_proto_tx_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "transport:%s len:%zu",
transport_name(event->transport),
event->dyndata.size);
}
static void profile_proto_tx_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct proto_tx_event *event = cast_proto_tx_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->transport);
nrf_profiler_log_encode_uint16(buf, (uint16_t)event->dyndata.size);
}
APP_EVENT_INFO_DEFINE(proto_tx_event,
ENCODE(NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U16),
ENCODE("transport", "len"),
profile_proto_tx_event);
APP_EVENT_TYPE_DEFINE(proto_tx_event,
log_proto_tx_event,
&proto_tx_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,29 @@
#include "settings_mode_event.h"
static void log_settings_mode_event(const struct app_event_header *aeh)
{
const struct settings_mode_event *event =
cast_settings_mode_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "active:%u", event->active);
}
static void profile_settings_mode_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct settings_mode_event *event =
cast_settings_mode_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->active ? 1U : 0U);
}
APP_EVENT_INFO_DEFINE(settings_mode_event,
ENCODE(NRF_PROFILER_ARG_U8),
ENCODE("active"),
profile_settings_mode_event);
APP_EVENT_TYPE_DEFINE(settings_mode_event,
log_settings_mode_event,
&settings_mode_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,33 @@
#include <stdint.h>
#include "settings_view_event.h"
static void log_settings_view_event(const struct app_event_header *aeh)
{
const struct settings_view_event *event =
cast_settings_view_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "page:%p animate:%u",
event->page, event->animate);
}
static void profile_settings_view_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct settings_view_event *event =
cast_settings_view_event(aeh);
nrf_profiler_log_encode_uint32(buf, (uint32_t)(uintptr_t)event->page);
nrf_profiler_log_encode_uint8(buf, event->animate ? 1U : 0U);
}
APP_EVENT_INFO_DEFINE(settings_view_event,
ENCODE(NRF_PROFILER_ARG_U32, NRF_PROFILER_ARG_U8),
ENCODE("page", "animate"),
profile_settings_view_event);
APP_EVENT_TYPE_DEFINE(settings_view_event,
log_settings_view_event,
&settings_view_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,34 @@
#include "theme_rgb_update_event.h"
static void log_theme_rgb_update_event(const struct app_event_header *aeh)
{
const struct theme_rgb_update_event *event =
cast_theme_rgb_update_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "r:%u g:%u b:%u",
event->theme.r, event->theme.g, event->theme.b);
}
static void profile_theme_rgb_update_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct theme_rgb_update_event *event =
cast_theme_rgb_update_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->theme.r);
nrf_profiler_log_encode_uint8(buf, event->theme.g);
nrf_profiler_log_encode_uint8(buf, event->theme.b);
}
APP_EVENT_INFO_DEFINE(theme_rgb_update_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("r", "g", "b"),
profile_theme_rgb_update_event);
APP_EVENT_TYPE_DEFINE(theme_rgb_update_event,
log_theme_rgb_update_event,
&theme_rgb_update_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,41 @@
#include "time_sync_event.h"
static void log_time_sync_event(const struct app_event_header *aeh)
{
const struct time_sync_event *event = cast_time_sync_event(aeh);
APP_EVENT_MANAGER_LOG(aeh,
"ver:%u flags:0x%08x tz:%d utc_ms:%llu acc:%u",
event->version,
event->flags,
event->timezone_min,
event->utc_ms,
event->accuracy_ms);
}
static void profile_time_sync_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct time_sync_event *event = cast_time_sync_event(aeh);
nrf_profiler_log_encode_uint32(buf, event->version);
nrf_profiler_log_encode_uint32(buf, event->flags);
nrf_profiler_log_encode_int32(buf, event->timezone_min);
nrf_profiler_log_encode_uint32(buf, (uint32_t)(event->utc_ms & 0xFFFFFFFFULL));
nrf_profiler_log_encode_uint32(buf, event->accuracy_ms);
}
APP_EVENT_INFO_DEFINE(time_sync_event,
ENCODE(NRF_PROFILER_ARG_U32,
NRF_PROFILER_ARG_U32,
NRF_PROFILER_ARG_S32,
NRF_PROFILER_ARG_U32,
NRF_PROFILER_ARG_U32),
ENCODE("version", "flags", "timezone_min", "utc_ms_lo", "accuracy_ms"),
profile_time_sync_event);
APP_EVENT_TYPE_DEFINE(time_sync_event,
log_time_sync_event,
&time_sync_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,78 @@
#include "transport_policy_event.h"
static const char *source_mode_name(enum mode_switch_mode mode)
{
switch (mode) {
case MODE_SWITCH_USB:
return "USB";
case MODE_SWITCH_BLE:
return "BLE";
case MODE_SWITCH_24G:
return "24G";
default:
return "?";
}
}
static const char *hid_transport_policy_name(enum hid_transport_policy transport)
{
switch (transport) {
case HID_TRANSPORT_POLICY_NONE:
return "none";
case HID_TRANSPORT_POLICY_USB:
return "usb";
case HID_TRANSPORT_POLICY_BLE:
return "ble";
default:
return "?";
}
}
static const char *ble_profile_policy_name(enum ble_profile_policy profile)
{
switch (profile) {
case BLE_PROFILE_POLICY_NONE:
return "none";
case BLE_PROFILE_POLICY_GENERAL:
return "general";
case BLE_PROFILE_POLICY_DONGLE:
return "dongle";
default:
return "?";
}
}
static void log_transport_policy_event(const struct app_event_header *aeh)
{
const struct transport_policy_event *event =
cast_transport_policy_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "source:%s hid:%s ble:%s",
source_mode_name(event->source_mode),
hid_transport_policy_name(event->hid_transport),
ble_profile_policy_name(event->ble_profile));
}
static void profile_transport_policy_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct transport_policy_event *event =
cast_transport_policy_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->source_mode);
nrf_profiler_log_encode_uint8(buf, event->hid_transport);
nrf_profiler_log_encode_uint8(buf, event->ble_profile);
}
APP_EVENT_INFO_DEFINE(transport_policy_event,
ENCODE(NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("source_mode", "hid_transport", "ble_profile"),
profile_transport_policy_event);
APP_EVENT_TYPE_DEFINE(transport_policy_event,
log_transport_policy_event,
&transport_policy_event_info,
APP_EVENT_FLAGS_CREATE(
IF_ENABLED(CONFIG_BRIDGE_LOG_TRANSPORT_POLICY_EVENT,
(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE))));

View File

@@ -0,0 +1,82 @@
#include "usb_control_event.h"
static const char *control_event_name(enum usb_control_event_type type)
{
switch (type) {
case USB_CONTROL_EVENT_CDC_LINE_STATE:
return "cdc_line_state";
case USB_CONTROL_EVENT_CDC_LINE_CODING:
return "cdc_line_coding";
default:
return "?";
}
}
static void log_usb_control_event(const struct app_event_header *aeh)
{
const struct usb_control_event *event = cast_usb_control_event(aeh);
switch (event->type) {
case USB_CONTROL_EVENT_CDC_LINE_STATE:
APP_EVENT_MANAGER_LOG(aeh, "type:%s dtr:%u",
control_event_name(event->type),
event->data.cdc_line_state.dtr);
break;
case USB_CONTROL_EVENT_CDC_LINE_CODING:
APP_EVENT_MANAGER_LOG(aeh,
"type:%s baud:%u data:%u stop:%u parity:%u flow:%u",
control_event_name(event->type),
event->data.cdc_line_coding.baudrate,
event->data.cdc_line_coding.data_bits,
event->data.cdc_line_coding.stop_bits,
event->data.cdc_line_coding.parity,
event->data.cdc_line_coding.flow_ctrl);
break;
default:
APP_EVENT_MANAGER_LOG(aeh, "type:%s",
control_event_name(event->type));
break;
}
}
static void profile_usb_control_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct usb_control_event *event = cast_usb_control_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->type);
switch (event->type) {
case USB_CONTROL_EVENT_CDC_LINE_STATE:
nrf_profiler_log_encode_uint8(buf, event->data.cdc_line_state.dtr);
break;
case USB_CONTROL_EVENT_CDC_LINE_CODING:
nrf_profiler_log_encode_uint32(buf, event->data.cdc_line_coding.baudrate);
nrf_profiler_log_encode_uint8(buf, event->data.cdc_line_coding.data_bits);
nrf_profiler_log_encode_uint8(buf, event->data.cdc_line_coding.stop_bits);
nrf_profiler_log_encode_uint8(buf, event->data.cdc_line_coding.parity);
nrf_profiler_log_encode_uint8(buf, event->data.cdc_line_coding.flow_ctrl);
break;
default:
break;
}
}
APP_EVENT_INFO_DEFINE(usb_control_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U32,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("type", "baud_or_zero", "arg1", "arg2", "arg3", "arg4"),
profile_usb_control_event);
APP_EVENT_TYPE_DEFINE(usb_control_event,
log_usb_control_event,
&usb_control_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,47 @@
#include <caf/events/module_state_event.h>
#include "usb_state_event.h"
static const char *usb_state_name(enum usb_state state)
{
switch (state) {
case USB_STATE_DISABLED:
return "disabled";
case USB_STATE_DISCONNECTED:
return "disconnected";
case USB_STATE_POWERED:
return "powered";
case USB_STATE_ACTIVE:
return "active";
case USB_STATE_SUSPENDED:
return "suspended";
default:
return "?";
}
}
static void log_usb_state_event(const struct app_event_header *aeh)
{
const struct usb_state_event *event = cast_usb_state_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "state:%s", usb_state_name(event->state));
}
static void profile_usb_state_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct usb_state_event *event = cast_usb_state_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->state);
}
APP_EVENT_INFO_DEFINE(usb_state_event,
ENCODE(NRF_PROFILER_ARG_U8),
ENCODE("state"),
profile_usb_state_event);
APP_EVENT_TYPE_DEFINE(usb_state_event,
log_usb_state_event,
&usb_state_event_info,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -10,12 +10,13 @@
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
#include "hid_channel_state_event.h"
#include "hid_report_sent_event.h" #include "hid_report_sent_event.h"
#include "hid_transport_state_event.h"
#include "hid_tx_report_event.h" #include "hid_tx_report_event.h"
#include "keyboard_core.h" #include "keyboard_core.h"
#include "keyboard_hid_report_event.h" #include "keyboard_hid_report_event.h"
#include "mode_switch_event.h" #include "module_lifecycle.h"
#include "transport_policy_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
@@ -37,49 +38,82 @@ struct queued_report {
uint8_t data[HID_FLOWCTRL_REPORT_DATA_MAX]; uint8_t data[HID_FLOWCTRL_REPORT_DATA_MAX];
}; };
struct hid_transport_state_data { struct hid_channel_state_data {
bool ready; uint8_t report_ready_bm;
bool keys_ready;
bool consumer_ready;
enum keyboard_protocol_mode protocol_mode; enum keyboard_protocol_mode protocol_mode;
}; };
struct in_flight_report { struct in_flight_report {
bool active; bool active;
enum hid_transport transport; enum hid_send_channel channel;
enum keyboard_report_type report_type; enum keyboard_report_type report_type;
uint16_t sequence; uint16_t sequence;
}; };
static struct hid_transport_state_data transport_state[HID_TRANSPORT_COUNT] = { struct hid_flowctrl_module_ctx {
[HID_TRANSPORT_USB] = { struct module_lifecycle_ctx lc;
.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT, struct hid_channel_state_data channel_state[HID_SEND_CH_COUNT];
struct pending_report pending_keys;
struct pending_report pending_consumer_latest;
struct queued_report consumer_fifo[HID_FLOWCTRL_FIFO_DEPTH];
uint8_t consumer_fifo_head;
uint8_t consumer_fifo_tail;
uint8_t consumer_fifo_count;
struct in_flight_report in_flight[HID_SEND_CH_COUNT];
enum hid_transport_policy current_transport;
uint16_t next_sequence;
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_POWER,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct hid_flowctrl_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
}, },
[HID_TRANSPORT_BLE] = { .channel_state = {
.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT, [HID_SEND_CH_USB_KEYS] = {
.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT,
},
[HID_SEND_CH_USB_CONSUMER] = {
.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT,
},
[HID_SEND_CH_BLE_SHARED] = {
.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT,
},
}, },
}; };
static struct pending_report pending_keys;
static struct pending_report pending_consumer_latest;
static struct queued_report consumer_fifo[HID_FLOWCTRL_FIFO_DEPTH];
static uint8_t consumer_fifo_head;
static uint8_t consumer_fifo_tail;
static uint8_t consumer_fifo_count;
static struct in_flight_report in_flight;
static enum mode_switch_mode current_mode;
static uint16_t next_sequence;
static bool initialized;
static bool running;
static bool mode_to_transport(enum mode_switch_mode mode, enum hid_transport *transport) static bool current_transport_to_channel(enum keyboard_report_type report_type,
enum hid_send_channel *channel)
{ {
switch (mode) { if (channel == NULL) {
case MODE_SWITCH_USB: return false;
*transport = HID_TRANSPORT_USB; }
switch (ctx.current_transport) {
case HID_TRANSPORT_POLICY_USB:
*channel = (report_type == KEYBOARD_REPORT_TYPE_KEYS) ?
HID_SEND_CH_USB_KEYS :
HID_SEND_CH_USB_CONSUMER;
return true; return true;
case MODE_SWITCH_BLE: case HID_TRANSPORT_POLICY_BLE:
*transport = HID_TRANSPORT_BLE; *channel = HID_SEND_CH_BLE_SHARED;
return true; return true;
default: default:
@@ -89,156 +123,185 @@ static bool mode_to_transport(enum mode_switch_mode mode, enum hid_transport *tr
static void clear_pending_reports(void) static void clear_pending_reports(void)
{ {
memset(&pending_keys, 0, sizeof(pending_keys)); memset(&ctx.pending_keys, 0, sizeof(ctx.pending_keys));
memset(&pending_consumer_latest, 0, sizeof(pending_consumer_latest)); memset(&ctx.pending_consumer_latest, 0, sizeof(ctx.pending_consumer_latest));
consumer_fifo_head = 0U; ctx.consumer_fifo_head = 0U;
consumer_fifo_tail = 0U; ctx.consumer_fifo_tail = 0U;
consumer_fifo_count = 0U; ctx.consumer_fifo_count = 0U;
memset(&in_flight, 0, sizeof(in_flight)); memset(&ctx.in_flight, 0, sizeof(ctx.in_flight));
} }
static void consumer_fifo_push(enum keyboard_report_type report_type, static void consumer_fifo_push(enum keyboard_report_type report_type,
enum keyboard_protocol_mode protocol_mode, enum keyboard_protocol_mode protocol_mode,
const uint8_t *data, size_t size) const uint8_t *data, size_t size)
{ {
if (consumer_fifo_count == HID_FLOWCTRL_FIFO_DEPTH) { if (ctx.consumer_fifo_count == HID_FLOWCTRL_FIFO_DEPTH) {
LOG_WRN("Consumer FIFO full, dropping oldest pulse"); LOG_WRN("Consumer FIFO full, dropping oldest pulse");
consumer_fifo_head = (consumer_fifo_head + 1U) % HID_FLOWCTRL_FIFO_DEPTH; ctx.consumer_fifo_head =
consumer_fifo_count--; (ctx.consumer_fifo_head + 1U) % HID_FLOWCTRL_FIFO_DEPTH;
ctx.consumer_fifo_count--;
} }
struct queued_report *entry = &consumer_fifo[consumer_fifo_tail]; struct queued_report *entry = &ctx.consumer_fifo[ctx.consumer_fifo_tail];
entry->report_type = report_type; entry->report_type = report_type;
entry->protocol_mode = protocol_mode; entry->protocol_mode = protocol_mode;
entry->size = size; entry->size = size;
memcpy(entry->data, data, size); memcpy(entry->data, data, size);
consumer_fifo_tail = (consumer_fifo_tail + 1U) % HID_FLOWCTRL_FIFO_DEPTH; ctx.consumer_fifo_tail =
consumer_fifo_count++; (ctx.consumer_fifo_tail + 1U) % HID_FLOWCTRL_FIFO_DEPTH;
ctx.consumer_fifo_count++;
} }
static bool consumer_fifo_pop(struct queued_report *entry) static bool consumer_fifo_pop(struct queued_report *entry)
{ {
if (consumer_fifo_count == 0U) { if (ctx.consumer_fifo_count == 0U) {
return false; return false;
} }
*entry = consumer_fifo[consumer_fifo_head]; *entry = ctx.consumer_fifo[ctx.consumer_fifo_head];
consumer_fifo_head = (consumer_fifo_head + 1U) % HID_FLOWCTRL_FIFO_DEPTH; ctx.consumer_fifo_head =
consumer_fifo_count--; (ctx.consumer_fifo_head + 1U) % HID_FLOWCTRL_FIFO_DEPTH;
ctx.consumer_fifo_count--;
return true; return true;
} }
static bool transport_can_send_report(enum keyboard_report_type report_type) static bool channel_can_send_report(enum hid_send_channel channel,
enum keyboard_report_type report_type,
enum keyboard_protocol_mode protocol_mode)
{ {
enum hid_transport transport; const struct hid_channel_state_data *state = &ctx.channel_state[channel];
struct hid_transport_state_data *state;
if (!mode_to_transport(current_mode, &transport) || in_flight.active) { if (ctx.in_flight[channel].active) {
return false;
}
state = &transport_state[transport];
if (!state->ready) {
return false; return false;
} }
if (report_type == KEYBOARD_REPORT_TYPE_KEYS) { if (report_type == KEYBOARD_REPORT_TYPE_KEYS) {
return state->keys_ready; return (state->report_ready_bm & BIT(KEYBOARD_REPORT_TYPE_KEYS)) &&
(state->protocol_mode == protocol_mode);
} }
return state->consumer_ready; if (channel == HID_SEND_CH_BLE_SHARED) {
return (state->report_ready_bm & BIT(KEYBOARD_REPORT_TYPE_CONSUMER)) &&
(state->protocol_mode == KEYBOARD_PROTOCOL_MODE_REPORT);
}
return (state->report_ready_bm & BIT(KEYBOARD_REPORT_TYPE_CONSUMER)) != 0U;
} }
static void submit_hid_tx_report_event(enum hid_transport transport, static void try_send_keys(void)
enum keyboard_report_type report_type,
enum keyboard_protocol_mode protocol_mode,
const uint8_t *data, size_t size)
{ {
struct hid_tx_report_event *event = new_hid_tx_report_event(size); enum hid_send_channel channel;
event->transport = transport; if (!ctx.pending_keys.valid) {
event->report_type = report_type; return;
event->protocol_mode = protocol_mode; }
event->sequence = next_sequence++;
memcpy(event->dyndata.data, data, size);
in_flight.active = true; if (!current_transport_to_channel(KEYBOARD_REPORT_TYPE_KEYS, &channel)) {
in_flight.transport = transport; return;
in_flight.report_type = report_type; }
in_flight.sequence = event->sequence;
APP_EVENT_SUBMIT(event); if (!channel_can_send_report(channel, ctx.pending_keys.report_type,
ctx.pending_keys.protocol_mode)) {
return;
}
ctx.in_flight[channel].active = true;
ctx.in_flight[channel].channel = channel;
ctx.in_flight[channel].report_type = ctx.pending_keys.report_type;
ctx.in_flight[channel].sequence = ctx.next_sequence++;
(void)submit_hid_tx_report_event(channel, ctx.pending_keys.report_type,
ctx.pending_keys.protocol_mode,
ctx.in_flight[channel].sequence,
ctx.pending_keys.data, ctx.pending_keys.size);
ctx.pending_keys.valid = false;
}
static void try_send_consumer_fifo(void)
{
struct queued_report queued;
enum hid_send_channel channel;
if (ctx.consumer_fifo_count == 0U) {
return;
}
if (!current_transport_to_channel(KEYBOARD_REPORT_TYPE_CONSUMER, &channel)) {
return;
}
if (!consumer_fifo_pop(&queued)) {
return;
}
if (!channel_can_send_report(channel, queued.report_type,
queued.protocol_mode)) {
if (queued.protocol_mode == ctx.channel_state[channel].protocol_mode) {
consumer_fifo_push(queued.report_type, queued.protocol_mode,
queued.data, queued.size);
}
return;
}
ctx.in_flight[channel].active = true;
ctx.in_flight[channel].channel = channel;
ctx.in_flight[channel].report_type = queued.report_type;
ctx.in_flight[channel].sequence = ctx.next_sequence++;
(void)submit_hid_tx_report_event(channel, queued.report_type,
queued.protocol_mode,
ctx.in_flight[channel].sequence,
queued.data, queued.size);
}
static void try_send_consumer_latest(void)
{
enum hid_send_channel channel;
if (!ctx.pending_consumer_latest.valid) {
return;
}
if (!current_transport_to_channel(KEYBOARD_REPORT_TYPE_CONSUMER, &channel)) {
return;
}
if (!channel_can_send_report(channel,
ctx.pending_consumer_latest.report_type,
ctx.pending_consumer_latest.protocol_mode)) {
return;
}
ctx.in_flight[channel].active = true;
ctx.in_flight[channel].channel = channel;
ctx.in_flight[channel].report_type =
ctx.pending_consumer_latest.report_type;
ctx.in_flight[channel].sequence = ctx.next_sequence++;
(void)submit_hid_tx_report_event(channel,
ctx.pending_consumer_latest.report_type,
ctx.pending_consumer_latest.protocol_mode,
ctx.in_flight[channel].sequence,
ctx.pending_consumer_latest.data,
ctx.pending_consumer_latest.size);
ctx.pending_consumer_latest.valid = false;
} }
static void try_send_next(void) static void try_send_next(void)
{ {
struct queued_report queued; if (!module_lifecycle_is_running(&ctx.lc)) {
enum hid_transport transport;
struct hid_transport_state_data *state;
if (!running || in_flight.active || !mode_to_transport(current_mode, &transport)) {
return; return;
} }
state = &transport_state[transport]; try_send_keys();
try_send_consumer_fifo();
if (!state->ready) { try_send_consumer_latest();
return;
}
if (pending_keys.valid && transport_can_send_report(KEYBOARD_REPORT_TYPE_KEYS)) {
if (pending_keys.protocol_mode != state->protocol_mode) {
LOG_WRN("Drop stale keys report after protocol change");
pending_keys.valid = false;
} else {
submit_hid_tx_report_event(transport,
pending_keys.report_type,
pending_keys.protocol_mode,
pending_keys.data,
pending_keys.size);
pending_keys.valid = false;
return;
}
}
if ((consumer_fifo_count > 0U) &&
transport_can_send_report(KEYBOARD_REPORT_TYPE_CONSUMER) &&
consumer_fifo_pop(&queued)) {
if (queued.protocol_mode != state->protocol_mode) {
LOG_WRN("Drop stale consumer report after protocol change");
} else {
submit_hid_tx_report_event(transport,
queued.report_type,
queued.protocol_mode,
queued.data,
queued.size);
return;
}
}
if (pending_consumer_latest.valid &&
transport_can_send_report(KEYBOARD_REPORT_TYPE_CONSUMER)) {
if (pending_consumer_latest.protocol_mode != state->protocol_mode) {
LOG_WRN("Drop stale latest consumer report after protocol change");
pending_consumer_latest.valid = false;
} else {
submit_hid_tx_report_event(transport,
pending_consumer_latest.report_type,
pending_consumer_latest.protocol_mode,
pending_consumer_latest.data,
pending_consumer_latest.size);
pending_consumer_latest.valid = false;
}
}
} }
static bool handle_keyboard_hid_report_event(const struct keyboard_hid_report_event *event) static bool handle_keyboard_hid_report_event(
const struct keyboard_hid_report_event *event)
{ {
if (!running || if (!module_lifecycle_is_running(&ctx.lc) ||
((event->mode != MODE_SWITCH_USB) && (event->mode != MODE_SWITCH_BLE))) { ((event->mode != MODE_SWITCH_USB) && (event->mode != MODE_SWITCH_BLE))) {
return false; return false;
} }
@@ -249,17 +312,18 @@ static bool handle_keyboard_hid_report_event(const struct keyboard_hid_report_ev
event->dyndata.data, event->dyndata.data,
event->dyndata.size); event->dyndata.size);
} else if (event->report_type == KEYBOARD_REPORT_TYPE_KEYS) { } else if (event->report_type == KEYBOARD_REPORT_TYPE_KEYS) {
pending_keys.valid = true; ctx.pending_keys.valid = true;
pending_keys.report_type = event->report_type; ctx.pending_keys.report_type = event->report_type;
pending_keys.protocol_mode = event->protocol_mode; ctx.pending_keys.protocol_mode = event->protocol_mode;
pending_keys.size = event->dyndata.size; ctx.pending_keys.size = event->dyndata.size;
memcpy(pending_keys.data, event->dyndata.data, event->dyndata.size); memcpy(ctx.pending_keys.data, event->dyndata.data,
event->dyndata.size);
} else { } else {
pending_consumer_latest.valid = true; ctx.pending_consumer_latest.valid = true;
pending_consumer_latest.report_type = event->report_type; ctx.pending_consumer_latest.report_type = event->report_type;
pending_consumer_latest.protocol_mode = event->protocol_mode; ctx.pending_consumer_latest.protocol_mode = event->protocol_mode;
pending_consumer_latest.size = event->dyndata.size; ctx.pending_consumer_latest.size = event->dyndata.size;
memcpy(pending_consumer_latest.data, event->dyndata.data, memcpy(ctx.pending_consumer_latest.data, event->dyndata.data,
event->dyndata.size); event->dyndata.size);
} }
@@ -267,41 +331,19 @@ static bool handle_keyboard_hid_report_event(const struct keyboard_hid_report_ev
return false; return false;
} }
static bool handle_hid_transport_state_event(const struct hid_transport_state_event *event) static bool handle_hid_channel_state_event(
const struct hid_channel_state_event *event)
{ {
enum hid_transport active_transport; if (event->channel >= HID_SEND_CH_COUNT) {
struct hid_transport_state_data *state;
if (event->transport >= HID_TRANSPORT_COUNT) {
return false; return false;
} }
state = &transport_state[event->transport]; ctx.channel_state[event->channel].report_ready_bm =
event->report_ready_bm;
ctx.channel_state[event->channel].protocol_mode = event->protocol_mode;
state->ready = event->ready; if (event->report_ready_bm == 0U) {
state->keys_ready = event->keys_ready; ctx.in_flight[event->channel].active = false;
state->consumer_ready = event->consumer_ready;
if (state->protocol_mode != event->protocol_mode) {
state->protocol_mode = event->protocol_mode;
if (mode_to_transport(current_mode, &active_transport) &&
(active_transport == event->transport)) {
pending_keys.valid = false;
pending_consumer_latest.valid = false;
consumer_fifo_head = 0U;
consumer_fifo_tail = 0U;
consumer_fifo_count = 0U;
}
}
if (!state->ready &&
mode_to_transport(current_mode, &active_transport) &&
(active_transport == event->transport)) {
consumer_fifo_head = 0U;
consumer_fifo_tail = 0U;
consumer_fifo_count = 0U;
in_flight.active = false;
} }
try_send_next(); try_send_next();
@@ -310,17 +352,21 @@ static bool handle_hid_transport_state_event(const struct hid_transport_state_ev
static bool handle_hid_report_sent_event(const struct hid_report_sent_event *event) static bool handle_hid_report_sent_event(const struct hid_report_sent_event *event)
{ {
if (!in_flight.active || (event->transport != in_flight.transport)) { if (event->channel >= HID_SEND_CH_COUNT) {
return false; return false;
} }
if (event->sequence != in_flight.sequence) { if (!ctx.in_flight[event->channel].active) {
return false;
}
if (event->sequence != ctx.in_flight[event->channel].sequence) {
LOG_WRN("Unexpected HID sent sequence %u (expected %u)", LOG_WRN("Unexpected HID sent sequence %u (expected %u)",
event->sequence, in_flight.sequence); event->sequence, ctx.in_flight[event->channel].sequence);
return false; return false;
} }
in_flight.active = false; ctx.in_flight[event->channel].active = false;
if (event->error) { if (event->error) {
LOG_WRN("HID report send failed for seq %u", event->sequence); LOG_WRN("HID report send failed for seq %u", event->sequence);
@@ -330,14 +376,16 @@ static bool handle_hid_report_sent_event(const struct hid_report_sent_event *eve
return false; return false;
} }
static bool handle_mode_switch_event(const struct mode_switch_event *event) static bool handle_transport_policy_event(
const struct transport_policy_event *event)
{ {
bool mode_changed = (current_mode != event->mode); bool transport_changed =
(ctx.current_transport != event->hid_transport);
current_mode = event->mode; ctx.current_transport = event->hid_transport;
if (mode_changed || ((current_mode != MODE_SWITCH_USB) && if (transport_changed ||
(current_mode != MODE_SWITCH_BLE))) { (ctx.current_transport == HID_TRANSPORT_POLICY_NONE)) {
clear_pending_reports(); clear_pending_reports();
} }
@@ -345,110 +393,86 @@ static bool handle_mode_switch_event(const struct mode_switch_event *event)
return false; return false;
} }
static int module_init(void) static int do_init(void)
{ {
clear_pending_reports(); clear_pending_reports();
current_mode = MODE_SWITCH_USB; ctx.current_transport = HID_TRANSPORT_POLICY_USB;
transport_state[HID_TRANSPORT_USB].ready = false; memset(ctx.channel_state, 0, sizeof(ctx.channel_state));
transport_state[HID_TRANSPORT_USB].keys_ready = false; ctx.channel_state[HID_SEND_CH_USB_KEYS].protocol_mode =
transport_state[HID_TRANSPORT_USB].consumer_ready = false;
transport_state[HID_TRANSPORT_USB].protocol_mode =
KEYBOARD_PROTOCOL_MODE_REPORT; KEYBOARD_PROTOCOL_MODE_REPORT;
transport_state[HID_TRANSPORT_BLE].ready = false; ctx.channel_state[HID_SEND_CH_USB_CONSUMER].protocol_mode =
transport_state[HID_TRANSPORT_BLE].keys_ready = false;
transport_state[HID_TRANSPORT_BLE].consumer_ready = false;
transport_state[HID_TRANSPORT_BLE].protocol_mode =
KEYBOARD_PROTOCOL_MODE_REPORT; KEYBOARD_PROTOCOL_MODE_REPORT;
next_sequence = 1U; ctx.channel_state[HID_SEND_CH_BLE_SHARED].protocol_mode =
KEYBOARD_PROTOCOL_MODE_REPORT;
ctx.next_sequence = 1U;
return 0; return 0;
} }
static int module_start(void) static int do_start(void)
{ {
if (running) { if (module_lifecycle_is_running(&ctx.lc)) {
return 0; return 0;
} }
running = true;
try_send_next(); try_send_next();
return 0; return 0;
} }
static void module_pause(void) static int do_stop(void)
{ {
if (!running) { if (!module_lifecycle_is_running(&ctx.lc)) {
return; return 0;
} }
clear_pending_reports(); clear_pending_reports();
running = false;
return 0;
} }
static bool app_event_handler(const struct app_event_header *aeh) static bool app_event_handler(const struct app_event_header *aeh)
{ {
if (is_keyboard_hid_report_event(aeh)) { if (is_keyboard_hid_report_event(aeh)) {
return handle_keyboard_hid_report_event(cast_keyboard_hid_report_event(aeh)); return handle_keyboard_hid_report_event(
cast_keyboard_hid_report_event(aeh));
} }
if (is_hid_transport_state_event(aeh)) { if (is_hid_channel_state_event(aeh)) {
return handle_hid_transport_state_event(cast_hid_transport_state_event(aeh)); return handle_hid_channel_state_event(
cast_hid_channel_state_event(aeh));
} }
if (is_hid_report_sent_event(aeh)) { if (is_hid_report_sent_event(aeh)) {
return handle_hid_report_sent_event(cast_hid_report_sent_event(aeh)); return handle_hid_report_sent_event(
cast_hid_report_sent_event(aeh));
} }
if (is_mode_switch_event(aeh)) { if (is_transport_policy_event(aeh)) {
return handle_mode_switch_event(cast_mode_switch_event(aeh)); return handle_transport_policy_event(
cast_transport_policy_event(aeh));
} }
if (is_module_state_event(aeh)) { if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_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)) { if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
int err; (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
if (!initialized) {
err = module_init();
if (err) {
module_set_state(MODULE_STATE_ERROR);
return false;
}
initialized = true;
}
err = module_start();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
} }
return false; return false;
} }
if (is_power_down_event(aeh)) { if (is_power_down_event(aeh)) {
if (initialized) { if (module_lifecycle_is_initialized(&ctx.lc)) {
module_pause(); (void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
module_set_state(MODULE_STATE_STANDBY);
} }
return false; return false;
} }
if (is_wake_up_event(aeh)) { if (is_wake_up_event(aeh)) {
if (initialized) { if (module_lifecycle_is_initialized(&ctx.lc)) {
int err = module_start(); (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
} }
return false; return false;
@@ -460,9 +484,9 @@ static bool app_event_handler(const struct app_event_header *aeh)
APP_EVENT_LISTENER(MODULE, app_event_handler); APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, keyboard_hid_report_event); APP_EVENT_SUBSCRIBE(MODULE, keyboard_hid_report_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_transport_state_event); APP_EVENT_SUBSCRIBE(MODULE, hid_channel_state_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_report_sent_event); APP_EVENT_SUBSCRIBE(MODULE, hid_report_sent_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_switch_event); APP_EVENT_SUBSCRIBE(MODULE, transport_policy_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event); APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

View File

@@ -15,10 +15,14 @@
#include <zephyr/sys/util.h> #include <zephyr/sys/util.h>
#include "encoder_event.h" #include "encoder_event.h"
#include "function_bitmap_state_event.h"
#include "function_bitmap_update_event.h"
#include "keyboard_core.h" #include "keyboard_core.h"
#include "keyboard_hid_report_event.h" #include "keyboard_hid_report_event.h"
#include "mode_switch_event.h" #include "module_lifecycle.h"
#include "settings_mode_event.h"
#include "set_protocol_event.h" #include "set_protocol_event.h"
#include "transport_policy_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
@@ -39,8 +43,8 @@ struct keymap_entry {
}; };
struct keyboard_state { struct keyboard_state {
uint8_t modifiers; uint8_t pressed_usage_bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES];
uint8_t keys_bitmap[KEYBOARD_NKRO_BITMAP_BYTES]; uint8_t function_pressed_bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES];
uint32_t consumer_bits; uint32_t consumer_bits;
}; };
@@ -83,25 +87,52 @@ static const uint16_t consumer_usage_map[KEYBOARD_CONSUMER_CTRL_COUNT] = {
[KEYBOARD_CONSUMER_CTRL_PREV_TRACK] = 0x00B6, [KEYBOARD_CONSUMER_CTRL_PREV_TRACK] = 0x00B6,
}; };
static struct keyboard_state keyboard_state; struct keyboard_core_module_ctx {
static struct keyboard_reports_cache reports_cache; struct module_lifecycle_ctx lc;
static enum keyboard_protocol_mode transport_protocol_modes[HID_TRANSPORT_COUNT] = { struct keyboard_state keyboard_state;
[HID_TRANSPORT_USB] = KEYBOARD_PROTOCOL_MODE_REPORT, struct keyboard_reports_cache reports_cache;
[HID_TRANSPORT_BLE] = KEYBOARD_PROTOCOL_MODE_REPORT, uint8_t function_usage_mask[KEYBOARD_PROTOCOL_BITMAP_BYTES];
enum keyboard_protocol_mode transport_protocol_modes[HID_TRANSPORT_COUNT];
enum hid_transport_policy current_transport;
bool settings_active;
}; };
static enum mode_switch_mode current_mode;
static bool initialized;
static bool running;
static bool mode_valid;
static bool mode_to_transport(enum mode_switch_mode mode, enum hid_transport *transport) static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_POWER,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct keyboard_core_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.transport_protocol_modes = {
[HID_TRANSPORT_USB] = KEYBOARD_PROTOCOL_MODE_REPORT,
[HID_TRANSPORT_BLE] = KEYBOARD_PROTOCOL_MODE_REPORT,
},
};
static bool policy_to_transport(enum hid_transport_policy policy,
enum hid_transport *transport)
{ {
switch (mode) { switch (policy) {
case MODE_SWITCH_USB: case HID_TRANSPORT_POLICY_USB:
*transport = HID_TRANSPORT_USB; *transport = HID_TRANSPORT_USB;
return true; return true;
case MODE_SWITCH_BLE: case HID_TRANSPORT_POLICY_BLE:
*transport = HID_TRANSPORT_BLE; *transport = HID_TRANSPORT_BLE;
return true; return true;
@@ -114,13 +145,32 @@ static enum keyboard_protocol_mode active_protocol_mode_get(void)
{ {
enum hid_transport transport; enum hid_transport transport;
if (mode_valid && mode_to_transport(current_mode, &transport)) { if (policy_to_transport(ctx.current_transport, &transport)) {
return transport_protocol_modes[transport]; return ctx.transport_protocol_modes[transport];
} }
return KEYBOARD_PROTOCOL_MODE_REPORT; return KEYBOARD_PROTOCOL_MODE_REPORT;
} }
static bool transport_policy_to_mode(enum hid_transport_policy policy,
enum mode_switch_mode *mode)
{
if (mode == NULL) {
return false;
}
switch (policy) {
case HID_TRANSPORT_POLICY_USB:
*mode = MODE_SWITCH_USB;
return true;
case HID_TRANSPORT_POLICY_BLE:
*mode = MODE_SWITCH_BLE;
return true;
default:
return false;
}
}
static const struct keymap_entry *keymap_get(uint16_t key_id) static const struct keymap_entry *keymap_get(uint16_t key_id)
{ {
size_t left = 0; size_t left = 0;
@@ -143,40 +193,61 @@ static const struct keymap_entry *keymap_get(uint16_t key_id)
return NULL; return NULL;
} }
static bool usage_is_modifier(uint16_t usage_id) static bool usage_to_bitmap_pos(uint16_t usage_id, uint8_t *byte_idx,
uint8_t *bit_idx)
{ {
return IN_RANGE(usage_id, KEYBOARD_USAGE_FIRST_MODIFIER, if ((byte_idx == NULL) || (bit_idx == NULL)) {
KEYBOARD_USAGE_LAST_MODIFIER); return false;
} }
static bool keyboard_key_update(uint16_t usage_id, bool pressed) if ((usage_id >= KEYBOARD_USAGE_FIRST_MODIFIER) &&
{ (usage_id <= KEYBOARD_USAGE_LAST_MODIFIER)) {
if (usage_is_modifier(usage_id)) { *byte_idx = 0U;
uint8_t new_modifiers = keyboard_state.modifiers; *bit_idx = (uint8_t)(usage_id - KEYBOARD_USAGE_FIRST_MODIFIER);
WRITE_BIT(new_modifiers, usage_id - KEYBOARD_USAGE_FIRST_MODIFIER, pressed);
if (new_modifiers == keyboard_state.modifiers) {
return false;
}
keyboard_state.modifiers = new_modifiers;
return true; return true;
} }
if (usage_id > KEYBOARD_NKRO_USAGE_MAX) { if (usage_id <= KEYBOARD_NKRO_USAGE_MAX) {
*byte_idx = (uint8_t)(1U + (usage_id / 8U));
*bit_idx = (uint8_t)(usage_id % 8U);
return true;
}
return false;
}
static bool usage_bitmap_test(const uint8_t *bitmap, uint16_t usage_id)
{
uint8_t byte_idx;
uint8_t bit_idx;
if ((bitmap == NULL) ||
!usage_to_bitmap_pos(usage_id, &byte_idx, &bit_idx)) {
return false;
}
return (bitmap[byte_idx] & BIT(bit_idx)) != 0U;
}
static bool usage_bitmap_write(uint8_t *bitmap, uint16_t usage_id, bool pressed)
{
uint8_t byte_idx;
uint8_t bit_idx;
bool was_pressed;
if ((bitmap == NULL) ||
!usage_to_bitmap_pos(usage_id, &byte_idx, &bit_idx)) {
LOG_WRN("Unsupported keyboard usage 0x%04x", usage_id); LOG_WRN("Unsupported keyboard usage 0x%04x", usage_id);
return false; return false;
} }
uint8_t byte_idx = usage_id / 8U; was_pressed = (bitmap[byte_idx] & BIT(bit_idx)) != 0U;
uint8_t bit_idx = usage_id % 8U;
bool was_pressed = (keyboard_state.keys_bitmap[byte_idx] & BIT(bit_idx)) != 0U;
if (was_pressed == pressed) { if (was_pressed == pressed) {
return false; return false;
} }
WRITE_BIT(keyboard_state.keys_bitmap[byte_idx], bit_idx, pressed); WRITE_BIT(bitmap[byte_idx], bit_idx, pressed);
return true; return true;
} }
@@ -187,41 +258,59 @@ static bool consumer_key_update(uint16_t consumer_id, bool pressed)
return false; return false;
} }
bool was_pressed = (keyboard_state.consumer_bits & BIT(consumer_id)) != 0U; bool was_pressed =
(ctx.keyboard_state.consumer_bits & BIT(consumer_id)) != 0U;
if (was_pressed == pressed) { if (was_pressed == pressed) {
return false; return false;
} }
WRITE_BIT(keyboard_state.consumer_bits, consumer_id, pressed); WRITE_BIT(ctx.keyboard_state.consumer_bits, consumer_id, pressed);
return true; return true;
} }
static void keyboard_state_clear(void) static void keyboard_state_clear(void)
{ {
memset(&keyboard_state, 0, sizeof(keyboard_state)); memset(&ctx.keyboard_state, 0, sizeof(ctx.keyboard_state));
}
static void function_usage_mask_clear(void)
{
memset(ctx.function_usage_mask, 0, sizeof(ctx.function_usage_mask));
} }
static void reports_cache_invalidate(void) static void reports_cache_invalidate(void)
{ {
reports_cache.boot_valid = false; ctx.reports_cache.boot_valid = false;
reports_cache.nkro_valid = false; ctx.reports_cache.nkro_valid = false;
reports_cache.consumer_valid = false; ctx.reports_cache.consumer_valid = false;
}
static void build_effective_hid_bitmap(uint8_t bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES])
{
for (size_t i = 0; i < KEYBOARD_PROTOCOL_BITMAP_BYTES; i++) {
bitmap[i] = ctx.keyboard_state.pressed_usage_bitmap[i] &
(uint8_t)~ctx.keyboard_state.function_pressed_bitmap[i];
}
} }
static void build_boot_report(uint8_t report[KEYBOARD_BOOT_REPORT_SIZE]) static void build_boot_report(uint8_t report[KEYBOARD_BOOT_REPORT_SIZE])
{ {
uint8_t effective_hid_bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES];
size_t key_count = 0; size_t key_count = 0;
build_effective_hid_bitmap(effective_hid_bitmap);
memset(report, 0, KEYBOARD_BOOT_REPORT_SIZE); memset(report, 0, KEYBOARD_BOOT_REPORT_SIZE);
report[0] = keyboard_state.modifiers; report[0] = effective_hid_bitmap[0];
report[1] = KEYBOARD_BOOT_RESERVED_BYTE; report[1] = KEYBOARD_BOOT_RESERVED_BYTE;
for (uint16_t usage_id = 0; usage_id <= KEYBOARD_NKRO_USAGE_MAX; usage_id++) { for (uint16_t usage_id = 0; usage_id <= KEYBOARD_NKRO_USAGE_MAX; usage_id++) {
uint8_t byte_idx = usage_id / 8U; uint8_t byte_idx;
uint8_t bit_idx = usage_id % 8U; uint8_t bit_idx;
if ((keyboard_state.keys_bitmap[byte_idx] & BIT(bit_idx)) == 0U) { (void)usage_to_bitmap_pos(usage_id, &byte_idx, &bit_idx);
if ((effective_hid_bitmap[byte_idx] & BIT(bit_idx)) == 0U) {
continue; continue;
} }
@@ -238,14 +327,17 @@ static void build_boot_report(uint8_t report[KEYBOARD_BOOT_REPORT_SIZE])
static void build_nkro_report(uint8_t report[KEYBOARD_NKRO_REPORT_SIZE]) static void build_nkro_report(uint8_t report[KEYBOARD_NKRO_REPORT_SIZE])
{ {
report[0] = keyboard_state.modifiers; uint8_t effective_hid_bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES];
memcpy(&report[1], keyboard_state.keys_bitmap, KEYBOARD_NKRO_BITMAP_BYTES);
build_effective_hid_bitmap(effective_hid_bitmap);
report[0] = effective_hid_bitmap[0];
memcpy(&report[1], &effective_hid_bitmap[1], KEYBOARD_NKRO_BITMAP_BYTES);
} }
static uint16_t active_consumer_usage_get(void) static uint16_t active_consumer_usage_get(void)
{ {
for (uint8_t consumer_id = 0; consumer_id < KEYBOARD_CONSUMER_CTRL_COUNT; consumer_id++) { for (uint8_t consumer_id = 0; consumer_id < KEYBOARD_CONSUMER_CTRL_COUNT; consumer_id++) {
if ((keyboard_state.consumer_bits & BIT(consumer_id)) != 0U) { if ((ctx.keyboard_state.consumer_bits & BIT(consumer_id)) != 0U) {
return consumer_usage_map[consumer_id]; return consumer_usage_map[consumer_id];
} }
} }
@@ -258,38 +350,23 @@ static void build_consumer_report(uint8_t report[KEYBOARD_CONSUMER_REPORT_SIZE])
sys_put_le16(active_consumer_usage_get(), report); sys_put_le16(active_consumer_usage_get(), report);
} }
static void submit_keyboard_report_event(enum keyboard_report_type report_type,
enum hid_queue_policy queue_policy,
const uint8_t *data, size_t size)
{
struct keyboard_hid_report_event *event =
new_keyboard_hid_report_event(size);
enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get();
event->mode = current_mode;
event->report_type = report_type;
event->protocol_mode = protocol_mode;
event->queue_policy = queue_policy;
memcpy(event->dyndata.data, data, size);
APP_EVENT_SUBMIT(event);
}
static void submit_consumer_fifo_frame(uint16_t usage_id) static void submit_consumer_fifo_frame(uint16_t usage_id)
{ {
uint8_t report_buf[KEYBOARD_CONSUMER_REPORT_SIZE]; uint8_t report_buf[KEYBOARD_CONSUMER_REPORT_SIZE];
enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get(); enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get();
enum mode_switch_mode mode;
if (!running || !mode_valid || if (!module_lifecycle_is_running(&ctx.lc) ||
ctx.settings_active ||
!transport_policy_to_mode(ctx.current_transport, &mode) ||
(protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) { (protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) {
return; return;
} }
sys_put_le16(usage_id, report_buf); sys_put_le16(usage_id, report_buf);
submit_keyboard_report_event(KEYBOARD_REPORT_TYPE_CONSUMER, (void)submit_keyboard_hid_report_event(
HID_QUEUE_POLICY_FIFO, mode, KEYBOARD_REPORT_TYPE_CONSUMER, protocol_mode,
report_buf, HID_QUEUE_POLICY_FIFO, report_buf, KEYBOARD_CONSUMER_REPORT_SIZE);
KEYBOARD_CONSUMER_REPORT_SIZE);
} }
static void submit_consumer_pulse_frames(enum keyboard_consumer_control control_id, static void submit_consumer_pulse_frames(enum keyboard_consumer_control control_id,
@@ -325,21 +402,26 @@ static void emit_keys_report(bool force)
uint8_t *cache_buf; uint8_t *cache_buf;
bool *cache_valid; bool *cache_valid;
enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get(); enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get();
enum mode_switch_mode mode;
if (!mode_valid) { if (!transport_policy_to_mode(ctx.current_transport, &mode)) {
return;
}
if (ctx.settings_active) {
return; return;
} }
if (protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) { if (protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) {
build_boot_report(report_buf); build_boot_report(report_buf);
report_size = KEYBOARD_BOOT_REPORT_SIZE; report_size = KEYBOARD_BOOT_REPORT_SIZE;
cache_buf = reports_cache.boot_report; cache_buf = ctx.reports_cache.boot_report;
cache_valid = &reports_cache.boot_valid; cache_valid = &ctx.reports_cache.boot_valid;
} else { } else {
build_nkro_report(report_buf); build_nkro_report(report_buf);
report_size = KEYBOARD_NKRO_REPORT_SIZE; report_size = KEYBOARD_NKRO_REPORT_SIZE;
cache_buf = reports_cache.nkro_report; cache_buf = ctx.reports_cache.nkro_report;
cache_valid = &reports_cache.nkro_valid; cache_valid = &ctx.reports_cache.nkro_valid;
} }
if (!force && *cache_valid && (memcmp(cache_buf, report_buf, report_size) == 0)) { if (!force && *cache_valid && (memcmp(cache_buf, report_buf, report_size) == 0)) {
@@ -349,35 +431,38 @@ static void emit_keys_report(bool force)
memcpy(cache_buf, report_buf, report_size); memcpy(cache_buf, report_buf, report_size);
*cache_valid = true; *cache_valid = true;
submit_keyboard_report_event(KEYBOARD_REPORT_TYPE_KEYS, (void)submit_keyboard_hid_report_event(
HID_QUEUE_POLICY_LATEST, mode, KEYBOARD_REPORT_TYPE_KEYS, protocol_mode,
report_buf, HID_QUEUE_POLICY_LATEST, report_buf, report_size);
report_size);
} }
static void emit_consumer_report(bool force) static void emit_consumer_report(bool force)
{ {
uint8_t report_buf[KEYBOARD_CONSUMER_REPORT_SIZE]; uint8_t report_buf[KEYBOARD_CONSUMER_REPORT_SIZE];
enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get(); enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get();
enum mode_switch_mode mode;
if (!mode_valid || (protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) { if (!transport_policy_to_mode(ctx.current_transport, &mode) ||
ctx.settings_active ||
(protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) {
return; return;
} }
build_consumer_report(report_buf); build_consumer_report(report_buf);
if (!force && reports_cache.consumer_valid && if (!force && ctx.reports_cache.consumer_valid &&
(memcmp(reports_cache.consumer_report, report_buf, (memcmp(ctx.reports_cache.consumer_report, report_buf,
KEYBOARD_CONSUMER_REPORT_SIZE) == 0)) { KEYBOARD_CONSUMER_REPORT_SIZE) == 0)) {
return; return;
} }
memcpy(reports_cache.consumer_report, report_buf, KEYBOARD_CONSUMER_REPORT_SIZE); memcpy(ctx.reports_cache.consumer_report, report_buf,
reports_cache.consumer_valid = true; KEYBOARD_CONSUMER_REPORT_SIZE);
ctx.reports_cache.consumer_valid = true;
submit_keyboard_report_event(KEYBOARD_REPORT_TYPE_CONSUMER, (void)submit_keyboard_hid_report_event(
HID_QUEUE_POLICY_LATEST, mode, KEYBOARD_REPORT_TYPE_CONSUMER, protocol_mode,
report_buf, HID_QUEUE_POLICY_LATEST, report_buf,
KEYBOARD_CONSUMER_REPORT_SIZE); KEYBOARD_CONSUMER_REPORT_SIZE);
} }
static void emit_all_reports(bool force) static void emit_all_reports(bool force)
@@ -389,71 +474,84 @@ static void emit_all_reports(bool force)
} }
} }
static void emit_release_reports(enum mode_switch_mode mode) static void emit_function_state_event(void)
{
(void)submit_function_bitmap_state_event(
ctx.keyboard_state.pressed_usage_bitmap);
}
static void emit_release_reports(enum hid_transport_policy transport_policy)
{ {
struct keyboard_hid_report_event *event;
uint8_t keys_report[KEYBOARD_NKRO_REPORT_SIZE] = { 0 }; uint8_t keys_report[KEYBOARD_NKRO_REPORT_SIZE] = { 0 };
uint8_t consumer_report[KEYBOARD_CONSUMER_REPORT_SIZE] = { 0 }; uint8_t consumer_report[KEYBOARD_CONSUMER_REPORT_SIZE] = { 0 };
enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get(); enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get();
enum mode_switch_mode mode;
size_t keys_report_size = size_t keys_report_size =
(protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) ? (protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) ?
KEYBOARD_BOOT_REPORT_SIZE : KEYBOARD_NKRO_REPORT_SIZE; KEYBOARD_BOOT_REPORT_SIZE : KEYBOARD_NKRO_REPORT_SIZE;
event = new_keyboard_hid_report_event(keys_report_size); switch (transport_policy) {
event->mode = mode; case HID_TRANSPORT_POLICY_USB:
event->report_type = KEYBOARD_REPORT_TYPE_KEYS; mode = MODE_SWITCH_USB;
event->protocol_mode = protocol_mode; break;
memcpy(event->dyndata.data, keys_report, keys_report_size); case HID_TRANSPORT_POLICY_BLE:
APP_EVENT_SUBMIT(event); mode = MODE_SWITCH_BLE;
break;
if (protocol_mode != KEYBOARD_PROTOCOL_MODE_BOOT) { default:
event = new_keyboard_hid_report_event(KEYBOARD_CONSUMER_REPORT_SIZE);
event->mode = mode;
event->report_type = KEYBOARD_REPORT_TYPE_CONSUMER;
event->protocol_mode = protocol_mode;
memcpy(event->dyndata.data, consumer_report,
KEYBOARD_CONSUMER_REPORT_SIZE);
APP_EVENT_SUBMIT(event);
}
}
static int module_init(void)
{
keyboard_state_clear();
reports_cache_invalidate();
mode_valid = false;
transport_protocol_modes[HID_TRANSPORT_USB] =
KEYBOARD_PROTOCOL_MODE_REPORT;
transport_protocol_modes[HID_TRANSPORT_BLE] =
KEYBOARD_PROTOCOL_MODE_REPORT;
return 0;
}
static int module_start(void)
{
if (running) {
return 0;
}
running = true;
return 0;
}
static void module_pause(void)
{
if (!running) {
return; return;
} }
if (mode_valid) { (void)submit_keyboard_hid_report_event(
emit_release_reports(current_mode); mode, KEYBOARD_REPORT_TYPE_KEYS, protocol_mode,
HID_QUEUE_POLICY_LATEST, keys_report, keys_report_size);
if (protocol_mode != KEYBOARD_PROTOCOL_MODE_BOOT) {
(void)submit_keyboard_hid_report_event(
mode, KEYBOARD_REPORT_TYPE_CONSUMER, protocol_mode,
HID_QUEUE_POLICY_LATEST, consumer_report,
KEYBOARD_CONSUMER_REPORT_SIZE);
} }
}
static int do_init(void)
{
keyboard_state_clear();
reports_cache_invalidate();
function_usage_mask_clear();
ctx.settings_active = false;
ctx.current_transport = HID_TRANSPORT_POLICY_USB;
ctx.transport_protocol_modes[HID_TRANSPORT_USB] =
KEYBOARD_PROTOCOL_MODE_REPORT;
ctx.transport_protocol_modes[HID_TRANSPORT_BLE] =
KEYBOARD_PROTOCOL_MODE_REPORT;
return 0;
}
static int do_start(void)
{
if (module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
return 0;
}
static int do_stop(void)
{
if (!module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
if (ctx.current_transport != HID_TRANSPORT_POLICY_NONE) {
emit_release_reports(ctx.current_transport);
}
emit_function_state_event();
keyboard_state_clear(); keyboard_state_clear();
reports_cache_invalidate(); reports_cache_invalidate();
mode_valid = false;
running = false; return 0;
} }
static bool handle_button_event(const struct button_event *event) static bool handle_button_event(const struct button_event *event)
@@ -461,7 +559,11 @@ static bool handle_button_event(const struct button_event *event)
const struct keymap_entry *entry; const struct keymap_entry *entry;
bool changed; bool changed;
if (!running) { if (!module_lifecycle_is_running(&ctx.lc)) {
return false;
}
if (ctx.settings_active) {
return false; return false;
} }
@@ -472,8 +574,30 @@ static bool handle_button_event(const struct button_event *event)
} }
if (entry->usage_type == KEY_USAGE_TYPE_KEYBOARD) { if (entry->usage_type == KEY_USAGE_TYPE_KEYBOARD) {
changed = keyboard_key_update(entry->usage_id, event->pressed); bool routed_to_function;
if (changed) {
changed = usage_bitmap_write(ctx.keyboard_state.pressed_usage_bitmap,
entry->usage_id, event->pressed);
if (!changed) {
return false;
}
if (event->pressed) {
routed_to_function =
usage_bitmap_test(ctx.function_usage_mask, entry->usage_id);
(void)usage_bitmap_write(ctx.keyboard_state.function_pressed_bitmap,
entry->usage_id, routed_to_function);
} else {
routed_to_function =
usage_bitmap_test(ctx.keyboard_state.function_pressed_bitmap,
entry->usage_id);
(void)usage_bitmap_write(ctx.keyboard_state.function_pressed_bitmap,
entry->usage_id, false);
}
if (routed_to_function) {
emit_function_state_event();
} else {
emit_keys_report(false); emit_keys_report(false);
} }
} else { } else {
@@ -486,32 +610,42 @@ static bool handle_button_event(const struct button_event *event)
return false; return false;
} }
static bool handle_mode_switch_event(const struct mode_switch_event *event) static bool handle_transport_policy_event(
const struct transport_policy_event *event)
{ {
bool mode_changed; bool transport_changed;
if (!running) { if (!module_lifecycle_is_running(&ctx.lc)) {
current_mode = event->mode; ctx.current_transport = event->hid_transport;
return false; return false;
} }
mode_changed = mode_valid && (current_mode != event->mode); transport_changed = (ctx.current_transport != HID_TRANSPORT_POLICY_NONE) &&
if (mode_changed) { (ctx.current_transport != event->hid_transport);
emit_release_reports(current_mode); if (transport_changed) {
emit_release_reports(ctx.current_transport);
emit_function_state_event();
keyboard_state_clear(); keyboard_state_clear();
reports_cache_invalidate(); reports_cache_invalidate();
} }
current_mode = event->mode; ctx.current_transport = event->hid_transport;
mode_valid = true;
emit_all_reports(true); if (ctx.current_transport != HID_TRANSPORT_POLICY_NONE) {
emit_all_reports(true);
}
return false; return false;
} }
static bool handle_encoder_event(const struct encoder_event *event) static bool handle_encoder_event(const struct encoder_event *event)
{ {
if (!running || !mode_valid) { if (!module_lifecycle_is_running(&ctx.lc) ||
(ctx.current_transport == HID_TRANSPORT_POLICY_NONE)) {
return false;
}
if (ctx.settings_active) {
return false; return false;
} }
@@ -526,6 +660,14 @@ static bool handle_encoder_event(const struct encoder_event *event)
return false; return false;
} }
static bool handle_function_bitmap_update_event(
const struct function_bitmap_update_event *event)
{
memcpy(ctx.function_usage_mask, event->bitmap,
sizeof(ctx.function_usage_mask));
return false;
}
static bool app_event_handler(const struct app_event_header *aeh) static bool app_event_handler(const struct app_event_header *aeh)
{ {
if (is_button_event(aeh)) { if (is_button_event(aeh)) {
@@ -536,6 +678,11 @@ static bool app_event_handler(const struct app_event_header *aeh)
return handle_encoder_event(cast_encoder_event(aeh)); return handle_encoder_event(cast_encoder_event(aeh));
} }
if (is_function_bitmap_update_event(aeh)) {
return handle_function_bitmap_update_event(
cast_function_bitmap_update_event(aeh));
}
if (is_set_protocol_event(aeh)) { if (is_set_protocol_event(aeh)) {
const struct set_protocol_event *event = cast_set_protocol_event(aeh); const struct set_protocol_event *event = cast_set_protocol_event(aeh);
enum hid_transport active_transport; enum hid_transport active_transport;
@@ -544,11 +691,13 @@ static bool app_event_handler(const struct app_event_header *aeh)
return false; return false;
} }
if (transport_protocol_modes[event->transport] != event->protocol_mode) { if (ctx.transport_protocol_modes[event->transport] !=
transport_protocol_modes[event->transport] = event->protocol_mode; event->protocol_mode) {
ctx.transport_protocol_modes[event->transport] =
event->protocol_mode;
if (running && mode_valid && if (module_lifecycle_is_running(&ctx.lc) &&
mode_to_transport(current_mode, &active_transport) && policy_to_transport(ctx.current_transport, &active_transport) &&
(active_transport == event->transport)) { (active_transport == event->transport)) {
reports_cache_invalidate(); reports_cache_invalidate();
emit_keys_report(true); emit_keys_report(true);
@@ -562,55 +711,53 @@ static bool app_event_handler(const struct app_event_header *aeh)
return false; return false;
} }
if (is_mode_switch_event(aeh)) { if (is_transport_policy_event(aeh)) {
return handle_mode_switch_event(cast_mode_switch_event(aeh)); return handle_transport_policy_event(
cast_transport_policy_event(aeh));
}
if (is_settings_mode_event(aeh)) {
const struct settings_mode_event *event =
cast_settings_mode_event(aeh);
if (ctx.settings_active == event->active) {
return false;
}
ctx.settings_active = event->active;
if (ctx.settings_active) {
if (ctx.current_transport != HID_TRANSPORT_POLICY_NONE) {
emit_release_reports(ctx.current_transport);
}
emit_function_state_event();
keyboard_state_clear();
reports_cache_invalidate();
}
return false;
} }
if (is_module_state_event(aeh)) { if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_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)) { if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
int err; (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
if (!initialized) {
err = module_init();
if (err) {
module_set_state(MODULE_STATE_ERROR);
return false;
}
initialized = true;
}
err = module_start();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
} }
return false; return false;
} }
if (is_power_down_event(aeh)) { if (is_power_down_event(aeh)) {
if (initialized) { if (module_lifecycle_is_initialized(&ctx.lc)) {
module_pause(); (void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
module_set_state(MODULE_STATE_STANDBY);
} }
return false; return false;
} }
if (is_wake_up_event(aeh)) { if (is_wake_up_event(aeh)) {
if (initialized) { if (module_lifecycle_is_initialized(&ctx.lc)) {
int err = module_start(); (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
} }
return false; return false;
@@ -623,8 +770,10 @@ static bool app_event_handler(const struct app_event_header *aeh)
APP_EVENT_LISTENER(MODULE, app_event_handler); APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, button_event); APP_EVENT_SUBSCRIBE(MODULE, button_event);
APP_EVENT_SUBSCRIBE(MODULE, encoder_event); APP_EVENT_SUBSCRIBE(MODULE, encoder_event);
APP_EVENT_SUBSCRIBE(MODULE, function_bitmap_update_event);
APP_EVENT_SUBSCRIBE(MODULE, set_protocol_event); APP_EVENT_SUBSCRIBE(MODULE, set_protocol_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_switch_event); APP_EVENT_SUBSCRIBE(MODULE, settings_mode_event);
APP_EVENT_SUBSCRIBE(MODULE, transport_policy_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event); APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

View File

@@ -0,0 +1,155 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <zephyr/sys/util.h>
#include "led_effect/led_effect.h"
#define KEY_FADE_LEVEL_MAX 255U
#define KEY_FADE_STEP_PER_TICK 12U
struct led_effect_key_fade_state {
const struct led_key_map *key_map;
size_t key_map_len;
size_t pixel_count;
uint8_t default_brightness;
uint8_t level[17];
struct theme_rgb theme;
};
static struct led_effect_key_fade_state key_fade_state;
static const struct led_key_map *find_key(const struct led_effect *effect,
uint16_t key_id)
{
const struct led_effect_key_fade_state *state = effect->state;
for (size_t i = 0; i < state->key_map_len; i++) {
if (state->key_map[i].key_id == key_id) {
return &state->key_map[i];
}
}
return NULL;
}
static int led_effect_key_fade_init(struct led_effect *effect,
const struct led_effect_config *cfg)
{
struct led_effect_key_fade_state *state = effect->state;
if ((effect == NULL) || (cfg == NULL) || (cfg->key_map == NULL) ||
(cfg->pixel_count > ARRAY_SIZE(state->level))) {
return -EINVAL;
}
memset(state, 0, sizeof(*state));
state->key_map = cfg->key_map;
state->key_map_len = cfg->key_map_len;
state->pixel_count = cfg->pixel_count;
state->default_brightness = cfg->default_brightness;
state->theme.r = BLINKY_THEME_DEFAULT_R;
state->theme.g = BLINKY_THEME_DEFAULT_G;
state->theme.b = BLINKY_THEME_DEFAULT_B;
return 0;
}
static void led_effect_key_fade_reset(struct led_effect *effect)
{
struct led_effect_key_fade_state *state = effect->state;
memset(state->level, 0, sizeof(state->level));
}
static void led_effect_key_fade_set_theme(struct led_effect *effect,
const struct theme_rgb *theme)
{
struct led_effect_key_fade_state *state = effect->state;
if (theme == NULL) {
return;
}
state->theme = *theme;
}
static void led_effect_key_fade_on_key_press(struct led_effect *effect,
uint16_t key_id)
{
struct led_effect_key_fade_state *state = effect->state;
const struct led_key_map *map = find_key(effect, key_id);
if ((map == NULL) || (map->led_idx >= state->pixel_count)) {
return;
}
state->level[map->led_idx] =
(state->default_brightness == 0U) ? KEY_FADE_LEVEL_MAX :
state->default_brightness;
}
static bool led_effect_key_fade_tick(struct led_effect *effect, uint32_t dt_ms,
struct led_rgb *pixels, size_t pixel_count)
{
struct led_effect_key_fade_state *state = effect->state;
bool active = false;
bool visible = false;
uint8_t decay;
ARG_UNUSED(dt_ms);
if ((pixels == NULL) || (pixel_count < state->pixel_count)) {
return false;
}
memset(pixels, 0, sizeof(*pixels) * pixel_count);
decay = KEY_FADE_STEP_PER_TICK;
for (size_t i = 0; i < state->pixel_count; i++) {
uint8_t level = state->level[i];
if (level == 0U) {
continue;
}
visible = true;
pixels[i].r = (uint8_t)(((uint16_t)state->theme.r * level) / 255U);
pixels[i].g = (uint8_t)(((uint16_t)state->theme.g * level) / 255U);
pixels[i].b = (uint8_t)(((uint16_t)state->theme.b * level) / 255U);
state->level[i] = (level > decay) ? (uint8_t)(level - decay) : 0U;
active = active || (state->level[i] > 0U);
}
return visible || active;
}
static bool led_effect_key_fade_is_active(const struct led_effect *effect)
{
const struct led_effect_key_fade_state *state = effect->state;
for (size_t i = 0; i < state->pixel_count; i++) {
if (state->level[i] > 0U) {
return true;
}
}
return false;
}
static const struct led_effect_ops key_fade_ops = {
.init = led_effect_key_fade_init,
.reset = led_effect_key_fade_reset,
.set_theme = led_effect_key_fade_set_theme,
.on_key_press = led_effect_key_fade_on_key_press,
.tick = led_effect_key_fade_tick,
.is_active = led_effect_key_fade_is_active,
};
struct led_effect led_effect_key_fade = {
.ops = &key_fade_ops,
.state = &key_fade_state,
};

View File

@@ -0,0 +1,50 @@
#ifndef BLINKY_LED_EFFECT_H_
#define BLINKY_LED_EFFECT_H_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <zephyr/drivers/led_strip.h>
#include "led_effect_types.h"
#include "theme_color.h"
#ifdef __cplusplus
extern "C" {
#endif
struct led_effect;
struct led_effect_config {
const struct led_key_map *key_map;
size_t key_map_len;
size_t pixel_count;
uint8_t default_brightness;
};
struct led_effect_ops {
int (*init)(struct led_effect *effect,
const struct led_effect_config *cfg);
void (*reset)(struct led_effect *effect);
void (*set_theme)(struct led_effect *effect,
const struct theme_rgb *theme);
void (*on_key_press)(struct led_effect *effect, uint16_t key_id);
bool (*tick)(struct led_effect *effect, uint32_t dt_ms,
struct led_rgb *pixels, size_t pixel_count);
bool (*is_active)(const struct led_effect *effect);
};
struct led_effect {
const struct led_effect_ops *ops;
void *state;
};
const struct led_effect *led_effect_get(enum led_effect_id id);
struct led_effect *led_effect_get_mutable(enum led_effect_id id);
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_LED_EFFECT_H_ */

View File

@@ -0,0 +1,27 @@
#include <stddef.h>
#include "led_effect.h"
extern struct led_effect led_effect_key_fade;
static struct led_effect *const effect_registry[LED_EFFECT_ID_COUNT] = {
[LED_EFFECT_ID_KEY_FADE] = &led_effect_key_fade,
};
const struct led_effect *led_effect_get(enum led_effect_id id)
{
if (id >= LED_EFFECT_ID_COUNT) {
return NULL;
}
return effect_registry[id];
}
struct led_effect *led_effect_get_mutable(enum led_effect_id id)
{
if (id >= LED_EFFECT_ID_COUNT) {
return NULL;
}
return effect_registry[id];
}

View File

@@ -0,0 +1,24 @@
#ifndef BLINKY_LED_EFFECT_TYPES_H_
#define BLINKY_LED_EFFECT_TYPES_H_
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct led_key_map {
uint16_t key_id;
uint8_t led_idx;
};
enum led_effect_id {
LED_EFFECT_ID_KEY_FADE = 0,
LED_EFFECT_ID_COUNT,
};
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_LED_EFFECT_TYPES_H_ */

370
src/led_strip_module.c Normal file
View File

@@ -0,0 +1,370 @@
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <app_event_manager.h>
#define MODULE led_strip_module
#include <caf/events/button_event.h>
#include <caf/events/module_state_event.h>
#include <caf/events/power_event.h>
#include <caf/key_id.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/led_strip.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "led_effect/led_effect.h"
#include "led_strip_en_event.h"
#include "module_lifecycle.h"
#include "theme_rgb_update_event.h"
#include "theme_color.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define LED_STRIP_NODE DT_CHOSEN(zephyr_led_strip)
#define LED_STRIP_NUM_PIXELS DT_PROP(LED_STRIP_NODE, chain_length)
#define LED_STRIP_TICK_INTERVAL K_MSEC(20)
BUILD_ASSERT(DT_NODE_HAS_STATUS(LED_STRIP_NODE, okay),
"Missing zephyr,led-strip chosen node");
BUILD_ASSERT(DT_NODE_HAS_PROP(LED_STRIP_NODE, supply_gpios),
"Missing supply-gpios on zephyr,led-strip node");
BUILD_ASSERT(LED_STRIP_NUM_PIXELS == 17U,
"LED strip key map expects 17 pixels");
struct led_strip_module_ctx {
struct module_lifecycle_ctx lc;
const struct device *strip;
struct gpio_dt_spec strip_en;
struct led_rgb pixels[LED_STRIP_NUM_PIXELS];
struct k_work_delayable effect_work;
struct led_effect *effect;
struct theme_rgb current_theme;
bool enabled;
};
static const struct led_key_map led_key_map[] = {
{ KEY_ID(0, 1), 0U },
{ KEY_ID(1, 1), 1U },
{ KEY_ID(2, 1), 2U },
{ KEY_ID(3, 1), 3U },
{ KEY_ID(0, 2), 4U },
{ KEY_ID(1, 2), 5U },
{ KEY_ID(2, 2), 6U },
{ KEY_ID(0, 3), 7U },
{ KEY_ID(1, 3), 8U },
{ KEY_ID(2, 3), 9U },
{ KEY_ID(3, 3), 10U },
{ KEY_ID(0, 4), 11U },
{ KEY_ID(1, 4), 12U },
{ KEY_ID(2, 4), 13U },
{ KEY_ID(0, 5), 14U },
{ KEY_ID(1, 5), 15U },
{ KEY_ID(3, 5), 16U },
};
static const struct led_effect_config effect_cfg = {
.key_map = led_key_map,
.key_map_len = ARRAY_SIZE(led_key_map),
.pixel_count = LED_STRIP_NUM_PIXELS,
.default_brightness = 0xFFU,
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_POWER,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct led_strip_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.strip = DEVICE_DT_GET(LED_STRIP_NODE),
.strip_en = GPIO_DT_SPEC_GET(LED_STRIP_NODE, supply_gpios),
.current_theme = {
.r = BLINKY_THEME_DEFAULT_R,
.g = BLINKY_THEME_DEFAULT_G,
.b = BLINKY_THEME_DEFAULT_B,
},
.enabled = true,
};
static void clear_pixels(void)
{
memset(ctx.pixels, 0, sizeof(ctx.pixels));
}
static int submit_frame(void)
{
int err;
err = led_strip_update_rgb(ctx.strip, ctx.pixels, ARRAY_SIZE(ctx.pixels));
if (err) {
LOG_WRN("led_strip_update_rgb failed (%d)", err);
}
return err;
}
static int set_strip_power(bool on)
{
int err;
err = gpio_pin_set_dt(&ctx.strip_en, on ? 1 : 0);
if (err) {
LOG_WRN("LED strip EN set failed (%d)", err);
}
return err;
}
static int refresh_idle_frame(void)
{
clear_pixels();
return submit_frame();
}
static void stop_effect_work(void)
{
k_work_cancel_delayable(&ctx.effect_work);
}
static void schedule_effect_tick(k_timeout_t delay)
{
k_work_reschedule(&ctx.effect_work, delay);
}
static int enable_strip_output(void)
{
int err;
err = set_strip_power(true);
if (err) {
return err;
}
return refresh_idle_frame();
}
static void disable_strip_output(void)
{
stop_effect_work();
clear_pixels();
(void)submit_frame();
(void)set_strip_power(false);
}
static void effect_work_handler(struct k_work *work)
{
bool keep_running;
ARG_UNUSED(work);
if (!module_lifecycle_is_running(&ctx.lc) || !ctx.enabled ||
(ctx.effect == NULL)) {
return;
}
keep_running = ctx.effect->ops->tick(ctx.effect,
k_ticks_to_ms_floor32(
LED_STRIP_TICK_INTERVAL.ticks),
ctx.pixels, ARRAY_SIZE(ctx.pixels));
(void)submit_frame();
if (keep_running) {
schedule_effect_tick(LED_STRIP_TICK_INTERVAL);
}
}
static int do_init(void)
{
int err;
if (!device_is_ready(ctx.strip)) {
LOG_ERR("LED strip device %s not ready", ctx.strip->name);
return -ENODEV;
}
if (!gpio_is_ready_dt(&ctx.strip_en)) {
LOG_ERR("LED strip supply GPIO not ready");
return -ENODEV;
}
err = gpio_pin_configure_dt(&ctx.strip_en, GPIO_OUTPUT_INACTIVE);
if (err) {
LOG_ERR("Failed to configure LED strip supply GPIO (%d)", err);
return err;
}
k_work_init_delayable(&ctx.effect_work, effect_work_handler);
clear_pixels();
ctx.effect = led_effect_get_mutable(LED_EFFECT_ID_KEY_FADE);
if ((ctx.effect == NULL) || (ctx.effect->ops == NULL)) {
LOG_ERR("LED effect not available");
return -ENOENT;
}
err = ctx.effect->ops->init(ctx.effect, &effect_cfg);
if (err) {
LOG_ERR("LED effect init failed (%d)", err);
return err;
}
ctx.effect->ops->set_theme(ctx.effect, &ctx.current_theme);
LOG_INF("LED strip ready len:%u", (uint32_t)led_strip_length(ctx.strip));
return 0;
}
static int do_start(void)
{
int err;
if (module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
if (!ctx.enabled) {
return 0;
}
err = enable_strip_output();
return err;
}
static int do_stop(void)
{
if (!module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
if ((ctx.effect != NULL) && (ctx.effect->ops != NULL)) {
ctx.effect->ops->reset(ctx.effect);
}
disable_strip_output();
return 0;
}
static bool handle_button_event(const struct button_event *event)
{
if (!module_lifecycle_is_running(&ctx.lc) || !ctx.enabled ||
(ctx.effect == NULL) || !event->pressed) {
return false;
}
ctx.effect->ops->on_key_press(ctx.effect, event->key_id);
schedule_effect_tick(K_NO_WAIT);
return false;
}
static bool handle_led_strip_en_event(const struct led_strip_en_event *event)
{
ctx.enabled = event->enabled;
if (!module_lifecycle_is_running(&ctx.lc)) {
return false;
}
if (ctx.enabled) {
int err = enable_strip_output();
if (err) {
LOG_WRN("LED strip enable request failed (%d)", err);
ctx.enabled = false;
return false;
}
} else {
if ((ctx.effect != NULL) && (ctx.effect->ops != NULL)) {
ctx.effect->ops->reset(ctx.effect);
}
disable_strip_output();
}
return false;
}
static bool handle_theme_rgb_update_event(const struct theme_rgb_update_event *event)
{
ctx.current_theme = event->theme;
if ((ctx.effect != NULL) && (ctx.effect->ops != NULL)) {
ctx.effect->ops->set_theme(ctx.effect, &ctx.current_theme);
}
if (module_lifecycle_is_running(&ctx.lc) && ctx.enabled &&
(ctx.effect != NULL) && ctx.effect->ops->is_active(ctx.effect)) {
schedule_effect_tick(K_NO_WAIT);
}
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_led_strip_en_event(aeh)) {
return handle_led_strip_en_event(cast_led_strip_en_event(aeh));
}
if (is_theme_rgb_update_event(aeh)) {
return handle_theme_rgb_update_event(cast_theme_rgb_update_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)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
if (is_power_down_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
}
return false;
}
if (is_wake_up_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, button_event);
APP_EVENT_SUBSCRIBE(MODULE, led_strip_en_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, theme_rgb_update_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

272
src/mode_policy_module.c Normal file
View File

@@ -0,0 +1,272 @@
#include <stdbool.h>
#include <app_event_manager.h>
#define MODULE mode_policy_module
#include <caf/events/module_state_event.h>
#include <caf/events/module_suspend_event.h>
#include <zephyr/logging/log.h>
#include "module_lifecycle.h"
#include "mode_switch_event.h"
#include "transport_policy_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
struct transport_policy_state {
enum hid_transport_policy hid_transport;
enum ble_profile_policy ble_profile;
};
struct mode_policy_module_ctx {
struct module_lifecycle_ctx lc;
enum mode_switch_mode active_mode;
struct transport_policy_state policy;
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_NONE,
.stopped_state = MODULE_STATE_OFF,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct mode_policy_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.active_mode = MODE_SWITCH_USB,
.policy = {
.hid_transport = HID_TRANSPORT_POLICY_USB,
.ble_profile = BLE_PROFILE_POLICY_NONE,
},
};
static void build_policy_from_mode(enum mode_switch_mode mode,
struct transport_policy_state *policy)
{
switch (mode) {
case MODE_SWITCH_USB:
policy->hid_transport = HID_TRANSPORT_POLICY_USB;
policy->ble_profile = BLE_PROFILE_POLICY_NONE;
break;
case MODE_SWITCH_BLE:
policy->hid_transport = HID_TRANSPORT_POLICY_BLE;
policy->ble_profile = BLE_PROFILE_POLICY_GENERAL;
break;
case MODE_SWITCH_24G:
policy->hid_transport = HID_TRANSPORT_POLICY_BLE;
policy->ble_profile = BLE_PROFILE_POLICY_DONGLE;
break;
default:
policy->hid_transport = HID_TRANSPORT_POLICY_NONE;
policy->ble_profile = BLE_PROFILE_POLICY_NONE;
break;
}
}
static bool policy_equal(const struct transport_policy_state *a,
const struct transport_policy_state *b)
{
return (a->hid_transport == b->hid_transport) &&
(a->ble_profile == b->ble_profile);
}
static void broadcast_policy(void)
{
submit_transport_policy_event(ctx.active_mode, ctx.policy.hid_transport,
ctx.policy.ble_profile);
}
static void mode_policy_set_ble(bool enable)
{
if (enable) {
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);
} else {
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);
}
}
static void mode_policy_set_usb(bool enable)
{
if (enable) {
struct module_resume_req_event *event = new_module_resume_req_event();
event->sink_module_id = MODULE_ID(usb_device_module);
event->src_module_id = MODULE_ID(MODULE);
APP_EVENT_SUBMIT(event);
} else {
struct module_suspend_req_event *event = new_module_suspend_req_event();
event->sink_module_id = MODULE_ID(usb_device_module);
event->src_module_id = MODULE_ID(MODULE);
APP_EVENT_SUBMIT(event);
}
}
static void apply_stack_policy(void)
{
switch (ctx.active_mode) {
case MODE_SWITCH_BLE:
mode_policy_set_ble(true);
mode_policy_set_usb(false);
break;
case MODE_SWITCH_USB:
mode_policy_set_ble(false);
mode_policy_set_usb(true);
break;
case MODE_SWITCH_24G:
mode_policy_set_ble(true);
mode_policy_set_usb(false);
break;
default:
break;
}
}
static void apply_ble_mode_policy(void)
{
switch (ctx.active_mode) {
case MODE_SWITCH_BLE:
mode_policy_set_ble(true);
break;
case MODE_SWITCH_USB:
mode_policy_set_ble(false);
break;
case MODE_SWITCH_24G:
mode_policy_set_ble(true);
break;
default:
break;
}
}
static void apply_usb_mode_policy(void)
{
switch (ctx.active_mode) {
case MODE_SWITCH_BLE:
mode_policy_set_usb(false);
break;
case MODE_SWITCH_USB:
mode_policy_set_usb(true);
break;
case MODE_SWITCH_24G:
mode_policy_set_usb(false);
break;
default:
break;
}
}
static void update_mode_policy(enum mode_switch_mode mode)
{
struct transport_policy_state next_policy;
bool mode_changed = (ctx.active_mode != mode);
bool policy_changed;
build_policy_from_mode(mode, &next_policy);
policy_changed = !policy_equal(&ctx.policy, &next_policy);
ctx.active_mode = mode;
ctx.policy = next_policy;
if (policy_changed || mode_changed) {
broadcast_policy();
}
apply_stack_policy();
}
static int do_init(void)
{
ctx.active_mode = MODE_SWITCH_USB;
build_policy_from_mode(ctx.active_mode, &ctx.policy);
return 0;
}
static int do_start(void)
{
broadcast_policy();
apply_stack_policy();
return 0;
}
static int do_stop(void)
{
return 0;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_mode_switch_event(aeh)) {
const struct mode_switch_event *event = cast_mode_switch_event(aeh);
if (module_lifecycle_is_running(&ctx.lc)) {
update_mode_policy(event->mode);
}
return false;
}
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)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
return false;
}
if (check_state(event, MODULE_ID(ble_adv), MODULE_STATE_READY)) {
if (module_lifecycle_is_running(&ctx.lc)) {
apply_ble_mode_policy();
}
return false;
}
if (check_state(event, MODULE_ID(usb_device_module),
MODULE_STATE_READY)) {
if (module_lifecycle_is_running(&ctx.lc)) {
apply_usb_mode_policy();
}
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, mode_switch_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);

View File

@@ -14,6 +14,7 @@
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
#include <zephyr/pm/device.h> #include <zephyr/pm/device.h>
#include "module_lifecycle.h"
#include "mode_switch_event.h" #include "mode_switch_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
@@ -26,15 +27,36 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
BUILD_ASSERT(DT_NODE_EXISTS(MODE_SWITCH_ADC_NODE), BUILD_ASSERT(DT_NODE_EXISTS(MODE_SWITCH_ADC_NODE),
"Missing mode_switch_adc node in devicetree"); "Missing mode_switch_adc node in devicetree");
static const struct device *const mode_switch_adc_dev = struct mode_switch_module_ctx {
DEVICE_DT_GET(MODE_SWITCH_ADC_NODE); struct module_lifecycle_ctx lc;
const struct device *mode_switch_adc_dev;
struct k_work_delayable mode_switch_sample_work;
enum mode_switch_mode last_mode;
};
static struct k_work_delayable mode_switch_sample_work; static int do_init(void);
static bool initialized; static int do_start(void);
static bool running; static int do_stop(void);
static bool force_report;
static bool mode_valid; static const struct module_lifecycle_cfg lifecycle_cfg = {
static enum mode_switch_mode last_mode; .mode = ML_MODE_POWER,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct mode_switch_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.mode_switch_adc_dev = DEVICE_DT_GET(MODE_SWITCH_ADC_NODE),
};
static enum mode_switch_mode detect_mode(uint16_t voltage_mv) static enum mode_switch_mode detect_mode(uint16_t voltage_mv)
{ {
@@ -53,7 +75,7 @@ static int mode_switch_enable(bool enable)
{ {
enum pm_device_action action = enable ? PM_DEVICE_ACTION_RESUME enum pm_device_action action = enable ? PM_DEVICE_ACTION_RESUME
: PM_DEVICE_ACTION_SUSPEND; : PM_DEVICE_ACTION_SUSPEND;
int err = pm_device_action_run(mode_switch_adc_dev, action); int err = pm_device_action_run(ctx.mode_switch_adc_dev, action);
if (err && (err != -EALREADY) && (err != -ENOTSUP)) { if (err && (err != -EALREADY) && (err != -ENOTSUP)) {
LOG_ERR("Cannot %s mode switch ADC (%d)", LOG_ERR("Cannot %s mode switch ADC (%d)",
@@ -71,17 +93,18 @@ static void mode_switch_sample_fn(struct k_work *work)
ARG_UNUSED(work); ARG_UNUSED(work);
if (!running) { if (!module_lifecycle_is_running(&ctx.lc)) {
return; return;
} }
err = sensor_sample_fetch(mode_switch_adc_dev); err = sensor_sample_fetch(ctx.mode_switch_adc_dev);
if (err) { if (err) {
LOG_WRN("Mode switch ADC sample fetch failed (%d)", err); LOG_WRN("Mode switch ADC sample fetch failed (%d)", err);
goto reschedule; goto reschedule;
} }
err = sensor_channel_get(mode_switch_adc_dev, SENSOR_CHAN_VOLTAGE, &voltage); err = sensor_channel_get(ctx.mode_switch_adc_dev, SENSOR_CHAN_VOLTAGE,
&voltage);
if (err) { if (err) {
LOG_WRN("Mode switch ADC channel get failed (%d)", err); LOG_WRN("Mode switch ADC channel get failed (%d)", err);
goto reschedule; goto reschedule;
@@ -90,43 +113,37 @@ static void mode_switch_sample_fn(struct k_work *work)
uint16_t sample_mv = (uint16_t)((voltage.val1 * 1000) + (voltage.val2 / 1000)); uint16_t sample_mv = (uint16_t)((voltage.val1 * 1000) + (voltage.val2 / 1000));
enum mode_switch_mode mode = detect_mode(sample_mv); enum mode_switch_mode mode = detect_mode(sample_mv);
if (force_report || !mode_valid || (mode != last_mode)) { if ((ctx.last_mode == MODE_SWITCH_INVALID) || (mode != ctx.last_mode)) {
struct mode_switch_event *event = new_mode_switch_event(); submit_mode_switch_event(mode, sample_mv);
event->mode = mode; ctx.last_mode = mode;
event->voltage_mv = sample_mv;
APP_EVENT_SUBMIT(event);
last_mode = mode;
mode_valid = true;
force_report = false;
} }
reschedule: reschedule:
if (running) { if (module_lifecycle_is_running(&ctx.lc)) {
k_work_reschedule(&mode_switch_sample_work, MODE_SWITCH_SAMPLE_INTERVAL); k_work_reschedule(&ctx.mode_switch_sample_work,
MODE_SWITCH_SAMPLE_INTERVAL);
} }
} }
static int module_init(void) static int do_init(void)
{ {
if (!device_is_ready(mode_switch_adc_dev)) { if (!device_is_ready(ctx.mode_switch_adc_dev)) {
LOG_ERR("Mode switch ADC device not ready"); LOG_ERR("Mode switch ADC device not ready");
return -ENODEV; return -ENODEV;
} }
k_work_init_delayable(&mode_switch_sample_work, mode_switch_sample_fn); k_work_init_delayable(&ctx.mode_switch_sample_work, mode_switch_sample_fn);
mode_valid = false; ctx.last_mode = MODE_SWITCH_INVALID;
force_report = false;
return 0; return 0;
} }
static int module_start(void) static int do_start(void)
{ {
int err; int err;
if (running) { if (module_lifecycle_is_running(&ctx.lc)) {
return 0; return 0;
} }
@@ -135,22 +152,21 @@ static int module_start(void)
return err; return err;
} }
running = true; k_work_reschedule(&ctx.mode_switch_sample_work, K_NO_WAIT);
force_report = true;
k_work_reschedule(&mode_switch_sample_work, K_NO_WAIT);
return 0; return 0;
} }
static void module_pause(void) static int do_stop(void)
{ {
if (!running) { if (!module_lifecycle_is_running(&ctx.lc)) {
return; return 0;
} }
(void)k_work_cancel_delayable(&mode_switch_sample_work); (void)k_work_cancel_delayable(&ctx.mode_switch_sample_work);
(void)mode_switch_enable(false); (void)mode_switch_enable(false);
running = false;
return 0;
} }
static bool app_event_handler(const struct app_event_header *aeh) static bool app_event_handler(const struct app_event_header *aeh)
@@ -159,46 +175,35 @@ static bool app_event_handler(const struct app_event_header *aeh)
const struct module_state_event *event = cast_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)) { if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
int err; (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
return false;
}
if (!initialized) { if (check_state(event, MODULE_ID(mode_policy_module),
err = module_init(); MODULE_STATE_READY)) {
if (err) { if (ctx.last_mode != MODE_SWITCH_INVALID) {
module_set_state(MODULE_STATE_ERROR); submit_mode_switch_event(ctx.last_mode, 0U);
return false; }
} return false;
initialized = true;
}
err = module_start();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
} }
return false; return false;
} }
if (is_power_down_event(aeh)) { if (is_power_down_event(aeh)) {
if (initialized) { if (module_lifecycle_is_initialized(&ctx.lc)) {
module_pause(); (void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
module_set_state(MODULE_STATE_STANDBY);
} }
return false; return false;
} }
if (is_wake_up_event(aeh)) { if (is_wake_up_event(aeh)) {
if (initialized) { if (module_lifecycle_is_initialized(&ctx.lc)) {
int err = module_start(); (void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
if (err) { if (ctx.last_mode != MODE_SWITCH_INVALID) {
module_set_state(MODULE_STATE_ERROR); submit_mode_switch_event(ctx.last_mode, 0U);
} else {
module_set_state(MODULE_STATE_READY);
} }
} }

642
src/protocol_module.c Normal file
View File

@@ -0,0 +1,642 @@
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <app_event_manager.h>
#define MODULE protocol_module
#include <caf/events/module_state_event.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
#include <pb_decode.h>
#include <pb_encode.h>
#include <proto/device_comm.pb.h>
#include "function_bitmap_state_event.h"
#include "function_bitmap_update_event.h"
#include "hid_led_event.h"
#include "module_lifecycle.h"
#include "proto_rx_event.h"
#include "proto_transport_state_event.h"
#include "proto_tx_event.h"
#include "protocol_module.h"
#include "theme_rgb_update_event.h"
#include "time_sync_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define PROTOCOL_VERSION 1U
#define PROTOCOL_VENDOR_ID 0x1915U
#define PROTOCOL_PRODUCT_ID 0x52F0U
#define PROTOCOL_FIRMWARE_MAJOR 0U
#define PROTOCOL_FIRMWARE_MINOR 0U
#define PROTOCOL_CAPABILITY_FLAGS (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4))
#define PROTOCOL_MAX_MSG_LEN PROTO_MAX_FRAME_LEN
enum proto_session_state {
PROTO_SESSION_DOWN = 0,
PROTO_SESSION_WAIT_HELLO,
PROTO_SESSION_ACTIVE,
};
struct protocol_module_ctx {
struct module_lifecycle_ctx lc;
enum proto_session_state session_state[PROTO_TRANSPORT_COUNT];
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_NONE,
.stopped_state = MODULE_STATE_OFF,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct protocol_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
};
static const char *proto_session_name(enum proto_session_state state)
{
switch (state) {
case PROTO_SESSION_DOWN:
return "DOWN";
case PROTO_SESSION_WAIT_HELLO:
return "WAIT_HELLO";
case PROTO_SESSION_ACTIVE:
return "ACTIVE";
default:
return "?";
}
}
static const char *proto_transport_name(enum proto_transport transport)
{
switch (transport) {
case PROTO_TRANSPORT_USB_CDC:
return "usb_cdc";
case PROTO_TRANSPORT_BLE_NUS:
return "ble_nus";
default:
return "?";
}
}
static void proto_session_set(enum proto_transport transport,
enum proto_session_state new_state,
const char *reason)
{
enum proto_session_state old_state;
if (transport >= PROTO_TRANSPORT_COUNT) {
return;
}
old_state = ctx.session_state[transport];
if (old_state == new_state) {
LOG_INF("Protocol session keep transport:%s state:%s reason:%s",
proto_transport_name(transport),
proto_session_name(new_state), reason);
return;
}
ctx.session_state[transport] = new_state;
LOG_INF("Protocol session transport:%s %s -> %s reason:%s",
proto_transport_name(transport), proto_session_name(old_state),
proto_session_name(new_state), reason);
}
static int decode_body(const uint8_t *payload, size_t payload_len,
DeviceMessage *body)
{
pb_istream_t stream;
if ((payload == NULL) || (body == NULL)) {
return -EINVAL;
}
*body = (DeviceMessage)DeviceMessage_init_zero;
stream = pb_istream_from_buffer(payload, payload_len);
if (!pb_decode(&stream, DeviceMessage_fields, body)) {
LOG_WRN("pb_decode failed: %s", PB_GET_ERROR(&stream));
return -EBADMSG;
}
return 0;
}
static int decode_frame(const uint8_t *frame, size_t frame_len,
const uint8_t **payload, size_t *payload_len)
{
uint16_t magic;
uint8_t len;
if ((frame == NULL) || (payload == NULL) || (payload_len == NULL)) {
return -EINVAL;
}
if (frame_len < PROTO_FRAME_HEADER_SIZE) {
return -EBADMSG;
}
magic = (uint16_t)frame[0] | ((uint16_t)frame[1] << 8);
if (magic != PROTO_FRAME_MAGIC) {
return -EBADMSG;
}
len = frame[2];
if ((len > PROTO_MAX_PAYLOAD_LEN) ||
(frame_len != (PROTO_FRAME_HEADER_SIZE + len))) {
return -EBADMSG;
}
*payload = &frame[PROTO_FRAME_HEADER_SIZE];
*payload_len = len;
return 0;
}
static int encode_body(const DeviceMessage *body, uint8_t *payload,
size_t payload_buf_size, size_t *payload_len)
{
pb_ostream_t stream;
if ((body == NULL) || (payload == NULL) || (payload_len == NULL)) {
return -EINVAL;
}
stream = pb_ostream_from_buffer(payload, payload_buf_size);
if (!pb_encode(&stream, DeviceMessage_fields, body)) {
LOG_WRN("pb_encode failed: %s", PB_GET_ERROR(&stream));
return -EIO;
}
*payload_len = stream.bytes_written;
return 0;
}
static int encode_frame(const DeviceMessage *body, uint8_t *frame,
size_t frame_buf_size, size_t *frame_len)
{
size_t payload_len;
int err;
if ((frame == NULL) || (frame_len == NULL)) {
return -EINVAL;
}
if (frame_buf_size < PROTO_FRAME_HEADER_SIZE) {
return -ENOSPC;
}
err = encode_body(body, &frame[PROTO_FRAME_HEADER_SIZE],
frame_buf_size - PROTO_FRAME_HEADER_SIZE, &payload_len);
if (err) {
return err;
}
if (payload_len > PROTO_MAX_PAYLOAD_LEN) {
return -EMSGSIZE;
}
frame[0] = (uint8_t)(PROTO_FRAME_MAGIC & 0xFFU);
frame[1] = (uint8_t)(PROTO_FRAME_MAGIC >> 8);
frame[2] = (uint8_t)payload_len;
*frame_len = PROTO_FRAME_HEADER_SIZE + payload_len;
return 0;
}
static int encode_hello_rsp(uint32_t reply_to, uint8_t *rsp_payload,
size_t rsp_payload_buf_size,
size_t *rsp_payload_len)
{
DeviceMessage body = DeviceMessage_init_zero;
body.reply_to = reply_to;
body.which_body = DeviceMessage_hello_rsp_tag;
body.body.hello_rsp.protocol_version = PROTOCOL_VERSION;
body.body.hello_rsp.vendor_id = PROTOCOL_VENDOR_ID;
body.body.hello_rsp.product_id = PROTOCOL_PRODUCT_ID;
body.body.hello_rsp.firmware_major = PROTOCOL_FIRMWARE_MAJOR;
body.body.hello_rsp.firmware_minor = PROTOCOL_FIRMWARE_MINOR;
body.body.hello_rsp.capability_flags = PROTOCOL_CAPABILITY_FLAGS;
return encode_frame(&body, rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
static int encode_response(uint32_t reply_to, ResponseCode error_code,
uint8_t *rsp_payload, size_t rsp_payload_buf_size,
size_t *rsp_payload_len)
{
DeviceMessage body = DeviceMessage_init_zero;
body.reply_to = reply_to;
body.which_body = DeviceMessage_response_tag;
body.body.response.error_code = error_code;
return encode_frame(&body, rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
static int encode_led_state(uint32_t led_mask, uint8_t *payload,
size_t payload_buf_size, size_t *payload_len)
{
DeviceMessage body = DeviceMessage_init_zero;
body.which_body = DeviceMessage_led_state_tag;
body.body.led_state.led_mask = led_mask;
return encode_frame(&body, payload, payload_buf_size, payload_len);
}
static int encode_function_bitmap_state(const uint8_t *bitmap, uint8_t *payload,
size_t payload_buf_size,
size_t *payload_len)
{
DeviceMessage body = DeviceMessage_init_zero;
if (bitmap == NULL) {
return -EINVAL;
}
body.which_body = DeviceMessage_function_key_event_tag;
body.body.function_key_event.usage_bitmap.size = KEYBOARD_PROTOCOL_BITMAP_BYTES;
memcpy(body.body.function_key_event.usage_bitmap.bytes, bitmap,
KEYBOARD_PROTOCOL_BITMAP_BYTES);
return encode_frame(&body, payload, payload_buf_size, payload_len);
}
static int do_init(void)
{
for (size_t i = 0; i < ARRAY_SIZE(ctx.session_state); i++) {
ctx.session_state[i] = PROTO_SESSION_DOWN;
}
LOG_INF("Protocol init done");
return 0;
}
static int do_start(void)
{
if (module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
return 0;
}
static int do_stop(void)
{
if (!module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
for (size_t i = 0; i < ARRAY_SIZE(ctx.session_state); i++) {
proto_session_set((enum proto_transport)i, PROTO_SESSION_DOWN,
"module_stop");
}
return 0;
}
int protocol_module_process_message(enum proto_transport transport,
const uint8_t *req_payload,
size_t req_payload_len,
uint8_t *rsp_payload,
size_t rsp_payload_buf_size,
size_t *rsp_payload_len)
{
DeviceMessage body;
const uint8_t *payload;
size_t payload_len;
int err;
if ((transport >= PROTO_TRANSPORT_COUNT) ||
(rsp_payload == NULL) || (rsp_payload_len == NULL)) {
return -EINVAL;
}
if (!module_lifecycle_is_running(&ctx.lc)) {
LOG_WRN("Reject proto msg transport:%s len:%u lc:%s",
proto_transport_name(transport), (uint32_t)req_payload_len,
module_lifecycle_name(ctx.lc.state));
return -EAGAIN;
}
err = decode_frame(req_payload, req_payload_len, &payload, &payload_len);
if (err) {
return err;
}
err = decode_body(payload, payload_len, &body);
if (err) {
return err;
}
switch (body.which_body) {
case DeviceMessage_hello_req_tag:
if (ctx.session_state[transport] == PROTO_SESSION_DOWN) {
LOG_WRN("Reject HelloReq transport:%s session:%s lc:%s",
proto_transport_name(transport),
proto_session_name(ctx.session_state[transport]),
module_lifecycle_name(ctx.lc.state));
return -EAGAIN;
}
LOG_INF("HelloReq transport:%u protocol_version:%u",
transport, body.body.hello_req.protocol_version);
if (body.body.hello_req.protocol_version != PROTOCOL_VERSION) {
LOG_WRN("Unexpected protocol version:%u",
body.body.hello_req.protocol_version);
}
proto_session_set(transport, PROTO_SESSION_ACTIVE, "hello_req");
return encode_hello_rsp(body.msg_id, rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
case DeviceMessage_bitmap_tag:
if (ctx.session_state[transport] != PROTO_SESSION_ACTIVE) {
return encode_response(body.msg_id,
ResponseCode_RESPONSE_CODE_NOT_READY,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
}
if (body.body.bitmap.usage_bitmap.size != KEYBOARD_PROTOCOL_BITMAP_BYTES) {
return encode_response(
body.msg_id, ResponseCode_RESPONSE_CODE_INVALID_LENGTH,
rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
err = submit_function_bitmap_update_event(
body.body.bitmap.usage_bitmap.bytes);
if (err) {
return encode_response(
body.msg_id, ResponseCode_RESPONSE_CODE_INVALID_PARAM,
rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
return encode_response(body.msg_id, ResponseCode_RESPONSE_CODE_OK,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
case DeviceMessage_time_sync_tag:
if (ctx.session_state[transport] != PROTO_SESSION_ACTIVE) {
return encode_response(body.msg_id,
ResponseCode_RESPONSE_CODE_NOT_READY,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
}
if (body.body.time_sync.version != 1U) {
return encode_response(
body.msg_id, ResponseCode_RESPONSE_CODE_INVALID_PARAM,
rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
submit_time_sync_event(body.body.time_sync.version,
body.body.time_sync.flags,
body.body.time_sync.timezone_min,
body.body.time_sync.utc_ms,
body.body.time_sync.accuracy_ms);
return encode_response(body.msg_id, ResponseCode_RESPONSE_CODE_OK,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
case DeviceMessage_theme_rgb_tag:
if (ctx.session_state[transport] != PROTO_SESSION_ACTIVE) {
return encode_response(body.msg_id,
ResponseCode_RESPONSE_CODE_NOT_READY,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
}
if ((body.body.theme_rgb.red > 255U) ||
(body.body.theme_rgb.green > 255U) ||
(body.body.theme_rgb.blue > 255U)) {
return encode_response(
body.msg_id, ResponseCode_RESPONSE_CODE_INVALID_PARAM,
rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
submit_theme_rgb_update_event((struct theme_rgb) {
.r = (uint8_t)body.body.theme_rgb.red,
.g = (uint8_t)body.body.theme_rgb.green,
.b = (uint8_t)body.body.theme_rgb.blue,
});
return encode_response(body.msg_id, ResponseCode_RESPONSE_CODE_OK,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
default:
LOG_WRN("Unsupported protobuf body case %d", body.which_body);
return encode_response(body.msg_id,
ResponseCode_RESPONSE_CODE_UNKNOWN_TYPE,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
}
}
static bool handle_proto_rx_event(const struct proto_rx_event *event)
{
uint8_t rsp_payload[PROTOCOL_MAX_MSG_LEN];
size_t rsp_payload_len = 0U;
int err;
if (!module_lifecycle_is_running(&ctx.lc)) {
return false;
}
err = protocol_module_process_message(event->transport,
event->dyndata.data,
event->dyndata.size,
rsp_payload,
sizeof(rsp_payload),
&rsp_payload_len);
if (err) {
if (err != -ENOTSUP) {
LOG_WRN("Protocol processing failed (%d) transport:%s session:%s lc:%s len:%u",
err, proto_transport_name(event->transport),
proto_session_name(ctx.session_state[event->transport]),
module_lifecycle_name(ctx.lc.state),
(uint32_t)event->dyndata.size);
}
return false;
}
LOG_INF("Protocol response ready transport:%s rsp_len:%u session:%s",
proto_transport_name(event->transport), (unsigned int)rsp_payload_len,
proto_session_name(ctx.session_state[event->transport]));
err = submit_proto_tx_event(event->transport, rsp_payload, rsp_payload_len);
if (err) {
LOG_WRN("Proto TX submit failed (%d)", err);
}
return false;
}
static bool handle_proto_transport_state_event(
const struct proto_transport_state_event *event)
{
if (event->transport >= PROTO_TRANSPORT_COUNT) {
return false;
}
switch (event->state) {
case PROTO_TRANSPORT_LINK_DOWN:
proto_session_set(event->transport, PROTO_SESSION_DOWN,
"transport_link_down");
break;
case PROTO_TRANSPORT_LINK_READY:
proto_session_set(event->transport, PROTO_SESSION_WAIT_HELLO,
"transport_link_ready");
break;
default:
return false;
}
return false;
}
static bool handle_function_bitmap_state_event(
const struct function_bitmap_state_event *event)
{
uint8_t payload[PROTOCOL_MAX_MSG_LEN];
size_t payload_len;
int err;
if (!module_lifecycle_is_running(&ctx.lc)) {
return false;
}
for (enum proto_transport transport = 0; transport < PROTO_TRANSPORT_COUNT;
transport++) {
if (ctx.session_state[transport] != PROTO_SESSION_ACTIVE) {
LOG_INF("FunctionKeyEvent skip transport:%s session:%s",
proto_transport_name(transport),
proto_session_name(ctx.session_state[transport]));
continue;
}
err = encode_function_bitmap_state(event->bitmap, payload,
sizeof(payload), &payload_len);
if (err) {
LOG_WRN("FunctionKeyEvent encode failed (%d)", err);
return false;
}
err = submit_proto_tx_event(transport, payload, payload_len);
if (err) {
LOG_WRN("FunctionKeyEvent submit failed (%d)", err);
} else {
LOG_INF("FunctionKeyEvent submit transport:%s len:%u",
proto_transport_name(transport),
(unsigned int)payload_len);
}
}
return false;
}
static bool handle_hid_led_event(const struct hid_led_event *event)
{
uint8_t payload[PROTOCOL_MAX_MSG_LEN];
size_t payload_len;
int err;
enum proto_transport transport;
if (!module_lifecycle_is_running(&ctx.lc)) {
return false;
}
transport = (event->transport == HID_TRANSPORT_USB) ?
PROTO_TRANSPORT_USB_CDC :
PROTO_TRANSPORT_BLE_NUS;
if (ctx.session_state[transport] != PROTO_SESSION_ACTIVE) {
LOG_INF("LedState skip transport:%s session:%s led_mask:0x%02x",
proto_transport_name(transport),
proto_session_name(ctx.session_state[transport]), event->led_bm);
return false;
}
err = encode_led_state(event->led_bm, payload, sizeof(payload), &payload_len);
if (err) {
LOG_WRN("LedState encode failed (%d)", err);
return false;
}
err = submit_proto_tx_event(transport, payload, payload_len);
if (err) {
LOG_WRN("LedState submit failed (%d)", err);
} else {
LOG_INF("LedState submit transport:%s len:%u led_mask:0x%02x",
proto_transport_name(transport), (unsigned int)payload_len,
event->led_bm);
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_proto_rx_event(aeh)) {
return handle_proto_rx_event(cast_proto_rx_event(aeh));
}
if (is_proto_transport_state_event(aeh)) {
return handle_proto_transport_state_event(
cast_proto_transport_state_event(aeh));
}
if (is_function_bitmap_state_event(aeh)) {
return handle_function_bitmap_state_event(
cast_function_bitmap_state_event(aeh));
}
if (is_hid_led_event(aeh)) {
return handle_hid_led_event(cast_hid_led_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)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
return false;
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, function_bitmap_state_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_led_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, proto_rx_event);
APP_EVENT_SUBSCRIBE(MODULE, proto_transport_state_event);

234
src/settings_module.c Normal file
View File

@@ -0,0 +1,234 @@
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <app_event_manager.h>
#define MODULE settings_module
#include <caf/events/click_event.h>
#include <caf/events/module_state_event.h>
#include <caf/events/power_event.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>
#include "ble_bond_multi_event.h"
#include "encoder_event.h"
#include "module_lifecycle.h"
#include "settings_mode_event.h"
#include "theme_color.h"
#include "theme_rgb_update_event.h"
#include "ui/ui_settings_controller.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define SETTINGS_MUTE_KEY_ID 0x180U
struct settings_module_ctx {
struct module_lifecycle_ctx lc;
bool active;
struct theme_rgb current_theme;
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_POWER,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct settings_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
};
static void settings_exit(void)
{
if (!ctx.active) {
return;
}
ctx.active = false;
ui_settings_controller_close();
submit_settings_mode_event(false);
}
static void settings_enter(void)
{
if (ctx.active) {
return;
}
ctx.active = true;
submit_settings_mode_event(true);
ui_settings_controller_open();
}
static int do_init(void)
{
ctx.active = false;
ctx.current_theme = (struct theme_rgb) {
.r = BLINKY_THEME_DEFAULT_R,
.g = BLINKY_THEME_DEFAULT_G,
.b = BLINKY_THEME_DEFAULT_B,
};
ui_settings_theme_set_current(ctx.current_theme);
return 0;
}
static int do_start(void)
{
return 0;
}
static int do_stop(void)
{
settings_exit();
return 0;
}
static bool handle_click_event(const struct click_event *event)
{
if (!module_lifecycle_is_running(&ctx.lc) ||
(event->key_id != SETTINGS_MUTE_KEY_ID)) {
return false;
}
if (!ctx.active) {
if (event->click == CLICK_LONG) {
settings_enter();
}
return false;
}
switch (event->click) {
case CLICK_SHORT:
ui_settings_controller_select();
break;
case CLICK_LONG:
{
bool active = ui_settings_controller_back();
if (!active) {
settings_exit();
}
}
break;
default:
break;
}
return false;
}
static bool handle_encoder_event(const struct encoder_event *event)
{
if (!module_lifecycle_is_running(&ctx.lc) || !ctx.active) {
return false;
}
ui_settings_controller_move(event->detents);
return false;
}
static bool handle_theme_rgb_update_event(
const struct theme_rgb_update_event *event)
{
ctx.current_theme = event->theme;
ui_settings_theme_set_current(event->theme);
if (ctx.active) {
ui_settings_controller_refresh(false);
}
return false;
}
static bool handle_ble_bond_multi_event(
const struct ble_bond_multi_event *event)
{
ui_settings_ble_set_current_slot(event->current_slot);
for (uint8_t i = 0U; i < ARRAY_SIZE(event->slots); i++) {
ui_settings_ble_set_slot_meta(i + 1U, &event->slots[i]);
}
if (ctx.active) {
ui_settings_controller_refresh(false);
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_click_event(aeh)) {
return handle_click_event(cast_click_event(aeh));
}
if (is_encoder_event(aeh)) {
return handle_encoder_event(cast_encoder_event(aeh));
}
if (is_theme_rgb_update_event(aeh)) {
return handle_theme_rgb_update_event(
cast_theme_rgb_update_event(aeh));
}
if (is_ble_bond_multi_event(aeh)) {
return handle_ble_bond_multi_event(
cast_ble_bond_multi_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)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
if (is_power_down_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
}
return false;
}
if (is_wake_up_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, click_event);
APP_EVENT_SUBSCRIBE(MODULE, encoder_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, theme_rgb_update_event);
APP_EVENT_SUBSCRIBE(MODULE, ble_bond_multi_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

58
src/swift_pair_module.c Normal file
View File

@@ -0,0 +1,58 @@
#include <bluetooth/adv_prov/swift_pair.h>
#define MODULE swift_pair_module
#include <caf/events/ble_common_event.h>
#include <zephyr/logging/log.h>
#include "ble_bond_multi_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
static void update_swift_pair_payload(uint8_t selected_identity)
{
bool enable = (selected_identity != BLE_BOND_MULTI_DONGLE_SLOT_ID);
bt_le_adv_prov_swift_pair_enable(enable);
LOG_INF("Swift Pair payload %s for identity %u",
enable ? "enabled" : "disabled", selected_identity);
}
static bool handle_ble_bond_multi_event(const struct ble_bond_multi_event *event)
{
update_swift_pair_payload(event->active_identity_id);
return false;
}
static bool handle_ble_peer_operation_event(const struct ble_peer_operation_event *event)
{
switch (event->op) {
case PEER_OPERATION_SELECTED:
case PEER_OPERATION_ERASED:
update_swift_pair_payload(event->bt_stack_id);
break;
default:
break;
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_ble_bond_multi_event(aeh)) {
return handle_ble_bond_multi_event(cast_ble_bond_multi_event(aeh));
}
if (is_ble_peer_operation_event(aeh)) {
return handle_ble_peer_operation_event(
cast_ble_peer_operation_event(aeh));
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, ble_bond_multi_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_operation_event);

191
src/time_sync_module.c Normal file
View File

@@ -0,0 +1,191 @@
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <app_event_manager.h>
#define MODULE time_sync_module
#include <caf/events/module_state_event.h>
#include <caf/events/power_event.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/printk.h>
#include "datetime_event.h"
#include "module_lifecycle.h"
#include "time_sync_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define TIME_SYNC_REFRESH_PERIOD K_SECONDS(1)
struct time_sync_module_ctx {
struct module_lifecycle_ctx lc;
struct k_work_delayable refresh_work;
int32_t timezone_min;
uint64_t utc_ms_base;
int64_t uptime_ms_base;
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_POWER,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct time_sync_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
};
static void publish_datetime_event(void)
{
struct tm tm_buf;
time_t seconds;
int64_t local_ms;
char date_text[DATETIME_EVENT_DATE_TEXT_LEN];
char time_text[DATETIME_EVENT_TIME_TEXT_LEN];
local_ms = (int64_t)ctx.utc_ms_base +
(k_uptime_get() - ctx.uptime_ms_base) +
((int64_t)ctx.timezone_min * 60LL * 1000LL);
if (local_ms < 0) {
local_ms = 0;
}
seconds = (time_t)(local_ms / 1000LL);
if (gmtime_r(&seconds, &tm_buf) == NULL) {
strncpy(date_text, "1970/01/01", sizeof(date_text));
date_text[sizeof(date_text) - 1] = '\0';
strncpy(time_text, "00:00:00", sizeof(time_text));
time_text[sizeof(time_text) - 1] = '\0';
} else {
snprintk(date_text, sizeof(date_text),
"%04u/%02u/%02u",
(unsigned int)(tm_buf.tm_year + 1900),
(unsigned int)(tm_buf.tm_mon + 1),
(unsigned int)tm_buf.tm_mday);
snprintk(time_text, sizeof(time_text),
"%02u:%02u:%02u",
(unsigned int)tm_buf.tm_hour,
(unsigned int)tm_buf.tm_min,
(unsigned int)tm_buf.tm_sec);
}
(void)submit_datetime_event(date_text, time_text);
}
static void refresh_work_handler(struct k_work *work)
{
ARG_UNUSED(work);
if (!module_lifecycle_is_running(&ctx.lc)) {
return;
}
publish_datetime_event();
k_work_reschedule(&ctx.refresh_work, TIME_SYNC_REFRESH_PERIOD);
}
static int do_init(void)
{
ctx.timezone_min = 0;
ctx.utc_ms_base = 0U;
ctx.uptime_ms_base = k_uptime_get();
k_work_init_delayable(&ctx.refresh_work, refresh_work_handler);
return 0;
}
static int do_start(void)
{
if (module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
k_work_reschedule(&ctx.refresh_work, K_NO_WAIT);
return 0;
}
static int do_stop(void)
{
if (!module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
k_work_cancel_delayable(&ctx.refresh_work);
return 0;
}
static bool handle_time_sync_event(const struct time_sync_event *event)
{
if (event->version != 1U) {
LOG_WRN("Unexpected TimeSync version:%u", event->version);
return false;
}
ctx.timezone_min = event->timezone_min;
ctx.utc_ms_base = event->utc_ms;
ctx.uptime_ms_base = k_uptime_get();
if (module_lifecycle_is_running(&ctx.lc)) {
k_work_reschedule(&ctx.refresh_work, K_NO_WAIT);
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_time_sync_event(aeh)) {
return handle_time_sync_event(cast_time_sync_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)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
if (is_power_down_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
}
return false;
}
if (is_wake_up_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, time_sync_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

409
src/ui/ui_main.c Normal file
View File

@@ -0,0 +1,409 @@
#include <string.h>
#include <lvgl.h>
#include <zephyr/sys/printk.h>
#include "ui_main.h"
enum ui_status_id {
UI_STATUS_USB = 0,
UI_STATUS_BLE,
UI_STATUS_NUMLOCK,
UI_STATUS_CAPSLOCK,
UI_STATUS_COUNT,
};
enum {
UI_LED_MASK_NUM_LOCK = BIT(0),
UI_LED_MASK_CAPS_LOCK = BIT(1),
};
struct ui_main_ctx {
lv_obj_t *content;
lv_obj_t *status_badges[UI_STATUS_COUNT];
lv_obj_t *status_labels[UI_STATUS_COUNT];
lv_obj_t *battery_icon;
lv_obj_t *battery_label;
lv_obj_t *battery_state_label;
lv_obj_t *ble_link_wrap;
lv_obj_t *ble_link_spinner;
lv_obj_t *ble_link_icon;
lv_obj_t *date_label;
lv_obj_t *time_label;
};
static struct ui_main_ctx g_ui;
static bool ui_initialized;
static const struct ui_main_model *page_model;
static const char *page_date_text;
static const char *page_time_text;
static const char *const status_texts[UI_STATUS_COUNT] = {
LV_SYMBOL_USB,
LV_SYMBOL_BLUETOOTH,
"1",
"A",
};
#define UI_BLE_LINK_BLUE lv_color_hex(0x0082FC)
static lv_color_t ui_main_get_battery_color(uint8_t battery_level)
{
if (battery_level > 70U) {
return lv_color_hex(0x8BD450);
}
if (battery_level >= 20U) {
return lv_color_hex(0xF4D35E);
}
return lv_color_hex(0xE63946);
}
static const char *ui_main_get_battery_symbol(uint8_t battery_level)
{
if (battery_level > 85U) {
return LV_SYMBOL_BATTERY_FULL;
}
if (battery_level > 60U) {
return LV_SYMBOL_BATTERY_3;
}
if (battery_level > 35U) {
return LV_SYMBOL_BATTERY_2;
}
if (battery_level >= 20U) {
return LV_SYMBOL_BATTERY_1;
}
return LV_SYMBOL_BATTERY_EMPTY;
}
static bool ui_main_status_is_active(enum ui_status_id id,
const struct ui_main_model *model)
{
switch (id) {
case UI_STATUS_USB:
return model->mode == MODE_SWITCH_USB;
case UI_STATUS_BLE:
return model->mode == MODE_SWITCH_BLE;
case UI_STATUS_NUMLOCK:
return (model->led_mask & UI_LED_MASK_NUM_LOCK) != 0U;
case UI_STATUS_CAPSLOCK:
return (model->led_mask & UI_LED_MASK_CAPS_LOCK) != 0U;
default:
return false;
}
}
static void ui_main_create_status_chip(lv_obj_t *parent, enum ui_status_id id)
{
lv_obj_t *badge = lv_obj_create(parent);
lv_obj_t *label = lv_label_create(badge);
lv_obj_remove_style_all(badge);
lv_obj_set_size(badge, 50, 32);
lv_obj_set_style_radius(badge, 10, 0);
lv_obj_set_style_bg_opa(badge, LV_OPA_COVER, 0);
lv_obj_set_style_pad_all(badge, 0, 0);
lv_label_set_text(label, status_texts[id]);
lv_obj_set_width(label, LV_PCT(100));
lv_obj_set_style_text_font(label, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_center(label);
g_ui.status_badges[id] = badge;
g_ui.status_labels[id] = label;
}
void ui_main_refresh_status_bar(const struct ui_main_model *model)
{
if (!ui_initialized) {
return;
}
for (uint32_t i = 0; i < UI_STATUS_COUNT; i++) {
lv_obj_t *badge = g_ui.status_badges[i];
lv_obj_t *label = g_ui.status_labels[i];
bool active = ui_main_status_is_active((enum ui_status_id)i, model);
if ((badge == NULL) || (label == NULL)) {
continue;
}
lv_obj_set_style_border_width(badge, 3, 0);
lv_obj_set_style_border_color(
badge,
active ? model->theme_color : model->inactive_border_color, 0);
lv_obj_set_style_bg_color(
badge,
active ? lv_color_hex(0x1D2735) : lv_color_hex(0x161A20), 0);
lv_obj_set_style_text_color(
label,
active ? lv_color_white() : lv_color_hex(0x7C8798), 0);
}
}
void ui_main_refresh_battery(const struct ui_main_model *model)
{
char battery_text[8];
const char *state_symbol = "";
lv_color_t battery_color;
lv_color_t state_color = lv_color_white();
if (!ui_initialized ||
(g_ui.battery_icon == NULL) || (g_ui.battery_label == NULL) ||
(g_ui.battery_state_label == NULL)) {
return;
}
battery_color = ui_main_get_battery_color(model->battery_level);
snprintk(battery_text, sizeof(battery_text), "%u%%", model->battery_level);
if (model->full) {
state_symbol = LV_SYMBOL_USB;
state_color = lv_color_hex(0x4C9EF5);
} else if (model->charging) {
state_symbol = LV_SYMBOL_CHARGE;
state_color = lv_color_hex(0xF4D35E);
}
lv_label_set_text(g_ui.battery_icon,
ui_main_get_battery_symbol(model->battery_level));
lv_obj_set_style_text_color(g_ui.battery_icon, battery_color, 0);
lv_label_set_text(g_ui.battery_label, battery_text);
lv_label_set_text(g_ui.battery_state_label, state_symbol);
lv_obj_set_style_text_color(g_ui.battery_state_label, state_color, 0);
if ((g_ui.ble_link_wrap == NULL) || (g_ui.ble_link_spinner == NULL) ||
(g_ui.ble_link_icon == NULL)) {
return;
}
switch (model->ble_link_state) {
case UI_BLE_LINK_SEARCHING:
lv_obj_clear_flag(g_ui.ble_link_wrap, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(g_ui.ble_link_spinner, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(g_ui.ble_link_icon, LV_OBJ_FLAG_HIDDEN);
break;
case UI_BLE_LINK_CONNECTED:
lv_obj_clear_flag(g_ui.ble_link_wrap, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(g_ui.ble_link_spinner, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(g_ui.ble_link_icon, LV_OBJ_FLAG_HIDDEN);
lv_obj_set_style_text_color(g_ui.ble_link_icon, UI_BLE_LINK_BLUE, 0);
break;
case UI_BLE_LINK_HIDDEN:
default:
lv_obj_add_flag(g_ui.ble_link_wrap, LV_OBJ_FLAG_HIDDEN);
break;
}
}
void ui_main_refresh_datetime(const char *date_text, const char *time_text)
{
if (!ui_initialized ||
(g_ui.date_label == NULL) || (g_ui.time_label == NULL)) {
return;
}
lv_label_set_text(g_ui.date_label, date_text);
lv_label_set_text(g_ui.time_label, time_text);
}
void ui_main_refresh_all(const struct ui_main_model *model,
const char *date_text,
const char *time_text)
{
ui_main_refresh_status_bar(model);
ui_main_refresh_battery(model);
ui_main_refresh_datetime(date_text, time_text);
}
void ui_main_init(const struct ui_main_model *model,
const char *date_text,
const char *time_text)
{
lv_obj_t *screen = lv_screen_active();
lv_obj_t *content;
lv_obj_t *top_row;
lv_obj_t *battery_wrap;
lv_obj_t *middle_row;
lv_obj_t *bottom_row;
if (ui_initialized) {
ui_main_refresh_all(model, date_text, time_text);
return;
}
memset(&g_ui, 0, sizeof(g_ui));
lv_obj_clean(screen);
lv_obj_set_style_bg_color(screen, lv_color_hex(0x0F1115), 0);
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0);
lv_obj_set_style_text_color(screen, lv_color_white(), 0);
lv_obj_set_style_pad_all(screen, 0, 0);
lv_obj_set_scrollbar_mode(screen, LV_SCROLLBAR_MODE_OFF);
content = lv_obj_create(screen);
g_ui.content = content;
lv_obj_remove_style_all(content);
lv_obj_set_size(content, LV_PCT(100), LV_PCT(100));
lv_obj_set_style_bg_opa(content, LV_OPA_TRANSP, 0);
lv_obj_set_style_pad_left(content, 14, 0);
lv_obj_set_style_pad_right(content, 14, 0);
lv_obj_set_style_pad_top(content, 8, 0);
lv_obj_set_style_pad_bottom(content, 8, 0);
lv_obj_set_layout(content, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(content, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(content, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER);
top_row = lv_obj_create(content);
lv_obj_remove_style_all(top_row);
lv_obj_set_width(top_row, LV_PCT(100));
lv_obj_set_flex_grow(top_row, 1);
lv_obj_set_style_bg_opa(top_row, LV_OPA_TRANSP, 0);
lv_obj_set_layout(top_row, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(top_row, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(top_row, LV_FLEX_ALIGN_SPACE_BETWEEN,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
g_ui.date_label = lv_label_create(top_row);
lv_obj_set_style_text_font(g_ui.date_label, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(g_ui.date_label, lv_color_hex(0xD8DEE9), 0);
battery_wrap = lv_obj_create(top_row);
lv_obj_remove_style_all(battery_wrap);
lv_obj_set_width(battery_wrap, LV_SIZE_CONTENT);
lv_obj_set_layout(battery_wrap, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(battery_wrap, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(battery_wrap, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_column(battery_wrap, 4, 0);
g_ui.battery_icon = lv_label_create(battery_wrap);
lv_obj_set_style_text_font(g_ui.battery_icon, &lv_font_montserrat_14, 0);
g_ui.battery_label = lv_label_create(battery_wrap);
lv_obj_set_style_text_font(g_ui.battery_label, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(g_ui.battery_label, lv_color_hex(0xD8DEE9), 0);
g_ui.battery_state_label = lv_label_create(battery_wrap);
lv_obj_set_style_text_font(g_ui.battery_state_label, &lv_font_montserrat_14, 0);
g_ui.ble_link_wrap = lv_obj_create(battery_wrap);
lv_obj_remove_style_all(g_ui.ble_link_wrap);
lv_obj_set_size(g_ui.ble_link_wrap, 18, 18);
lv_obj_add_flag(g_ui.ble_link_wrap, LV_OBJ_FLAG_HIDDEN);
g_ui.ble_link_spinner = lv_spinner_create(g_ui.ble_link_wrap);
lv_obj_center(g_ui.ble_link_spinner);
lv_obj_set_size(g_ui.ble_link_spinner, 16, 16);
lv_spinner_set_anim_params(g_ui.ble_link_spinner, 2000, 200);
lv_obj_set_style_arc_width(g_ui.ble_link_spinner, 2, LV_PART_MAIN);
lv_obj_set_style_arc_width(g_ui.ble_link_spinner, 2, LV_PART_INDICATOR);
lv_obj_set_style_arc_color(g_ui.ble_link_spinner, lv_color_hex(0x2A3442),
LV_PART_MAIN);
lv_obj_set_style_arc_color(g_ui.ble_link_spinner, model->theme_color,
LV_PART_INDICATOR);
lv_obj_remove_style(g_ui.ble_link_spinner, NULL, LV_PART_KNOB);
lv_obj_clear_flag(g_ui.ble_link_spinner, LV_OBJ_FLAG_CLICKABLE);
g_ui.ble_link_icon = lv_label_create(g_ui.ble_link_wrap);
lv_label_set_text(g_ui.ble_link_icon, LV_SYMBOL_BLUETOOTH);
lv_obj_center(g_ui.ble_link_icon);
lv_obj_set_style_text_font(g_ui.ble_link_icon, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(g_ui.ble_link_icon, UI_BLE_LINK_BLUE, 0);
lv_obj_add_flag(g_ui.ble_link_icon, LV_OBJ_FLAG_HIDDEN);
middle_row = lv_obj_create(content);
lv_obj_remove_style_all(middle_row);
lv_obj_set_width(middle_row, LV_PCT(100));
lv_obj_set_flex_grow(middle_row, 2);
lv_obj_set_style_bg_opa(middle_row, LV_OPA_TRANSP, 0);
g_ui.time_label = lv_label_create(middle_row);
lv_obj_set_style_text_font(g_ui.time_label, &lv_font_montserrat_32, 0);
lv_obj_set_style_text_color(g_ui.time_label, lv_color_white(), 0);
lv_obj_center(g_ui.time_label);
bottom_row = lv_obj_create(content);
lv_obj_remove_style_all(bottom_row);
lv_obj_set_width(bottom_row, LV_PCT(100));
lv_obj_set_flex_grow(bottom_row, 1);
lv_obj_set_style_bg_opa(bottom_row, LV_OPA_TRANSP, 0);
lv_obj_set_layout(bottom_row, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(bottom_row, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(bottom_row, LV_FLEX_ALIGN_CENTER,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_column(bottom_row, 6, 0);
for (uint32_t i = 0; i < UI_STATUS_COUNT; i++) {
ui_main_create_status_chip(bottom_row, (enum ui_status_id)i);
}
ui_main_refresh_all(model, date_text, time_text);
ui_initialized = true;
}
void ui_main_deinit(void)
{
if (!ui_initialized) {
return;
}
if (g_ui.content != NULL) {
lv_obj_delete(g_ui.content);
}
memset(&g_ui, 0, sizeof(g_ui));
ui_initialized = false;
}
static void main_page_init(struct ui_page *page)
{
ARG_UNUSED(page);
ui_main_init(page_model, page_date_text, page_time_text);
}
static void main_page_deinit(struct ui_page *page)
{
ARG_UNUSED(page);
ui_main_deinit();
}
static void main_page_refresh(struct ui_page *page)
{
ARG_UNUSED(page);
ui_main_refresh_all(page_model, page_date_text, page_time_text);
}
static const struct ui_page_ops main_page_ops = {
.init = main_page_init,
.deinit = main_page_deinit,
.refresh = main_page_refresh,
};
static struct ui_page main_page = {
.ops = &main_page_ops,
};
struct ui_page *ui_main_page_get(const struct ui_main_model *model,
const char *date_text,
const char *time_text)
{
page_model = model;
page_date_text = date_text;
page_time_text = time_text;
return &main_page;
}

51
src/ui/ui_main.h Normal file
View File

@@ -0,0 +1,51 @@
#ifndef BLINKY_UI_MAIN_H_
#define BLINKY_UI_MAIN_H_
#include <stdbool.h>
#include <stdint.h>
#include <lvgl.h>
#include "mode_switch_event.h"
#include "ui/ui_page.h"
#ifdef __cplusplus
extern "C" {
#endif
enum ui_ble_link_state {
UI_BLE_LINK_HIDDEN = 0,
UI_BLE_LINK_SEARCHING,
UI_BLE_LINK_CONNECTED,
};
struct ui_main_model {
lv_color_t theme_color;
lv_color_t inactive_border_color;
uint8_t battery_level;
enum mode_switch_mode mode;
enum ui_ble_link_state ble_link_state;
uint8_t led_mask;
bool charging;
bool full;
};
void ui_main_init(const struct ui_main_model *model,
const char *date_text,
const char *time_text);
void ui_main_refresh_all(const struct ui_main_model *model,
const char *date_text,
const char *time_text);
void ui_main_refresh_status_bar(const struct ui_main_model *model);
void ui_main_refresh_battery(const struct ui_main_model *model);
void ui_main_refresh_datetime(const char *date_text, const char *time_text);
void ui_main_deinit(void);
struct ui_page *ui_main_page_get(const struct ui_main_model *model,
const char *date_text,
const char *time_text);
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_UI_MAIN_H_ */

233
src/ui/ui_settings.c Normal file
View File

@@ -0,0 +1,233 @@
#include <string.h>
#include <lvgl.h>
#include "ui_settings.h"
#define UI_SETTINGS_LIST_H 104
#define UI_SETTINGS_MAX_ITEMS 8U
struct ui_settings_row {
lv_obj_t *row;
lv_obj_t *icon;
lv_obj_t *title;
lv_obj_t *value;
lv_obj_t *custom;
};
struct ui_settings_ctx {
lv_obj_t *content;
lv_obj_t *title;
lv_obj_t *hint;
lv_obj_t *scroll;
lv_obj_t *value;
struct ui_settings_row rows[UI_SETTINGS_MAX_ITEMS];
lv_group_t *focus_group;
uint8_t row_count;
struct ui_settings_page *page;
bool initialized;
};
static struct ui_settings_ctx g_ui;
static void row_clear_custom(struct ui_settings_row *row)
{
lv_obj_clean(row->custom);
lv_obj_set_style_bg_opa(row->custom, LV_OPA_TRANSP, 0);
lv_obj_set_style_bg_color(row->custom, lv_color_hex(0x0B1017), 0);
}
static void create_row(struct ui_settings_row *row)
{
row->row = lv_obj_create(g_ui.scroll);
lv_obj_remove_style_all(row->row);
lv_obj_set_width(row->row, LV_PCT(100));
lv_obj_set_height(row->row, 30);
lv_obj_set_style_bg_opa(row->row, LV_OPA_TRANSP, 0);
lv_obj_set_style_radius(row->row, 9, 0);
lv_obj_set_style_border_width(row->row, 2, 0);
lv_obj_set_style_border_color(row->row, lv_color_hex(0x0B1017), 0);
lv_obj_set_style_pad_hor(row->row, 6, 0);
lv_obj_set_layout(row->row, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(row->row, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(row->row, LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_column(row->row, 6, 0);
lv_obj_add_flag(row->row, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
lv_group_add_obj(g_ui.focus_group, row->row);
row->icon = lv_label_create(row->row);
lv_obj_set_width(row->icon, 20);
lv_obj_set_style_text_font(row->icon, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(row->icon, lv_color_hex(0x8A95A5), 0);
row->title = lv_label_create(row->row);
lv_obj_set_width(row->title, 118);
lv_obj_set_style_text_font(row->title, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(row->title, lv_color_hex(0xD8DEE9), 0);
lv_label_set_long_mode(row->title, LV_LABEL_LONG_CLIP);
row->value = lv_label_create(row->row);
lv_obj_set_flex_grow(row->value, 1);
lv_obj_set_style_text_font(row->value, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(row->value, lv_color_hex(0x97A3B5), 0);
lv_obj_set_style_text_align(row->value, LV_TEXT_ALIGN_LEFT, 0);
lv_label_set_long_mode(row->value, LV_LABEL_LONG_SCROLL);
row->custom = lv_obj_create(row->row);
lv_obj_remove_style_all(row->custom);
lv_obj_set_size(row->custom, 16, 16);
row_clear_custom(row);
}
static void rebuild_rows(uint8_t count)
{
if (count > UI_SETTINGS_MAX_ITEMS) {
count = UI_SETTINGS_MAX_ITEMS;
}
if (g_ui.row_count == count) {
return;
}
lv_obj_clean(g_ui.scroll);
memset(g_ui.rows, 0, sizeof(g_ui.rows));
g_ui.row_count = count;
lv_group_delete(g_ui.focus_group);
g_ui.focus_group = lv_group_create();
for (uint8_t i = 0; i < g_ui.row_count; i++) {
create_row(&g_ui.rows[i]);
}
}
static void row_set(struct ui_settings_row *row,
const struct ui_settings_item *item,
bool selected,
lv_color_t accent)
{
lv_label_set_text(row->icon, item->icon ? item->icon : "");
lv_label_set_text(row->title, item->title ? item->title : "");
lv_label_set_text(row->value, item->value ? item->value : "");
lv_obj_set_style_border_color(row->row,
selected ? accent : lv_color_hex(0x0B1017),
0);
row_clear_custom(row);
if (item->draw != NULL) {
item->draw(item, row->custom);
}
}
static void focus_row(uint8_t index, bool animate)
{
if (index >= g_ui.row_count) {
return;
}
lv_obj_update_layout(g_ui.scroll);
lv_group_focus_obj(g_ui.rows[index].row);
lv_obj_scroll_to_view(g_ui.rows[index].row,
animate ? LV_ANIM_ON : LV_ANIM_OFF);
}
void ui_settings_init(void)
{
lv_obj_t *screen = lv_screen_active();
if (g_ui.initialized) {
return;
}
memset(&g_ui, 0, sizeof(g_ui));
g_ui.focus_group = lv_group_create();
g_ui.content = lv_obj_create(screen);
lv_obj_remove_style_all(g_ui.content);
lv_obj_set_size(g_ui.content, LV_PCT(100), LV_PCT(100));
lv_obj_set_style_bg_color(g_ui.content, lv_color_hex(0x0B1017), 0);
lv_obj_set_style_bg_opa(g_ui.content, LV_OPA_COVER, 0);
lv_obj_set_style_pad_left(g_ui.content, 14, 0);
lv_obj_set_style_pad_right(g_ui.content, 14, 0);
lv_obj_set_style_pad_top(g_ui.content, 8, 0);
lv_obj_set_style_pad_bottom(g_ui.content, 8, 0);
lv_obj_set_layout(g_ui.content, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(g_ui.content, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(g_ui.content, LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
lv_obj_set_style_pad_row(g_ui.content, 6, 0);
g_ui.title = lv_label_create(g_ui.content);
lv_obj_set_style_text_font(g_ui.title, &lv_font_montserrat_32, 0);
lv_obj_set_style_text_color(g_ui.title, lv_color_white(), 0);
g_ui.hint = lv_label_create(g_ui.content);
lv_obj_set_style_text_font(g_ui.hint, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(g_ui.hint, lv_color_hex(0x97A3B5), 0);
g_ui.scroll = lv_obj_create(g_ui.content);
lv_obj_remove_style_all(g_ui.scroll);
lv_obj_set_width(g_ui.scroll, LV_PCT(100));
lv_obj_set_height(g_ui.scroll, UI_SETTINGS_LIST_H);
lv_obj_set_style_bg_opa(g_ui.scroll, LV_OPA_TRANSP, 0);
lv_obj_set_layout(g_ui.scroll, LV_LAYOUT_FLEX);
lv_obj_set_flex_flow(g_ui.scroll, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(g_ui.scroll, LV_FLEX_ALIGN_START,
LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
lv_obj_set_style_pad_row(g_ui.scroll, 6, 0);
lv_obj_set_scroll_dir(g_ui.scroll, LV_DIR_VER);
lv_obj_set_scrollbar_mode(g_ui.scroll, LV_SCROLLBAR_MODE_OFF);
g_ui.value = lv_label_create(g_ui.content);
lv_obj_set_width(g_ui.value, LV_PCT(100));
lv_obj_set_style_text_font(g_ui.value, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(g_ui.value, lv_color_hex(0x97A3B5), 0);
g_ui.initialized = true;
}
void ui_settings_show(struct ui_settings_page *page, bool animate)
{
uint8_t count;
lv_color_t accent = lv_color_hex(0x4C9EF5);
if ((page == NULL) || !g_ui.initialized ||
(page->ops == NULL) || (page->ops->get_count == NULL) ||
(page->ops->get_item == NULL)) {
return;
}
count = page->ops->get_count(page);
rebuild_rows(count);
g_ui.page = page;
lv_label_set_text(g_ui.title, page->title ? page->title : "");
lv_label_set_text(g_ui.hint, page->hint ? page->hint : "");
lv_label_set_text(g_ui.value, "");
for (uint8_t i = 0; i < g_ui.row_count; i++) {
struct ui_settings_item item = { 0 };
page->ops->get_item(page, i, &item);
row_set(&g_ui.rows[i], &item, page->selected == i, accent);
}
focus_row(page->selected, animate);
}
void ui_settings_deinit(void)
{
if (!g_ui.initialized) {
return;
}
if (g_ui.focus_group != NULL) {
lv_group_delete(g_ui.focus_group);
}
if (g_ui.content != NULL) {
lv_obj_delete(g_ui.content);
}
memset(&g_ui, 0, sizeof(g_ui));
}

18
src/ui/ui_settings.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef BLINKY_UI_SETTINGS_H_
#define BLINKY_UI_SETTINGS_H_
#include "ui/ui_settings_page.h"
#ifdef __cplusplus
extern "C" {
#endif
void ui_settings_init(void);
void ui_settings_show(struct ui_settings_page *page, bool animate);
void ui_settings_deinit(void);
#ifdef __cplusplus
}
#endif
#endif /* BLINKY_UI_SETTINGS_H_ */

60
src/ui/ui_settings_ble.c Normal file
View File

@@ -0,0 +1,60 @@
#include <lvgl.h>
#include <zephyr/sys/util.h>
#include "ui/ui_settings_controller.h"
static uint8_t ble_get_count(struct ui_settings_page *page)
{
ARG_UNUSED(page);
return 4U;
}
static void ble_get_item(struct ui_settings_page *page, uint8_t index,
struct ui_settings_item *item)
{
ARG_UNUSED(page);
if (index < 3U) {
static const char *const titles[] = {
"Slot 1",
"Slot 2",
"Slot 3",
};
item->icon = LV_SYMBOL_BLUETOOTH;
item->title = titles[index];
item->value = ui_settings_ble_slot_label(index);
return;
}
item->icon = LV_SYMBOL_TRASH;
item->title = "Erase Bond";
item->value = ui_settings_ble_current_label();
}
static void ble_on_select(struct ui_settings_page *page, uint8_t index)
{
if (index < 3U) {
ui_settings_ble_select_slot(index);
} else {
ui_settings_ble_erase_current();
}
(void)ui_settings_controller_back();
}
static const struct ui_settings_page_ops ble_ops = {
.get_count = ble_get_count,
.get_item = ble_get_item,
.on_select = ble_on_select,
};
struct ui_settings_page ui_settings_ble_page = {
.base = {
.ops = &ble_ops.base,
},
.ops = &ble_ops,
.title = LV_SYMBOL_BLUETOOTH " Bluetooth",
.hint = "Short: select and return",
};

View File

@@ -0,0 +1,226 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <zephyr/sys/util.h>
#include "ble_bond_multi_event.h"
#include "ui/ui_settings_controller.h"
#include "settings_view_event.h"
#include "ui_settings_pages.h"
#define BLE_SLOT_COUNT 3U
int ble_bond_multi_select_slot(uint8_t slot);
int ble_bond_multi_erase_current_slot(void);
struct controller_ctx {
struct ui_settings_page *current;
bool active;
uint8_t active_ble_slot;
char ble_labels[BLE_SLOT_COUNT][BLE_BOND_MULTI_DISPLAY_NAME_MAX_LEN];
struct theme_rgb theme;
};
static struct controller_ctx ctx = {
.active_ble_slot = 0U,
.ble_labels = {
"Empty",
"Empty",
"Empty",
},
.theme = {
.r = BLINKY_THEME_DEFAULT_R,
.g = BLINKY_THEME_DEFAULT_G,
.b = BLINKY_THEME_DEFAULT_B,
},
};
static uint8_t wrap_index(uint8_t current, uint8_t count, int8_t delta)
{
int32_t next = current;
if (count == 0U) {
return 0U;
}
next += delta;
while (next < 0) {
next += count;
}
return (uint8_t)(next % count);
}
static void publish_view(bool animate)
{
if (ctx.active && (ctx.current != NULL)) {
submit_settings_view_event(ctx.current, animate);
}
}
void ui_settings_controller_switch_to(struct ui_settings_page *page,
struct ui_page *parent)
{
if (page == NULL) {
return;
}
if (ctx.current != NULL) {
ui_page_deinit(&ctx.current->base);
}
page->base.parent = parent;
ctx.current = page;
ui_page_init(&page->base);
if ((page->ops != NULL) && (page->ops->on_enter != NULL)) {
page->ops->on_enter(page);
}
}
void ui_settings_controller_open(void)
{
ctx.active = true;
ui_settings_controller_switch_to(&ui_settings_root_page, NULL);
publish_view(false);
}
void ui_settings_controller_close(void)
{
if (ctx.current != NULL) {
ui_page_deinit(&ctx.current->base);
ctx.current = NULL;
}
ctx.active = false;
}
bool ui_settings_controller_back(void)
{
struct ui_page *parent;
if (ctx.current == NULL) {
return false;
}
if ((ctx.current->ops != NULL) && (ctx.current->ops->on_back != NULL)) {
ctx.current->ops->on_back(ctx.current);
publish_view(true);
return ctx.active;
}
parent = ctx.current->base.parent;
if (parent == NULL) {
ui_settings_controller_close();
return false;
}
ui_settings_controller_switch_to(ui_page_to_settings(parent),
parent->parent);
publish_view(true);
return true;
}
void ui_settings_controller_select(void)
{
if ((ctx.current == NULL) || (ctx.current->ops == NULL) ||
(ctx.current->ops->on_select == NULL)) {
return;
}
ctx.current->ops->on_select(ctx.current, ctx.current->selected);
publish_view(true);
}
void ui_settings_controller_move(int8_t delta)
{
uint8_t count;
if ((ctx.current == NULL) || (ctx.current->ops == NULL) ||
(ctx.current->ops->get_count == NULL)) {
return;
}
count = ctx.current->ops->get_count(ctx.current);
ctx.current->selected = wrap_index(ctx.current->selected, count, delta);
publish_view(true);
}
void ui_settings_controller_refresh(bool animate)
{
if (ctx.current != NULL) {
publish_view(animate);
}
}
bool ui_settings_controller_is_active(void)
{
return ctx.active;
}
const char *ui_settings_ble_current_label(void)
{
return (ctx.active_ble_slot == 0U) ? "Slot 1" :
(ctx.active_ble_slot == 1U) ? "Slot 2" : "Slot 3";
}
void ui_settings_ble_select_slot(uint8_t slot)
{
if (slot < BLE_SLOT_COUNT) {
(void)ble_bond_multi_select_slot(slot + 1U);
}
}
void ui_settings_ble_erase_current(void)
{
(void)ble_bond_multi_erase_current_slot();
}
const char *ui_settings_ble_slot_label(uint8_t slot)
{
if (slot >= BLE_SLOT_COUNT) {
return "";
}
return ctx.ble_labels[slot];
}
void ui_settings_ble_set_current_slot(uint8_t slot)
{
if ((slot >= 1U) && (slot <= BLE_SLOT_COUNT)) {
ctx.active_ble_slot = slot - 1U;
}
}
void ui_settings_ble_set_slot_meta(uint8_t slot,
const struct ble_bond_multi_slot_meta *meta)
{
if ((meta == NULL) || (slot < 1U) || (slot > BLE_SLOT_COUNT)) {
return;
}
strncpy(ctx.ble_labels[slot - 1U],
meta->display_name[0] ? meta->display_name : "Empty",
sizeof(ctx.ble_labels[slot - 1U]));
ctx.ble_labels[slot - 1U][sizeof(ctx.ble_labels[slot - 1U]) - 1U] = '\0';
}
void ui_settings_theme_set_current(struct theme_rgb theme)
{
ctx.theme = theme;
}
const char *ui_settings_theme_current_name(void)
{
extern const char *ui_settings_theme_name_for_color(struct theme_rgb theme);
return ui_settings_theme_name_for_color(ctx.theme);
}
uint8_t ui_settings_theme_current_index(void)
{
extern uint8_t ui_settings_theme_index_for_color(struct theme_rgb theme);
return ui_settings_theme_index_for_color(ctx.theme);
}

View File

@@ -0,0 +1,10 @@
#ifndef BLINKY_UI_SETTINGS_PAGES_H_
#define BLINKY_UI_SETTINGS_PAGES_H_
#include "ui/ui_settings_page.h"
extern struct ui_settings_page ui_settings_root_page;
extern struct ui_settings_page ui_settings_ble_page;
extern struct ui_settings_page ui_settings_theme_page;
#endif /* BLINKY_UI_SETTINGS_PAGES_H_ */

64
src/ui/ui_settings_root.c Normal file
View File

@@ -0,0 +1,64 @@
#include <lvgl.h>
#include <zephyr/sys/util.h>
#include "ui/ui_settings_controller.h"
#include "ui_settings_pages.h"
static uint8_t root_get_count(struct ui_settings_page *page)
{
ARG_UNUSED(page);
return 2U;
}
static void root_get_item(struct ui_settings_page *page, uint8_t index,
struct ui_settings_item *item)
{
ARG_UNUSED(page);
if (index == 0U) {
item->icon = LV_SYMBOL_BLUETOOTH;
item->title = "Bluetooth";
item->value = ui_settings_ble_current_label();
return;
}
item->icon = LV_SYMBOL_TINT;
item->title = "Theme";
item->value = ui_settings_theme_current_name();
}
static void root_on_enter(struct ui_settings_page *page)
{
page->selected = 0U;
}
static void root_on_select(struct ui_settings_page *page, uint8_t index)
{
ui_settings_controller_switch_to(
(index == 0U) ? &ui_settings_ble_page : &ui_settings_theme_page,
&page->base);
}
static void root_on_back(struct ui_settings_page *page)
{
ARG_UNUSED(page);
ui_settings_controller_close();
}
static const struct ui_settings_page_ops root_ops = {
.get_count = root_get_count,
.get_item = root_get_item,
.on_enter = root_on_enter,
.on_select = root_on_select,
.on_back = root_on_back,
};
struct ui_settings_page ui_settings_root_page = {
.base = {
.ops = &root_ops.base,
},
.ops = &root_ops,
.title = LV_SYMBOL_SETTINGS " Settings",
.hint = "Rotate select Tap OK Hold exit",
};

109
src/ui/ui_settings_theme.c Normal file
View File

@@ -0,0 +1,109 @@
#include <lvgl.h>
#include <zephyr/sys/util.h>
#include "theme_rgb_update_event.h"
#include "ui/ui_settings_controller.h"
struct theme_option {
const char *name;
struct theme_rgb color;
};
static const struct theme_option themes[] = {
{ "Red", { .r = 0xFF, .g = 0x00, .b = 0x00 } },
{ "Amber", { .r = 0xFF, .g = 0x95, .b = 0x00 } },
{ "Default", { .r = BLINKY_THEME_DEFAULT_R,
.g = BLINKY_THEME_DEFAULT_G,
.b = BLINKY_THEME_DEFAULT_B } },
{ "Green", { .r = 0x34, .g = 0xC7, .b = 0x59 } },
{ "Purple", { .r = 0xBF, .g = 0x5A, .b = 0xF2 } },
{ "White", { .r = 0xF2, .g = 0xF2, .b = 0xF7 } },
};
static bool theme_equal(struct theme_rgb lhs, struct theme_rgb rhs)
{
return (lhs.r == rhs.r) && (lhs.g == rhs.g) && (lhs.b == rhs.b);
}
uint8_t ui_settings_theme_index_for_color(struct theme_rgb theme)
{
for (uint8_t i = 0; i < ARRAY_SIZE(themes); i++) {
if (theme_equal(theme, themes[i].color)) {
return i;
}
}
return 0U;
}
const char *ui_settings_theme_name_for_color(struct theme_rgb theme)
{
for (uint8_t i = 0; i < ARRAY_SIZE(themes); i++) {
if (theme_equal(theme, themes[i].color)) {
return themes[i].name;
}
}
return "Custom";
}
static void draw_swatch(const struct ui_settings_item *item, lv_obj_t *container)
{
const struct theme_rgb *color = item->user_data;
if (color == NULL) {
return;
}
lv_obj_set_style_bg_opa(container, LV_OPA_COVER, 0);
lv_obj_set_style_bg_color(container,
lv_color_make(color->r, color->g, color->b), 0);
}
static uint8_t theme_get_count(struct ui_settings_page *page)
{
ARG_UNUSED(page);
return ARRAY_SIZE(themes);
}
static void theme_get_item(struct ui_settings_page *page, uint8_t index,
struct ui_settings_item *item)
{
ARG_UNUSED(page);
item->icon = LV_SYMBOL_TINT;
item->title = themes[index].name;
item->draw = draw_swatch;
item->user_data = (void *)&themes[index].color;
}
static void theme_on_enter(struct ui_settings_page *page)
{
page->selected = ui_settings_theme_current_index();
}
static void theme_on_select(struct ui_settings_page *page, uint8_t index)
{
struct theme_rgb theme = themes[index].color;
ui_settings_theme_set_current(theme);
submit_theme_rgb_update_event(theme);
(void)ui_settings_controller_back();
}
static const struct ui_settings_page_ops theme_ops = {
.get_count = theme_get_count,
.get_item = theme_get_item,
.on_enter = theme_on_enter,
.on_select = theme_on_select,
};
struct ui_settings_page ui_settings_theme_page = {
.base = {
.ops = &theme_ops.base,
},
.ops = &theme_ops,
.title = LV_SYMBOL_TINT " Theme",
.hint = "Short: apply and return",
};

649
src/usb_cdc_module.c Normal file
View File

@@ -0,0 +1,649 @@
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <app_event_manager.h>
#define MODULE usb_cdc_module
#include <caf/events/module_state_event.h>
#include <caf/events/power_event.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/sys/util.h>
#include "module_lifecycle.h"
#include "proto_rx_event.h"
#include "proto_transport_state_event.h"
#include "proto_tx_event.h"
#include "usb_control_event.h"
#include "usb_state_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define USB_CDC_RX_RING_BUF_SIZE 256
#define USB_CDC_TX_RING_BUF_SIZE 256
#define USB_CDC_RX_CHUNK_SIZE 32
#define USB_CDC_PROTO_RX_BUF_SIZE PROTO_MAX_FRAME_LEN
#define USB_CDC_EXPECTED_BAUDRATE 115200U
enum usb_cdc_business_state {
USB_CDC_BUS_OFFLINE = 0,
USB_CDC_WAIT_DTR,
USB_CDC_SESSION_READY,
};
struct usb_cdc_ctx {
struct module_lifecycle_ctx lc;
enum usb_cdc_business_state business;
const struct device *cdc_dev;
uint8_t rx_ring_buffer[USB_CDC_RX_RING_BUF_SIZE];
uint8_t tx_ring_buffer[USB_CDC_TX_RING_BUF_SIZE];
struct ring_buf rx_ringbuf;
struct ring_buf tx_ringbuf;
struct k_work rx_work;
uint8_t proto_rx_buf[USB_CDC_PROTO_RX_BUF_SIZE];
bool usb_active;
size_t proto_rx_len;
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_POWER,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct usb_cdc_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.business = USB_CDC_BUS_OFFLINE,
.cdc_dev = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart0)),
.usb_active = false,
.proto_rx_len = 0U,
};
static void validate_line_coding(void);
static const char *usb_cdc_business_state_name(enum usb_cdc_business_state state)
{
switch (state) {
case USB_CDC_BUS_OFFLINE:
return "BUS_OFFLINE";
case USB_CDC_WAIT_DTR:
return "WAIT_DTR";
case USB_CDC_SESSION_READY:
return "SESSION_READY";
default:
return "?";
}
}
static const char *proto_link_state_name(enum proto_transport_link_state state)
{
switch (state) {
case PROTO_TRANSPORT_LINK_DOWN:
return "DOWN";
case PROTO_TRANSPORT_LINK_READY:
return "READY";
default:
return "?";
}
}
static bool lifecycle_is_ready(void)
{
return module_lifecycle_is_running(&ctx.lc);
}
static enum proto_transport_link_state transport_link_state_get(void)
{
return (lifecycle_is_ready() &&
(ctx.business == USB_CDC_SESSION_READY)) ?
PROTO_TRANSPORT_LINK_READY :
PROTO_TRANSPORT_LINK_DOWN;
}
static void reset_ring_buffers(void)
{
unsigned int key = irq_lock();
ring_buf_init(&ctx.rx_ringbuf, sizeof(ctx.rx_ring_buffer), ctx.rx_ring_buffer);
ring_buf_init(&ctx.tx_ringbuf, sizeof(ctx.tx_ring_buffer), ctx.tx_ring_buffer);
irq_unlock(key);
}
static void disable_uart_io(void)
{
uart_irq_rx_disable(ctx.cdc_dev);
uart_irq_tx_disable(ctx.cdc_dev);
ctx.proto_rx_len = 0U;
reset_ring_buffers();
}
static void state_reconcile(enum module_lifecycle old_lifecycle,
enum usb_cdc_business_state old_business)
{
enum proto_transport_link_state old_link =
((old_lifecycle == LC_RUNNING) &&
(old_business == USB_CDC_SESSION_READY)) ?
PROTO_TRANSPORT_LINK_READY :
PROTO_TRANSPORT_LINK_DOWN;
enum proto_transport_link_state new_link = transport_link_state_get();
if ((old_lifecycle == LC_RUNNING) &&
(old_business == USB_CDC_SESSION_READY) &&
(new_link == PROTO_TRANSPORT_LINK_DOWN)) {
LOG_INF("CDC reconcile: link READY -> DOWN, disable UART IO");
disable_uart_io();
}
if ((old_link == PROTO_TRANSPORT_LINK_DOWN) &&
(new_link == PROTO_TRANSPORT_LINK_READY)) {
int err;
LOG_INF("CDC reconcile: link DOWN -> READY (business:%s usb_active:%u lc:%s)",
usb_cdc_business_state_name(ctx.business), ctx.usb_active,
module_lifecycle_name(ctx.lc.state));
validate_line_coding();
err = uart_line_ctrl_set(ctx.cdc_dev, UART_LINE_CTRL_DCD, 1);
if (err) {
LOG_WRN("Failed to set DCD (%d)", err);
}
err = uart_line_ctrl_set(ctx.cdc_dev, UART_LINE_CTRL_DSR, 1);
if (err) {
LOG_WRN("Failed to set DSR (%d)", err);
}
uart_irq_rx_enable(ctx.cdc_dev);
}
if (old_link != new_link) {
LOG_INF("CDC link %s -> %s (business:%s usb_active:%u lc:%s)",
proto_link_state_name(old_link),
proto_link_state_name(new_link),
usb_cdc_business_state_name(ctx.business), ctx.usb_active,
module_lifecycle_name(ctx.lc.state));
submit_proto_transport_state_event(PROTO_TRANSPORT_USB_CDC,
new_link);
}
}
static void business_state_set(enum usb_cdc_business_state new_state)
{
enum module_lifecycle old_lifecycle = ctx.lc.state;
enum usb_cdc_business_state old_business = ctx.business;
if (ctx.business == new_state) {
return;
}
ctx.business = new_state;
LOG_INF("CDC business %s -> %s (usb_active:%u lc:%s)",
usb_cdc_business_state_name(old_business),
usb_cdc_business_state_name(ctx.business), ctx.usb_active,
module_lifecycle_name(ctx.lc.state));
state_reconcile(old_lifecycle, old_business);
}
static void business_state_sync_from_usb(void)
{
LOG_INF("CDC sync from USB (usb_active:%u business:%s lc:%s)",
ctx.usb_active, usb_cdc_business_state_name(ctx.business),
module_lifecycle_name(ctx.lc.state));
if (!ctx.usb_active) {
business_state_set(USB_CDC_BUS_OFFLINE);
return;
}
if (!lifecycle_is_ready()) {
return;
}
if (ctx.business == USB_CDC_BUS_OFFLINE) {
business_state_set(USB_CDC_WAIT_DTR);
}
}
static void kick_tx(void)
{
if (transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) {
LOG_INF("CDC kick_tx skipped: link=%s business:%s usb_active:%u tx_rb:%u",
proto_link_state_name(transport_link_state_get()),
usb_cdc_business_state_name(ctx.business), ctx.usb_active,
ring_buf_size_get(&ctx.tx_ringbuf));
return;
}
LOG_INF("CDC kick_tx: enable TX IRQ (tx_rb:%u)", ring_buf_size_get(&ctx.tx_ringbuf));
uart_irq_tx_enable(ctx.cdc_dev);
}
static void validate_line_coding(void)
{
uint32_t baudrate = 0U;
int err;
err = uart_line_ctrl_get(ctx.cdc_dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
if (err) {
LOG_WRN("Failed to get CDC baudrate (%d)", err);
} else {
LOG_INF("CDC baudrate %u", baudrate);
if (baudrate != USB_CDC_EXPECTED_BAUDRATE) {
LOG_WRN("Expected CDC baudrate %u, got %u",
USB_CDC_EXPECTED_BAUDRATE, baudrate);
}
}
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
{
struct uart_config cfg;
err = uart_config_get(ctx.cdc_dev, &cfg);
if (err) {
LOG_WRN("uart_config_get failed (%d)", err);
} else {
LOG_INF("CDC line coding data:%u stop:%u parity:%u flow:%u",
cfg.data_bits, cfg.stop_bits, cfg.parity,
cfg.flow_ctrl);
if ((cfg.data_bits != UART_CFG_DATA_BITS_8) ||
(cfg.stop_bits != UART_CFG_STOP_BITS_1) ||
(cfg.parity != UART_CFG_PARITY_NONE) ||
(cfg.flow_ctrl != UART_CFG_FLOW_CTRL_NONE)) {
LOG_WRN("Expected CDC line coding 115200 8N1 no flow control");
}
}
}
#endif
}
static bool try_extract_frame(void)
{
uint8_t *buf = ctx.proto_rx_buf;
size_t frame_len;
uint16_t magic;
if (ctx.proto_rx_len < PROTO_FRAME_HEADER_SIZE) {
return false;
}
magic = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
if (magic != PROTO_FRAME_MAGIC) {
LOG_WRN("CDC invalid frame magic 0x%04x", magic);
memmove(buf, &buf[1], ctx.proto_rx_len - 1U);
ctx.proto_rx_len--;
return true;
}
if (buf[2] > PROTO_MAX_PAYLOAD_LEN) {
LOG_WRN("CDC invalid frame len:%u", buf[2]);
memmove(buf, &buf[1], ctx.proto_rx_len - 1U);
ctx.proto_rx_len--;
return true;
}
frame_len = PROTO_FRAME_HEADER_SIZE + buf[2];
if (ctx.proto_rx_len < frame_len) {
return false;
}
LOG_INF("CDC submit framed proto_rx len:%u", (unsigned int)frame_len);
(void)submit_proto_rx_event(PROTO_TRANSPORT_USB_CDC, buf, frame_len);
if (ctx.proto_rx_len > frame_len) {
memmove(buf, &buf[frame_len], ctx.proto_rx_len - frame_len);
}
ctx.proto_rx_len -= frame_len;
return true;
}
static void rx_work_handler(struct k_work *work)
{
uint8_t buffer[USB_CDC_RX_CHUNK_SIZE];
ARG_UNUSED(work);
while (true) {
uint32_t len;
unsigned int key = irq_lock();
len = ring_buf_get(&ctx.rx_ringbuf, buffer, sizeof(buffer));
irq_unlock(key);
if (len == 0U) {
break;
}
LOG_INF("CDC rx_work pulled %u bytes from RX ring (proto_rx_len:%u)",
(unsigned int)len, (unsigned int)ctx.proto_rx_len);
if ((ctx.proto_rx_len + len) > sizeof(ctx.proto_rx_buf)) {
LOG_WRN("Drop oversized CDC framed data len:%u",
(uint32_t)(ctx.proto_rx_len + len));
ctx.proto_rx_len = 0U;
}
if (len > 0U) {
memcpy(&ctx.proto_rx_buf[ctx.proto_rx_len], buffer, len);
ctx.proto_rx_len += len;
}
}
while (try_extract_frame()) {
}
}
static void cdc_interrupt_handler(const struct device *dev, void *user_data)
{
ARG_UNUSED(user_data);
while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
if (uart_irq_rx_ready(dev)) {
uint8_t buffer[USB_CDC_RX_CHUNK_SIZE];
int recv_len = uart_fifo_read(dev, buffer, sizeof(buffer));
if (recv_len < 0) {
LOG_ERR("Failed to read CDC RX FIFO");
continue;
}
if (recv_len > 0) {
uint32_t written;
unsigned int key = irq_lock();
written = ring_buf_put(&ctx.rx_ringbuf, buffer,
(uint32_t)recv_len);
irq_unlock(key);
if (written < (uint32_t)recv_len) {
LOG_WRN("Drop %d CDC RX bytes",
recv_len - (int)written);
}
LOG_INF("CDC IRQ RX recv:%d written:%u rx_rb:%u",
recv_len, (unsigned int)written,
ring_buf_size_get(&ctx.rx_ringbuf));
k_work_submit(&ctx.rx_work);
}
}
if (uart_irq_tx_ready(dev)) {
uint8_t buffer[USB_CDC_RX_CHUNK_SIZE];
uint32_t len;
int sent_len;
unsigned int key = irq_lock();
len = ring_buf_get(&ctx.tx_ringbuf, buffer, sizeof(buffer));
irq_unlock(key);
if (len == 0U) {
LOG_INF("CDC IRQ TX ready but no pending data, disable TX IRQ");
uart_irq_tx_disable(dev);
continue;
}
sent_len = uart_fifo_fill(dev, buffer, len);
if (sent_len < 0) {
LOG_ERR("Failed to write CDC TX FIFO");
uart_irq_tx_disable(dev);
} else if ((uint32_t)sent_len < len) {
LOG_WRN("Drop %u CDC TX bytes",
(unsigned int)(len - (uint32_t)sent_len));
}
LOG_INF("CDC IRQ TX requested:%u sent:%d tx_rb_remain:%u",
(unsigned int)len, sent_len,
ring_buf_size_get(&ctx.tx_ringbuf));
}
}
}
static int do_init(void)
{
if (!device_is_ready(ctx.cdc_dev)) {
LOG_ERR("CDC ACM device not ready");
return -ENODEV;
}
reset_ring_buffers();
k_work_init(&ctx.rx_work, rx_work_handler);
uart_irq_callback_set(ctx.cdc_dev, cdc_interrupt_handler);
ctx.business = USB_CDC_BUS_OFFLINE;
ctx.usb_active = false;
ctx.proto_rx_len = 0U;
LOG_INF("CDC init done (business:%s usb_active:%u lc:%s dev:%p)",
usb_cdc_business_state_name(ctx.business), ctx.usb_active,
module_lifecycle_name(ctx.lc.state), ctx.cdc_dev);
return 0;
}
static int do_start(void)
{
return 0;
}
static int do_stop(void)
{
LOG_INF("CDC stop requested (business:%s usb_active:%u lc:%s)",
usb_cdc_business_state_name(ctx.business), ctx.usb_active,
module_lifecycle_name(ctx.lc.state));
ctx.business = USB_CDC_BUS_OFFLINE;
return 0;
}
static int apply_lifecycle(enum module_lifecycle target)
{
enum module_lifecycle old_lifecycle = ctx.lc.state;
enum usb_cdc_business_state old_business = ctx.business;
int err = module_set_lifecycle(&ctx.lc, target);
if (err) {
return err;
}
state_reconcile(old_lifecycle, old_business);
if (target == LC_RUNNING) {
business_state_sync_from_usb();
}
LOG_INF("CDC lifecycle %s -> %s done (business:%s usb_active:%u link:%s)",
module_lifecycle_name(old_lifecycle), module_lifecycle_name(target),
usb_cdc_business_state_name(ctx.business), ctx.usb_active,
proto_link_state_name(transport_link_state_get()));
return 0;
}
static bool handle_usb_state_event(const struct usb_state_event *event)
{
bool new_usb_active = (event->state == USB_STATE_ACTIVE);
if (new_usb_active == ctx.usb_active) {
return false;
}
ctx.usb_active = new_usb_active;
LOG_INF("CDC usb_active -> %u from usb_state:%d (business:%s lc:%s)",
ctx.usb_active, event->state,
usb_cdc_business_state_name(ctx.business),
module_lifecycle_name(ctx.lc.state));
business_state_sync_from_usb();
return false;
}
static bool handle_usb_control_event(const struct usb_control_event *event)
{
if (event->dev != ctx.cdc_dev) {
return false;
}
switch (event->type) {
case USB_CONTROL_EVENT_CDC_LINE_STATE:
LOG_INF("CDC control event line_state dtr:%u usb_active:%u lc:%s business:%s",
event->data.cdc_line_state.dtr, ctx.usb_active,
module_lifecycle_name(ctx.lc.state),
usb_cdc_business_state_name(ctx.business));
if (!ctx.usb_active || !lifecycle_is_ready()) {
LOG_INF("CDC line_state ignored: usb_active:%u lc:%s",
ctx.usb_active, module_lifecycle_name(ctx.lc.state));
return false;
}
if (event->data.cdc_line_state.dtr) {
LOG_INF("CDC DTR set");
business_state_set(USB_CDC_SESSION_READY);
kick_tx();
} else {
LOG_INF("CDC DTR cleared");
business_state_set(USB_CDC_WAIT_DTR);
}
return false;
case USB_CONTROL_EVENT_CDC_LINE_CODING:
LOG_INF("CDC control event line_coding baud:%u data:%u stop:%u parity:%u flow:%u business:%s",
event->data.cdc_line_coding.baudrate,
event->data.cdc_line_coding.data_bits,
event->data.cdc_line_coding.stop_bits,
event->data.cdc_line_coding.parity,
event->data.cdc_line_coding.flow_ctrl,
usb_cdc_business_state_name(ctx.business));
if (event->data.cdc_line_coding.baudrate != 0U) {
LOG_INF("CDC baudrate %u",
event->data.cdc_line_coding.baudrate);
if (event->data.cdc_line_coding.baudrate !=
USB_CDC_EXPECTED_BAUDRATE) {
LOG_WRN("Expected CDC baudrate %u, got %u",
USB_CDC_EXPECTED_BAUDRATE,
event->data.cdc_line_coding.baudrate);
}
}
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
if ((event->data.cdc_line_coding.data_bits != 0U) ||
(event->data.cdc_line_coding.stop_bits != 0U) ||
(event->data.cdc_line_coding.parity != 0U) ||
(event->data.cdc_line_coding.flow_ctrl != 0U)) {
LOG_INF("CDC line coding data:%u stop:%u parity:%u flow:%u",
event->data.cdc_line_coding.data_bits,
event->data.cdc_line_coding.stop_bits,
event->data.cdc_line_coding.parity,
event->data.cdc_line_coding.flow_ctrl);
}
#endif
return false;
default:
return false;
}
}
static bool handle_proto_tx_event(const struct proto_tx_event *event)
{
uint32_t written;
unsigned int key;
if (event->transport != PROTO_TRANSPORT_USB_CDC) {
return false;
}
if (transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) {
LOG_INF("CDC proto_tx ignored len:%u link:%s business:%s usb_active:%u",
(unsigned int)event->dyndata.size,
proto_link_state_name(transport_link_state_get()),
usb_cdc_business_state_name(ctx.business), ctx.usb_active);
return false;
}
key = irq_lock();
written = ring_buf_put(&ctx.tx_ringbuf, event->dyndata.data,
(uint32_t)event->dyndata.size);
irq_unlock(key);
LOG_INF("CDC proto_tx queued len:%u written:%u tx_rb:%u",
(unsigned int)event->dyndata.size, (unsigned int)written,
ring_buf_size_get(&ctx.tx_ringbuf));
if (written < event->dyndata.size) {
LOG_WRN("Drop %zu CDC TX bytes", event->dyndata.size - written);
}
if (written > 0U) {
kick_tx();
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_usb_state_event(aeh)) {
return handle_usb_state_event(cast_usb_state_event(aeh));
}
if (is_proto_tx_event(aeh)) {
return handle_proto_tx_event(cast_proto_tx_event(aeh));
}
if (is_usb_control_event(aeh)) {
return handle_usb_control_event(cast_usb_control_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)) {
(void)apply_lifecycle(LC_RUNNING);
}
return false;
}
if (is_power_down_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)apply_lifecycle(LC_STOPPED);
}
return false;
}
if (is_wake_up_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)apply_lifecycle(LC_RUNNING);
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, proto_tx_event);
APP_EVENT_SUBSCRIBE(MODULE, usb_control_event);
APP_EVENT_SUBSCRIBE(MODULE, usb_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

554
src/usb_device_module.c Normal file
View File

@@ -0,0 +1,554 @@
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <app_event_manager.h>
#define MODULE usb_device_module
#include <caf/events/module_state_event.h>
#include <caf/events/module_suspend_event.h>
#include <caf/events/power_event.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/logging/log.h>
#include <zephyr/usb/usbd.h>
#include <caf/events/power_manager_event.h>
#include "module_lifecycle.h"
#include "usb_function_hook.h"
#include "usb_control_event.h"
#include "usb_state_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define USB_DEVICE_VID 0x1915
#define USB_DEVICE_PID 0x52F0
#define USB_DEVICE_MANUFACTURER "Atguigu"
#define USB_DEVICE_PRODUCT "WH Mini Keyboard"
USBD_DEVICE_DEFINE(blinky_usbd, DEVICE_DT_GET(DT_NODELABEL(usbd)),
USB_DEVICE_VID, USB_DEVICE_PID);
USBD_DESC_LANG_DEFINE(blinky_lang);
USBD_DESC_MANUFACTURER_DEFINE(blinky_mfr, USB_DEVICE_MANUFACTURER);
USBD_DESC_PRODUCT_DEFINE(blinky_product, USB_DEVICE_PRODUCT);
USBD_DESC_CONFIG_DEFINE(blinky_fs_cfg_desc, "FS Configuration");
USBD_CONFIGURATION_DEFINE(blinky_fs_config, 0, 250, &blinky_fs_cfg_desc);
static const char *const class_blocklist[] = {
NULL,
};
enum usb_stack_state {
USB_STACK_UNINITIALIZED = 0,
USB_STACK_READY,
USB_STACK_ENABLED,
};
enum usb_bus_state {
USB_BUS_DISCONNECTED = 0,
USB_BUS_POWERED,
USB_BUS_ACTIVE,
USB_BUS_SUSPENDED,
};
struct usb_owner_ctx {
struct module_lifecycle_ctx lc;
enum usb_stack_state stack;
enum usb_bus_state bus;
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
/* Project exception: mode policy suspend/resume controls USB availability,
* while global power events still map to standby lifecycle transitions.
*/
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_SUSPEND,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct usb_owner_ctx usb_ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.stack = USB_STACK_UNINITIALIZED,
.bus = USB_BUS_DISCONNECTED,
};
static const char *usb_stack_state_name(enum usb_stack_state state)
{
switch (state) {
case USB_STACK_UNINITIALIZED:
return "UNINITIALIZED";
case USB_STACK_READY:
return "READY";
case USB_STACK_ENABLED:
return "ENABLED";
default:
return "?";
}
}
static const char *usb_bus_state_name(enum usb_bus_state state)
{
switch (state) {
case USB_BUS_DISCONNECTED:
return "DISCONNECTED";
case USB_BUS_POWERED:
return "POWERED";
case USB_BUS_ACTIVE:
return "ACTIVE";
case USB_BUS_SUSPENDED:
return "SUSPENDED";
default:
return "?";
}
}
static const char *usb_public_state_name(enum usb_state state)
{
switch (state) {
case USB_STATE_DISABLED:
return "DISABLED";
case USB_STATE_DISCONNECTED:
return "DISCONNECTED";
case USB_STATE_POWERED:
return "POWERED";
case USB_STATE_ACTIVE:
return "ACTIVE";
case USB_STATE_SUSPENDED:
return "SUSPENDED";
default:
return "?";
}
}
static inline enum usb_state usb_public_state_get(void)
{
if (!module_lifecycle_is_running(&usb_ctx.lc) ||
(usb_ctx.stack == USB_STACK_UNINITIALIZED)) {
return USB_STATE_DISABLED;
}
switch (usb_ctx.bus) {
case USB_BUS_DISCONNECTED:
return USB_STATE_DISCONNECTED;
case USB_BUS_POWERED:
return USB_STATE_POWERED;
case USB_BUS_ACTIVE:
return USB_STATE_ACTIVE;
case USB_BUS_SUSPENDED:
return USB_STATE_SUSPENDED;
default:
return USB_STATE_DISABLED;
}
}
static inline void usb_bus_set(enum usb_bus_state state)
{
enum usb_bus_state old_state = usb_ctx.bus;
if (usb_ctx.bus == state) {
return;
}
usb_ctx.bus = state;
LOG_INF("USB bus %s -> %s (stack:%s lc:%s public:%s)",
usb_bus_state_name(old_state),
usb_bus_state_name(usb_ctx.bus),
usb_stack_state_name(usb_ctx.stack),
module_lifecycle_name(usb_ctx.lc.state),
usb_public_state_name(usb_public_state_get()));
if (module_lifecycle_is_initialized(&usb_ctx.lc)) {
submit_usb_state(usb_public_state_get());
}
}
static void update_power_manager_restriction(bool vbus_present)
{
power_manager_restrict(MODULE_IDX(MODULE),
vbus_present ? POWER_MANAGER_LEVEL_ALIVE :
POWER_MANAGER_LEVEL_SUSPENDED);
}
static int usb_descriptors_init(void)
{
int err;
err = usbd_add_descriptor(&blinky_usbd, &blinky_lang);
if (err) {
return err;
}
err = usbd_add_descriptor(&blinky_usbd, &blinky_mfr);
if (err) {
return err;
}
err = usbd_add_descriptor(&blinky_usbd, &blinky_product);
if (err) {
return err;
}
err = usbd_add_configuration(&blinky_usbd, USBD_SPEED_FS, &blinky_fs_config);
if (err) {
return err;
}
err = usbd_register_all_classes(&blinky_usbd, USBD_SPEED_FS, 1, class_blocklist);
if (err) {
return err;
}
usbd_device_set_code_triple(&blinky_usbd, USBD_SPEED_FS, 0, 0, 0);
return 0;
}
static void usbd_msg_cb(struct usbd_context *const usbd_ctx,
const struct usbd_msg *const msg)
{
ARG_UNUSED(usbd_ctx);
LOG_INF("USBD msg:%s status:%u dev:%p stack:%s bus:%s lc:%s",
usbd_msg_type_string(msg->type), msg->status, msg->dev,
usb_stack_state_name(usb_ctx.stack),
usb_bus_state_name(usb_ctx.bus),
module_lifecycle_name(usb_ctx.lc.state));
if (msg->type == USBD_MSG_CDC_ACM_CONTROL_LINE_STATE) {
uint32_t dtr = 0U;
int err = uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_DTR, &dtr);
if (err) {
LOG_WRN("Failed to get CDC DTR (%d)", err);
} else {
LOG_INF("CDC control line state DTR:%u dev:%p", dtr, msg->dev);
submit_usb_control_cdc_line_state_event(msg->dev, dtr != 0U);
}
return;
}
if (msg->type == USBD_MSG_CDC_ACM_LINE_CODING) {
uint32_t baudrate = 0U;
uint8_t data_bits = 0U;
uint8_t stop_bits = 0U;
uint8_t parity = 0U;
uint8_t flow_ctrl = 0U;
int err;
err = uart_line_ctrl_get(msg->dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
if (err) {
LOG_WRN("Failed to get CDC baudrate (%d)", err);
}
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
{
struct uart_config cfg;
err = uart_config_get(msg->dev, &cfg);
if (err) {
LOG_WRN("uart_config_get failed (%d)", err);
} else {
data_bits = (uint8_t)cfg.data_bits;
stop_bits = (uint8_t)cfg.stop_bits;
parity = (uint8_t)cfg.parity;
flow_ctrl = (uint8_t)cfg.flow_ctrl;
}
}
#endif
LOG_INF("CDC line coding baud:%u data:%u stop:%u parity:%u flow:%u dev:%p",
baudrate, data_bits, stop_bits, parity, flow_ctrl, msg->dev);
submit_usb_control_cdc_line_coding_event(msg->dev, baudrate,
data_bits, stop_bits,
parity, flow_ctrl);
return;
}
switch (msg->type) {
case USBD_MSG_VBUS_READY:
update_power_manager_restriction(true);
usb_bus_set(USB_BUS_POWERED);
if (module_lifecycle_is_running(&usb_ctx.lc) &&
(usb_ctx.stack == USB_STACK_READY)) {
int err = do_start();
if (err) {
LOG_ERR("USB start on VBUS ready failed (%d)", err);
}
}
break;
case USBD_MSG_VBUS_REMOVED:
if (usb_ctx.stack == USB_STACK_ENABLED) {
int err = do_stop();
if (err) {
LOG_ERR("USB stop on VBUS removed failed (%d)", err);
}
} else {
update_power_manager_restriction(false);
}
if (usb_ctx.stack != USB_STACK_UNINITIALIZED) {
usb_ctx.stack = USB_STACK_READY;
}
usb_bus_set(USB_BUS_DISCONNECTED);
break;
case USBD_MSG_CONFIGURATION:
if (msg->status) {
usb_bus_set(USB_BUS_ACTIVE);
} else if (usb_ctx.stack == USB_STACK_ENABLED) {
usb_bus_set(USB_BUS_POWERED);
}
break;
case USBD_MSG_SUSPEND:
if (usb_ctx.stack == USB_STACK_ENABLED) {
usb_bus_set(USB_BUS_SUSPENDED);
}
break;
case USBD_MSG_RESUME:
if (usb_ctx.stack == USB_STACK_ENABLED) {
usb_bus_set(USB_BUS_ACTIVE);
}
break;
default:
break;
}
}
static int do_init(void)
{
int err;
usb_ctx.stack = USB_STACK_UNINITIALIZED;
usb_ctx.bus = USB_BUS_DISCONNECTED;
LOG_INF("USB init start (stack:%s bus:%s lc:%s)",
usb_stack_state_name(usb_ctx.stack),
usb_bus_state_name(usb_ctx.bus),
module_lifecycle_name(usb_ctx.lc.state));
update_power_manager_restriction(false);
STRUCT_SECTION_FOREACH(usb_function_hook, hook) {
if (hook->pre_stack_init == NULL) {
continue;
}
err = hook->pre_stack_init();
if (err) {
LOG_ERR("USB function hook %s failed (%d)", hook->name, err);
return err;
}
}
err = usbd_msg_register_cb(&blinky_usbd, usbd_msg_cb);
if (err) {
LOG_ERR("usbd_msg_register_cb failed (%d)", err);
return err;
}
err = usb_descriptors_init();
if (err) {
LOG_ERR("usb descriptor init failed (%d)", err);
return err;
}
err = usbd_init(&blinky_usbd);
if (err) {
LOG_ERR("usbd_init failed (%d)", err);
return err;
}
usb_ctx.stack = USB_STACK_READY;
LOG_INF("USB init done (stack:%s bus:%s lc:%s public:%s)",
usb_stack_state_name(usb_ctx.stack),
usb_bus_state_name(usb_ctx.bus),
module_lifecycle_name(usb_ctx.lc.state),
usb_public_state_name(usb_public_state_get()));
return 0;
}
static int do_start(void)
{
int err;
LOG_INF("USB start requested (stack:%s bus:%s lc:%s)",
usb_stack_state_name(usb_ctx.stack),
usb_bus_state_name(usb_ctx.bus),
module_lifecycle_name(usb_ctx.lc.state));
if (usb_ctx.stack == USB_STACK_ENABLED) {
LOG_INF("USB start ignored: already enabled");
return 0;
}
if (!usbd_can_detect_vbus(&blinky_usbd)) {
err = usbd_enable(&blinky_usbd);
if (err) {
LOG_ERR("usbd_enable failed (%d)", err);
return err;
}
usb_ctx.stack = USB_STACK_ENABLED;
usb_ctx.bus = USB_BUS_POWERED;
LOG_INF("USB enabled without VBUS detect (stack:%s bus:%s)",
usb_stack_state_name(usb_ctx.stack),
usb_bus_state_name(usb_ctx.bus));
update_power_manager_restriction(true);
return 0;
}
if (usb_ctx.bus == USB_BUS_DISCONNECTED) {
LOG_INF("USB start deferred: waiting for VBUS");
return 0;
}
err = usbd_enable(&blinky_usbd);
if (err) {
LOG_ERR("usbd_enable failed (%d)", err);
return err;
}
usb_ctx.stack = USB_STACK_ENABLED;
LOG_INF("USB enabled (stack:%s bus:%s)", usb_stack_state_name(usb_ctx.stack),
usb_bus_state_name(usb_ctx.bus));
update_power_manager_restriction(true);
return 0;
}
static int do_stop(void)
{
int err;
LOG_INF("USB stop requested (stack:%s bus:%s lc:%s)",
usb_stack_state_name(usb_ctx.stack),
usb_bus_state_name(usb_ctx.bus),
module_lifecycle_name(usb_ctx.lc.state));
if (usb_ctx.stack == USB_STACK_ENABLED) {
err = usbd_disable(&blinky_usbd);
if (err) {
LOG_ERR("usbd_disable failed (%d)", err);
return err;
}
}
usb_ctx.stack = USB_STACK_READY;
LOG_INF("USB stopped (stack:%s bus:%s)", usb_stack_state_name(usb_ctx.stack),
usb_bus_state_name(usb_ctx.bus));
update_power_manager_restriction(false);
return 0;
}
static bool handle_module_state_event(const struct module_state_event *event)
{
if (!check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
return false;
}
if (module_set_lifecycle(&usb_ctx.lc, LC_RUNNING) == 0) {
submit_usb_state(usb_public_state_get());
}
return false;
}
static bool handle_module_suspend_req_event(
const struct module_suspend_req_event *event)
{
if ((event->sink_module_id != MODULE_ID(MODULE)) ||
(usb_ctx.lc.state != LC_RUNNING)) {
return false;
}
if (module_set_lifecycle(&usb_ctx.lc, LC_STOPPED) == 0) {
submit_usb_state(usb_public_state_get());
}
return false;
}
static bool handle_module_resume_req_event(
const struct module_resume_req_event *event)
{
if ((event->sink_module_id != MODULE_ID(MODULE)) ||
(usb_ctx.lc.state != LC_STOPPED)) {
return false;
}
if (module_set_lifecycle(&usb_ctx.lc, LC_RUNNING) == 0) {
submit_usb_state(usb_public_state_get());
}
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_module_suspend_req_event(aeh)) {
return handle_module_suspend_req_event(
cast_module_suspend_req_event(aeh));
}
if (is_module_resume_req_event(aeh)) {
return handle_module_resume_req_event(
cast_module_resume_req_event(aeh));
}
if (is_power_down_event(aeh)) {
if (module_lifecycle_is_initialized(&usb_ctx.lc)) {
if (module_set_lifecycle(&usb_ctx.lc, LC_STOPPED) == 0) {
submit_usb_state(usb_public_state_get());
}
}
return false;
}
if (is_wake_up_event(aeh)) {
if (module_lifecycle_is_initialized(&usb_ctx.lc)) {
if (module_set_lifecycle(&usb_ctx.lc, LC_RUNNING) == 0) {
submit_usb_state(usb_public_state_get());
}
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, module_suspend_req_event);
APP_EVENT_SUBSCRIBE(MODULE, module_resume_req_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

1
src/usb_function_hook.ld Normal file
View File

@@ -0,0 +1 @@
ITERABLE_SECTION_ROM(usb_function_hook, Z_LINK_ITERABLE_SUBALIGN)

View File

@@ -0,0 +1,315 @@
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <app_event_manager.h>
#define MODULE usb_hid_consumer_module
#include <caf/events/module_state_event.h>
#include <caf/events/power_event.h>
#include <zephyr/device.h>
#include <zephyr/drivers/usb/udc_buf.h>
#include <zephyr/logging/log.h>
#include <zephyr/usb/class/usbd_hid.h>
#include "hid_channel_state_event.h"
#include "hid_report_sent_event.h"
#include "hid_tx_report_event.h"
#include "keyboard_core.h"
#include "module_lifecycle.h"
#include "usb_function_hook.h"
#include "usb_state_event.h"
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
static const uint8_t consumer_report_desc[] = {
0x05, 0x0C, 0x09, 0x01, 0xA1, 0x01, 0x15, 0x00,
0x26, 0xFF, 0x03, 0x19, 0x00, 0x2A, 0xFF, 0x03,
0x75, 0x10, 0x95, 0x01, 0x81, 0x00, 0xC0
};
struct usb_hid_consumer_module_ctx {
struct module_lifecycle_ctx lc;
const struct device *hid_dev;
bool usb_active;
bool iface_ready;
bool report_in_flight;
uint16_t in_flight_sequence;
};
static int do_init(void);
static int do_start(void);
static int do_stop(void);
static const struct module_lifecycle_cfg lifecycle_cfg = {
.mode = ML_MODE_POWER,
.stopped_state = MODULE_STATE_STANDBY,
};
static const struct module_lifecycle_ops lifecycle_ops = {
.do_init = do_init,
.do_start = do_start,
.do_stop = do_stop,
};
static struct usb_hid_consumer_module_ctx ctx = {
.lc = {
.state = LC_UNINIT,
.cfg = &lifecycle_cfg,
.ops = &lifecycle_ops,
},
.hid_dev = DEVICE_DT_GET(DT_NODELABEL(hid_consumer)),
};
UDC_STATIC_BUF_DEFINE(consumer_tx_buf, KEYBOARD_CONSUMER_REPORT_SIZE);
static void publish_consumer_state(void)
{
bool ready = module_lifecycle_is_running(&ctx.lc) &&
ctx.usb_active && ctx.iface_ready;
submit_hid_channel_state_event(HID_SEND_CH_USB_CONSUMER,
ready ? BIT(KEYBOARD_REPORT_TYPE_CONSUMER) : 0U,
KEYBOARD_PROTOCOL_MODE_REPORT);
}
static void consumer_iface_ready(const struct device *dev, const bool ready)
{
ARG_UNUSED(dev);
ctx.iface_ready = ready;
if (!ready) {
ctx.report_in_flight = false;
}
LOG_INF("%s interface %s", ctx.hid_dev->name,
ready ? "ready" : "not ready");
publish_consumer_state();
}
static int consumer_get_report(const struct device *dev,
const uint8_t type, const uint8_t id,
const uint16_t len, uint8_t *const buf)
{
ARG_UNUSED(dev);
ARG_UNUSED(type);
ARG_UNUSED(id);
ARG_UNUSED(len);
ARG_UNUSED(buf);
return -ENOTSUP;
}
static int consumer_set_report(const struct device *dev,
const uint8_t type, const uint8_t id,
const uint16_t len, const uint8_t *const buf)
{
ARG_UNUSED(dev);
ARG_UNUSED(type);
ARG_UNUSED(id);
ARG_UNUSED(len);
ARG_UNUSED(buf);
return -ENOTSUP;
}
static void consumer_set_idle(const struct device *dev,
const uint8_t id, const uint32_t duration)
{
ARG_UNUSED(dev);
ARG_UNUSED(id);
ARG_UNUSED(duration);
}
static uint32_t consumer_get_idle(const struct device *dev, const uint8_t id)
{
ARG_UNUSED(dev);
ARG_UNUSED(id);
return 0U;
}
static void consumer_set_protocol(const struct device *dev, const uint8_t proto)
{
ARG_UNUSED(dev);
ARG_UNUSED(proto);
}
static void consumer_input_report_done(const struct device *dev,
const uint8_t *const report)
{
ARG_UNUSED(dev);
ARG_UNUSED(report);
ctx.report_in_flight = false;
submit_hid_report_sent_event(HID_SEND_CH_USB_CONSUMER,
KEYBOARD_REPORT_TYPE_CONSUMER,
ctx.in_flight_sequence, false);
}
static void consumer_output_report(const struct device *dev,
const uint16_t len,
const uint8_t *const buf)
{
ARG_UNUSED(dev);
ARG_UNUSED(len);
ARG_UNUSED(buf);
}
static const struct hid_device_ops consumer_ops = {
.iface_ready = consumer_iface_ready,
.get_report = consumer_get_report,
.set_report = consumer_set_report,
.set_idle = consumer_set_idle,
.get_idle = consumer_get_idle,
.set_protocol = consumer_set_protocol,
.input_report_done = consumer_input_report_done,
.output_report = consumer_output_report,
};
static int usb_hid_consumer_register_device(void)
{
if (!device_is_ready(ctx.hid_dev)) {
LOG_ERR("HID device %s not ready", ctx.hid_dev->name);
return -ENODEV;
}
return hid_device_register(ctx.hid_dev, consumer_report_desc,
sizeof(consumer_report_desc), &consumer_ops);
}
USB_FUNCTION_HOOK_DEFINE(usb_hid_consumer_hook, usb_hid_consumer_register_device);
static int do_init(void)
{
ctx.usb_active = false;
ctx.iface_ready = false;
ctx.report_in_flight = false;
return 0;
}
static int do_start(void)
{
if (module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
publish_consumer_state();
return 0;
}
static int do_stop(void)
{
if (!module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
ctx.report_in_flight = false;
publish_consumer_state();
return 0;
}
static bool handle_usb_state_event(const struct usb_state_event *event)
{
bool new_usb_active = (event->state == USB_STATE_ACTIVE);
if (new_usb_active == ctx.usb_active) {
return false;
}
ctx.usb_active = new_usb_active;
if (!ctx.usb_active) {
ctx.iface_ready = false;
ctx.report_in_flight = false;
}
publish_consumer_state();
return false;
}
static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event)
{
int err;
if (!module_lifecycle_is_running(&ctx.lc) || !ctx.usb_active ||
(event->channel != HID_SEND_CH_USB_CONSUMER)) {
return false;
}
if (event->report_type != KEYBOARD_REPORT_TYPE_CONSUMER) {
return false;
}
if (!ctx.iface_ready) {
return false;
}
if (ctx.report_in_flight) {
LOG_WRN("Drop USB consumer report while previous report is in flight");
return false;
}
memcpy(consumer_tx_buf, event->dyndata.data, event->dyndata.size);
err = hid_device_submit_report(ctx.hid_dev, (uint16_t)event->dyndata.size,
consumer_tx_buf);
if (err) {
LOG_WRN("USB consumer report submit failed (%d)", err);
submit_hid_report_sent_event(HID_SEND_CH_USB_CONSUMER,
KEYBOARD_REPORT_TYPE_CONSUMER,
event->sequence, true);
} else {
ctx.report_in_flight = true;
ctx.in_flight_sequence = event->sequence;
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_hid_tx_report_event(aeh)) {
return handle_hid_tx_report_event(cast_hid_tx_report_event(aeh));
}
if (is_usb_state_event(aeh)) {
return handle_usb_state_event(cast_usb_state_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)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
if (is_power_down_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
}
return false;
}
if (is_wake_up_event(aeh)) {
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_tx_report_event);
APP_EVENT_SUBSCRIBE(MODULE, usb_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

Some files were not shown because too many files have changed in this diff Show More