Initial import of firmware and host projects

This commit is contained in:
2026-04-10 16:53:41 +08:00
commit 42a36164be
124 changed files with 13943 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
#include "battery_status_event.h"
static void log_battery_status_event(const struct app_event_header *aeh)
{
const struct battery_status_event *event = cast_battery_status_event(aeh);
APP_EVENT_MANAGER_LOG(aeh,
"flags=0x%02x charging=%u full=%u soc=%u",
event->flags,
BATTERY_STATUS_IS_CHARGING(event->flags),
BATTERY_STATUS_IS_FULL(event->flags),
event->soc);
}
static void profile_battery_status_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct battery_status_event *event = cast_battery_status_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->flags);
nrf_profiler_log_encode_uint8(buf, event->soc);
}
APP_EVENT_INFO_DEFINE(battery_status_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("flags", "soc"),
profile_battery_status_event);
APP_EVENT_TYPE_DEFINE(battery_status_event,
log_battery_status_event,
&battery_status_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,61 @@
#ifndef BATTERY_STATUS_EVENT_H
#define BATTERY_STATUS_EVENT_H
#include <stdbool.h>
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
struct battery_status_event
{
struct app_event_header header;
uint8_t flags;
uint8_t soc;
};
#define BATTERY_STATUS_FLAG_CHARGING (1U << 0)
#define BATTERY_STATUS_FLAG_FULL (1U << 1)
#define BATTERY_STATUS_IS_CHARGING(flags) (((flags) & BATTERY_STATUS_FLAG_CHARGING) != 0U)
#define BATTERY_STATUS_IS_FULL(flags) (((flags) & BATTERY_STATUS_FLAG_FULL) != 0U)
APP_EVENT_TYPE_DECLARE(battery_status_event);
static inline void battery_status_event_submit(bool charging, bool full, uint8_t soc)
{
struct battery_status_event *event = new_battery_status_event();
event->flags = 0U;
if (charging) {
event->flags |= BATTERY_STATUS_FLAG_CHARGING;
}
if (full) {
event->flags |= BATTERY_STATUS_FLAG_FULL;
}
event->soc = soc;
APP_EVENT_SUBMIT(event);
}
static inline uint8_t battery_status_event_get_flags(const struct battery_status_event *event)
{
return event->flags;
}
static inline bool battery_status_event_is_charging(const struct battery_status_event *event)
{
return BATTERY_STATUS_IS_CHARGING(event->flags);
}
static inline bool battery_status_event_is_full(const struct battery_status_event *event)
{
return BATTERY_STATUS_IS_FULL(event->flags);
}
static inline uint8_t battery_status_event_get_soc(const struct battery_status_event *event)
{
return event->soc;
}
#endif

18
src/events/config_event.c Normal file
View File

@@ -0,0 +1,18 @@
#include "config_event.h"
static void log_config_event(const struct app_event_header *aeh)
{
const struct config_event *event = cast_config_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "status:%u %s rcpt:%02x id:%02x",
event->status,
event->is_request ? "req" : "rsp",
event->recipient,
event->event_id);
}
APP_EVENT_TYPE_DEFINE(config_event,
log_config_event,
NULL,
APP_EVENT_FLAGS_CREATE(
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

54
src/events/config_event.h Normal file
View File

@@ -0,0 +1,54 @@
/*
* Lightweight config event used for local module configuration.
*/
#ifndef NEW_KBD_CONFIG_EVENT_H__
#define NEW_KBD_CONFIG_EVENT_H__
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Keep the same local recipient as nRF Desktop config channel. */
#define CFG_CHAN_RECIPIENT_LOCAL 0x00
/* Event ID field layout (compatible with nRF Desktop config_event encoding). */
#define MOD_FIELD_POS 4
#define MOD_FIELD_SIZE 4
#define MOD_FIELD_MASK BIT_MASK(MOD_FIELD_SIZE)
#define MOD_FIELD_GET(id) (((id) >> MOD_FIELD_POS) & MOD_FIELD_MASK)
#define OPT_FIELD_POS 0
#define OPT_FIELD_SIZE 4
#define OPT_FIELD_MASK BIT_MASK(OPT_FIELD_SIZE)
#define OPT_FIELD_GET(id) (((id) >> OPT_FIELD_POS) & OPT_FIELD_MASK)
#define OPT_ID_GET(opt) ((opt) - 1U)
enum config_status {
CONFIG_STATUS_SET = 0,
CONFIG_STATUS_FETCH,
CONFIG_STATUS_SUCCESS,
CONFIG_STATUS_REJECT,
};
struct config_event {
struct app_event_header header;
uint16_t transport_id;
bool is_request;
uint8_t event_id;
uint8_t recipient;
uint8_t status;
struct event_dyndata dyndata;
};
APP_EVENT_TYPE_DYNDATA_DECLARE(config_event);
#ifdef __cplusplus
}
#endif
#endif /* NEW_KBD_CONFIG_EVENT_H__ */

View File

@@ -0,0 +1,31 @@
#include "display_theme_event.h"
static void log_display_theme_event(const struct app_event_header *aeh)
{
const struct display_theme_event *event = cast_display_theme_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "rgb=(%u,%u,%u)",
event->red, event->green, event->blue);
}
static void profile_display_theme_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct display_theme_event *event = cast_display_theme_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->red);
nrf_profiler_log_encode_uint8(buf, event->green);
nrf_profiler_log_encode_uint8(buf, event->blue);
}
APP_EVENT_INFO_DEFINE(display_theme_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("red", "green", "blue"),
profile_display_theme_event);
APP_EVENT_TYPE_DEFINE(display_theme_event,
log_display_theme_event,
&display_theme_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,30 @@
#ifndef DISPLAY_THEME_EVENT_H__
#define DISPLAY_THEME_EVENT_H__
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
struct display_theme_event {
struct app_event_header header;
uint8_t red;
uint8_t green;
uint8_t blue;
};
APP_EVENT_TYPE_DECLARE(display_theme_event);
static inline void display_theme_event_submit(uint8_t red,
uint8_t green,
uint8_t blue)
{
struct display_theme_event *event = new_display_theme_event();
event->red = red;
event->green = green;
event->blue = blue;
APP_EVENT_SUBMIT(event);
}
#endif /* DISPLAY_THEME_EVENT_H__ */

View File

@@ -0,0 +1,26 @@
#include "hid_boot_event.h"
static void log_hid_boot_event(const struct app_event_header *aeh)
{
const struct hid_boot_event *event = cast_hid_boot_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "payload_len=%u", event->dyndata.size);
}
static void profile_hid_boot_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_boot_event *event = cast_hid_boot_event(aeh);
nrf_profiler_log_encode_uint16(buf, event->dyndata.size);
}
APP_EVENT_INFO_DEFINE(hid_boot_event,
ENCODE(NRF_PROFILER_ARG_U16),
ENCODE("len"),
profile_hid_boot_event);
APP_EVENT_TYPE_DEFINE(hid_boot_event,
log_hid_boot_event,
&hid_boot_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,45 @@
#ifndef HID_BOOT_EVENT_H__
#define HID_BOOT_EVENT_H__
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
/*
* HID Boot 输入报告事件:
* - dyndata 仅包含 boot keyboard payload
* - 不包含 report_id消费方固定按 boot keyboard 语义处理。
*/
struct hid_boot_event {
struct app_event_header header;
struct event_dyndata dyndata;
};
APP_EVENT_TYPE_DYNDATA_DECLARE(hid_boot_event);
static inline void hid_boot_event_submit(const uint8_t *data, size_t size)
{
struct hid_boot_event *event = new_hid_boot_event(size);
if ((size > 0U) && (data != NULL)) {
memcpy(event->dyndata.data, data, size);
}
APP_EVENT_SUBMIT(event);
}
static inline const uint8_t *hid_boot_event_get_data(const struct hid_boot_event *event)
{
return event->dyndata.data;
}
static inline size_t hid_boot_event_get_size(const struct hid_boot_event *event)
{
return event->dyndata.size;
}
#endif /* HID_BOOT_EVENT_H__ */

View File

@@ -0,0 +1,29 @@
#include "hid_host_ack_event.h"
static void log_hid_host_ack_event(const struct app_event_header *aeh)
{
const struct hid_host_ack_event *event = cast_hid_host_ack_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "transport=%u cmd=0x%02x",
event->transport, event->cmd);
}
static void profile_hid_host_ack_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_host_ack_event *event = cast_hid_host_ack_event(aeh);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->transport);
nrf_profiler_log_encode_uint8(buf, event->cmd);
}
APP_EVENT_INFO_DEFINE(hid_host_ack_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("transport", "cmd"),
profile_hid_host_ack_event);
APP_EVENT_TYPE_DEFINE(hid_host_ack_event,
log_hid_host_ack_event,
&hid_host_ack_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,29 @@
#ifndef HID_HOST_ACK_EVENT_H__
#define HID_HOST_ACK_EVENT_H__
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "hid_host_transport.h"
struct hid_host_ack_event {
struct app_event_header header;
enum hid_host_transport transport;
uint8_t cmd;
};
APP_EVENT_TYPE_DECLARE(hid_host_ack_event);
static inline void hid_host_ack_event_submit(enum hid_host_transport transport,
uint8_t cmd)
{
struct hid_host_ack_event *event = new_hid_host_ack_event();
event->transport = transport;
event->cmd = cmd;
APP_EVENT_SUBMIT(event);
}
#endif /* HID_HOST_ACK_EVENT_H__ */

View File

@@ -0,0 +1,36 @@
#include "hid_host_command_error_event.h"
static void log_hid_host_command_error_event(const struct app_event_header *aeh)
{
const struct hid_host_command_error_event *event =
cast_hid_host_command_error_event(aeh);
APP_EVENT_MANAGER_LOG(aeh,
"transport=%u cmd=0x%02x reason=%u",
event->transport,
event->cmd,
event->reason);
}
static void profile_hid_host_command_error_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_host_command_error_event *event =
cast_hid_host_command_error_event(aeh);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->transport);
nrf_profiler_log_encode_uint8(buf, event->cmd);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->reason);
}
APP_EVENT_INFO_DEFINE(hid_host_command_error_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("transport", "cmd", "reason"),
profile_hid_host_command_error_event);
APP_EVENT_TYPE_DEFINE(hid_host_command_error_event,
log_hid_host_command_error_event,
&hid_host_command_error_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,41 @@
#ifndef HID_HOST_COMMAND_ERROR_EVENT_H__
#define HID_HOST_COMMAND_ERROR_EVENT_H__
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "hid_host_transport.h"
enum hid_host_command_error_reason {
HID_HOST_COMMAND_ERROR_UNKNOWN_CMD = 0,
HID_HOST_COMMAND_ERROR_INVALID_LENGTH,
HID_HOST_COMMAND_ERROR_INVALID_PARAM,
HID_HOST_COMMAND_ERROR_NOT_READY,
};
struct hid_host_command_error_event {
struct app_event_header header;
enum hid_host_transport transport;
enum hid_host_command_error_reason reason;
uint8_t cmd;
};
APP_EVENT_TYPE_DECLARE(hid_host_command_error_event);
static inline void hid_host_command_error_event_submit(
enum hid_host_transport transport,
uint8_t cmd,
enum hid_host_command_error_reason reason)
{
struct hid_host_command_error_event *event =
new_hid_host_command_error_event();
event->transport = transport;
event->cmd = cmd;
event->reason = reason;
APP_EVENT_SUBMIT(event);
}
#endif /* HID_HOST_COMMAND_ERROR_EVENT_H__ */

View File

@@ -0,0 +1,47 @@
#include "hid_host_command_event.h"
static const char *transport_name(enum hid_host_transport transport)
{
switch (transport) {
case HID_HOST_TRANSPORT_USB:
return "usb";
case HID_HOST_TRANSPORT_BLE:
return "ble";
default:
return "unknown";
}
}
static void log_hid_host_command_event(const struct app_event_header *aeh)
{
const struct hid_host_command_event *event =
cast_hid_host_command_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "transport=%s cmd=0x%02x data_len=%u",
transport_name(event->transport),
event->cmd,
event->data_len);
}
static void profile_hid_host_command_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_host_command_event *event =
cast_hid_host_command_event(aeh);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->transport);
nrf_profiler_log_encode_uint8(buf, event->cmd);
nrf_profiler_log_encode_uint8(buf, event->data_len);
}
APP_EVENT_INFO_DEFINE(hid_host_command_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("transport", "cmd", "data_len"),
profile_hid_host_command_event);
APP_EVENT_TYPE_DEFINE(hid_host_command_event,
log_hid_host_command_event,
&hid_host_command_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,44 @@
#ifndef HID_HOST_COMMAND_EVENT_H__
#define HID_HOST_COMMAND_EVENT_H__
#include <stdint.h>
#include <string.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include <zephyr/sys/util.h>
#include "hid_host_command_protocol.h"
#include "hid_host_transport.h"
struct hid_host_command_event {
struct app_event_header header;
enum hid_host_transport transport;
uint8_t cmd;
uint8_t data_len;
uint8_t data[HID_HOST_CMD_DATA_SIZE];
};
APP_EVENT_TYPE_DECLARE(hid_host_command_event);
static inline void hid_host_command_event_submit(enum hid_host_transport transport,
uint8_t cmd,
const uint8_t *data,
size_t data_len)
{
struct hid_host_command_event *event = new_hid_host_command_event();
size_t copy_len = MIN(data_len, (size_t)HID_HOST_CMD_DATA_SIZE);
event->transport = transport;
event->cmd = cmd;
event->data_len = (uint8_t)copy_len;
memset(event->data, 0, sizeof(event->data));
if ((copy_len > 0U) && (data != NULL)) {
memcpy(event->data, data, copy_len);
}
APP_EVENT_SUBMIT(event);
}
#endif /* HID_HOST_COMMAND_EVENT_H__ */

View File

@@ -0,0 +1,34 @@
#include "hid_protocol_event.h"
static const char *const hid_protocol_name[] = {
[HID_PROTO_BOOT] = "BOOT",
[HID_PROTO_REPORT] = "REPORT",
};
static void log_hid_protocol_event(const struct app_event_header *aeh)
{
const struct hid_protocol_event *event = cast_hid_protocol_event(aeh);
__ASSERT_NO_MSG(event->protocol < ARRAY_SIZE(hid_protocol_name));
APP_EVENT_MANAGER_LOG(aeh, "protocol=%s",
hid_protocol_name[event->protocol]);
}
static void profile_hid_protocol_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_protocol_event *event = cast_hid_protocol_event(aeh);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->protocol);
}
APP_EVENT_INFO_DEFINE(hid_protocol_event,
ENCODE(NRF_PROFILER_ARG_U8),
ENCODE("protocol"),
profile_hid_protocol_event);
APP_EVENT_TYPE_DEFINE(hid_protocol_event,
log_hid_protocol_event,
&hid_protocol_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,49 @@
#ifndef HID_PROTOCOL_EVENT_H__
#define HID_PROTOCOL_EVENT_H__
#include <stdbool.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
enum hid_protocol_type {
HID_PROTO_BOOT = 0,
HID_PROTO_REPORT,
};
/*
* HID 传输层在收到主机 set_protocol 请求后上报该事件。
*/
struct hid_protocol_event {
struct app_event_header header;
enum hid_protocol_type protocol;
};
APP_EVENT_TYPE_DECLARE(hid_protocol_event);
static inline void hid_protocol_event_submit(enum hid_protocol_type protocol)
{
struct hid_protocol_event *event = new_hid_protocol_event();
event->protocol = protocol;
APP_EVENT_SUBMIT(event);
}
static inline enum hid_protocol_type hid_protocol_event_get_protocol(
const struct hid_protocol_event *event)
{
return event->protocol;
}
static inline bool hid_protocol_event_is_boot_protocol(const struct hid_protocol_event *event)
{
return event->protocol == HID_PROTO_BOOT;
}
static inline bool hid_protocol_event_is_report_protocol(const struct hid_protocol_event *event)
{
return event->protocol == HID_PROTO_REPORT;
}
#endif /* HID_PROTOCOL_EVENT_H__ */

View File

@@ -0,0 +1,42 @@
#include "hid_report_event.h"
static void log_hid_report_event(const struct app_event_header *aeh)
{
const struct hid_report_event *event = cast_hid_report_event(aeh);
uint8_t report_id = 0x00;
uint16_t payload_len = 0U;
if (event->dyndata.size >= 1U) {
report_id = event->dyndata.data[0];
payload_len = event->dyndata.size - 1U;
}
APP_EVENT_MANAGER_LOG(aeh, "report_id=0x%02x payload_len=%u",
report_id,
payload_len);
}
static void profile_hid_report_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_report_event *event = cast_hid_report_event(aeh);
uint8_t report_id = 0x00;
if (event->dyndata.size >= 1U) {
report_id = event->dyndata.data[0];
}
nrf_profiler_log_encode_uint8(buf, report_id);
nrf_profiler_log_encode_uint16(buf, event->dyndata.size);
}
APP_EVENT_INFO_DEFINE(hid_report_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U16),
ENCODE("report_id", "len"),
profile_hid_report_event);
APP_EVENT_TYPE_DEFINE(hid_report_event,
log_hid_report_event,
&hid_report_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,45 @@
#ifndef HID_REPORT_EVENT_H__
#define HID_REPORT_EVENT_H__
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
/*
* HID Report 输入报告事件:
* - dyndata 编码为 [report_id | payload]
* - 仅承载 Report protocol 报文。
*/
struct hid_report_event {
struct app_event_header header;
struct event_dyndata dyndata;
};
APP_EVENT_TYPE_DYNDATA_DECLARE(hid_report_event);
static inline void hid_report_event_submit(const uint8_t *data, size_t size)
{
struct hid_report_event *event = new_hid_report_event(size);
if ((size > 0U) && (data != NULL)) {
memcpy(event->dyndata.data, data, size);
}
APP_EVENT_SUBMIT(event);
}
static inline const uint8_t *hid_report_event_get_data(const struct hid_report_event *event)
{
return event->dyndata.data;
}
static inline size_t hid_report_event_get_size(const struct hid_report_event *event)
{
return event->dyndata.size;
}
#endif /* HID_REPORT_EVENT_H__ */

View File

@@ -0,0 +1,29 @@
#include "hid_tx_done_event.h"
static void log_hid_tx_done_event(const struct app_event_header *aeh)
{
const struct hid_tx_done_event *event = cast_hid_tx_done_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "kind=%u success=%u",
event->kind, event->success);
}
static void profile_hid_tx_done_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_tx_done_event *event = cast_hid_tx_done_event(aeh);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->kind);
nrf_profiler_log_encode_uint8(buf, event->success ? 1U : 0U);
}
APP_EVENT_INFO_DEFINE(hid_tx_done_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("kind", "success"),
profile_hid_tx_done_event);
APP_EVENT_TYPE_DEFINE(hid_tx_done_event,
log_hid_tx_done_event,
&hid_tx_done_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,28 @@
#ifndef HID_TX_DONE_EVENT_H__
#define HID_TX_DONE_EVENT_H__
#include <stdbool.h>
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "hid_tx_event.h"
struct hid_tx_done_event {
struct app_event_header header;
enum hid_tx_kind kind;
bool success;
};
APP_EVENT_TYPE_DECLARE(hid_tx_done_event);
static inline void hid_tx_done_event_submit(enum hid_tx_kind kind, bool success)
{
struct hid_tx_done_event *event = new_hid_tx_done_event();
event->kind = kind;
event->success = success;
APP_EVENT_SUBMIT(event);
}
#endif /* HID_TX_DONE_EVENT_H__ */

49
src/events/hid_tx_event.c Normal file
View File

@@ -0,0 +1,49 @@
#include "hid_tx_event.h"
static void log_hid_tx_event(const struct app_event_header *aeh)
{
const struct hid_tx_event *event = cast_hid_tx_event(aeh);
uint8_t report_id = 0x00;
uint16_t payload_len = event->dyndata.size;
if ((event->kind == HID_TX_KIND_REPORT) && (event->dyndata.size >= 1U)) {
report_id = event->dyndata.data[0];
payload_len = event->dyndata.size - 1U;
}
APP_EVENT_MANAGER_LOG(aeh,
"kind=%u route=%u report_id=0x%02x payload_len=%u",
event->kind,
event->route,
report_id,
payload_len);
}
static void profile_hid_tx_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_tx_event *event = cast_hid_tx_event(aeh);
uint8_t report_id = 0x00;
if ((event->kind == HID_TX_KIND_REPORT) && (event->dyndata.size >= 1U)) {
report_id = event->dyndata.data[0];
}
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->kind);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->route);
nrf_profiler_log_encode_uint8(buf, report_id);
nrf_profiler_log_encode_uint16(buf, event->dyndata.size);
}
APP_EVENT_INFO_DEFINE(hid_tx_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U16),
ENCODE("kind", "route", "report_id", "len"),
profile_hid_tx_event);
APP_EVENT_TYPE_DEFINE(hid_tx_event,
log_hid_tx_event,
&hid_tx_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

69
src/events/hid_tx_event.h Normal file
View File

@@ -0,0 +1,69 @@
#ifndef HID_TX_EVENT_H__
#define HID_TX_EVENT_H__
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
enum hid_tx_kind {
HID_TX_KIND_BOOT = 0,
HID_TX_KIND_REPORT,
};
enum hid_tx_route {
HID_TX_ROUTE_AUTO = 0,
HID_TX_ROUTE_USB,
HID_TX_ROUTE_BLE,
};
struct hid_tx_event {
struct app_event_header header;
enum hid_tx_kind kind;
enum hid_tx_route route;
struct event_dyndata dyndata;
};
APP_EVENT_TYPE_DYNDATA_DECLARE(hid_tx_event);
static inline void hid_tx_event_submit_routed(enum hid_tx_kind kind,
enum hid_tx_route route,
const uint8_t *data,
size_t size)
{
struct hid_tx_event *event = new_hid_tx_event(size);
event->kind = kind;
event->route = route;
if ((size > 0U) && (data != NULL)) {
memcpy(event->dyndata.data, data, size);
}
APP_EVENT_SUBMIT(event);
}
static inline void hid_tx_event_submit(enum hid_tx_kind kind,
const uint8_t *data,
size_t size)
{
hid_tx_event_submit_routed(kind, HID_TX_ROUTE_AUTO, data, size);
}
static inline const uint8_t *hid_tx_event_get_data(const struct hid_tx_event *event)
{
return event->dyndata.data;
}
static inline size_t hid_tx_event_get_size(const struct hid_tx_event *event)
{
return event->dyndata.size;
}
static inline enum hid_tx_route hid_tx_event_get_route(const struct hid_tx_event *event)
{
return event->route;
}
#endif /* HID_TX_EVENT_H__ */

View File

@@ -0,0 +1,26 @@
#include "hid_vendor_mask_event.h"
static void log_hid_vendor_mask_event(const struct app_event_header *aeh)
{
const struct hid_vendor_mask_event *event = cast_hid_vendor_mask_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "len=%u", event->dyndata.size);
}
static void profile_hid_vendor_mask_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_vendor_mask_event *event = cast_hid_vendor_mask_event(aeh);
nrf_profiler_log_encode_uint16(buf, event->dyndata.size);
}
APP_EVENT_INFO_DEFINE(hid_vendor_mask_event,
ENCODE(NRF_PROFILER_ARG_U16),
ENCODE("len"),
profile_hid_vendor_mask_event);
APP_EVENT_TYPE_DEFINE(hid_vendor_mask_event,
log_hid_vendor_mask_event,
&hid_vendor_mask_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,39 @@
#ifndef HID_VENDOR_MASK_EVENT_H__
#define HID_VENDOR_MASK_EVENT_H__
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
struct hid_vendor_mask_event {
struct app_event_header header;
struct event_dyndata dyndata;
};
APP_EVENT_TYPE_DYNDATA_DECLARE(hid_vendor_mask_event);
static inline void hid_vendor_mask_event_submit(const uint8_t *data, size_t size)
{
struct hid_vendor_mask_event *event = new_hid_vendor_mask_event(size);
if ((size > 0U) && (data != NULL)) {
memcpy(event->dyndata.data, data, size);
}
APP_EVENT_SUBMIT(event);
}
static inline const uint8_t *hid_vendor_mask_event_get_data(const struct hid_vendor_mask_event *event)
{
return event->dyndata.data;
}
static inline size_t hid_vendor_mask_event_get_size(const struct hid_vendor_mask_event *event)
{
return event->dyndata.size;
}
#endif /* HID_VENDOR_MASK_EVENT_H__ */

View File

@@ -0,0 +1,28 @@
#include "keyboard_led_event.h"
static void log_keyboard_led_event(const struct app_event_header *aeh)
{
const struct keyboard_led_event *event =
cast_keyboard_led_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "mask=0x%02x", event->led_mask);
}
static void profile_keyboard_led_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct keyboard_led_event *event =
cast_keyboard_led_event(aeh);
nrf_profiler_log_encode_uint8(buf, event->led_mask);
}
APP_EVENT_INFO_DEFINE(keyboard_led_event,
ENCODE(NRF_PROFILER_ARG_U8),
ENCODE("led_mask"),
profile_keyboard_led_event);
APP_EVENT_TYPE_DEFINE(keyboard_led_event,
log_keyboard_led_event,
&keyboard_led_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,73 @@
#ifndef KEYBOARD_LED_EVENT_H__
#define KEYBOARD_LED_EVENT_H__
#include <stdbool.h>
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
/*
* 键盘 LED 事件:
* - 由 USB/BLE HID 接收主机输出报告后上报;
* - 仅保留主机下发的原始 LED mask消费方按需解码。
*/
struct keyboard_led_event {
struct app_event_header header;
uint8_t led_mask;
};
#define KEYBOARD_LED_MASK_NUM_LOCK (1U << 0)
#define KEYBOARD_LED_MASK_CAPS_LOCK (1U << 1)
#define KEYBOARD_LED_MASK_SCROLL_LOCK (1U << 2)
#define KEYBOARD_LED_MASK_COMPOSE (1U << 3)
#define KEYBOARD_LED_MASK_KANA (1U << 4)
#define KEYBOARD_LED_NUM_LOCK(mask) (((mask) & KEYBOARD_LED_MASK_NUM_LOCK) != 0U)
#define KEYBOARD_LED_CAPS_LOCK(mask) (((mask) & KEYBOARD_LED_MASK_CAPS_LOCK) != 0U)
#define KEYBOARD_LED_SCROLL_LOCK(mask) (((mask) & KEYBOARD_LED_MASK_SCROLL_LOCK) != 0U)
#define KEYBOARD_LED_COMPOSE(mask) (((mask) & KEYBOARD_LED_MASK_COMPOSE) != 0U)
#define KEYBOARD_LED_KANA(mask) (((mask) & KEYBOARD_LED_MASK_KANA) != 0U)
APP_EVENT_TYPE_DECLARE(keyboard_led_event);
static inline void keyboard_led_event_submit(uint8_t led_mask)
{
struct keyboard_led_event *event = new_keyboard_led_event();
event->led_mask = led_mask;
APP_EVENT_SUBMIT(event);
}
static inline uint8_t keyboard_led_event_get_mask(const struct keyboard_led_event *event)
{
return event->led_mask;
}
static inline bool keyboard_led_event_is_num_lock_on(const struct keyboard_led_event *event)
{
return KEYBOARD_LED_NUM_LOCK(event->led_mask);
}
static inline bool keyboard_led_event_is_caps_lock_on(const struct keyboard_led_event *event)
{
return KEYBOARD_LED_CAPS_LOCK(event->led_mask);
}
static inline bool keyboard_led_event_is_scroll_lock_on(const struct keyboard_led_event *event)
{
return KEYBOARD_LED_SCROLL_LOCK(event->led_mask);
}
static inline bool keyboard_led_event_is_compose_on(const struct keyboard_led_event *event)
{
return KEYBOARD_LED_COMPOSE(event->led_mask);
}
static inline bool keyboard_led_event_is_kana_on(const struct keyboard_led_event *event)
{
return KEYBOARD_LED_KANA(event->led_mask);
}
#endif /* KEYBOARD_LED_EVENT_H__ */

34
src/events/mode_event.c Normal file
View File

@@ -0,0 +1,34 @@
#include "mode_event.h"
static const char *const mode_name[] = {
[MODE_TYPE_USB] = "USB",
[MODE_TYPE_BLE] = "BLE",
[MODE_TYPE_2G4] = "2.4G",
};
static void log_mode_event(const struct app_event_header *aeh)
{
const struct mode_event *event = cast_mode_event(aeh);
__ASSERT_NO_MSG(event->mode_type < MODE_TYPE_COUNT);
APP_EVENT_MANAGER_LOG(aeh, "mode=%s(%u)", mode_name[event->mode_type], event->mode_type);
}
static void profile_mode_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct mode_event *event = cast_mode_event(aeh);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->mode_type);
}
APP_EVENT_INFO_DEFINE(mode_event,
ENCODE(NRF_PROFILER_ARG_U8),
ENCODE("mode"),
profile_mode_event);
APP_EVENT_TYPE_DEFINE(mode_event,
log_mode_event,
&mode_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

53
src/events/mode_event.h Normal file
View File

@@ -0,0 +1,53 @@
#ifndef MODE_EVENT_H
#define MODE_EVENT_H
#include <stdbool.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
typedef enum
{
MODE_TYPE_USB,
MODE_TYPE_BLE,
MODE_TYPE_2G4,
MODE_TYPE_COUNT,
} mode_type_t;
struct mode_event
{
struct app_event_header header;
mode_type_t mode_type;
};
APP_EVENT_TYPE_DECLARE(mode_event);
static inline void mode_event_submit(mode_type_t mode)
{
struct mode_event *event = new_mode_event();
event->mode_type = mode;
APP_EVENT_SUBMIT(event);
}
static inline mode_type_t mode_event_get_mode(const struct mode_event *event)
{
return event->mode_type;
}
static inline bool mode_event_is_usb(const struct mode_event *event)
{
return event->mode_type == MODE_TYPE_USB;
}
static inline bool mode_event_is_ble(const struct mode_event *event)
{
return event->mode_type == MODE_TYPE_BLE;
}
static inline bool mode_event_is_2g4(const struct mode_event *event)
{
return event->mode_type == MODE_TYPE_2G4;
}
#endif

View File

@@ -0,0 +1,26 @@
#include "qdec_step_event.h"
static void log_qdec_step_event(const struct app_event_header *aeh)
{
const struct qdec_step_event *event = cast_qdec_step_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "step=%d", event->step);
}
static void profile_qdec_step_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct qdec_step_event *event = cast_qdec_step_event(aeh);
nrf_profiler_log_encode_int8(buf, event->step);
}
APP_EVENT_INFO_DEFINE(qdec_step_event,
ENCODE(NRF_PROFILER_ARG_S8),
ENCODE("step"),
profile_qdec_step_event);
APP_EVENT_TYPE_DEFINE(qdec_step_event,
log_qdec_step_event,
&qdec_step_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,36 @@
#ifndef QDEC_STEP_EVENT_H__
#define QDEC_STEP_EVENT_H__
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
struct qdec_step_event {
struct app_event_header header;
int8_t step;
};
APP_EVENT_TYPE_DECLARE(qdec_step_event);
static inline void qdec_step_event_submit(int8_t step)
{
struct qdec_step_event *event;
__ASSERT((step == 1) || (step == -1), "qdec step event must be +/-1");
if (step == 0) {
return;
}
event = new_qdec_step_event();
event->step = step;
APP_EVENT_SUBMIT(event);
}
static inline int8_t qdec_step_event_get_step(const struct qdec_step_event *event)
{
return event->step;
}
#endif /* QDEC_STEP_EVENT_H__ */

View File

@@ -0,0 +1,39 @@
#include "time_sync_event.h"
/* 统一输出来源字符串,便于日志快速确认是哪条链路在校时。 */
static const char *time_sync_source_name(enum time_sync_source source)
{
switch (source) {
case TIME_SYNC_SOURCE_NONE:
return "none";
case TIME_SYNC_SOURCE_BLE:
return "ble";
case TIME_SYNC_SOURCE_USB:
return "usb";
case TIME_SYNC_SOURCE_MANUAL:
return "manual";
case TIME_SYNC_SOURCE_HID:
return "hid";
default:
return "unknown";
}
}
/* 事件日志聚焦关键信息:来源、时区和 UTC 毫秒时间戳。 */
static void log_time_sync_event(const struct app_event_header *aeh)
{
const struct time_sync_event *event = cast_time_sync_event(aeh);
const struct time_sync_update *update = time_sync_event_get_update(event);
APP_EVENT_MANAGER_LOG(aeh,
"src=%s tz=%d utc_ms=%llu acc=%u",
time_sync_source_name(update->source),
update->timezone_min,
(unsigned long long)update->utc_ms,
update->accuracy_ms);
}
APP_EVENT_TYPE_DEFINE(time_sync_event,
log_time_sync_event,
NULL,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,35 @@
#ifndef TIME_SYNC_EVENT_H__
#define TIME_SYNC_EVENT_H__
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "time_manager.h"
/*
* time_sync_event 是“同步入口 -> time_manager”的统一事件
* - BLE/USB/本地设置都提交同一类事件;
* - time_manager 是唯一消费者,也是唯一能修改运行时钟状态的模块。
*/
struct time_sync_event {
struct app_event_header header;
struct time_sync_update update;
};
APP_EVENT_TYPE_DECLARE(time_sync_event);
static inline void time_sync_event_submit(const struct time_sync_update *update)
{
struct time_sync_event *event = new_time_sync_event();
event->update = *update;
APP_EVENT_SUBMIT(event);
}
static inline const struct time_sync_update *time_sync_event_get_update(
const struct time_sync_event *event)
{
return &event->update;
}
#endif /* TIME_SYNC_EVENT_H__ */

18
src/main.c Normal file
View File

@@ -0,0 +1,18 @@
#include <app_event_manager.h>
#define MODULE main
#include <caf/events/module_state_event.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE);
int main(void)
{
if (app_event_manager_init()) {
LOG_ERR("Application Event Manager not initialized");
} else {
module_set_state(MODULE_STATE_READY);
}
return 0;
}

View File

@@ -0,0 +1,392 @@
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/power/ip5306.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/util.h>
#include <app_event_manager.h>
#include <caf/events/power_event.h>
#include <caf/events/power_manager_event.h>
#define MODULE battery
#include <caf/events/module_state_event.h>
#include "battery_status_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE);
#define BATTERY_SENSE_NODE DT_NODELABEL(battery_sense)
#define BATTERY_SAMPLE_INTERVAL_MS 1000
#define BATTERY_MV_WINDOW_SIZE 10
/*
* SOC 估算策略:
* 这里采用线性估算,默认把 3.30V 映射为 0%4.20V 映射为 100%。
* 若后续实测曲线和电压分压比例不同,只需调整两个阈值即可。
*/
#define BATTERY_EMPTY_MV 3300
#define BATTERY_FULL_MV 4100
static const struct device *const ip5306_dev = DEVICE_DT_GET(DT_NODELABEL(ip5306));
static const struct device *const battery_sensor_dev = DEVICE_DT_GET(BATTERY_SENSE_NODE);
/*
* 板级电源采样结果:
* - 由 board provider 负责给出“原始但可用”的充电状态与电压值;
* - 本模块基于这些采样结果做滤波、SOC 估算和事件发布。
*/
struct board_power_sample
{
int32_t voltage_mv;
bool charging;
bool full;
};
/*
* 对外上报状态:
* - 保持现有 battery_status_event 语义不变;
* - 只承载业务层需要的 charging/full/soc 三元组。
*/
struct battery_status
{
bool charging;
bool full;
uint8_t soc;
};
struct battery_filter_state
{
int32_t window[BATTERY_MV_WINDOW_SIZE];
int64_t sum;
size_t count;
size_t index;
};
/*
* 模块上下文:
* - sample_work 周期性拉取 board power sample
* - filter 负责平滑电池电压;
* - last_status 用于抑制重复事件;
* - pm_restrict_level 跟踪当前对 power manager 的限制等级。
*/
struct battery_ctx
{
struct k_work_delayable sample_work;
atomic_t active;
struct battery_status last_status;
bool has_last_status;
struct battery_filter_state filter;
enum power_manager_level pm_restrict_level;
};
static struct battery_ctx battery = {
.pm_restrict_level = POWER_MANAGER_LEVEL_MAX,
};
/* 线性 SOC 估算:把平滑后的电池电压映射到 0~100%。 */
static uint8_t soc_from_mv(int32_t mv)
{
if (mv <= BATTERY_EMPTY_MV) {
return 0;
}
if (mv >= BATTERY_FULL_MV) {
return 100;
}
int32_t soc = ((mv - BATTERY_EMPTY_MV) * 100) / (BATTERY_FULL_MV - BATTERY_EMPTY_MV);
return (uint8_t)CLAMP(soc, 0, 100);
}
/* 初始化/恢复时清空滤波器,避免旧样本影响新一轮估算。 */
static void battery_filter_reset(void)
{
battery.filter.sum = 0;
battery.filter.count = 0;
battery.filter.index = 0;
}
/*
* 将最新电压样本写入固定窗口平均滤波器。
* 返回值始终是“窗口平均后的电池电压”,供上层做 SOC 估算。
*/
static int32_t battery_filter_apply(int32_t voltage_mv)
{
if (battery.filter.count < BATTERY_MV_WINDOW_SIZE) {
battery.filter.window[battery.filter.index] = voltage_mv;
battery.filter.sum += voltage_mv;
battery.filter.count++;
} else {
battery.filter.sum -= battery.filter.window[battery.filter.index];
battery.filter.window[battery.filter.index] = voltage_mv;
battery.filter.sum += voltage_mv;
}
battery.filter.index = (battery.filter.index + 1U) % BATTERY_MV_WINDOW_SIZE;
return (int32_t)(battery.filter.sum / (int64_t)battery.filter.count);
}
/*
* 控制 board-provided battery_sense sensor 的供电状态。
* 这里不直接操纵 GPIO而是走 sensor 的 PM action让 power-gpios
* 与 ADC runtime PM 都由 voltage-divider 驱动统一管理。
*/
static int board_power_monitor_set_voltage_sensor_enabled(bool enable)
{
return pm_device_action_run(battery_sensor_dev,
enable ? PM_DEVICE_ACTION_RESUME :
PM_DEVICE_ACTION_SUSPEND);
}
/* 从 battery_sense 读取一次当前电池电压(单位 mV。 */
static int board_power_monitor_read_voltage_mv(int32_t *voltage_mv)
{
struct sensor_value value;
int err = sensor_sample_fetch(battery_sensor_dev);
if (err) {
LOG_WRN("sensor_sample_fetch(battery) failed (err=%d)", err);
return err;
}
err = sensor_channel_get(battery_sensor_dev, SENSOR_CHAN_VOLTAGE, &value);
if (err) {
LOG_WRN("sensor_channel_get(battery) failed (err=%d)", err);
return err;
}
*voltage_mv = (int32_t)sensor_value_to_milli(&value);
return 0;
}
/* 从 IP5306 读取一次充电态与满电态。 */
static int board_power_monitor_read_charge_state(bool *charging, bool *full)
{
int err = ip5306_is_charging(ip5306_dev, charging);
if (err) {
LOG_WRN("ip5306_is_charging failed (err=%d)", err);
return err;
}
err = ip5306_is_charge_full(ip5306_dev, full);
if (err) {
LOG_WRN("ip5306_is_charge_full failed (err=%d)", err);
return err;
}
return 0;
}
/*
* 聚合一次完整的 board power sample
* 1) 先读 PMIC 状态;
* 2) 再读 battery_sense 电压;
* 3) 最后对电压做窗口平均,输出稳定值。
*/
static int board_power_monitor_collect_sample(struct board_power_sample *sample)
{
int32_t voltage_mv;
int err = board_power_monitor_read_charge_state(&sample->charging, &sample->full);
if (err) {
return err;
}
err = board_power_monitor_read_voltage_mv(&voltage_mv);
if (err) {
return err;
}
sample->voltage_mv = battery_filter_apply(voltage_mv);
return 0;
}
/* 将 board sample 映射成对外 battery status。 */
static void battery_status_from_sample(const struct board_power_sample *sample,
struct battery_status *status)
{
status->charging = sample->charging;
status->full = sample->full;
status->soc = soc_from_mv(sample->voltage_mv);
}
/* 统一封装 battery_status_event 发布,隔离事件总线细节。 */
static void publish_battery_status_event(const struct battery_status *status)
{
battery_status_event_submit(status->charging, status->full, status->soc);
}
/* 判断本轮状态是否值得上报,避免重复事件淹没总线。 */
static bool battery_status_changed(const struct battery_status *lhs,
const struct battery_status *rhs)
{
return (lhs->charging != rhs->charging) ||
(lhs->full != rhs->full) ||
(lhs->soc != rhs->soc);
}
/*
* 电源限制策略:
* - 充电线插入charging=true时限制到 ALIVE禁止自动休眠
* - 非充电时恢复到 SUSPENDED允许系统进入挂起但不进入 OFF。
*/
static void update_power_restrict_by_charging(bool charging)
{
enum power_manager_level target = charging ?
POWER_MANAGER_LEVEL_ALIVE : POWER_MANAGER_LEVEL_SUSPENDED;
if (battery.pm_restrict_level == target)
return;
battery.pm_restrict_level = target;
power_manager_restrict(MODULE_IDX(MODULE), target);
}
/*
* 启停采样:
* - enable=true 时恢复 battery_sense等待前端稳定后开始周期采样
* - enable=false 时停止 work 并挂起 battery_sense避免持续耗电。
*/
static void battery_sampling_set_enabled(bool enable)
{
atomic_set(&battery.active, enable);
int err = board_power_monitor_set_voltage_sensor_enabled(enable);
if (err) {
LOG_WRN("board_power_monitor_set_voltage_sensor_enabled(%d) failed (err=%d)",
enable, err);
}
if (enable) {
/* 延迟开始采样,等待板上采样前端和分压网络稳定。 */
k_work_reschedule(&battery.sample_work, K_MSEC(2000));
} else {
(void)k_work_cancel_delayable(&battery.sample_work);
}
}
/* 周期性读取 board power sample并在需要时上报业务状态。 */
static void battery_sample_fn(struct k_work *work)
{
ARG_UNUSED(work);
if (!atomic_get(&battery.active)) {
return;
}
struct board_power_sample sample;
struct battery_status status;
int err = board_power_monitor_collect_sample(&sample);
if (err) {
goto out_reschedule;
}
battery_status_from_sample(&sample, &status);
update_power_restrict_by_charging(status.charging);
if (!battery.has_last_status ||
battery_status_changed(&status, &battery.last_status)) {
battery.last_status = status;
battery.has_last_status = true;
publish_battery_status_event(&status);
}
out_reschedule:
k_work_reschedule(&battery.sample_work, K_MSEC(BATTERY_SAMPLE_INTERVAL_MS));
}
/* 初始化 board power monitor consumer并拉起首轮采样。 */
static int battery_module_init(void)
{
if (!device_is_ready(ip5306_dev)) {
LOG_ERR("IP5306 device not ready");
return -ENODEV;
}
if (!device_is_ready(battery_sensor_dev)) {
LOG_ERR("Battery sense device not ready");
return -ENODEV;
}
/* 默认非充电态允许进入 SUSPENDED但禁止进入 OFF。 */
update_power_restrict_by_charging(false);
k_work_init_delayable(&battery.sample_work, battery_sample_fn);
battery.has_last_status = false;
battery_filter_reset();
atomic_set(&battery.active, false);
battery_sampling_set_enabled(true);
return 0;
}
/* 响应系统挂起:停止采样,并把本模块切到 STANDBY。 */
static void battery_module_suspend(void)
{
if (!atomic_get(&battery.active)) {
/* 已经处于挂起态,避免重复上报 STANDBY 造成 power_down 循环。 */
return;
}
battery_sampling_set_enabled(false);
module_set_state(MODULE_STATE_STANDBY);
}
/* 响应系统唤醒:恢复电压传感器并重启周期采样。 */
static void battery_module_resume(void)
{
if (atomic_get(&battery.active)) {
return;
}
battery_sampling_set_enabled(true);
module_set_state(MODULE_STATE_READY);
}
/* 仅处理模块 ready 和系统电源状态事件,保持模块职责单一。 */
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
int err = battery_module_init();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
}
return false;
}
if (is_power_down_event(aeh))
{
battery_module_suspend();
return false;
}
if (is_wake_up_event(aeh))
{
battery_module_resume();
return false;
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

View File

@@ -0,0 +1,85 @@
#include <app_event_manager.h>
#define MODULE ble_adv_ctrl
#include <caf/events/module_state_event.h>
#include <caf/events/module_suspend_event.h>
#include "mode_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
/*
* 该模块负责把“模式选择”转换成对 ble_adv 的挂起/恢复请求:
* - BLE 模式:恢复 ble_adv允许广播。
* - USB/2.4G 模式:挂起 ble_adv禁止广播。
*
* 说明:
* - 这里是控制层,不直接操作 bt_le_adv_start/stop
* - 实际广播执行仍由 CAF 的 ble_adv 模块处理。
*/
static bool ble_adv_suspended = true;
static void send_ble_adv_ctrl_req(bool suspend)
{
if (suspend) {
struct module_suspend_req_event *event = new_module_suspend_req_event();
event->sink_module_id = MODULE_ID(ble_adv);
event->src_module_id = MODULE_ID(MODULE);
APP_EVENT_SUBMIT(event);
} else {
struct module_resume_req_event *event = new_module_resume_req_event();
event->sink_module_id = MODULE_ID(ble_adv);
event->src_module_id = MODULE_ID(MODULE);
APP_EVENT_SUBMIT(event);
}
}
static bool handle_mode_event(const struct mode_event *event)
{
bool new_suspend = (event->mode_type != MODE_TYPE_BLE);
if (new_suspend == ble_adv_suspended) {
return false;
}
ble_adv_suspended = new_suspend;
send_ble_adv_ctrl_req(ble_adv_suspended);
LOG_INF("BLE advertising %s by mode %u",
ble_adv_suspended ? "suspended" : "resumed",
event->mode_type);
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
/*
* 上电默认先请求挂起,避免在 mode_switch 首次采样前出现短暂误广播。
*/
send_ble_adv_ctrl_req(true);
module_set_state(MODULE_STATE_READY);
}
return false;
}
if (is_mode_event(aeh)) {
return handle_mode_event(cast_mode_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event);

View File

@@ -0,0 +1,51 @@
#include <zephyr/bluetooth/services/bas.h>
#include <app_event_manager.h>
#define MODULE ble_battery
#include <caf/events/module_state_event.h>
#include "battery_status_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
static bool handle_battery_status_event(const struct battery_status_event *event)
{
uint8_t battery_level = battery_status_event_get_soc(event);
int err = bt_bas_set_battery_level(battery_level);
if (err) {
LOG_ERR("bt_bas_set_battery_level failed: %d", err);
}
return false;
}
static bool handle_module_state_event(const struct module_state_event *event)
{
if (!check_state(event, MODULE_ID(ble_state), MODULE_STATE_READY)) {
return false;
}
module_set_state(MODULE_STATE_READY);
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_battery_status_event(aeh)) {
return handle_battery_status_event(cast_battery_status_event(aeh));
}
if (is_module_state_event(aeh)) {
return handle_module_state_event(cast_module_state_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, battery_status_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);

View File

@@ -0,0 +1,583 @@
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/settings/settings.h>
#include <errno.h>
#include <app_event_manager.h>
#define MODULE ble_bond
#include <caf/events/module_state_event.h>
#include <caf/events/ble_common_event.h>
#include <caf/events/power_event.h>
#include "config_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
/* Application-visible options carried by config_event. */
enum ble_bond_cfg_opt {
BLE_BOND_CFG_PEER_SELECT = 0,
BLE_BOND_CFG_PEER_ERASE,
BLE_BOND_CFG_PEER_ERASE_ALL,
BLE_BOND_CFG_OPT_COUNT
};
/* Module ID in config_event event_id[7:4]. Keep stable for host side tooling. */
#define BLE_BOND_CFG_MODULE_ID 0x01
#define PEER_ID_KEY "peer_id"
#define BT_LUT_KEY "bt_lut"
enum ble_bond_state {
BLE_BOND_STATE_DISABLED,
BLE_BOND_STATE_IDLE,
BLE_BOND_STATE_STANDBY,
};
BUILD_ASSERT(CONFIG_BT_ID_MAX >= 2, "Need at least one resettable identity");
#define APP_PEER_COUNT (CONFIG_BT_ID_MAX - 1)
#define BLE_BOND_SLOT_COUNT 3
BUILD_ASSERT(BLE_BOND_SLOT_COUNT <= APP_PEER_COUNT,
"BLE slot count exceeds available Bluetooth identities");
struct ble_bond_storage {
uint8_t bt_stack_id_lut[APP_PEER_COUNT];
bool bt_stack_id_lut_valid;
uint8_t cur_peer_id;
bool cur_peer_id_valid;
};
struct ble_bond_ctx {
enum ble_bond_state state;
struct ble_bond_storage storage;
bool auto_switch_in_progress;
};
static struct ble_bond_ctx bond = {
.state = BLE_BOND_STATE_DISABLED,
};
static const char *state_name(enum ble_bond_state s)
{
switch (s) {
case BLE_BOND_STATE_DISABLED:
return "DISABLED";
case BLE_BOND_STATE_IDLE:
return "IDLE";
case BLE_BOND_STATE_STANDBY:
return "STANDBY";
default:
return "UNKNOWN";
}
}
static uint8_t get_bt_stack_peer_id(uint8_t app_id)
{
__ASSERT_NO_MSG(app_id < BLE_BOND_SLOT_COUNT);
return bond.storage.bt_stack_id_lut[app_id];
}
static int store_peer_id(uint8_t peer_id)
{
char key[] = MODULE_NAME "/" PEER_ID_KEY;
return settings_save_one(key, &peer_id, sizeof(peer_id));
}
static int store_bt_stack_id_lut(void)
{
char key[] = MODULE_NAME "/" BT_LUT_KEY;
return settings_save_one(key,
bond.storage.bt_stack_id_lut,
sizeof(bond.storage.bt_stack_id_lut));
}
static void submit_peer_op_event(enum peer_operation op, uint8_t app_id)
{
struct ble_peer_operation_event *event = new_ble_peer_operation_event();
event->op = op;
event->bt_app_id = app_id;
event->bt_stack_id = get_bt_stack_peer_id(app_id);
APP_EVENT_SUBMIT(event);
}
static void init_bt_stack_id_lut(void)
{
for (size_t i = 0; i < ARRAY_SIZE(bond.storage.bt_stack_id_lut); i++) {
/* Keep id 0 (BT_ID_DEFAULT) untouched for safe reset/unpair flow. */
bond.storage.bt_stack_id_lut[i] = i + 1;
}
}
static bool storage_data_is_valid(void)
{
if (!bond.storage.cur_peer_id_valid || !bond.storage.bt_stack_id_lut_valid) {
LOG_WRN("Stored data invalid: peer_valid=%d lut_valid=%d",
bond.storage.cur_peer_id_valid, bond.storage.bt_stack_id_lut_valid);
return false;
}
if (bond.storage.cur_peer_id >= BLE_BOND_SLOT_COUNT) {
LOG_WRN("Stored peer id out of range: peer_id=%u max=%u",
bond.storage.cur_peer_id, BLE_BOND_SLOT_COUNT - 1);
return false;
}
for (size_t i = 0; i < ARRAY_SIZE(bond.storage.bt_stack_id_lut); i++) {
if ((bond.storage.bt_stack_id_lut[i] == BT_ID_DEFAULT) ||
(bond.storage.bt_stack_id_lut[i] >= CONFIG_BT_ID_MAX)) {
LOG_WRN("Stored LUT invalid at idx=%u value=%u",
(uint32_t)i, bond.storage.bt_stack_id_lut[i]);
return false;
}
}
return true;
}
static int settings_set(const char *key, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
ssize_t rc;
if (!strcmp(key, PEER_ID_KEY)) {
if (len_rd != sizeof(bond.storage.cur_peer_id)) {
LOG_WRN("Settings '%s' size mismatch: got=%u expect=%u",
PEER_ID_KEY, (uint32_t)len_rd, sizeof(bond.storage.cur_peer_id));
bond.storage.cur_peer_id_valid = false;
return 0;
}
rc = read_cb(cb_arg, &bond.storage.cur_peer_id, sizeof(bond.storage.cur_peer_id));
bond.storage.cur_peer_id_valid = (rc == sizeof(bond.storage.cur_peer_id));
if (!bond.storage.cur_peer_id_valid) {
LOG_WRN("Settings '%s' read failed: rc=%d", PEER_ID_KEY, (int)rc);
}
} else if (!strcmp(key, BT_LUT_KEY)) {
if (len_rd != sizeof(bond.storage.bt_stack_id_lut)) {
LOG_WRN("Settings '%s' size mismatch: got=%u expect=%u",
BT_LUT_KEY, (uint32_t)len_rd, sizeof(bond.storage.bt_stack_id_lut));
bond.storage.bt_stack_id_lut_valid = false;
return 0;
}
rc = read_cb(cb_arg,
bond.storage.bt_stack_id_lut,
sizeof(bond.storage.bt_stack_id_lut));
bond.storage.bt_stack_id_lut_valid = (rc == sizeof(bond.storage.bt_stack_id_lut));
if (!bond.storage.bt_stack_id_lut_valid) {
LOG_WRN("Settings '%s' read failed: rc=%d", BT_LUT_KEY, (int)rc);
}
}
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(ble_bond, MODULE_NAME, NULL, settings_set, NULL, NULL);
static int load_identities(void)
{
bt_addr_le_t addrs[CONFIG_BT_ID_MAX];
size_t count = ARRAY_SIZE(addrs);
bt_id_get(addrs, &count);
LOG_INF("Identity count before ensure: %u / %u", (uint32_t)count, CONFIG_BT_ID_MAX);
for (; count < CONFIG_BT_ID_MAX; count++) {
int err = bt_id_create(NULL, NULL);
if (err < 0) {
LOG_ERR("Cannot create identity (err:%d)", err);
return err;
}
LOG_INF("Created identity idx=%u", (uint32_t)count);
}
return 0;
}
static void disconnect_le_conn_cb(struct bt_conn *conn, void *user_data)
{
(void)user_data;
int err = bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (!err) {
LOG_INF("Disconnect LE peer for slot switch");
} else if (err == -ENOTCONN) {
LOG_INF("LE peer already disconnected during slot switch");
} else {
LOG_WRN("Failed to disconnect LE peer for slot switch err=%d", err);
}
}
static void mark_le_conn_found_cb(struct bt_conn *conn, void *user_data)
{
bool *found = user_data;
ARG_UNUSED(conn);
*found = true;
}
static bool has_any_le_conn(void)
{
bool found = false;
bt_conn_foreach(BT_CONN_TYPE_LE, mark_le_conn_found_cb, &found);
return found;
}
struct peer_bond_lookup {
const bt_addr_le_t *peer_addr;
bool found;
};
static void peer_bond_lookup_cb(const struct bt_bond_info *info, void *user_data)
{
struct peer_bond_lookup *lookup = user_data;
if (!bt_addr_le_cmp(&info->addr, lookup->peer_addr)) {
lookup->found = true;
}
}
static bool slot_has_peer_bond(uint8_t app_id, const bt_addr_le_t *peer_addr)
{
struct peer_bond_lookup lookup = {
.peer_addr = peer_addr,
};
bt_foreach_bond(get_bt_stack_peer_id(app_id), peer_bond_lookup_cb, &lookup);
return lookup.found;
}
static bool find_peer_owner_slot(const bt_addr_le_t *peer_addr, uint8_t *owner_slot)
{
for (uint8_t slot = 0; slot < BLE_BOND_SLOT_COUNT; slot++) {
if (slot_has_peer_bond(slot, peer_addr)) {
*owner_slot = slot;
return true;
}
}
return false;
}
static int select_peer(uint8_t peer_id)
{
if (peer_id >= BLE_BOND_SLOT_COUNT) {
return -EINVAL;
}
uint8_t previous_peer_id = bond.storage.cur_peer_id;
if (bond.storage.cur_peer_id_valid && (previous_peer_id == peer_id)) {
LOG_INF("Peer slot already selected: slot=%u stack_id=%u",
peer_id, get_bt_stack_peer_id(peer_id));
return 0;
}
bond.storage.cur_peer_id = peer_id;
bond.storage.cur_peer_id_valid = true;
int err = store_peer_id(bond.storage.cur_peer_id);
if (err) {
LOG_ERR("Failed to store peer_id=%u (err:%d)", bond.storage.cur_peer_id, err);
return err;
}
submit_peer_op_event(PEER_OPERATION_SELECTED, bond.storage.cur_peer_id);
bt_conn_foreach(BT_CONN_TYPE_LE, disconnect_le_conn_cb, NULL);
return 0;
}
static bool handle_ble_peer_event(const struct ble_peer_event *event)
{
if (event->state == PEER_STATE_CONNECTED) {
const bt_addr_le_t *peer_addr = bt_conn_get_dst(event->id);
uint8_t owner_slot;
if (!peer_addr) {
return false;
}
if (find_peer_owner_slot(peer_addr, &owner_slot) &&
(owner_slot != bond.storage.cur_peer_id)) {
char addr_str[BT_ADDR_LE_STR_LEN];
int err;
bt_addr_le_to_str(peer_addr, addr_str, sizeof(addr_str));
LOG_INF("Peer %s belongs to slot=%u, auto-switch from slot=%u",
addr_str, owner_slot, bond.storage.cur_peer_id);
bond.auto_switch_in_progress = true;
err = select_peer(owner_slot);
if (err) {
bond.auto_switch_in_progress = false;
LOG_ERR("Auto-switch to slot=%u failed err=%d",
owner_slot, err);
module_set_state(MODULE_STATE_ERROR);
}
}
return false;
}
if (event->state == PEER_STATE_DISCONNECTED) {
if (bond.auto_switch_in_progress) {
bond.auto_switch_in_progress = false;
LOG_INF("Auto-switch disconnect complete, waiting for reconnect on slot=%u",
bond.storage.cur_peer_id);
}
return false;
}
if (event->state != PEER_STATE_SECURED) {
return false;
}
struct bt_conn_info info;
int err = bt_conn_get_info(event->id, &info);
if (err) {
LOG_ERR("Cannot get conn info for secured peer err=%d", err);
module_set_state(MODULE_STATE_ERROR);
return false;
}
uint8_t expected_stack_id = get_bt_stack_peer_id(bond.storage.cur_peer_id);
if (info.id == expected_stack_id) {
LOG_INF("Secured peer matches selected slot=%u stack_id=%u",
bond.storage.cur_peer_id,
expected_stack_id);
return false;
}
LOG_INF("Disconnect peer on old id=%u expected=%u selected_slot=%u",
info.id,
expected_stack_id,
bond.storage.cur_peer_id);
err = bt_conn_disconnect(event->id, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (err && (err != -ENOTCONN)) {
LOG_ERR("Cannot disconnect peer on old id err=%d", err);
module_set_state(MODULE_STATE_ERROR);
}
return false;
}
static int erase_peer(uint8_t app_id)
{
uint8_t stack_id = get_bt_stack_peer_id(app_id);
int err;
/* Tell ble_adv to restart advertising session for this identity. */
submit_peer_op_event(PEER_OPERATION_ERASE_ADV, app_id);
err = bt_unpair(stack_id, NULL);
if (err) {
LOG_ERR("Cannot unpair id %u (err:%d)", stack_id, err);
return err;
}
err = bt_id_reset(stack_id, NULL, NULL);
if (err < 0) {
LOG_ERR("Cannot reset id %u (err:%d)", stack_id, err);
return err;
}
submit_peer_op_event(PEER_OPERATION_ERASED, app_id);
return 0;
}
static int erase_all_peers(void)
{
for (uint8_t i = 0; i < BLE_BOND_SLOT_COUNT; i++) {
int err = erase_peer(i);
if (err) {
return err;
}
}
return 0;
}
static bool handle_config_event(const struct config_event *event)
{
if (!event->is_request || (event->recipient != CFG_CHAN_RECIPIENT_LOCAL)) {
return false;
}
if (MOD_FIELD_GET(event->event_id) != BLE_BOND_CFG_MODULE_ID) {
return false;
}
struct config_event *rsp = new_config_event(0);
rsp->transport_id = event->transport_id;
rsp->is_request = false;
rsp->event_id = event->event_id;
rsp->recipient = event->recipient;
rsp->status = CONFIG_STATUS_REJECT;
if (event->status == CONFIG_STATUS_SET) {
uint8_t opt_field = OPT_FIELD_GET(event->event_id);
uint8_t opt_id = (opt_field == 0) ? UINT8_MAX : OPT_ID_GET(opt_field);
switch (opt_id) {
case BLE_BOND_CFG_PEER_SELECT:
if (event->dyndata.size >= 1) {
uint8_t peer_id = event->dyndata.data[0];
if (!select_peer(peer_id)) {
rsp->status = CONFIG_STATUS_SUCCESS;
}
}
break;
case BLE_BOND_CFG_PEER_ERASE:
if (!erase_peer(bond.storage.cur_peer_id)) {
rsp->status = CONFIG_STATUS_SUCCESS;
}
break;
case BLE_BOND_CFG_PEER_ERASE_ALL:
if (!erase_all_peers()) {
rsp->status = CONFIG_STATUS_SUCCESS;
}
break;
default:
break;
}
} else if (event->status == CONFIG_STATUS_FETCH) {
uint8_t opt_field = OPT_FIELD_GET(event->event_id);
uint8_t opt_id = (opt_field == 0) ? UINT8_MAX : OPT_ID_GET(opt_field);
if (opt_id == BLE_BOND_CFG_PEER_SELECT) {
struct config_event *rsp_data = new_config_event(1);
rsp_data->transport_id = event->transport_id;
rsp_data->is_request = false;
rsp_data->event_id = event->event_id;
rsp_data->recipient = event->recipient;
rsp_data->status = CONFIG_STATUS_SUCCESS;
rsp_data->dyndata.data[0] = bond.storage.cur_peer_id;
APP_EVENT_SUBMIT(rsp_data);
return true;
}
}
APP_EVENT_SUBMIT(rsp);
return true;
}
static int init_after_settings_loaded(void)
{
int err = load_identities();
if (err) {
LOG_ERR("Identity initialization failed: %d", err);
return err;
}
if (!storage_data_is_valid()) {
LOG_WRN("Stored BLE bond data invalid, reinitializing defaults");
bond.storage.cur_peer_id = 0;
bond.storage.cur_peer_id_valid = true;
init_bt_stack_id_lut();
bond.storage.bt_stack_id_lut_valid = true;
err = store_peer_id(bond.storage.cur_peer_id);
if (err) {
LOG_ERR("Failed to store peer_id=%u (err:%d)",
bond.storage.cur_peer_id, err);
return -EIO;
}
err = store_bt_stack_id_lut();
if (err) {
LOG_ERR("Failed to store bt_stack_id_lut (err:%d)", err);
return -EIO;
}
}
bond.state = BLE_BOND_STATE_IDLE;
LOG_INF("ble_bond init done: state=%s peer_id=%u stack_id=%u",
state_name(bond.state),
bond.storage.cur_peer_id,
get_bt_stack_peer_id(bond.storage.cur_peer_id));
submit_peer_op_event(PEER_OPERATION_SELECTED, bond.storage.cur_peer_id);
module_set_state(MODULE_STATE_READY);
return 0;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(settings_loader), MODULE_STATE_READY) &&
(bond.state == BLE_BOND_STATE_DISABLED)) {
LOG_INF("settings_loader ready, starting ble_bond init");
int err = init_after_settings_loaded();
if (err) {
LOG_ERR("ble_bond init failed (err:%d), state=%s",
err, state_name(bond.state));
module_set_state(MODULE_STATE_ERROR);
}
}
return false;
}
if (is_power_down_event(aeh)) {
if (bond.state == BLE_BOND_STATE_IDLE) {
bond.state = BLE_BOND_STATE_STANDBY;
module_set_state(MODULE_STATE_OFF);
}
return false;
}
if (is_wake_up_event(aeh)) {
if (bond.state == BLE_BOND_STATE_STANDBY) {
bond.state = BLE_BOND_STATE_IDLE;
module_set_state(MODULE_STATE_READY);
/*
* If a LE link survived suspend, keep it untouched. CAF ble_adv
* treats PEER_OPERATION_SELECTED as a real identity switch and
* will disconnect the current peer. If no link exists, re-emit
* the selection so advertising resumes on the selected slot.
*/
if (!has_any_le_conn()) {
submit_peer_op_event(PEER_OPERATION_SELECTED,
bond.storage.cur_peer_id);
}
}
return false;
}
if (is_ble_peer_event(aeh)) {
return handle_ble_peer_event(cast_ble_peer_event(aeh));
}
if (is_config_event(aeh)) {
return handle_config_event(cast_config_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
APP_EVENT_SUBSCRIBE(MODULE, ble_peer_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, config_event);

View File

@@ -0,0 +1,404 @@
#include <bluetooth/services/hids.h>
#include <stdint.h>
#include <app_event_manager.h>
#define MODULE ble_hid
#include <caf/events/module_state_event.h>
#include <caf/events/ble_common_event.h>
#include "hid_protocol_event.h"
#include "hid_host_command_event.h"
#include "hid_report_descriptor.h"
#include "hid_tx_done_event.h"
#include "hid_tx_event.h"
#include "hid_vendor_mask_event.h"
#include "keyboard_led_event.h"
#include "mode_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define INPUT_REPORT_COUNT 4
#define OUTPUT_REPORT_COUNT 3
BT_HIDS_DEF(hids_obj, INPUT_REPORT_COUNT, OUTPUT_REPORT_COUNT, 0);
struct ble_hid_link {
struct bt_conn *conn;
enum bt_hids_pm protocol_mode;
};
struct ble_hid_policy {
bool ble_mode_selected;
};
struct ble_hid_led_state {
bool valid;
uint8_t led_mask;
};
struct ble_hid_ctx {
struct ble_hid_link link;
struct ble_hid_policy policy;
struct ble_hid_led_state led;
};
static struct ble_hid_ctx ble_hid = {
.link.protocol_mode = BT_HIDS_PM_REPORT,
};
static bool ble_hid_is_connected(void)
{
return ble_hid.link.conn != NULL;
}
static bool ble_hid_should_handle_tx_event(const struct hid_tx_event *event)
{
switch (hid_tx_event_get_route(event)) {
case HID_TX_ROUTE_AUTO:
return ble_hid.policy.ble_mode_selected;
case HID_TX_ROUTE_BLE:
return true;
case HID_TX_ROUTE_USB:
default:
return false;
}
}
static bool ble_hid_is_boot_mode(void)
{
return ble_hid.link.protocol_mode == BT_HIDS_PM_BOOT;
}
static bool ble_hid_is_report_mode(void)
{
return ble_hid.link.protocol_mode == BT_HIDS_PM_REPORT;
}
static void publish_hid_protocol_event(enum hid_protocol_type protocol)
{
hid_protocol_event_submit(protocol);
}
/* 主机 LED 输出报告变化时才上报,避免重复事件淹没总线。 */
static void publish_num_lock_state_from_led_mask(uint8_t led_mask)
{
if (ble_hid.led.valid && (ble_hid.led.led_mask == led_mask)) {
return;
}
ble_hid.led.valid = true;
ble_hid.led.led_mask = led_mask;
keyboard_led_event_submit(led_mask);
}
static void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn)
{
ARG_UNUSED(conn);
switch (evt) {
case BT_HIDS_PM_EVT_BOOT_MODE_ENTERED:
ble_hid.link.protocol_mode = BT_HIDS_PM_BOOT;
LOG_INF("HIDS protocol: boot");
if (ble_hid_is_connected()) {
publish_hid_protocol_event(HID_PROTO_BOOT);
}
break;
case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED:
ble_hid.link.protocol_mode = BT_HIDS_PM_REPORT;
LOG_INF("HIDS protocol: report");
if (ble_hid_is_connected()) {
publish_hid_protocol_event(HID_PROTO_REPORT);
}
break;
default:
break;
}
}
static void report_notify_handler(enum bt_hids_notify_evt evt)
{
ARG_UNUSED(evt);
}
static void boot_keyboard_notif_handler(enum bt_hids_notify_evt evt)
{
ARG_UNUSED(evt);
}
static void boot_keyboard_output_report_handler(struct bt_hids_rep *rep,
struct bt_conn *conn,
bool write)
{
ARG_UNUSED(conn);
if (!write || !rep || (rep->size == 0) || !rep->data) {
return;
}
publish_num_lock_state_from_led_mask(rep->data[0]);
LOG_DBG("Boot KB out report 0x%02x", rep->data[0]);
}
static void keyboard_output_report_handler(struct bt_hids_rep *rep,
struct bt_conn *conn,
bool write)
{
ARG_UNUSED(conn);
if (!write || !rep || !rep->data || (rep->size < HID_KBD_LED_PAYLOAD_SIZE)) {
return;
}
publish_num_lock_state_from_led_mask(rep->data[0]);
LOG_DBG("Report KB out report 0x%02x", rep->data[0]);
}
static void vendor_output_report_handler(struct bt_hids_rep *rep,
struct bt_conn *conn,
bool write)
{
ARG_UNUSED(conn);
if (!write || !rep || !rep->data || (rep->size != HID_VENDOR_PAYLOAD_SIZE)) {
return;
}
hid_vendor_mask_event_submit(rep->data, rep->size);
LOG_INF("Vendor mask updated over BLE len=%u", rep->size);
}
static void vendor_cmd_output_report_handler(struct bt_hids_rep *rep,
struct bt_conn *conn,
bool write)
{
ARG_UNUSED(conn);
if (!write || !rep || !rep->data ||
(rep->size != HID_HOST_CMD_OUTPUT_PAYLOAD_SIZE)) {
return;
}
hid_host_command_event_submit(HID_HOST_TRANSPORT_BLE,
rep->data[0],
&rep->data[1],
rep->size - 1U);
LOG_INF("Vendor cmd updated over BLE cmd=0x%02x len=%u",
rep->data[0], rep->size);
}
static int hids_service_init(void)
{
static const uint8_t report_map[] = HID_DESC_KEYBOARD_NKRO_CONSUMER();
struct bt_hids_init_param init_param = { 0 };
struct bt_hids_inp_rep *input_report = &init_param.inp_rep_group_init.reports[0];
struct bt_hids_outp_feat_rep *output_report = &init_param.outp_rep_group_init.reports[0];
init_param.info.bcd_hid = 0x0101;
init_param.info.b_country_code = 0x00;
init_param.info.flags = BT_HIDS_REMOTE_WAKE | BT_HIDS_NORMALLY_CONNECTABLE;
init_param.rep_map.data = report_map;
init_param.rep_map.size = sizeof(report_map);
input_report[0].id = REPORT_ID_KEYBOARD;
input_report[0].size = HID_KBD_PAYLOAD_SIZE;
input_report[0].handler = report_notify_handler;
input_report[1].id = REPORT_ID_CONSUMER;
input_report[1].size = HID_CONSUMER_PAYLOAD_SIZE;
input_report[1].handler = report_notify_handler;
input_report[2].id = REPORT_ID_VENDOR;
input_report[2].size = HID_VENDOR_PAYLOAD_SIZE;
input_report[2].handler = report_notify_handler;
input_report[3].id = REPORT_ID_VENDOR_CMD;
input_report[3].size = HID_VENDOR_ACK_PAYLOAD_SIZE;
input_report[3].handler = report_notify_handler;
output_report[0].id = REPORT_ID_KEYBOARD;
output_report[0].size = HID_KBD_LED_PAYLOAD_SIZE;
output_report[0].handler = keyboard_output_report_handler;
output_report[1].id = REPORT_ID_VENDOR;
output_report[1].size = HID_VENDOR_PAYLOAD_SIZE;
output_report[1].handler = vendor_output_report_handler;
output_report[2].id = REPORT_ID_VENDOR_CMD;
output_report[2].size = HID_HOST_CMD_OUTPUT_PAYLOAD_SIZE;
output_report[2].handler = vendor_cmd_output_report_handler;
init_param.inp_rep_group_init.cnt = INPUT_REPORT_COUNT;
init_param.outp_rep_group_init.cnt = OUTPUT_REPORT_COUNT;
init_param.pm_evt_handler = pm_evt_handler;
init_param.is_kb = true;
init_param.boot_kb_notif_handler = boot_keyboard_notif_handler;
init_param.boot_kb_outp_rep_handler = boot_keyboard_output_report_handler;
return bt_hids_init(&hids_obj, &init_param);
}
static void handle_ble_peer_event(const struct ble_peer_event *event)
{
switch (event->state) {
case PEER_STATE_CONNECTED:
__ASSERT_NO_MSG(ble_hid.link.conn == NULL);
ble_hid.link.conn = event->id;
if (bt_hids_connected(&hids_obj, ble_hid.link.conn)) {
LOG_WRN("bt_hids_connected failed");
}
break;
case PEER_STATE_DISCONNECTED:
if (ble_hid.link.conn == event->id) {
if (bt_hids_disconnected(&hids_obj, ble_hid.link.conn)) {
LOG_WRN("bt_hids_disconnected failed");
}
ble_hid.link.conn = NULL;
}
break;
default:
break;
}
}
static bool handle_hid_tx_event(const struct hid_tx_event *event)
{
if (!ble_hid_should_handle_tx_event(event)) {
return false;
}
if (!ble_hid_is_connected()) {
hid_tx_done_event_submit(event->kind, false);
return false;
}
if (event->kind == HID_TX_KIND_BOOT) {
const uint8_t *payload = hid_tx_event_get_data(event);
size_t payload_len = hid_tx_event_get_size(event);
int err;
if (!ble_hid_is_boot_mode()) {
hid_tx_done_event_submit(HID_TX_KIND_BOOT, false);
return false;
}
if (payload_len != HID_BOOT_KBD_PAYLOAD_SIZE) {
LOG_WRN("Invalid boot keyboard payload len=%u", payload_len);
hid_tx_done_event_submit(HID_TX_KIND_BOOT, false);
return false;
}
err = bt_hids_boot_kb_inp_rep_send(&hids_obj, ble_hid.link.conn,
payload, payload_len, NULL);
if (err) {
LOG_WRN("BLE HID boot send failed err=%d", err);
}
hid_tx_done_event_submit(HID_TX_KIND_BOOT, (err == 0));
return false;
}
if (!ble_hid_is_report_mode()) {
hid_tx_done_event_submit(HID_TX_KIND_REPORT, false);
return false;
}
const uint8_t *data = hid_tx_event_get_data(event);
size_t data_len = hid_tx_event_get_size(event);
uint8_t report_id;
const uint8_t *payload;
size_t payload_len;
if (data_len < 1U) {
hid_tx_done_event_submit(HID_TX_KIND_REPORT, false);
return false;
}
report_id = data[0];
payload = &data[1];
payload_len = data_len - 1U;
uint8_t rep_index;
if (report_id == REPORT_ID_KEYBOARD) {
rep_index = 0U;
} else if (report_id == REPORT_ID_CONSUMER) {
rep_index = 1U;
} else if (report_id == REPORT_ID_VENDOR) {
rep_index = 2U;
} else if (report_id == REPORT_ID_VENDOR_CMD) {
rep_index = 3U;
} else {
hid_tx_done_event_submit(HID_TX_KIND_REPORT, false);
return false;
}
if (payload_len > UINT8_MAX) {
LOG_WRN("Payload too large=%u", payload_len);
hid_tx_done_event_submit(HID_TX_KIND_REPORT, false);
return false;
}
int err = bt_hids_inp_rep_send(&hids_obj, ble_hid.link.conn, rep_index,
payload, (uint8_t)payload_len, NULL);
if (err) {
LOG_WRN("BLE HID send failed report=0x%02x err=%d", report_id, err);
}
hid_tx_done_event_submit(HID_TX_KIND_REPORT, (err == 0));
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
static bool initialized;
__ASSERT_NO_MSG(!initialized);
initialized = true;
if (hids_service_init()) {
LOG_ERR("Cannot initialize HIDS service");
module_set_state(MODULE_STATE_ERROR);
} else {
module_set_state(MODULE_STATE_READY);
}
}
return false;
}
if (is_ble_peer_event(aeh)) {
handle_ble_peer_event(cast_ble_peer_event(aeh));
return false;
}
if (is_mode_event(aeh)) {
const struct mode_event *event = cast_mode_event(aeh);
ble_hid.policy.ble_mode_selected = (event->mode_type == MODE_TYPE_BLE);
return false;
}
if (is_hid_tx_event(aeh)) {
return handle_hid_tx_event(cast_hid_tx_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_tx_event);

View File

@@ -0,0 +1,281 @@
#include <string.h>
#include <zephyr/kernel.h>
#include <app_event_manager.h>
#define MODULE ble_slot_ctrl
#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 "config_event.h"
#include "mode_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define BLE_BOND_CFG_MODULE_ID 0x01
#define BLE_BOND_CFG_PEER_SELECT 0
#define BLE_BOND_CFG_PEER_ERASE 1
#define BLE_SLOT_CTRL_MUTE_HOLD_MS 3000
#define BLE_SLOT_CTRL_SLOT_KEY_NONE UINT16_MAX
#define BLE_SLOT_CTRL_KEY_MUTE KEY_ID(3, 0)
#define BLE_SLOT_CTRL_SLOT_COUNT 3
struct ble_slot_ctrl_ctx {
bool ble_mode_selected;
bool mute_pressed;
bool mute_hold_fired;
bool slot_combo_used;
bool initialized;
bool passthrough_mute_press;
bool passthrough_mute_release;
uint16_t slot_key_id;
struct k_work_delayable mute_hold_work;
};
static struct ble_slot_ctrl_ctx slot_ctrl = {
.slot_key_id = BLE_SLOT_CTRL_SLOT_KEY_NONE,
};
static uint8_t ble_slot_ctrl_make_event_id(uint8_t opt_id)
{
return (BLE_BOND_CFG_MODULE_ID << MOD_FIELD_POS) | (uint8_t)(opt_id + 1U);
}
static void ble_slot_ctrl_submit_config_request(uint8_t opt_id,
const uint8_t *payload,
size_t payload_len)
{
struct config_event *event = new_config_event(payload_len);
event->transport_id = 0U;
event->is_request = true;
event->event_id = ble_slot_ctrl_make_event_id(opt_id);
event->recipient = CFG_CHAN_RECIPIENT_LOCAL;
event->status = CONFIG_STATUS_SET;
if ((payload_len > 0U) && (payload != NULL)) {
memcpy(event->dyndata.data, payload, payload_len);
}
LOG_INF("Submit cfg request opt=%u len=%u", opt_id, payload_len);
APP_EVENT_SUBMIT(event);
}
static void ble_slot_ctrl_request_select(uint8_t slot_id)
{
ble_slot_ctrl_submit_config_request(BLE_BOND_CFG_PEER_SELECT, &slot_id, sizeof(slot_id));
}
static void ble_slot_ctrl_request_erase_current(void)
{
ble_slot_ctrl_submit_config_request(BLE_BOND_CFG_PEER_ERASE, NULL, 0U);
}
static void ble_slot_ctrl_reset(void)
{
LOG_DBG("Reset slot ctrl state mute=%d hold=%d combo=%d key=0x%04x",
slot_ctrl.mute_pressed,
slot_ctrl.mute_hold_fired,
slot_ctrl.slot_combo_used,
slot_ctrl.slot_key_id);
slot_ctrl.mute_pressed = false;
slot_ctrl.mute_hold_fired = false;
slot_ctrl.slot_combo_used = false;
slot_ctrl.slot_key_id = BLE_SLOT_CTRL_SLOT_KEY_NONE;
if (slot_ctrl.initialized) {
(void)k_work_cancel_delayable(&slot_ctrl.mute_hold_work);
}
}
static void ble_slot_ctrl_submit_button(uint16_t key_id, bool pressed)
{
struct button_event *event = new_button_event();
event->key_id = key_id;
event->pressed = pressed;
APP_EVENT_SUBMIT(event);
}
static void ble_slot_ctrl_forward_mute_tap(void)
{
LOG_INF("Forward mute tap to HID path");
slot_ctrl.passthrough_mute_press = true;
slot_ctrl.passthrough_mute_release = true;
ble_slot_ctrl_submit_button(BLE_SLOT_CTRL_KEY_MUTE, true);
ble_slot_ctrl_submit_button(BLE_SLOT_CTRL_KEY_MUTE, false);
}
static bool ble_slot_ctrl_try_get_slot_id(uint16_t key_id, uint8_t *slot_id)
{
switch (key_id) {
case KEY_ID(0, 4):
*slot_id = 0U;
return true;
case KEY_ID(1, 4):
*slot_id = 1U;
return true;
case KEY_ID(2, 4):
*slot_id = 2U;
return true;
default:
return false;
}
}
static bool ble_slot_ctrl_active(void)
{
return slot_ctrl.ble_mode_selected;
}
static void ble_slot_ctrl_mute_hold_work_fn(struct k_work *work)
{
ARG_UNUSED(work);
if (!ble_slot_ctrl_active() ||
!slot_ctrl.mute_pressed ||
slot_ctrl.slot_combo_used) {
LOG_DBG("Ignore mute hold active=%d mute=%d combo=%d",
ble_slot_ctrl_active(),
slot_ctrl.mute_pressed,
slot_ctrl.slot_combo_used);
return;
}
ble_slot_ctrl_request_erase_current();
slot_ctrl.mute_hold_fired = true;
LOG_INF("Requested erase current BLE slot by mute hold");
}
static bool handle_button_event(const struct button_event *event)
{
if (!ble_slot_ctrl_active()) {
return false;
}
if (event->key_id == BLE_SLOT_CTRL_KEY_MUTE) {
if (event->pressed && slot_ctrl.passthrough_mute_press) {
LOG_DBG("Pass through synthetic mute press");
slot_ctrl.passthrough_mute_press = false;
return false;
}
if (!event->pressed && slot_ctrl.passthrough_mute_release) {
LOG_DBG("Pass through synthetic mute release");
slot_ctrl.passthrough_mute_release = false;
return false;
}
if (event->pressed) {
if (slot_ctrl.mute_pressed) {
LOG_DBG("Drop repeated mute press");
return true;
}
LOG_INF("Mute pressed: start hold timer %u ms", BLE_SLOT_CTRL_MUTE_HOLD_MS);
slot_ctrl.mute_pressed = true;
slot_ctrl.mute_hold_fired = false;
slot_ctrl.slot_combo_used = false;
slot_ctrl.slot_key_id = BLE_SLOT_CTRL_SLOT_KEY_NONE;
k_work_reschedule(&slot_ctrl.mute_hold_work,
K_MSEC(BLE_SLOT_CTRL_MUTE_HOLD_MS));
return true;
}
if (!slot_ctrl.mute_pressed) {
return false;
}
LOG_INF("Mute released hold=%d combo=%d",
slot_ctrl.mute_hold_fired,
slot_ctrl.slot_combo_used);
(void)k_work_cancel_delayable(&slot_ctrl.mute_hold_work);
if (!slot_ctrl.mute_hold_fired && !slot_ctrl.slot_combo_used) {
ble_slot_ctrl_forward_mute_tap();
}
ble_slot_ctrl_reset();
return true;
}
if (!slot_ctrl.mute_pressed) {
return false;
}
if (!event->pressed && (slot_ctrl.slot_key_id == event->key_id)) {
slot_ctrl.slot_key_id = BLE_SLOT_CTRL_SLOT_KEY_NONE;
return true;
}
if (!event->pressed) {
return false;
}
uint8_t slot_id;
if (!ble_slot_ctrl_try_get_slot_id(event->key_id, &slot_id)) {
LOG_DBG("Mute combo ignore key_id=0x%04x", event->key_id);
return false;
}
(void)k_work_cancel_delayable(&slot_ctrl.mute_hold_work);
slot_ctrl.slot_combo_used = true;
slot_ctrl.slot_key_id = event->key_id;
ble_slot_ctrl_request_select(slot_id);
LOG_INF("Requested BLE slot=%u from key_id=0x%04x", slot_id, event->key_id);
return true;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
if (!slot_ctrl.initialized) {
k_work_init_delayable(&slot_ctrl.mute_hold_work,
ble_slot_ctrl_mute_hold_work_fn);
slot_ctrl.initialized = true;
}
module_set_state(MODULE_STATE_READY);
}
return false;
}
if (is_mode_event(aeh)) {
const struct mode_event *event = cast_mode_event(aeh);
slot_ctrl.ble_mode_selected = mode_event_is_ble(event);
LOG_INF("BLE slot ctrl mode selected=%d", slot_ctrl.ble_mode_selected);
if (!slot_ctrl.ble_mode_selected) {
ble_slot_ctrl_reset();
}
return false;
}
if (is_power_down_event(aeh)) {
ble_slot_ctrl_reset();
return false;
}
if (is_button_event(aeh)) {
return handle_button_event(cast_button_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, button_event);

View File

@@ -0,0 +1,175 @@
#include <errno.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/sys/byteorder.h>
#include <app_event_manager.h>
#define MODULE ble_time_sync
#include <caf/events/module_state_event.h>
#include "time_manager.h"
#include "time_sync_event.h"
#include "time_sync_protocol.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define BT_UUID_TIME_SYNC_SERVICE_VAL \
BT_UUID_128_ENCODE(0x0b7f5000, 0x38d2, 0x4f62, 0x8f6f, 0x36c4fd73a110)
#define BT_UUID_TIME_SYNC_WRITE_CHAR_VAL \
BT_UUID_128_ENCODE(0x0b7f5001, 0x38d2, 0x4f62, 0x8f6f, 0x36c4fd73a110)
#define BT_UUID_TIME_SYNC_SERVICE \
BT_UUID_DECLARE_128(BT_UUID_TIME_SYNC_SERVICE_VAL)
#define BT_UUID_TIME_SYNC_WRITE_CHAR \
BT_UUID_DECLARE_128(BT_UUID_TIME_SYNC_WRITE_CHAR_VAL)
struct ble_time_sync_ctx {
bool ble_stack_ready;
bool time_manager_ready;
bool module_ready;
};
static struct ble_time_sync_ctx ble_time_sync;
/* 统一检查协议版本和长度,避免在回调里分散出现偏移判断。 */
static bool ble_time_sync_payload_is_valid(const uint8_t *buf, uint16_t len)
{
if (!buf || (len != TIME_SYNC_PROTOCOL_PAYLOAD_SIZE)) {
return false;
}
if (buf[TIME_SYNC_PROTOCOL_OFFSET_VERSION] != TIME_SYNC_PROTOCOL_VERSION) {
return false;
}
if ((buf[TIME_SYNC_PROTOCOL_OFFSET_FLAGS] &
TIME_SYNC_PROTOCOL_FLAG_TIMEZONE_VALID) == 0U) {
return false;
}
return true;
}
/*
* 把私有 GATT payload 解码为统一的 time_sync_update
* - BLE 只负责协议适配;
* - 传输无关的时间语义都转成公共结构体后再交给事件层。
*/
static void ble_time_sync_decode_payload(const uint8_t *buf,
struct time_sync_update *update)
{
update->utc_ms = sys_get_le64(&buf[TIME_SYNC_PROTOCOL_OFFSET_UTC_MS]);
update->timezone_min =
(int16_t)sys_get_le16(&buf[TIME_SYNC_PROTOCOL_OFFSET_TIMEZONE]);
update->accuracy_ms =
sys_get_le32(&buf[TIME_SYNC_PROTOCOL_OFFSET_ACCURACY_MS]);
update->source = TIME_SYNC_SOURCE_BLE;
}
/*
* GATT 写回调必须尽量短:
* - 不支持 offset/prepare write避免被拆成长写事务
* - 校验和解码完成后直接提交统一事件,不在这里做耗时存储。
*/
static ssize_t write_time_sync(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *buf,
uint16_t len,
uint16_t offset,
uint8_t flags)
{
struct time_sync_update update;
ARG_UNUSED(conn);
ARG_UNUSED(attr);
if (!ble_time_sync.module_ready || !time_manager_is_ready()) {
return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
}
if (offset != 0U) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
if ((flags & BT_GATT_WRITE_FLAG_PREPARE) != 0U) {
return BT_GATT_ERR(BT_ATT_ERR_ATTRIBUTE_NOT_LONG);
}
if (!ble_time_sync_payload_is_valid(buf, len)) {
return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED);
}
ble_time_sync_decode_payload(buf, &update);
time_sync_event_submit(&update);
LOG_INF("Accepted BLE time sync utc_ms=%llu tz=%d acc=%u",
(unsigned long long)update.utc_ms,
update.timezone_min,
update.accuracy_ms);
return len;
}
BT_GATT_SERVICE_DEFINE(ble_time_sync_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_TIME_SYNC_SERVICE),
BT_GATT_CHARACTERISTIC(BT_UUID_TIME_SYNC_WRITE_CHAR,
BT_GATT_CHRC_WRITE |
BT_GATT_CHRC_WRITE_WITHOUT_RESP,
BT_GATT_PERM_WRITE_ENCRYPT,
NULL,
write_time_sync,
NULL),
);
/*
* 只有 BLE 栈和 time_manager 都 ready 后,才把模块状态标记为 READY
* - 虽然静态 GATT service 会跟随蓝牙栈注册;
* - 但真正是否接受写入,仍由 module_ready 再做一层保护。
*/
static void ble_time_sync_update_ready_state(void)
{
bool should_be_ready = ble_time_sync.ble_stack_ready &&
ble_time_sync.time_manager_ready;
if (should_be_ready == ble_time_sync.module_ready) {
return;
}
ble_time_sync.module_ready = should_be_ready;
module_set_state(should_be_ready ? MODULE_STATE_READY : MODULE_STATE_STANDBY);
LOG_INF("BLE time sync %s", should_be_ready ? "ready" : "standby");
}
/* 模块依赖只来自 ble_state 和 time_manager两者 READY 顺序不做假设。 */
static bool handle_module_state_event(const struct module_state_event *event)
{
if (check_state(event, MODULE_ID(ble_state), MODULE_STATE_READY)) {
ble_time_sync.ble_stack_ready = true;
ble_time_sync_update_ready_state();
return false;
}
if (check_state(event, MODULE_ID(time_manager), MODULE_STATE_READY)) {
ble_time_sync.time_manager_ready = true;
ble_time_sync_update_ready_state();
return false;
}
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));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);

View File

@@ -0,0 +1,549 @@
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/led.h>
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <app_event_manager.h>
#include <lvgl.h>
#include <lvgl_zephyr.h>
#define MODULE display
#include <caf/events/button_event.h>
#include <caf/events/module_state_event.h>
#include <caf/events/power_event.h>
#include "battery_status_event.h"
#include "display_theme_event.h"
#include "display_ui.h"
#include "keyboard_led_event.h"
#include "mode_event.h"
#include "time_manager.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define DISPLAY_UPDATE_PERIOD_MS 1000
#define DISPLAY_IDLE_TIMEOUT_MIN 1
#define DISPLAY_BACKLIGHT_BRIGHTNESS 100
#define DISPLAY_THEME_SAVE_DELAY K_SECONDS(1)
#define DISPLAY_THEME_STORAGE_KEY "theme"
#define DISPLAY_DEMO_BASE_YEAR 2026
#define DISPLAY_DEMO_BASE_MONTH 3
#define DISPLAY_DEMO_BASE_DAY 27
#define DISPLAY_DEMO_BASE_HOUR 14
#define DISPLAY_DEMO_BASE_MIN 28
#define DISPLAY_DEMO_BASE_SEC 36
enum display_pm_state
{
DISPLAY_PM_STATE_ACTIVE = 0,
DISPLAY_PM_STATE_OFF,
};
struct display_theme_storage {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t valid_marker;
};
struct display_ctx
{
const struct device *dev;
struct display_capabilities caps;
struct k_work_delayable update_work;
struct k_work_delayable idle_work;
struct k_work_delayable theme_save_work;
struct display_ui_model ui;
uint32_t tick_count;
enum display_pm_state pm_state;
bool initialized;
bool theme_storage_dirty;
bool theme_storage_loaded;
char date_text[16];
char time_text[16];
};
static struct display_ctx disp = {
.dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)),
.ui.theme_color = LV_COLOR_MAKE(0x4C, 0xC9, 0xF0),
.ui.inactive_border_color = LV_COLOR_MAKE(0xA0, 0xA7, 0xB4),
.ui.battery_level = 15U,
.ui.battery_flags = 0U,
.ui.mode = MODE_TYPE_USB,
.ui.led_mask = 0U,
.pm_state = DISPLAY_PM_STATE_OFF,
};
static struct display_theme_storage display_theme_storage;
static const struct led_dt_spec display_backlight =
LED_DT_SPEC_GET(DT_NODELABEL(backlight));
static int display_theme_store(const struct display_theme_storage *storage)
{
char key[] = MODULE_NAME "/" DISPLAY_THEME_STORAGE_KEY;
int err = settings_save_one(key, storage, sizeof(*storage));
if (err) {
LOG_ERR("Failed to save display theme err=%d", err);
return err;
}
LOG_INF("Stored display theme rgb=(%u,%u,%u)",
storage->red, storage->green, storage->blue);
return 0;
}
static void display_theme_set_rgb(uint8_t red,
uint8_t green,
uint8_t blue,
bool persist)
{
disp.ui.theme_color = lv_color_make(red, green, blue);
if (persist) {
display_theme_storage.red = red;
display_theme_storage.green = green;
display_theme_storage.blue = blue;
display_theme_storage.valid_marker = 1U;
disp.theme_storage_loaded = true;
disp.theme_storage_dirty = true;
k_work_reschedule(&disp.theme_save_work, DISPLAY_THEME_SAVE_DELAY);
}
}
static void display_theme_apply_loaded_storage(void)
{
if (!disp.theme_storage_loaded ||
(display_theme_storage.valid_marker != 1U)) {
return;
}
display_theme_set_rgb(display_theme_storage.red,
display_theme_storage.green,
display_theme_storage.blue,
false);
}
static void display_theme_save_work_fn(struct k_work *work)
{
struct display_theme_storage storage;
ARG_UNUSED(work);
if (!disp.theme_storage_dirty || !disp.theme_storage_loaded) {
return;
}
disp.theme_storage_dirty = false;
storage = display_theme_storage;
(void)display_theme_store(&storage);
}
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, DISPLAY_THEME_STORAGE_KEY) != 0) {
return 0;
}
if (len_rd != sizeof(display_theme_storage)) {
disp.theme_storage_loaded = false;
return 0;
}
rc = read_cb(cb_arg, &display_theme_storage, sizeof(display_theme_storage));
disp.theme_storage_loaded = (rc == sizeof(display_theme_storage));
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(display,
MODULE_NAME,
NULL,
settings_set,
NULL,
NULL);
static void display_schedule_update(k_timeout_t delay)
{
#ifdef CONFIG_LV_Z_RUN_LVGL_ON_WORKQUEUE
k_work_schedule_for_queue(lvgl_get_workqueue(), &disp.update_work, delay);
#else
k_work_reschedule(&disp.update_work, delay);
#endif
}
static void display_schedule_idle_timeout(k_timeout_t delay)
{
k_work_reschedule(&disp.idle_work, delay);
}
static int display_backlight_set(uint8_t brightness)
{
int err;
if (!led_is_ready_dt(&display_backlight))
{
LOG_WRN("Display backlight device not ready");
return 0;
}
err = led_set_brightness_dt(&display_backlight, brightness);
if (err)
{
LOG_ERR("Failed to set backlight brightness(%u): %d", brightness, err);
return err;
}
return 0;
}
static bool display_is_active(void)
{
return disp.pm_state == DISPLAY_PM_STATE_ACTIVE;
}
static void display_update_datetime_text(void)
{
struct time_manager_snapshot snapshot;
int err = time_manager_get_snapshot(&snapshot);
if (!err)
{
time_t local_seconds;
struct tm tm_buf;
struct tm *tm_info;
local_seconds = (time_t)(snapshot.utc_ms / 1000ULL) +
(time_t)((int32_t)snapshot.timezone_min * 60);
tm_info = gmtime_r(&local_seconds, &tm_buf);
if (tm_info)
{
unsigned int year = (unsigned int)(tm_info->tm_year + 1900);
unsigned int month = (unsigned int)(tm_info->tm_mon + 1);
unsigned int day = (unsigned int)tm_info->tm_mday;
unsigned int hour = (unsigned int)tm_info->tm_hour;
unsigned int minute = (unsigned int)tm_info->tm_min;
unsigned int second = (unsigned int)tm_info->tm_sec;
snprintk(disp.date_text, sizeof(disp.date_text), "%04u/%02u/%02u",
year, month, day);
snprintk(disp.time_text, sizeof(disp.time_text), "%02u:%02u:%02u",
hour, minute, second);
return;
}
}
{
uint32_t seconds = disp.tick_count;
uint32_t hour = (DISPLAY_DEMO_BASE_HOUR + (seconds / 3600U)) % 24U;
uint32_t minute = (DISPLAY_DEMO_BASE_MIN + ((seconds / 60U) % 60U)) % 60U;
uint32_t second = (DISPLAY_DEMO_BASE_SEC + (seconds % 60U)) % 60U;
snprintk(disp.date_text, sizeof(disp.date_text), "%04d/%02d/%02d",
DISPLAY_DEMO_BASE_YEAR,
DISPLAY_DEMO_BASE_MONTH,
DISPLAY_DEMO_BASE_DAY);
snprintk(disp.time_text, sizeof(disp.time_text), "%02u:%02u:%02u",
hour, minute, second);
}
}
static void display_refresh_all_locked(void)
{
display_update_datetime_text();
display_ui_refresh_all(&disp.ui, disp.date_text, disp.time_text);
}
static void display_kick_idle_timer(void)
{
if (!disp.initialized || !display_is_active())
return;
display_schedule_idle_timeout(K_MINUTES(DISPLAY_IDLE_TIMEOUT_MIN));
}
static void display_sleep(void)
{
int err;
if (!disp.initialized || !display_is_active())
return;
(void)k_work_cancel_delayable(&disp.update_work);
(void)k_work_cancel_delayable(&disp.idle_work);
(void)k_work_cancel_delayable(&disp.theme_save_work);
if (disp.theme_storage_dirty && disp.theme_storage_loaded) {
disp.theme_storage_dirty = false;
(void)display_theme_store(&display_theme_storage);
}
err = display_blanking_on(disp.dev);
if (err)
LOG_WRN("Display blanking on failed: %d", err);
(void)display_backlight_set(0U);
disp.pm_state = DISPLAY_PM_STATE_OFF;
module_set_state(MODULE_STATE_OFF);
}
static void display_wake(void)
{
int err;
if (!disp.initialized)
return;
if (display_is_active()) {
display_kick_idle_timer();
return;
}
err = display_blanking_off(disp.dev);
if (err)
LOG_WRN("Display blanking off failed: %d", err);
(void)display_backlight_set(DISPLAY_BACKLIGHT_BRIGHTNESS);
lvgl_lock();
display_refresh_all_locked();
lvgl_unlock();
disp.pm_state = DISPLAY_PM_STATE_ACTIVE;
display_schedule_update(K_NO_WAIT);
display_kick_idle_timer();
module_set_state(MODULE_STATE_READY);
}
static void display_idle_timeout_fn(struct k_work *work)
{
ARG_UNUSED(work);
display_sleep();
}
static void display_update_work_fn(struct k_work *work)
{
ARG_UNUSED(work);
if (!disp.initialized)
return;
if (!display_is_active())
return;
disp.tick_count++;
display_update_datetime_text();
lvgl_lock();
display_ui_refresh_datetime(disp.date_text, disp.time_text);
lvgl_unlock();
display_schedule_update(K_MSEC(DISPLAY_UPDATE_PERIOD_MS));
}
static int display_init(void)
{
int err;
if (!device_is_ready(disp.dev))
{
LOG_ERR("Display device not ready");
return -ENODEV;
}
display_get_capabilities(disp.dev, &disp.caps);
LOG_INF("Display caps: %ux%u fmt=%d",
disp.caps.x_resolution,
disp.caps.y_resolution,
disp.caps.current_pixel_format);
k_work_init_delayable(&disp.update_work, display_update_work_fn);
k_work_init_delayable(&disp.idle_work, display_idle_timeout_fn);
k_work_init_delayable(&disp.theme_save_work, display_theme_save_work_fn);
disp.tick_count = 0U;
display_theme_apply_loaded_storage();
display_update_datetime_text();
err = display_blanking_off(disp.dev);
if (err)
{
LOG_ERR("Display blanking off failed: %d", err);
return err;
}
err = display_backlight_set(DISPLAY_BACKLIGHT_BRIGHTNESS);
if (err)
return err;
lvgl_lock();
display_ui_init(&disp.ui, disp.date_text, disp.time_text);
lvgl_unlock();
disp.initialized = true;
disp.pm_state = DISPLAY_PM_STATE_ACTIVE;
display_schedule_update(K_NO_WAIT);
display_kick_idle_timer();
LOG_INF("Display UI initialized");
return 0;
}
static bool handle_battery_status_event(const struct battery_status_event *event)
{
disp.ui.battery_level = battery_status_event_get_soc(event);
disp.ui.battery_flags = battery_status_event_get_flags(event);
if (!disp.initialized || !display_is_active()) {
return false;
}
lvgl_lock();
display_ui_refresh_battery(&disp.ui);
lvgl_unlock();
return false;
}
static bool handle_mode_event(const struct mode_event *event)
{
disp.ui.mode = event->mode_type;
if (!disp.initialized || !display_is_active()) {
return false;
}
lvgl_lock();
display_ui_refresh_status_bar(&disp.ui);
lvgl_unlock();
return false;
}
static bool handle_keyboard_led_event(const struct keyboard_led_event *event)
{
disp.ui.led_mask = keyboard_led_event_get_mask(event);
if (!disp.initialized || !display_is_active()) {
return false;
}
lvgl_lock();
display_ui_refresh_status_bar(&disp.ui);
lvgl_unlock();
return false;
}
static bool handle_display_theme_event(const struct display_theme_event *event)
{
display_theme_set_rgb(event->red, event->green, event->blue, true);
if (!disp.initialized || !display_is_active()) {
return false;
}
lvgl_lock();
display_ui_refresh_status_bar(&disp.ui);
lvgl_unlock();
return false;
}
static bool handle_button_event(const struct button_event *event)
{
ARG_UNUSED(event);
display_wake();
return false;
}
static bool handle_power_down_event(void)
{
display_sleep();
return false;
}
static bool handle_wake_up_event(void)
{
display_wake();
return false;
}
static bool handle_module_state_event(const struct module_state_event *event)
{
if (check_state(event, MODULE_ID(settings_loader), MODULE_STATE_READY)) {
display_theme_apply_loaded_storage();
if (disp.initialized && display_is_active()) {
lvgl_lock();
display_ui_refresh_status_bar(&disp.ui);
lvgl_unlock();
}
return false;
}
if (!check_state(event, MODULE_ID(main), MODULE_STATE_READY))
return false;
if (!display_init())
{
module_set_state(MODULE_STATE_READY);
}
else
{
module_set_state(MODULE_STATE_ERROR);
}
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_battery_status_event(aeh))
return handle_battery_status_event(cast_battery_status_event(aeh));
if (is_mode_event(aeh))
return handle_mode_event(cast_mode_event(aeh));
if (is_keyboard_led_event(aeh))
return handle_keyboard_led_event(cast_keyboard_led_event(aeh));
if (is_display_theme_event(aeh))
return handle_display_theme_event(cast_display_theme_event(aeh));
if (is_button_event(aeh))
return handle_button_event(cast_button_event(aeh));
if (is_power_down_event(aeh))
return handle_power_down_event();
if (is_wake_up_event(aeh))
return handle_wake_up_event();
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, battery_status_event);
APP_EVENT_SUBSCRIBE(MODULE, display_theme_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
APP_EVENT_SUBSCRIBE(MODULE, keyboard_led_event);
APP_EVENT_SUBSCRIBE(MODULE, button_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

View File

@@ -0,0 +1,121 @@
#include <zephyr/sys/byteorder.h>
#include <app_event_manager.h>
#define MODULE hid_host_command
#include <caf/events/module_state_event.h>
#include "display_theme_event.h"
#include "hid_host_ack_event.h"
#include "hid_host_command_error_event.h"
#include "hid_host_command_event.h"
#include "hid_host_command_protocol.h"
#include "time_manager.h"
#include "time_sync_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
static bool module_ready;
static bool handle_theme_color_command(const struct hid_host_command_event *event)
{
if (event->data_len < HID_HOST_CMD_THEME_PARAM_SIZE) {
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_INVALID_LENGTH);
return false;
}
display_theme_event_submit(event->data[0], event->data[1], event->data[2]);
hid_host_ack_event_submit(event->transport, event->cmd);
return false;
}
static bool handle_time_sync_command(const struct hid_host_command_event *event)
{
struct time_sync_update update = {
.timezone_min = 0,
.accuracy_ms = 0,
.source = TIME_SYNC_SOURCE_HID,
};
if (event->data_len != HID_HOST_CMD_TIME_SYNC_PARAM_SIZE) {
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_INVALID_LENGTH);
return false;
}
if (!time_manager_is_ready()) {
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_NOT_READY);
return false;
}
update.utc_ms = sys_get_le64(event->data);
if (update.utc_ms == 0U) {
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_INVALID_PARAM);
return false;
}
time_sync_event_submit(&update);
hid_host_ack_event_submit(event->transport, event->cmd);
return false;
}
static bool handle_hid_host_command_event(const struct hid_host_command_event *event)
{
if (!module_ready) {
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_NOT_READY);
return false;
}
switch (event->cmd) {
case HID_HOST_CMD_ID_THEME_COLOR:
return handle_theme_color_command(event);
case HID_HOST_CMD_ID_TIME_SYNC:
return handle_time_sync_command(event);
default:
hid_host_command_error_event_submit(
event->transport,
event->cmd,
HID_HOST_COMMAND_ERROR_UNKNOWN_CMD);
return false;
}
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
module_ready = true;
module_set_state(MODULE_STATE_READY);
}
return false;
}
if (is_hid_host_command_event(aeh)) {
return handle_hid_host_command_event(cast_hid_host_command_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_host_command_event);

View File

@@ -0,0 +1,313 @@
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/atomic.h>
#include <app_event_manager.h>
#define MODULE hid_tx_manager
#include <caf/events/module_state_event.h>
#include "hid_report_descriptor.h"
#include "hid_boot_event.h"
#include "hid_host_ack_event.h"
#include "hid_report_event.h"
#include "hid_tx_done_event.h"
#include "hid_tx_event.h"
#include "mode_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define HID_TX_QUEUE_SIZE 16
#define HID_TX_MAX_DATA 32
enum hid_tx_flag {
HID_TX_FLAG_INITIALIZED = 0,
HID_TX_FLAG_IN_FLIGHT,
HID_TX_FLAG_BOOT_VALID,
HID_TX_FLAG_BOOT_DIRTY,
HID_TX_FLAG_KEYBOARD_VALID,
HID_TX_FLAG_KEYBOARD_DIRTY,
HID_TX_FLAG_VENDOR_VALID,
HID_TX_FLAG_VENDOR_DIRTY,
};
struct hid_tx_item {
enum hid_tx_kind kind;
enum hid_tx_route route;
size_t len;
uint8_t data[HID_TX_MAX_DATA];
};
struct hid_tx_ctx {
atomic_t flags;
struct hid_tx_item boot_state;
struct hid_tx_item keyboard_state;
struct hid_tx_item vendor_state;
struct hid_tx_item inflight_item;
mode_type_t active_mode;
};
static struct hid_tx_ctx tx = {
.active_mode = MODE_TYPE_COUNT,
};
K_MSGQ_DEFINE(hid_tx_consumer_msgq, sizeof(struct hid_tx_item), HID_TX_QUEUE_SIZE, 4);
K_MSGQ_DEFINE(hid_tx_ack_msgq, sizeof(struct hid_tx_item), HID_TX_QUEUE_SIZE, 4);
K_MSGQ_DEFINE(hid_tx_misc_msgq, sizeof(struct hid_tx_item), HID_TX_QUEUE_SIZE, 4);
static bool hid_tx_item_store(struct hid_tx_item *item,
enum hid_tx_kind kind,
enum hid_tx_route route,
const uint8_t *data,
size_t len)
{
if (len > HID_TX_MAX_DATA) {
LOG_WRN("Drop HID tx kind=%u len=%u: too large", kind, len);
return false;
}
item->kind = kind;
item->route = route;
item->len = len;
if ((len > 0U) && (data != NULL)) {
memcpy(item->data, data, len);
}
return true;
}
static bool hid_tx_queue_push(struct k_msgq *queue,
enum hid_tx_kind kind,
enum hid_tx_route route,
const uint8_t *data,
size_t len)
{
struct hid_tx_item item;
if (!hid_tx_item_store(&item, kind, route, data, len)) {
return false;
}
if (k_msgq_put(queue, &item, K_NO_WAIT)) {
LOG_WRN("Drop HID tx kind=%u len=%u: queue full", kind, len);
return false;
}
return true;
}
static bool hid_tx_auto_route_available(void)
{
return (tx.active_mode == MODE_TYPE_USB) || (tx.active_mode == MODE_TYPE_BLE);
}
static bool hid_tx_dispatch_item(const struct hid_tx_item *item)
{
tx.inflight_item = *item;
atomic_set_bit(&tx.flags, HID_TX_FLAG_IN_FLIGHT);
hid_tx_event_submit_routed(item->kind, item->route, item->data, item->len);
return true;
}
static void dispatch_next_if_possible(void)
{
struct hid_tx_item item;
if (!atomic_test_bit(&tx.flags, HID_TX_FLAG_INITIALIZED) ||
atomic_test_bit(&tx.flags, HID_TX_FLAG_IN_FLIGHT)) {
return;
}
if (hid_tx_auto_route_available() &&
atomic_test_bit(&tx.flags, HID_TX_FLAG_KEYBOARD_DIRTY) &&
atomic_test_bit(&tx.flags, HID_TX_FLAG_KEYBOARD_VALID)) {
atomic_clear_bit(&tx.flags, HID_TX_FLAG_KEYBOARD_DIRTY);
(void)hid_tx_dispatch_item(&tx.keyboard_state);
return;
}
if (hid_tx_auto_route_available() &&
atomic_test_bit(&tx.flags, HID_TX_FLAG_BOOT_DIRTY) &&
atomic_test_bit(&tx.flags, HID_TX_FLAG_BOOT_VALID)) {
atomic_clear_bit(&tx.flags, HID_TX_FLAG_BOOT_DIRTY);
(void)hid_tx_dispatch_item(&tx.boot_state);
return;
}
if (hid_tx_auto_route_available() &&
!k_msgq_get(&hid_tx_consumer_msgq, &item, K_NO_WAIT)) {
(void)hid_tx_dispatch_item(&item);
return;
}
if (hid_tx_auto_route_available() &&
atomic_test_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY) &&
atomic_test_bit(&tx.flags, HID_TX_FLAG_VENDOR_VALID)) {
atomic_clear_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY);
(void)hid_tx_dispatch_item(&tx.vendor_state);
return;
}
if (!k_msgq_get(&hid_tx_ack_msgq, &item, K_NO_WAIT)) {
(void)hid_tx_dispatch_item(&item);
return;
}
if (hid_tx_auto_route_available() &&
!k_msgq_get(&hid_tx_misc_msgq, &item, K_NO_WAIT)) {
(void)hid_tx_dispatch_item(&item);
}
}
static bool handle_module_state_event(const struct module_state_event *event)
{
if (!check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
return false;
}
__ASSERT_NO_MSG(!atomic_test_bit(&tx.flags, HID_TX_FLAG_INITIALIZED));
atomic_set_bit(&tx.flags, HID_TX_FLAG_INITIALIZED);
module_set_state(MODULE_STATE_READY);
dispatch_next_if_possible();
return false;
}
static bool handle_mode_event(const struct mode_event *event)
{
tx.active_mode = event->mode_type;
dispatch_next_if_possible();
return false;
}
static enum hid_tx_route hid_tx_route_from_transport(enum hid_host_transport transport)
{
switch (transport) {
case HID_HOST_TRANSPORT_USB:
return HID_TX_ROUTE_USB;
case HID_HOST_TRANSPORT_BLE:
return HID_TX_ROUTE_BLE;
default:
return HID_TX_ROUTE_AUTO;
}
}
static bool handle_hid_boot_request_event(const struct hid_boot_event *event)
{
(void)hid_tx_item_store(&tx.boot_state,
HID_TX_KIND_BOOT,
HID_TX_ROUTE_AUTO,
hid_boot_event_get_data(event),
hid_boot_event_get_size(event));
atomic_set_bit(&tx.flags, HID_TX_FLAG_BOOT_VALID);
atomic_set_bit(&tx.flags, HID_TX_FLAG_BOOT_DIRTY);
dispatch_next_if_possible();
return true;
}
static bool handle_hid_report_request_event(const struct hid_report_event *event)
{
const uint8_t *data = hid_report_event_get_data(event);
size_t len = hid_report_event_get_size(event);
if ((len > 0U) && (data[0] == REPORT_ID_KEYBOARD)) {
(void)hid_tx_item_store(&tx.keyboard_state,
HID_TX_KIND_REPORT,
HID_TX_ROUTE_AUTO,
data, len);
atomic_set_bit(&tx.flags, HID_TX_FLAG_KEYBOARD_VALID);
atomic_set_bit(&tx.flags, HID_TX_FLAG_KEYBOARD_DIRTY);
} else if ((len > 0U) && (data[0] == REPORT_ID_CONSUMER)) {
(void)hid_tx_queue_push(&hid_tx_consumer_msgq,
HID_TX_KIND_REPORT,
HID_TX_ROUTE_AUTO,
data, len);
} else if ((len > 0U) && (data[0] == REPORT_ID_VENDOR)) {
(void)hid_tx_item_store(&tx.vendor_state,
HID_TX_KIND_REPORT,
HID_TX_ROUTE_AUTO,
data, len);
atomic_set_bit(&tx.flags, HID_TX_FLAG_VENDOR_VALID);
atomic_set_bit(&tx.flags, HID_TX_FLAG_VENDOR_DIRTY);
} else {
(void)hid_tx_queue_push(&hid_tx_misc_msgq,
HID_TX_KIND_REPORT,
HID_TX_ROUTE_AUTO,
data, len);
}
dispatch_next_if_possible();
return true;
}
static bool handle_hid_host_ack_event(const struct hid_host_ack_event *event)
{
uint8_t report[1U + HID_VENDOR_ACK_PAYLOAD_SIZE] = {
REPORT_ID_VENDOR_CMD,
event->cmd,
};
(void)hid_tx_queue_push(&hid_tx_ack_msgq,
HID_TX_KIND_REPORT,
hid_tx_route_from_transport(event->transport),
report,
sizeof(report));
dispatch_next_if_possible();
return true;
}
static bool handle_hid_tx_done_event(const struct hid_tx_done_event *event)
{
if (!atomic_test_bit(&tx.flags, HID_TX_FLAG_IN_FLIGHT)) {
return false;
}
if (event->kind != tx.inflight_item.kind) {
return false;
}
atomic_clear_bit(&tx.flags, HID_TX_FLAG_IN_FLIGHT);
dispatch_next_if_possible();
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
return handle_module_state_event(cast_module_state_event(aeh));
}
if (is_mode_event(aeh)) {
return handle_mode_event(cast_mode_event(aeh));
}
if (is_hid_boot_event(aeh)) {
return handle_hid_boot_request_event(cast_hid_boot_event(aeh));
}
if (is_hid_report_event(aeh)) {
return handle_hid_report_request_event(cast_hid_report_event(aeh));
}
if (is_hid_tx_done_event(aeh)) {
return handle_hid_tx_done_event(cast_hid_tx_done_event(aeh));
}
if (is_hid_host_ack_event(aeh)) {
return handle_hid_host_ack_event(cast_hid_host_ack_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_boot_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_report_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_host_ack_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_tx_done_event);

View File

@@ -0,0 +1,440 @@
#include <string.h>
#include <stdlib.h>
#include <app_event_manager.h>
#define MODULE keyboard
#include <caf/events/module_state_event.h>
#include <caf/events/button_event.h>
#include <caf/key_id.h>
#include "hid_report_descriptor.h"
#include "hid_boot_event.h"
#include "hid_protocol_event.h"
#include "hid_report_event.h"
#include "hid_vendor_mask_event.h"
#include "qdec_step_event.h"
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
/*
* 参考 nrf_desktop 的表驱动设计:
* - key_id -> (usage_id, report_id) 映射定义在外部 hid_keymap_def.h
* - keyboard_module 内部完成映射表校验与查询,不再依赖独立 hid_keymap 模块。
*/
struct hid_keymap {
uint16_t key_id;
uint16_t usage_id;
uint8_t report_id;
};
#include APP_HID_KEYMAP_DEF_PATH
static bool hid_keymap_initialized;
/* 比较函数:供 bsearch 按 key_id 升序查找映射项。 */
static int hid_keymap_compare(const void *a, const void *b)
{
const struct hid_keymap *pa = a;
const struct hid_keymap *pb = b;
return ((int)pa->key_id - (int)pb->key_id);
}
/*
* 初始化并校验 hid_keymap
* - 仅在 CONFIG_ASSERT 打开时执行校验,避免 release 构建引入额外开销;
* - 校验 key_id 严格升序,确保二分查找行为正确;
* - 校验 report_id 只落在当前模块支持的 Keyboard/Consumer 两类。
*/
static void hid_keymap_init_local(void)
{
if (!IS_ENABLED(CONFIG_ASSERT) || hid_keymap_initialized) {
return;
}
for (size_t i = 0; i < ARRAY_SIZE(hid_keymap); i++) {
if (i > 0U) {
__ASSERT(hid_keymap[i - 1].key_id < hid_keymap[i].key_id,
"hid_keymap must be sorted by key_id");
}
__ASSERT((hid_keymap[i].report_id == REPORT_ID_KEYBOARD) ||
(hid_keymap[i].report_id == REPORT_ID_CONSUMER),
"hid_keymap uses unsupported report_id");
}
hid_keymap_initialized = true;
}
/* 查询指定 key_id 的 HID 映射,查不到返回 NULL。 */
static const struct hid_keymap *hid_keymap_get_local(uint16_t key_id)
{
if (ARRAY_SIZE(hid_keymap) == 0U) {
return NULL;
}
struct hid_keymap key = {
.key_id = key_id,
.usage_id = 0U,
.report_id = 0U,
};
return bsearch(&key,
hid_keymap,
ARRAY_SIZE(hid_keymap),
sizeof(hid_keymap[0]),
hid_keymap_compare);
}
/* Report 协议键盘 payload: modifier(1) + usage bitset(0..0xE7 => 29B)。 */
#define KEYBOARD_USAGE_MAX HID_KBD_USAGE_MAX
#define KEYBOARD_BITMAP_SIZE HID_KBD_BITMAP_SIZE
struct keyboard_state {
uint8_t physical_modifier_bm;
uint8_t physical_usage_bm[KEYBOARD_BITMAP_SIZE];
uint8_t mask_modifier_bm;
uint8_t mask_bm[KEYBOARD_BITMAP_SIZE];
enum hid_protocol_type current_protocol;
uint16_t consumer_usage;
};
static struct keyboard_state ks = {
.current_protocol = HID_PROTO_REPORT,
.consumer_usage = 0,
};
/* 当前 HID 报告编码仅跟最近一次 set_protocol 结果相关。 */
static enum hid_protocol_type active_protocol_get(void)
{
return ks.current_protocol;
}
static void submit_hid_report(enum hid_protocol_type protocol,
uint8_t report_id,
const uint8_t *payload,
size_t payload_len);
static void keyboard_mask_init(void)
{
ks.mask_modifier_bm = 0xFF;
memset(ks.mask_bm, 0xFF, sizeof(ks.mask_bm));
}
/* 查询某 usage 位在当前键盘位图里是否处于按下状态。 */
static bool usage_pressed(uint16_t usage)
{
if (usage > KEYBOARD_USAGE_MAX) {
return false;
}
return (ks.physical_usage_bm[usage / 8] & BIT(usage % 8)) != 0U;
}
/*
* 更新键盘 usage 位图与 modifier 状态。
* 返回 true 表示状态有变化,需要向传输层同步新报告。
*/
static bool keyboard_usage_update(uint16_t usage_id, bool pressed)
{
if (usage_id > KEYBOARD_USAGE_MAX) {
LOG_WRN("Unsupported usage_id=0x%04x", usage_id);
return false;
}
uint8_t idx = usage_id / 8;
uint8_t mask = BIT(usage_id % 8);
bool changed = false;
if (pressed) {
if ((ks.physical_usage_bm[idx] & mask) == 0U) {
ks.physical_usage_bm[idx] |= mask;
changed = true;
}
} else {
if ((ks.physical_usage_bm[idx] & mask) != 0U) {
ks.physical_usage_bm[idx] &= (uint8_t)~mask;
changed = true;
}
}
/* modifier(E0~E7) 额外维护一份 bitmask便于 Boot/Report 复用。 */
if ((usage_id >= 0x00E0) && (usage_id <= 0x00E7)) {
uint8_t mod_mask = BIT(usage_id - 0x00E0);
if (pressed) {
ks.physical_modifier_bm |= mod_mask;
} else {
ks.physical_modifier_bm &= (uint8_t)~mod_mask;
}
}
return changed;
}
static uint8_t masked_modifier_get(void)
{
return ks.physical_modifier_bm & ks.mask_modifier_bm;
}
static void build_masked_keyboard_payload(uint8_t payload[HID_KBD_PAYLOAD_SIZE])
{
payload[0] = masked_modifier_get();
for (size_t i = 0; i < KEYBOARD_BITMAP_SIZE; i++) {
payload[1U + i] = ks.physical_usage_bm[i] & ks.mask_bm[i];
}
}
static void submit_vendor_report_payload(void)
{
uint8_t payload[HID_VENDOR_PAYLOAD_SIZE];
if (active_protocol_get() != HID_PROTO_REPORT) {
return;
}
payload[0] = ks.physical_modifier_bm;
memcpy(&payload[1], ks.physical_usage_bm, sizeof(ks.physical_usage_bm));
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_VENDOR, payload, sizeof(payload));
}
/*
* 提交 HID 报告事件:
* - Report 协议编码为 [report_id | payload]
* - Boot 协议编码为 [payload](不含 report_id
*/
static void submit_hid_report(enum hid_protocol_type protocol,
uint8_t report_id,
const uint8_t *payload,
size_t payload_len)
{
uint8_t report_buf[HID_FULL_REPORT_SIZE(HID_KBD_PAYLOAD_SIZE)];
if (protocol == HID_PROTO_REPORT) {
size_t report_len = payload_len + 1U;
report_buf[0] = report_id;
memcpy(&report_buf[1], payload, payload_len);
hid_report_event_submit(report_buf, report_len);
} else {
hid_boot_event_submit(payload, payload_len);
}
}
/*
* 组包并提交键盘报告:
* - Report 协议发送 NKRO payload
* - Boot 协议降级为 6KRO 固定 8 字节格式。
*/
static void submit_keyboard_report_payload(enum hid_protocol_type protocol)
{
if (protocol == HID_PROTO_REPORT) {
uint8_t payload[HID_KBD_PAYLOAD_SIZE];
build_masked_keyboard_payload(payload);
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_KEYBOARD,
payload, sizeof(payload));
return;
}
/*
* Boot 协议只支持 6KRO。
* 从 usage 位图中按升序提取最多 6 个普通键modifier 走独立字节。
*/
uint8_t payload[HID_BOOT_KBD_PAYLOAD_SIZE] = { 0 };
size_t key_pos = 2;
payload[0] = masked_modifier_get();
for (uint16_t usage = 0x04; usage <= 0x65; usage++) {
if (!usage_pressed(usage)) {
continue;
}
payload[key_pos++] = (uint8_t)usage;
if (key_pos >= ARRAY_SIZE(payload)) {
break;
}
}
submit_hid_report(HID_PROTO_BOOT, REPORT_ID_KEYBOARD, payload, sizeof(payload));
}
/* 组包并提交 consumer 报告16-bit usage。 */
static void submit_consumer_report_payload(void)
{
uint8_t payload[HID_CONSUMER_PAYLOAD_SIZE];
payload[0] = ks.consumer_usage & 0xFF;
payload[1] = (ks.consumer_usage >> 8) & 0xFF;
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_CONSUMER, payload, sizeof(payload));
}
static void submit_consumer_click_usage(uint16_t usage_id)
{
uint8_t payload[HID_CONSUMER_PAYLOAD_SIZE];
if (active_protocol_get() == HID_PROTO_BOOT) {
return;
}
payload[0] = usage_id & 0xFF;
payload[1] = (usage_id >> 8) & 0xFF;
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_CONSUMER, payload, sizeof(payload));
payload[0] = 0U;
payload[1] = 0U;
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_CONSUMER, payload, sizeof(payload));
}
/*
* 处理键盘类 usage
* - 仅在按键状态实际变化时提交报告,避免无效重复上报。
*/
static bool handle_keyboard_usage_event(const struct hid_keymap *map, bool pressed)
{
if (!keyboard_usage_update(map->usage_id, pressed))
return false;
submit_keyboard_report_payload(active_protocol_get());
submit_vendor_report_payload();
return false;
}
/*
* 处理 consumer 类 usage
* - Boot 协议不发送 consumer 报告;
* - 按下时上报 usage抬起时上报 0 清状态。
*/
static bool handle_consumer_usage_event(const struct hid_keymap *map, bool pressed)
{
if (active_protocol_get() == HID_PROTO_BOOT)
return false;
if (pressed) {
if (ks.consumer_usage == map->usage_id)
return false;
ks.consumer_usage = map->usage_id;
submit_consumer_report_payload();
return false;
}
if (ks.consumer_usage != map->usage_id)
return false;
ks.consumer_usage = 0U;
submit_consumer_report_payload();
return false;
}
/*
* 处理 button_event
* - 先查 key_id 映射;
* - 再按 report_id 分派到键盘/consumer 分支。
*/
static bool handle_button_event(const struct button_event *event)
{
const struct hid_keymap *map = hid_keymap_get_local(event->key_id);
if (!map) {
return false;
}
if (map->report_id == REPORT_ID_KEYBOARD)
return handle_keyboard_usage_event(map, event->pressed);
if (map->report_id == REPORT_ID_CONSUMER)
return handle_consumer_usage_event(map, event->pressed);
LOG_WRN("Unsupported report_id=%u key_id=0x%04x", map->report_id, event->key_id);
return false;
}
/* 记录最近一次 set_protocol 结果。 */
static bool handle_hid_protocol_event(const struct hid_protocol_event *event)
{
ks.current_protocol = hid_protocol_event_get_protocol(event);
return false;
}
static bool handle_hid_vendor_mask_event(const struct hid_vendor_mask_event *event)
{
const uint8_t *data = hid_vendor_mask_event_get_data(event);
size_t size = hid_vendor_mask_event_get_size(event);
if (size != HID_VENDOR_PAYLOAD_SIZE) {
LOG_WRN("Ignore vendor mask len=%u expect=%u",
size, HID_VENDOR_PAYLOAD_SIZE);
return false;
}
ks.mask_modifier_bm = data[0];
memcpy(ks.mask_bm, &data[1], sizeof(ks.mask_bm));
submit_keyboard_report_payload(active_protocol_get());
submit_vendor_report_payload();
return false;
}
static bool handle_qdec_step_event(const struct qdec_step_event *event)
{
int8_t step = qdec_step_event_get_step(event);
uint16_t usage_id;
if (step == 0) {
return false;
}
usage_id = (step > 0) ? 0x00E9U : 0x00EAU;
submit_consumer_click_usage(usage_id);
return false;
}
/* 模块总事件分发入口。 */
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_button_event(aeh)) {
return handle_button_event(cast_button_event(aeh));
}
if (is_hid_protocol_event(aeh)) {
return handle_hid_protocol_event(cast_hid_protocol_event(aeh));
}
if (is_qdec_step_event(aeh)) {
return handle_qdec_step_event(cast_qdec_step_event(aeh));
}
if (is_hid_vendor_mask_event(aeh)) {
return handle_hid_vendor_mask_event(cast_hid_vendor_mask_event(aeh));
}
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
/* 主模块 ready 后做一次 keymap 结构校验。 */
hid_keymap_init_local();
keyboard_mask_init();
module_set_state(MODULE_STATE_READY);
}
return false;
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, button_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_protocol_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_vendor_mask_event);
APP_EVENT_SUBSCRIBE(MODULE, qdec_step_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);

View File

@@ -0,0 +1,180 @@
#include <zephyr/kernel.h>
#include <app_event_manager.h>
#define MODULE led_state
#include <caf/events/module_state_event.h>
#include <caf/events/ble_common_event.h>
#include <caf/events/led_event.h>
#include "keyboard_led_event.h"
#include "led_state.h"
#include "mode_event.h"
#include "led_state_def.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
static uint8_t connected_peer_count;
static bool ble_mode_selected;
static bool peer_search_active;
static enum peer_operation peer_op = PEER_OPERATION_CANCEL;
static bool num_lock_on;
/* 根据当前聚合上下文决定 BLE 状态灯的逻辑状态。 */
static enum led_ble_state resolve_ble_led_state(void)
{
if (!ble_mode_selected)
return LED_BLE_STATE_OFF;
switch (peer_op) {
case PEER_OPERATION_SELECT:
case PEER_OPERATION_ERASE:
case PEER_OPERATION_ERASE_ADV:
return LED_BLE_STATE_PAIRING;
case PEER_OPERATION_SELECTED:
case PEER_OPERATION_ERASE_ADV_CANCEL:
case PEER_OPERATION_ERASED:
case PEER_OPERATION_CANCEL:
case PEER_OPERATION_SCAN_REQUEST:
if (peer_search_active)
return LED_BLE_STATE_PAIRING;
if (connected_peer_count > 0U)
return LED_BLE_STATE_CONNECTED;
return LED_BLE_STATE_WAIT_RECONNECT;
default:
__ASSERT_NO_MSG(false);
return LED_BLE_STATE_OFF;
}
}
/* 发布 Num Lock 灯效。 */
static void submit_num_lock_led(void)
{
if (led_map[LED_ID_NUM_LOCK] == LED_UNAVAILABLE)
return;
enum led_num_lock_state state =
num_lock_on ? LED_NUM_LOCK_STATE_ON : LED_NUM_LOCK_STATE_OFF;
struct led_event *event = new_led_event();
event->led_id = led_map[LED_ID_NUM_LOCK];
event->led_effect = &led_num_lock_state_effect[state];
APP_EVENT_SUBMIT(event);
}
/* 发布 BLE 状态灯效。 */
static void submit_ble_led(void)
{
if (led_map[LED_ID_BLE_STATE] == LED_UNAVAILABLE)
return;
enum led_ble_state state = resolve_ble_led_state();
struct led_event *event = new_led_event();
event->led_id = led_map[LED_ID_BLE_STATE];
event->led_effect = &led_ble_state_effect[state];
APP_EVENT_SUBMIT(event);
}
static bool handle_mode_event(const struct mode_event *event)
{
ble_mode_selected = (event->mode_type == MODE_TYPE_BLE);
submit_ble_led();
return false;
}
static bool handle_ble_peer_event(const struct ble_peer_event *event)
{
switch (event->state) {
case PEER_STATE_CONNECTED:
__ASSERT_NO_MSG(connected_peer_count < UINT8_MAX);
connected_peer_count++;
break;
case PEER_STATE_DISCONNECTED:
__ASSERT_NO_MSG(connected_peer_count > 0U);
connected_peer_count--;
break;
case PEER_STATE_SECURED:
case PEER_STATE_CONN_FAILED:
case PEER_STATE_DISCONNECTING:
break;
default:
__ASSERT_NO_MSG(false);
break;
}
submit_ble_led();
return false;
}
static bool handle_ble_peer_search_event(const struct ble_peer_search_event *event)
{
peer_search_active = event->active;
submit_ble_led();
return false;
}
static bool handle_ble_peer_operation_event(const struct ble_peer_operation_event *event)
{
peer_op = event->op;
submit_ble_led();
return false;
}
static bool handle_keyboard_led_event(const struct keyboard_led_event *event)
{
num_lock_on = keyboard_led_event_is_num_lock_on(event);
submit_num_lock_led();
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
submit_num_lock_led();
submit_ble_led();
module_set_state(MODULE_STATE_READY);
}
return false;
}
if (is_mode_event(aeh))
return handle_mode_event(cast_mode_event(aeh));
if (is_keyboard_led_event(aeh))
return handle_keyboard_led_event(cast_keyboard_led_event(aeh));
if (IS_ENABLED(CONFIG_CAF_BLE_COMMON_EVENTS) && is_ble_peer_event(aeh))
return handle_ble_peer_event(cast_ble_peer_event(aeh));
if (IS_ENABLED(CONFIG_CAF_BLE_COMMON_EVENTS) && is_ble_peer_search_event(aeh))
return handle_ble_peer_search_event(cast_ble_peer_search_event(aeh));
if (IS_ENABLED(CONFIG_CAF_BLE_COMMON_EVENTS) && is_ble_peer_operation_event(aeh))
return handle_ble_peer_operation_event(cast_ble_peer_operation_event(aeh));
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
APP_EVENT_SUBSCRIBE(MODULE, keyboard_led_event);
#ifdef CONFIG_CAF_BLE_COMMON_EVENTS
APP_EVENT_SUBSCRIBE(MODULE, ble_peer_event);
APP_EVENT_SUBSCRIBE(MODULE, ble_peer_search_event);
APP_EVENT_SUBSCRIBE(MODULE, ble_peer_operation_event);
#endif

View File

@@ -0,0 +1,219 @@
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/util.h>
#include <stdlib.h>
#include <app_event_manager.h>
#include <caf/events/power_event.h>
#include <caf/events/keep_alive_event.h>
#define MODULE mode_switch
#include <caf/events/module_state_event.h>
#include "mode_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE);
#define MODE_SENSE_NODE DT_NODELABEL(mode_sense)
#define MODE_SAMPLE_INTERVAL_MS 50
static const struct device *const mode_sensor_dev = DEVICE_DT_GET(MODE_SENSE_NODE);
static struct k_work_delayable mode_sample_work;
static atomic_t active;
static mode_type_t current_mode;
static uint8_t mode_stable_status;
static int init_mode_sensor(void)
{
if (!device_is_ready(mode_sensor_dev))
{
LOG_ERR("Mode sense device not ready");
return -ENODEV;
}
return 0;
}
static mode_type_t classify_mode_from_mv(int32_t mv)
{
/*
* 使用“距离最近中心点”的分类方式,避免阈值边界附近抖动时出现模式跳变。
* 三个中心点直接对应硬件设计目标电压USB=0mV、2.4G=1650mV、BLE=3300mV。
*/
static const int32_t centers[MODE_TYPE_COUNT] = {
[MODE_TYPE_USB] = 0,
[MODE_TYPE_BLE] = 3300,
[MODE_TYPE_2G4] = 1650,
};
int32_t best_diff = INT32_MAX;
mode_type_t best_mode = MODE_TYPE_USB;
for (size_t i = 0; i < MODE_TYPE_COUNT; i++)
{
int32_t diff = abs(mv - centers[i]);
if (diff < best_diff)
{
best_diff = diff;
best_mode = (mode_type_t)i;
}
}
return best_mode;
}
static int read_mode(mode_type_t *mode)
{
struct sensor_value value;
int err = sensor_sample_fetch(mode_sensor_dev);
if (err)
{
LOG_WRN("sensor_sample_fetch(mode) failed (err=%d)", err);
return err;
}
err = sensor_channel_get(mode_sensor_dev, SENSOR_CHAN_VOLTAGE, &value);
if (err)
{
LOG_WRN("sensor_channel_get(mode) failed (err=%d)", err);
return err;
}
int32_t v = (int32_t)sensor_value_to_milli(&value);
*mode = classify_mode_from_mv(v);
return 0;
}
static void publish_mode_event(mode_type_t mode)
{
if (current_mode == mode)
return;
current_mode = mode;
mode_event_submit(mode);
/*
* 模式切换是明确的人机交互动作。这里同步上报 keep_alive_event
* 让 power manager 重置休眠倒计时,避免用户刚切换模式就进入省电流程。
*/
keep_alive();
}
static void mode_sample_fn(struct k_work *work)
{
ARG_UNUSED(work);
if (!atomic_get(&active))
{
return;
}
mode_type_t sampled_mode;
int err = read_mode(&sampled_mode);
if (err)
{
LOG_ERR("ADC read failed (err=%d)", err);
module_set_state(MODULE_STATE_ERROR);
return;
}
mode_stable_status = mode_stable_status << 2;
mode_stable_status |= (sampled_mode & 0x3);
switch (mode_stable_status)
{
case 0b00000000:
publish_mode_event(MODE_TYPE_USB);
break;
case 0b01010101:
publish_mode_event(MODE_TYPE_BLE);
break;
case 0b10101010:
publish_mode_event(MODE_TYPE_2G4);
break;
default:
break;
}
k_work_reschedule(&mode_sample_work, K_MSEC(MODE_SAMPLE_INTERVAL_MS));
}
static void mode_switch_suspend(void)
{
if (!atomic_get(&active))
return;
atomic_set(&active, false);
(void)k_work_cancel_delayable(&mode_sample_work);
module_set_state(MODULE_STATE_STANDBY);
}
static void mode_switch_resume(void)
{
if (atomic_get(&active))
return;
atomic_set(&active, true);
k_work_reschedule(&mode_sample_work, K_NO_WAIT);
module_set_state(MODULE_STATE_READY);
}
static void init_mode_switch(void)
{
if (atomic_get(&active))
return;
if (init_mode_sensor())
{
module_set_state(MODULE_STATE_ERROR);
return;
}
mode_stable_status = 0xFF;
current_mode = MODE_TYPE_COUNT;
atomic_set(&active, false);
k_work_init_delayable(&mode_sample_work, mode_sample_fn);
mode_switch_resume();
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh))
{
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY))
{
init_mode_switch();
}
return false;
}
if (is_power_down_event(aeh))
{
mode_switch_suspend();
return false;
}
if (is_wake_up_event(aeh))
{
mode_switch_resume();
return false;
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

243
src/modules/qdec_module.c Normal file
View File

@@ -0,0 +1,243 @@
#include <errno.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/atomic.h>
#include <app_event_manager.h>
#include <caf/events/power_event.h>
#define MODULE qdec
#include <caf/events/module_state_event.h>
#include "qdec_step_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define QDEC_DEG_PER_STEP_EVENT 36
#define QDEC_EVENT_INTERVAL_MS 20
BUILD_ASSERT(QDEC_DEG_PER_STEP_EVENT > 0, "QDEC_DEG_PER_STEP_EVENT must be positive");
BUILD_ASSERT(QDEC_EVENT_INTERVAL_MS > 0, "QDEC_EVENT_INTERVAL_MS must be positive");
struct qdec_ctx {
const struct device *dev;
struct sensor_trigger trigger;
struct k_work_delayable emit_work;
atomic_t active;
int32_t acc_deg;
bool emit_scheduled;
};
static struct qdec_ctx qdec = {
.dev = DEVICE_DT_GET(DT_NODELABEL(qdec)),
.trigger = {
.type = SENSOR_TRIG_DATA_READY,
.chan = SENSOR_CHAN_ROTATION,
},
};
static void qdec_reset_state(void)
{
qdec.acc_deg = 0;
qdec.emit_scheduled = false;
}
static int qdec_device_set_enabled(bool enable)
{
int err = pm_device_action_run(qdec.dev,
enable ? PM_DEVICE_ACTION_RESUME :
PM_DEVICE_ACTION_SUSPEND);
if ((err == 0) || (err == -EALREADY)) {
return 0;
}
return err;
}
static void schedule_emit_work(void)
{
if (!atomic_get(&qdec.active)) {
return;
}
if (qdec.emit_scheduled) {
return;
}
qdec.emit_scheduled = true;
(void)k_work_reschedule(&qdec.emit_work, K_MSEC(QDEC_EVENT_INTERVAL_MS));
}
static void qdec_emit_work_handler(struct k_work *work)
{
int8_t step_delta;
ARG_UNUSED(work);
if (!atomic_get(&qdec.active)) {
qdec.emit_scheduled = false;
return;
}
qdec.emit_scheduled = false;
if ((qdec.acc_deg < QDEC_DEG_PER_STEP_EVENT) &&
(qdec.acc_deg > -QDEC_DEG_PER_STEP_EVENT)) {
return;
}
if (qdec.acc_deg > 0) {
step_delta = 1;
qdec.acc_deg -= QDEC_DEG_PER_STEP_EVENT;
} else {
step_delta = -1;
qdec.acc_deg += QDEC_DEG_PER_STEP_EVENT;
}
qdec_step_event_submit(step_delta);
if ((qdec.acc_deg >= QDEC_DEG_PER_STEP_EVENT) ||
(qdec.acc_deg <= -QDEC_DEG_PER_STEP_EVENT)) {
schedule_emit_work();
}
}
static void qdec_data_handler(const struct device *dev,
const struct sensor_trigger *trigger)
{
struct sensor_value rotation = {0};
int err;
ARG_UNUSED(trigger);
if (!atomic_get(&qdec.active)) {
return;
}
err = sensor_sample_fetch_chan(dev, SENSOR_CHAN_ROTATION);
if (err) {
LOG_ERR("QDEC sample fetch failed: %d", err);
return;
}
err = sensor_channel_get(dev, SENSOR_CHAN_ROTATION, &rotation);
if (err) {
LOG_ERR("QDEC channel get failed: %d", err);
return;
}
qdec.acc_deg += rotation.val1;
LOG_DBG("QDEC rotation=%d acc_deg=%d", rotation.val1, qdec.acc_deg);
if ((qdec.acc_deg >= QDEC_DEG_PER_STEP_EVENT) ||
(qdec.acc_deg <= -QDEC_DEG_PER_STEP_EVENT)) {
schedule_emit_work();
}
}
static int qdec_init(void)
{
int err;
if (!device_is_ready(qdec.dev)) {
LOG_ERR("QDEC device not ready");
return -ENODEV;
}
qdec_reset_state();
atomic_set(&qdec.active, false);
k_work_init_delayable(&qdec.emit_work, qdec_emit_work_handler);
err = sensor_trigger_set(qdec.dev, &qdec.trigger, qdec_data_handler);
if (err) {
LOG_ERR("QDEC trigger set failed: %d", err);
return err;
}
LOG_INF("QDEC initialized: %d deg/step, <=1 step per %d ms",
QDEC_DEG_PER_STEP_EVENT, QDEC_EVENT_INTERVAL_MS);
return 0;
}
static void qdec_module_suspend(void)
{
int err;
if (!atomic_get(&qdec.active)) {
return;
}
atomic_set(&qdec.active, false);
(void)k_work_cancel_delayable(&qdec.emit_work);
qdec_reset_state();
err = qdec_device_set_enabled(false);
if (err) {
LOG_WRN("QDEC suspend failed: %d", err);
}
module_set_state(MODULE_STATE_STANDBY);
}
static void qdec_module_resume(void)
{
int err;
if (atomic_get(&qdec.active)) {
return;
}
err = qdec_device_set_enabled(true);
if (err) {
LOG_ERR("QDEC resume failed: %d", err);
module_set_state(MODULE_STATE_ERROR);
return;
}
qdec_reset_state();
atomic_set(&qdec.active, true);
module_set_state(MODULE_STATE_READY);
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
int err = qdec_init();
if (err) {
module_set_state(MODULE_STATE_ERROR);
} else {
qdec_module_resume();
}
}
return false;
}
if (is_power_down_event(aeh)) {
qdec_module_suspend();
return false;
}
if (is_wake_up_event(aeh)) {
qdec_module_resume();
return false;
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);

View File

@@ -0,0 +1,374 @@
#include <errno.h>
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/spinlock.h>
#include <app_event_manager.h>
#define MODULE time_manager
#include <caf/events/module_state_event.h>
#include <caf/events/power_event.h>
#include "time_manager.h"
#include "time_sync_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define TIME_MANAGER_SAVE_DELAY K_HOURS(24)
#define TIME_MANAGER_STORAGE_KEY "state"
/*
* 持久化数据只记录“最近一次成功校时”的元数据:
* - 它可以帮助我们知道设备曾经被校时过;
* - 但由于当前没有独立 RTC重启后不能把这份数据当作“当前仍准确”的时间。
*/
struct time_manager_storage_data {
uint64_t utc_ms;
int16_t timezone_min;
uint32_t accuracy_ms;
uint8_t source;
uint8_t valid_marker;
};
struct time_manager_ctx {
struct k_spinlock lock;
struct k_work_delayable save_work;
struct time_manager_storage_data persisted;
uint64_t base_utc_ms;
int64_t base_uptime_ms;
int16_t timezone_min;
uint32_t accuracy_ms;
enum time_sync_source source;
bool ready;
bool synchronized;
bool has_persisted_time;
bool storage_dirty;
bool storage_loaded;
};
static struct time_manager_ctx time_ctx;
/* 统一判断来源是否合法,避免把损坏配置或“未设置来源”写进时间状态。 */
static bool time_manager_source_is_valid(enum time_sync_source source)
{
switch (source) {
case TIME_SYNC_SOURCE_BLE:
case TIME_SYNC_SOURCE_USB:
case TIME_SYNC_SOURCE_MANUAL:
case TIME_SYNC_SOURCE_HID:
return true;
default:
return false;
}
}
/* 对时区做保守校验,避免异常包把显示层带到离谱偏移。 */
static bool time_manager_timezone_is_valid(int16_t timezone_min)
{
return (timezone_min >= -(24 * 60)) && (timezone_min <= (24 * 60));
}
/*
* 保存工作在系统工作队列里执行:
* - 这样同步入口只做内存更新;
* - flash 写入被节流到 24 小时窗口,降低频繁校时带来的磨损。
*/
static int time_manager_store_state(const struct time_manager_storage_data *storage)
{
char key[] = MODULE_NAME "/" TIME_MANAGER_STORAGE_KEY;
int err = settings_save_one(key, storage, sizeof(*storage));
if (err) {
LOG_ERR("Failed to save time state err=%d", err);
return err;
}
LOG_INF("Stored time state src=%u utc_ms=%llu",
storage->source,
(unsigned long long)storage->utc_ms);
return 0;
}
/*
* 从运行态复制一份稳定快照,再在锁外执行 flash 写入:
* - 锁内只做小块 memcpy避免长时间持锁
* - 即使保存失败,也不回滚内存时间状态,避免影响当前功能。
*/
static void time_manager_save_work_fn(struct k_work *work)
{
struct time_manager_storage_data storage;
bool should_store;
k_spinlock_key_t key;
ARG_UNUSED(work);
key = k_spin_lock(&time_ctx.lock);
should_store = time_ctx.storage_dirty && time_ctx.has_persisted_time;
storage = time_ctx.persisted;
time_ctx.storage_dirty = false;
k_spin_unlock(&time_ctx.lock, key);
if (!should_store) {
return;
}
(void)time_manager_store_state(&storage);
}
/*
* 把一次同步结果写入运行态:
* - base_utc_ms + base_uptime_ms 组成当前时间基准;
* - persisted 只保存“最近一次同步结果”,后续异步落盘。
*/
static void time_manager_apply_update(const struct time_sync_update *update)
{
k_spinlock_key_t key = k_spin_lock(&time_ctx.lock);
time_ctx.base_utc_ms = update->utc_ms;
time_ctx.base_uptime_ms = k_uptime_get();
time_ctx.timezone_min = update->timezone_min;
time_ctx.accuracy_ms = update->accuracy_ms;
time_ctx.source = update->source;
time_ctx.synchronized = true;
time_ctx.has_persisted_time = true;
time_ctx.persisted.utc_ms = update->utc_ms;
time_ctx.persisted.timezone_min = update->timezone_min;
time_ctx.persisted.accuracy_ms = update->accuracy_ms;
time_ctx.persisted.source = (uint8_t)update->source;
time_ctx.persisted.valid_marker = 1U;
time_ctx.storage_dirty = true;
k_spin_unlock(&time_ctx.lock, key);
/*
* 时间同步允许立即生效,但 flash 落盘不要求同步完成后立刻发生。
* 这里将写入节流到 24 小时窗口:只要当前还没有待执行的保存工作,
* 就挂一个延迟保存;后续新的校时只更新内存快照,不反复重置定时器。
*/
if (!k_work_delayable_is_pending(&time_ctx.save_work)) {
k_work_schedule(&time_ctx.save_work, TIME_MANAGER_SAVE_DELAY);
}
LOG_INF("Time synchronized src=%u tz=%d utc_ms=%llu acc=%u",
update->source,
update->timezone_min,
(unsigned long long)update->utc_ms,
update->accuracy_ms);
}
/*
* settings 子系统回调只负责恢复原始持久化结构:
* - 不在这里决定“当前时间是否有效”;
* - 运行态初始化放到 settings_loader ready 之后统一完成。
*/
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, TIME_MANAGER_STORAGE_KEY) != 0) {
return 0;
}
if (len_rd != sizeof(time_ctx.persisted)) {
LOG_WRN("Time state size mismatch got=%u expect=%u",
(uint32_t)len_rd,
sizeof(time_ctx.persisted));
time_ctx.storage_loaded = false;
return 0;
}
rc = read_cb(cb_arg, &time_ctx.persisted, sizeof(time_ctx.persisted));
time_ctx.storage_loaded = (rc == sizeof(time_ctx.persisted));
if (!time_ctx.storage_loaded) {
LOG_WRN("Time state read failed rc=%d", (int)rc);
}
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(time_manager,
MODULE_NAME,
NULL,
settings_set,
NULL,
NULL);
/*
* settings 恢复完成后初始化运行态:
* - 有持久化历史仅表示“以前同步过”,不能直接把时间标记成有效;
* - 当前 boot 仍然需要新的同步事件来建立可信时间基准。
*/
static void time_manager_init_after_settings_loaded(void)
{
k_spinlock_key_t key = k_spin_lock(&time_ctx.lock);
time_ctx.ready = true;
time_ctx.synchronized = false;
time_ctx.source = TIME_SYNC_SOURCE_NONE;
time_ctx.base_utc_ms = 0U;
time_ctx.base_uptime_ms = 0;
time_ctx.timezone_min = 0;
time_ctx.accuracy_ms = 0U;
time_ctx.has_persisted_time = time_ctx.storage_loaded &&
(time_ctx.persisted.valid_marker == 1U) &&
time_manager_source_is_valid(
(enum time_sync_source)time_ctx.persisted.source) &&
time_manager_timezone_is_valid(
time_ctx.persisted.timezone_min);
time_ctx.storage_dirty = false;
k_spin_unlock(&time_ctx.lock, key);
if (time_ctx.has_persisted_time) {
LOG_INF("Loaded persisted time metadata tz=%d src=%u utc_ms=%llu",
time_ctx.persisted.timezone_min,
time_ctx.persisted.source,
(unsigned long long)time_ctx.persisted.utc_ms);
} else {
LOG_INF("No valid persisted time metadata");
}
module_set_state(MODULE_STATE_READY);
}
/*
* 校时事件入口只做快速校验和内存更新:
* - 数据格式错误直接丢弃;
* - flash 持久化交给延迟工作处理。
*/
static bool handle_time_sync_event(const struct time_sync_event *event)
{
const struct time_sync_update *update = time_sync_event_get_update(event);
if (!time_ctx.ready) {
LOG_WRN("Drop time sync before manager ready");
return false;
}
if (!time_manager_source_is_valid(update->source)) {
LOG_WRN("Drop time sync invalid source=%u", update->source);
return false;
}
if (!time_manager_timezone_is_valid(update->timezone_min)) {
LOG_WRN("Drop time sync invalid timezone=%d", update->timezone_min);
return false;
}
if (update->utc_ms == 0U) {
LOG_WRN("Drop time sync utc_ms=0");
return false;
}
time_manager_apply_update(update);
return false;
}
/* 仅在 settings_loader 完成后宣布 READY保证外部读取到的是稳定状态。 */
static bool handle_module_state_event(const struct module_state_event *event)
{
if (!check_state(event, MODULE_ID(settings_loader), MODULE_STATE_READY)) {
return false;
}
if (time_ctx.ready) {
return false;
}
k_work_init_delayable(&time_ctx.save_work, time_manager_save_work_fn);
time_manager_init_after_settings_loaded();
return false;
}
bool time_manager_is_ready(void)
{
k_spinlock_key_t key = k_spin_lock(&time_ctx.lock);
bool ready = time_ctx.ready;
k_spin_unlock(&time_ctx.lock, key);
return ready;
}
/*
* 获取快照时只复制一次基准,再在锁外计算当前 UTC
* - 这样不会让调用者在锁里做时间换算;
* - 也避免 64 位字段在 32 位 MCU 上被撕裂读取。
*/
int time_manager_get_snapshot(struct time_manager_snapshot *snapshot)
{
uint64_t base_utc_ms;
int64_t base_uptime_ms;
int16_t timezone_min;
uint32_t accuracy_ms;
enum time_sync_source source;
bool ready;
bool synchronized;
bool has_persisted_time;
k_spinlock_key_t key;
int64_t elapsed_ms;
if (!snapshot) {
return -EINVAL;
}
key = k_spin_lock(&time_ctx.lock);
ready = time_ctx.ready;
synchronized = time_ctx.synchronized;
has_persisted_time = time_ctx.has_persisted_time;
base_utc_ms = time_ctx.base_utc_ms;
base_uptime_ms = time_ctx.base_uptime_ms;
timezone_min = time_ctx.timezone_min;
accuracy_ms = time_ctx.accuracy_ms;
source = time_ctx.source;
k_spin_unlock(&time_ctx.lock, key);
snapshot->ready = ready;
snapshot->synchronized = synchronized;
snapshot->has_persisted_time = has_persisted_time;
snapshot->timezone_min = timezone_min;
snapshot->accuracy_ms = accuracy_ms;
snapshot->source = source;
snapshot->utc_ms = 0U;
if (!ready) {
return -EAGAIN;
}
if (!synchronized) {
return -ENODATA;
}
elapsed_ms = k_uptime_get() - base_uptime_ms;
if (elapsed_ms < 0) {
elapsed_ms = 0;
}
snapshot->utc_ms = base_utc_ms + (uint64_t)elapsed_ms;
return 0;
}
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_time_sync_event(aeh)) {
return handle_time_sync_event(cast_time_sync_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, time_sync_event);

View File

@@ -0,0 +1,925 @@
#include <errno.h>
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/usb/class/usbd_hid.h>
#include <zephyr/usb/usbd.h>
#include <app_event_manager.h>
#include <caf/events/power_event.h>
#define MODULE usb_hid
#include <caf/events/module_state_event.h>
#include "hid_report_descriptor.h"
#include "hid_boot_event.h"
#include "hid_host_command_event.h"
#include "hid_protocol_event.h"
#include "hid_tx_done_event.h"
#include "hid_tx_event.h"
#include "hid_vendor_mask_event.h"
#include "keyboard_led_event.h"
#include "mode_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define APP_USB_VID 0x1209
#define APP_USB_PID 0x0001
/*
* 模块目标:
* 1) 模块内聚控制 USB HID 栈生命周期(初始化/启用/禁用)。
* 2) 对外发布 HID 协议事件和 NumLock 指示事件。
* 3) 仅响应 mode_eventUSB/BLE/2.4G)和 power_event休眠/唤醒)。
*
* 约束:
* - 启动时只做 USB 设备初始化,不自动 enable USB 栈;
* - 只有当 mode 切到 USB 且系统非休眠时才 enable
* - BLE 逻辑保持不变,不在本模块中触碰。
*/
struct usb_hid_iface
{
const struct device *dev;
bool iface_ready;
bool in_flight;
};
enum usb_hid_stack_state
{
USB_HID_STACK_STATE_OFF,
USB_HID_STACK_STATE_READY,
USB_HID_STACK_STATE_ACTIVE,
USB_HID_STACK_STATE_ERROR,
};
struct usb_hid_policy
{
bool usb_mode_selected;
bool pm_suspended;
};
struct usb_hid_ctx
{
struct usb_hid_iface boot;
struct usb_hid_iface nkro;
struct usb_hid_iface raw;
enum usb_hid_stack_state stack_state;
struct usb_hid_policy policy;
enum hid_protocol_type current_protocol;
};
static struct usb_hid_ctx g_usb_hid = {
.current_protocol = HID_PROTO_REPORT,
};
static void submit_usb_tx_done(enum hid_tx_kind kind, bool success)
{
hid_tx_done_event_submit(kind, success);
}
USBD_DEVICE_DEFINE(new_kbd_usbd,
DEVICE_DT_GET(DT_NODELABEL(usbd)),
APP_USB_VID, APP_USB_PID);
USBD_DESC_LANG_DEFINE(new_kbd_lang);
USBD_DESC_MANUFACTURER_DEFINE(new_kbd_mfr, "new_kbd");
USBD_DESC_PRODUCT_DEFINE(new_kbd_product, "new_kbd composite HID");
USBD_DESC_CONFIG_DEFINE(new_kbd_fs_cfg_desc, "FS Configuration");
USBD_CONFIGURATION_DEFINE(new_kbd_fs_config, 0, 100, &new_kbd_fs_cfg_desc);
static const uint8_t boot_report_desc[] = HID_KEYBOARD_REPORT_DESC();
static const uint8_t nkro_report_desc[] = HID_DESC_KEYBOARD_NKRO_CONSUMER();
static const uint8_t raw_report_desc[] = HID_DESC_RAW_64();
static bool usb_hid_stack_is_active(void)
{
return g_usb_hid.stack_state == USB_HID_STACK_STATE_ACTIVE;
}
static bool usb_hid_stack_is_error(void)
{
return g_usb_hid.stack_state == USB_HID_STACK_STATE_ERROR;
}
static bool usb_hid_should_be_active(void)
{
return g_usb_hid.policy.usb_mode_selected && !g_usb_hid.policy.pm_suspended;
}
static bool usb_hid_should_handle_tx_event(const struct hid_tx_event *event)
{
switch (hid_tx_event_get_route(event)) {
case HID_TX_ROUTE_AUTO:
return g_usb_hid.policy.usb_mode_selected;
case HID_TX_ROUTE_USB:
return true;
case HID_TX_ROUTE_BLE:
default:
return false;
}
}
static struct usb_hid_iface *usb_hid_iface_from_dev(const struct device *dev)
{
if (dev == g_usb_hid.boot.dev)
{
return &g_usb_hid.boot;
}
if (dev == g_usb_hid.nkro.dev)
{
return &g_usb_hid.nkro;
}
if (dev == g_usb_hid.raw.dev)
{
return &g_usb_hid.raw;
}
return NULL;
}
static void usb_hid_clear_runtime_iface_state(void)
{
g_usb_hid.boot.iface_ready = false;
g_usb_hid.nkro.iface_ready = false;
g_usb_hid.raw.iface_ready = false;
g_usb_hid.boot.in_flight = false;
g_usb_hid.nkro.in_flight = false;
g_usb_hid.raw.in_flight = false;
}
static bool should_handle_led_input_from_dev(const struct device *dev)
{
if (g_usb_hid.current_protocol == HID_PROTO_BOOT)
return (dev == g_usb_hid.boot.dev);
return (dev == g_usb_hid.nkro.dev);
}
static bool try_extract_led_mask(const struct device *dev,
uint16_t len,
const uint8_t *buf,
uint8_t *led_mask)
{
if ((buf == NULL) || (len == 0U))
return false;
if (dev == g_usb_hid.boot.dev)
{
*led_mask = buf[0];
return true;
}
if (dev != g_usb_hid.nkro.dev)
return false;
if (len >= 2U)
{
if (buf[0] != REPORT_ID_KEYBOARD)
return false;
*led_mask = buf[1];
return true;
}
*led_mask = buf[0];
return true;
}
static bool try_extract_vendor_mask(const struct device *dev,
uint16_t len,
const uint8_t *buf,
const uint8_t **mask_data,
size_t *mask_len)
{
if ((buf == NULL) || (len < 1U))
{
return false;
}
if (dev != g_usb_hid.nkro.dev)
{
return false;
}
if (buf[0] != REPORT_ID_VENDOR)
{
return false;
}
if ((len - 1U) != HID_VENDOR_PAYLOAD_SIZE)
{
return false;
}
*mask_data = &buf[1];
*mask_len = len - 1U;
return true;
}
static bool try_extract_host_command(const struct device *dev,
uint16_t len,
const uint8_t *buf,
uint8_t *cmd,
const uint8_t **data,
size_t *data_len)
{
if ((buf == NULL) || (len < 1U)) {
return false;
}
if (dev != g_usb_hid.nkro.dev) {
return false;
}
if (buf[0] != REPORT_ID_VENDOR_CMD) {
return false;
}
if ((len - 1U) != HID_HOST_CMD_OUTPUT_PAYLOAD_SIZE) {
return false;
}
*cmd = buf[1];
*data = &buf[2];
*data_len = len - 2U;
return true;
}
static int hid_stub_get_report(const struct device *dev,
uint8_t type, uint8_t id,
uint16_t len, uint8_t *buf)
{
ARG_UNUSED(dev);
ARG_UNUSED(type);
ARG_UNUSED(id);
ARG_UNUSED(len);
ARG_UNUSED(buf);
return -ENOTSUP;
}
static int hid_stub_set_report(const struct device *dev,
uint8_t type, uint8_t id,
uint16_t len, const uint8_t *buf)
{
ARG_UNUSED(type);
ARG_UNUSED(id);
const uint8_t *mask_data;
size_t mask_len;
uint8_t cmd;
const uint8_t *cmd_data;
size_t cmd_len;
if (try_extract_vendor_mask(dev, len, buf, &mask_data, &mask_len))
{
LOG_INF("hid_stub_set_report vendor mask len=%u", mask_len);
hid_vendor_mask_event_submit(mask_data, mask_len);
return 0;
}
if (try_extract_host_command(dev, len, buf, &cmd, &cmd_data, &cmd_len))
{
LOG_INF("hid_stub_set_report vendor cmd=0x%02x len=%u", cmd, cmd_len);
hid_host_command_event_submit(HID_HOST_TRANSPORT_USB, cmd, cmd_data, cmd_len);
return 0;
}
if (!should_handle_led_input_from_dev(dev))
{
return 0;
}
uint8_t led_mask;
if (!try_extract_led_mask(dev, len, buf, &led_mask))
{
return 0;
}
LOG_INF("hid_stub_set_report led_mask=0x%02x", led_mask);
keyboard_led_event_submit(led_mask);
return 0;
}
static void hid_stub_set_idle(const struct device *dev, uint8_t id, uint32_t duration)
{
ARG_UNUSED(dev);
ARG_UNUSED(id);
ARG_UNUSED(duration);
}
static uint32_t hid_stub_get_idle(const struct device *dev, uint8_t id)
{
ARG_UNUSED(dev);
ARG_UNUSED(id);
return 0;
}
static void hid_stub_set_protocol(const struct device *dev, uint8_t proto)
{
ARG_UNUSED(dev);
enum hid_protocol_type new_protocol =
(proto == HID_PROTOCOL_BOOT) ? HID_PROTO_BOOT : HID_PROTO_REPORT;
if (g_usb_hid.current_protocol == new_protocol)
{
return;
}
g_usb_hid.current_protocol = new_protocol;
/*
* 按需求USB HID 在连接后收到 set_protocol 时上报 hid_protocol_event。
* 这里额外检查接口 ready避免在未枚举完成阶段上报无意义协议切换。
*/
if (g_usb_hid.boot.iface_ready || g_usb_hid.nkro.iface_ready)
{
hid_protocol_event_submit(new_protocol);
}
}
static void hid_stub_input_done(const struct device *dev, const uint8_t *report)
{
ARG_UNUSED(report);
/*
* 发送完成回调:
* - 仅在这里清除“在途发送”标志,确保“上一包未完成则丢弃新包”的策略可闭环;
* - 若收到未知 dev 的回调,仅记录告警,避免静默状态错乱。
*/
struct usb_hid_iface *iface = usb_hid_iface_from_dev(dev);
if (iface)
{
iface->in_flight = false;
submit_usb_tx_done((dev == g_usb_hid.boot.dev) ? HID_TX_KIND_BOOT : HID_TX_KIND_REPORT,
true);
return;
}
LOG_WRN("input_done from unknown HID dev: %p", (void *)dev);
}
static void hid_stub_output_report(const struct device *dev, uint16_t len, const uint8_t *buf)
{
const uint8_t *mask_data;
size_t mask_len;
uint8_t cmd;
const uint8_t *cmd_data;
size_t cmd_len;
if (try_extract_vendor_mask(dev, len, buf, &mask_data, &mask_len))
{
LOG_INF("hid_stub_output_report vendor mask len=%u", mask_len);
hid_vendor_mask_event_submit(mask_data, mask_len);
return;
}
if (try_extract_host_command(dev, len, buf, &cmd, &cmd_data, &cmd_len))
{
LOG_INF("hid_stub_output_report vendor cmd=0x%02x len=%u", cmd, cmd_len);
hid_host_command_event_submit(HID_HOST_TRANSPORT_USB,
cmd, cmd_data, cmd_len);
return;
}
if (!should_handle_led_input_from_dev(dev))
{
return;
}
uint8_t led_mask;
if (!try_extract_led_mask(dev, len, buf, &led_mask))
{
return;
}
LOG_INF("hid_stub_output_report led_mask=0x%02x", led_mask);
keyboard_led_event_submit(led_mask);
}
static void hid_iface_ready_cb(const struct device *dev, bool ready)
{
struct usb_hid_iface *iface = usb_hid_iface_from_dev(dev);
if (!iface)
{
return;
}
iface->iface_ready = ready;
if (!ready)
{
iface->in_flight = false;
}
}
static const struct hid_device_ops boot_hid_ops = {
.iface_ready = hid_iface_ready_cb,
.get_report = hid_stub_get_report,
.set_report = hid_stub_set_report,
.set_idle = hid_stub_set_idle,
.get_idle = hid_stub_get_idle,
.set_protocol = hid_stub_set_protocol,
.input_report_done = hid_stub_input_done,
.output_report = hid_stub_output_report,
};
static const struct hid_device_ops report_hid_ops = {
.iface_ready = hid_iface_ready_cb,
.get_report = hid_stub_get_report,
.set_report = hid_stub_set_report,
.set_idle = hid_stub_set_idle,
.get_idle = hid_stub_get_idle,
.set_protocol = hid_stub_set_protocol,
.input_report_done = hid_stub_input_done,
.output_report = hid_stub_output_report,
};
static const struct hid_device_ops raw_hid_ops = {
.iface_ready = hid_iface_ready_cb,
.get_report = hid_stub_get_report,
.set_report = hid_stub_set_report,
.set_idle = hid_stub_set_idle,
.get_idle = hid_stub_get_idle,
.input_report_done = hid_stub_input_done,
.output_report = hid_stub_output_report,
};
static void usbd_msg_cb(struct usbd_context *const usbd_ctx,
const struct usbd_msg *const msg)
{
switch (msg->type)
{
case USBD_MSG_VBUS_READY:
if (g_usb_hid.policy.pm_suspended)
{
LOG_INF("VBUS ready: submit wake_up_event");
APP_EVENT_SUBMIT(new_wake_up_event());
}
/*
* 只有在 USB 模式下才允许拉起 USB 栈。
* 这样即使插着线,只要用户切到 BLE/2.4G,也不会强制进入 USB HID。
*/
if (usbd_can_detect_vbus(usbd_ctx) && usb_hid_stack_is_active())
{
(void)usbd_enable(usbd_ctx);
}
break;
case USBD_MSG_VBUS_REMOVED:
break;
case USBD_MSG_SUSPEND:
case USBD_MSG_RESUME:
case USBD_MSG_CONFIGURATION:
break;
case USBD_MSG_UDC_ERROR:
case USBD_MSG_STACK_ERROR:
LOG_ERR("USBD stack error message: %d", msg->type);
g_usb_hid.stack_state = USB_HID_STACK_STATE_ERROR;
break;
default:
break;
}
}
static bool usb_hid_devices_ready(void)
{
if (!device_is_ready(g_usb_hid.boot.dev))
{
LOG_ERR("HID boot device is not ready");
return false;
}
if (!device_is_ready(g_usb_hid.nkro.dev))
{
LOG_ERR("HID nkro device is not ready");
return false;
}
if (!device_is_ready(g_usb_hid.raw.dev))
{
LOG_ERR("HID raw device is not ready");
return false;
}
if (!device_is_ready(DEVICE_DT_GET(DT_NODELABEL(usbd))))
{
LOG_ERR("USBD device is not ready");
return false;
}
return true;
}
static int usb_hid_register_hid_devices(void)
{
int err = hid_device_register(g_usb_hid.boot.dev,
boot_report_desc, sizeof(boot_report_desc),
&boot_hid_ops);
if (err)
{
LOG_ERR("hid_device_register(boot) failed: %d", err);
return err;
}
err = hid_device_register(g_usb_hid.nkro.dev,
nkro_report_desc, sizeof(nkro_report_desc),
&report_hid_ops);
if (err)
{
LOG_ERR("hid_device_register(nkro) failed: %d", err);
return err;
}
err = hid_device_register(g_usb_hid.raw.dev,
raw_report_desc, sizeof(raw_report_desc),
&raw_hid_ops);
if (err)
{
LOG_ERR("hid_device_register(raw) failed: %d", err);
return err;
}
return 0;
}
static int usb_hid_configure_usbd(void)
{
int err = usbd_add_descriptor(&new_kbd_usbd, &new_kbd_lang);
if (err)
{
LOG_ERR("usbd_add_descriptor(lang) failed: %d", err);
return err;
}
err = usbd_add_descriptor(&new_kbd_usbd, &new_kbd_mfr);
if (err)
{
LOG_ERR("usbd_add_descriptor(mfr) failed: %d", err);
return err;
}
err = usbd_add_descriptor(&new_kbd_usbd, &new_kbd_product);
if (err)
{
LOG_ERR("usbd_add_descriptor(product) failed: %d", err);
return err;
}
err = usbd_add_configuration(&new_kbd_usbd, USBD_SPEED_FS, &new_kbd_fs_config);
if (err)
{
LOG_ERR("usbd_add_configuration failed: %d", err);
return err;
}
err = usbd_register_all_classes(&new_kbd_usbd, USBD_SPEED_FS, 1, NULL);
if (err)
{
LOG_ERR("usbd_register_all_classes failed: %d", err);
return err;
}
return 0;
}
static int usb_hid_init_usbd_stack(void)
{
int err;
usbd_device_set_code_triple(&new_kbd_usbd, USBD_SPEED_FS, 0, 0, 0);
err = usbd_msg_register_cb(&new_kbd_usbd, usbd_msg_cb);
if (err)
{
LOG_ERR("usbd_msg_register_cb failed: %d", err);
return err;
}
err = usbd_init(&new_kbd_usbd);
if (err && (err != -EALREADY))
{
LOG_ERR("usbd_init failed: %d", err);
return err;
}
return 0;
}
static int usb_hid_stack_init(void)
{
if (g_usb_hid.stack_state != USB_HID_STACK_STATE_OFF)
{
return 0;
}
g_usb_hid.boot.dev = DEVICE_DT_GET(DT_NODELABEL(hid_dev_0));
g_usb_hid.nkro.dev = DEVICE_DT_GET(DT_NODELABEL(hid_dev_1));
g_usb_hid.raw.dev = DEVICE_DT_GET(DT_NODELABEL(raw_hid));
if (!usb_hid_devices_ready())
{
return -ENODEV;
}
int err = usb_hid_register_hid_devices();
if (err)
{
return err;
}
err = usb_hid_configure_usbd();
if (err)
{
return err;
}
err = usb_hid_init_usbd_stack();
if (err)
{
return err;
}
g_usb_hid.stack_state = USB_HID_STACK_STATE_READY;
return 0;
}
static int usb_hid_set_enabled(bool enable)
{
int err;
if (usb_hid_stack_is_error())
{
return -EIO;
}
if (g_usb_hid.stack_state == USB_HID_STACK_STATE_OFF)
{
err = usb_hid_stack_init();
if (err)
{
return err;
}
}
if (enable && usb_hid_stack_is_active())
{
return 0;
}
if (!enable && (g_usb_hid.stack_state == USB_HID_STACK_STATE_READY))
{
return 0;
}
if (enable)
{
err = usbd_enable(&new_kbd_usbd);
}
else
{
err = usbd_disable(&new_kbd_usbd);
usb_hid_clear_runtime_iface_state();
}
if (err && (err != -EALREADY))
{
LOG_ERR("usbd_%s failed: %d", enable ? "enable" : "disable", err);
g_usb_hid.stack_state = USB_HID_STACK_STATE_ERROR;
return err;
}
g_usb_hid.stack_state = enable ? USB_HID_STACK_STATE_ACTIVE : USB_HID_STACK_STATE_READY;
return 0;
}
static void refresh_usb_state_by_policy(void)
{
/*
* 控制策略:
* - USB 模式 + 非休眠:启用 USB HID。
* - 其他情况:关闭 USB HID不销毁初始化结果后续可快速恢复
*/
bool should_enable = usb_hid_should_be_active();
int err = usb_hid_set_enabled(should_enable);
if (err)
{
LOG_ERR("usb_hid_set_enabled(%d) failed: %d", should_enable, err);
}
}
static bool handle_module_state_event(const struct module_state_event *event)
{
if (!check_state(event, MODULE_ID(main), MODULE_STATE_READY))
{
return false;
}
int err = usb_hid_stack_init();
if (err)
{
LOG_ERR("USB HID stack init failed: %d", err);
g_usb_hid.stack_state = USB_HID_STACK_STATE_ERROR;
module_set_state(MODULE_STATE_ERROR);
return false;
}
module_set_state(MODULE_STATE_READY);
return false;
}
static bool handle_mode_event(const struct mode_event *event)
{
g_usb_hid.policy.usb_mode_selected = (event->mode_type == MODE_TYPE_USB);
refresh_usb_state_by_policy();
return false;
}
static bool handle_power_down_event(void)
{
if (g_usb_hid.policy.pm_suspended)
{
/* 避免重复上报 STANDBY 导致 power_manager 在 SUSPENDING 期间反复迭代。 */
return false;
}
g_usb_hid.policy.pm_suspended = true;
refresh_usb_state_by_policy();
module_set_state(MODULE_STATE_STANDBY);
return false;
}
static bool handle_wake_up_event(void)
{
if (!g_usb_hid.policy.pm_suspended)
{
return false;
}
g_usb_hid.policy.pm_suspended = false;
refresh_usb_state_by_policy();
module_set_state(MODULE_STATE_READY);
return false;
}
static bool handle_hid_tx_event(const struct hid_tx_event *event)
{
if (!usb_hid_should_handle_tx_event(event))
{
return false;
}
if (!usb_hid_stack_is_active())
{
submit_usb_tx_done(event->kind, false);
return false;
}
if (event->kind == HID_TX_KIND_BOOT)
{
const uint8_t *payload = hid_tx_event_get_data(event);
size_t payload_len = hid_tx_event_get_size(event);
int err;
if (g_usb_hid.current_protocol != HID_PROTO_BOOT)
{
submit_usb_tx_done(HID_TX_KIND_BOOT, false);
return false;
}
if (!g_usb_hid.boot.iface_ready || !g_usb_hid.boot.dev)
{
submit_usb_tx_done(HID_TX_KIND_BOOT, false);
return false;
}
if (g_usb_hid.boot.in_flight)
{
LOG_WRN("Drop boot tx: previous report not sent");
submit_usb_tx_done(HID_TX_KIND_BOOT, false);
return false;
}
err = hid_device_submit_report(g_usb_hid.boot.dev,
payload_len,
payload);
if (err)
{
LOG_WRN("USB boot report send failed err=%d", err);
submit_usb_tx_done(HID_TX_KIND_BOOT, false);
}
else
{
g_usb_hid.boot.in_flight = true;
}
return false;
}
/*
* USB 侧仅在 active 条件满足时发送:
* - 当前 mode 为 USB
* - USB HID 栈已启用且对应接口 ready。
*/
if (g_usb_hid.current_protocol != HID_PROTO_REPORT)
{
submit_usb_tx_done(HID_TX_KIND_REPORT, false);
return false;
}
const uint8_t *data = hid_tx_event_get_data(event);
size_t data_len = hid_tx_event_get_size(event);
uint8_t report_id;
if (data_len < 1U)
{
submit_usb_tx_done(HID_TX_KIND_REPORT, false);
return false;
}
report_id = data[0];
if (!g_usb_hid.nkro.iface_ready || !g_usb_hid.nkro.dev)
{
submit_usb_tx_done(HID_TX_KIND_REPORT, false);
return false;
}
if ((report_id != REPORT_ID_KEYBOARD) &&
(report_id != REPORT_ID_CONSUMER) &&
(report_id != REPORT_ID_VENDOR) &&
(report_id != REPORT_ID_VENDOR_CMD))
{
submit_usb_tx_done(HID_TX_KIND_REPORT, false);
return false;
}
if (g_usb_hid.nkro.in_flight)
{
LOG_WRN("Drop tx report id=0x%02x: previous report not sent", report_id);
submit_usb_tx_done(HID_TX_KIND_REPORT, false);
return false;
}
/* Report 协议下 dyndata 是 [report_id|payload],可直接透传。 */
int err = hid_device_submit_report(g_usb_hid.nkro.dev, data_len, data);
if (err)
{
LOG_WRN("USB report send failed id=0x%02x err=%d", report_id, err);
submit_usb_tx_done(HID_TX_KIND_REPORT, false);
}
else
{
g_usb_hid.nkro.in_flight = true;
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh))
{
return handle_module_state_event(cast_module_state_event(aeh));
}
if (is_mode_event(aeh))
{
return handle_mode_event(cast_mode_event(aeh));
}
if (is_power_down_event(aeh))
{
return handle_power_down_event();
}
if (is_wake_up_event(aeh))
{
return handle_wake_up_event();
}
if (is_hid_tx_event(aeh))
{
return handle_hid_tx_event(cast_hid_tx_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_tx_event);

306
src/ui/display_ui.c Normal file
View File

@@ -0,0 +1,306 @@
#include <string.h>
#include <lvgl.h>
#include <zephyr/sys/printk.h>
#include "battery_status_event.h"
#include "display_ui.h"
#include "keyboard_led_event.h"
#define DISPLAY_SYMBOL_PLUG "\xEF\x87\xA6"
LV_FONT_DECLARE(ui_font_keyboard_small_18);
LV_FONT_DECLARE(ui_font_keyboard_time_48);
enum display_status_id
{
DISPLAY_STATUS_USB = 0,
DISPLAY_STATUS_BLE,
DISPLAY_STATUS_NUMLOCK,
DISPLAY_STATUS_CAPSLOCK,
DISPLAY_STATUS_COUNT,
};
struct display_ui_ctx
{
lv_obj_t *status_badges[DISPLAY_STATUS_COUNT];
lv_obj_t *status_labels[DISPLAY_STATUS_COUNT];
lv_obj_t *battery_icon;
lv_obj_t *battery_label;
lv_obj_t *battery_state_label;
lv_obj_t *date_label;
lv_obj_t *time_label;
};
static struct display_ui_ctx g_display_ui;
static const char *const g_status_texts[DISPLAY_STATUS_COUNT] = {
LV_SYMBOL_USB,
LV_SYMBOL_BLUETOOTH,
"1",
"A",
};
static lv_color_t display_ui_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 *display_ui_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 display_ui_status_is_active(enum display_status_id id,
const struct display_ui_model *model)
{
switch (id) {
case DISPLAY_STATUS_USB:
return model->mode == MODE_TYPE_USB;
case DISPLAY_STATUS_BLE:
return model->mode == MODE_TYPE_BLE;
case DISPLAY_STATUS_NUMLOCK:
return (model->led_mask & KEYBOARD_LED_MASK_NUM_LOCK) != 0U;
case DISPLAY_STATUS_CAPSLOCK:
return (model->led_mask & KEYBOARD_LED_MASK_CAPS_LOCK) != 0U;
default:
return false;
}
}
static void display_ui_create_status_chip(lv_obj_t *parent, enum display_status_id id)
{
lv_obj_t *badge = lv_obj_create(parent);
lv_obj_t *label;
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);
label = lv_label_create(badge);
lv_label_set_text(label, g_status_texts[id]);
lv_obj_set_width(label, LV_PCT(100));
lv_obj_set_style_text_font(label, &ui_font_keyboard_small_18, 0);
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_center(label);
g_display_ui.status_badges[id] = badge;
g_display_ui.status_labels[id] = label;
}
void display_ui_refresh_status_bar(const struct display_ui_model *model)
{
for (uint32_t i = 0; i < DISPLAY_STATUS_COUNT; i++) {
lv_obj_t *badge = g_display_ui.status_badges[i];
lv_obj_t *label = g_display_ui.status_labels[i];
bool active = display_ui_status_is_active((enum display_status_id)i, model);
if (!badge || !label) {
continue;
}
lv_obj_set_style_border_width(badge, 4, 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 display_ui_refresh_battery(const struct display_ui_model *model)
{
char battery_text[8];
lv_color_t battery_color;
const char *state_symbol = "";
lv_color_t state_color = lv_color_white();
if (!g_display_ui.battery_icon ||
!g_display_ui.battery_label ||
!g_display_ui.battery_state_label) {
return;
}
battery_color = display_ui_get_battery_color(model->battery_level);
snprintk(battery_text, sizeof(battery_text), "%u%%", model->battery_level);
if ((model->battery_flags & BATTERY_STATUS_FLAG_FULL) != 0U) {
state_symbol = DISPLAY_SYMBOL_PLUG;
state_color = lv_color_hex(0x4C9EF5);
} else if ((model->battery_flags & BATTERY_STATUS_FLAG_CHARGING) != 0U) {
state_symbol = LV_SYMBOL_CHARGE;
state_color = lv_color_hex(0xF4D35E);
}
lv_label_set_text(g_display_ui.battery_icon,
display_ui_get_battery_symbol(model->battery_level));
lv_obj_set_style_text_color(g_display_ui.battery_icon, battery_color, 0);
lv_label_set_text(g_display_ui.battery_label, battery_text);
lv_label_set_text(g_display_ui.battery_state_label, state_symbol);
lv_obj_set_style_text_color(g_display_ui.battery_state_label, state_color, 0);
}
void display_ui_refresh_datetime(const char *date_text, const char *time_text)
{
if (!g_display_ui.date_label || !g_display_ui.time_label) {
return;
}
lv_label_set_text(g_display_ui.date_label, date_text);
lv_label_set_text(g_display_ui.time_label, time_text);
}
void display_ui_refresh_all(const struct display_ui_model *model,
const char *date_text,
const char *time_text)
{
display_ui_refresh_status_bar(model);
display_ui_refresh_battery(model);
display_ui_refresh_datetime(date_text, time_text);
}
void display_ui_init(const struct display_ui_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;
memset(&g_display_ui, 0, sizeof(g_display_ui));
lv_obj_clean(screen);
lv_obj_set_style_bg_color(screen, lv_color_hex(0x0F1115), 0);
lv_obj_set_style_bg_grad_color(screen, lv_color_hex(0x1A1F29), 0);
lv_obj_set_style_bg_grad_dir(screen, LV_GRAD_DIR_VER, 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);
lv_obj_remove_style_all(content);
lv_obj_set_size(content, LV_PCT(100), LV_PCT(100));
lv_obj_set_style_bg_color(content, lv_color_hex(0x0F1115), 0);
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_color(top_row, lv_color_hex(0x0F1115), 0);
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_display_ui.date_label = lv_label_create(top_row);
lv_obj_set_style_text_font(g_display_ui.date_label, &ui_font_keyboard_small_18, 0);
lv_obj_set_style_text_color(g_display_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_display_ui.battery_icon = lv_label_create(battery_wrap);
lv_obj_set_style_text_font(g_display_ui.battery_icon, &ui_font_keyboard_small_18, 0);
g_display_ui.battery_label = lv_label_create(battery_wrap);
lv_obj_set_style_text_font(g_display_ui.battery_label, &ui_font_keyboard_small_18, 0);
lv_obj_set_style_text_color(g_display_ui.battery_label, lv_color_hex(0xD8DEE9), 0);
g_display_ui.battery_state_label = lv_label_create(battery_wrap);
lv_obj_set_style_text_font(g_display_ui.battery_state_label, &ui_font_keyboard_small_18, 0);
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_color(middle_row, lv_color_hex(0x0F1115), 0);
lv_obj_set_style_bg_opa(middle_row, LV_OPA_TRANSP, 0);
g_display_ui.time_label = lv_label_create(middle_row);
lv_obj_set_style_text_font(g_display_ui.time_label, &ui_font_keyboard_time_48, 0);
lv_obj_set_style_text_color(g_display_ui.time_label, lv_color_white(), 0);
lv_obj_center(g_display_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_color(bottom_row, lv_color_hex(0x0F1115), 0);
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 < DISPLAY_STATUS_COUNT; i++) {
display_ui_create_status_chip(bottom_row, (enum display_status_id)i);
}
display_ui_refresh_all(model, date_text, time_text);
}

34
src/ui/display_ui.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef DISPLAY_UI_H
#define DISPLAY_UI_H
#include <stdint.h>
#include <lvgl.h>
#include "mode_event.h"
struct display_ui_model
{
lv_color_t theme_color;
lv_color_t inactive_border_color;
uint8_t battery_level;
mode_type_t mode;
uint8_t led_mask;
uint8_t battery_flags;
};
void display_ui_init(const struct display_ui_model *model,
const char *date_text,
const char *time_text);
void display_ui_refresh_all(const struct display_ui_model *model,
const char *date_text,
const char *time_text);
void display_ui_refresh_status_bar(const struct display_ui_model *model);
void display_ui_refresh_battery(const struct display_ui_model *model);
void display_ui_refresh_datetime(const char *date_text, const char *time_text);
#endif /* DISPLAY_UI_H */

View File

@@ -0,0 +1,379 @@
/*******************************************************************************
* Size: 18 px
* Bpp: 4
* Opts: --format lvgl --output E:\projects\lvgl\lv_port_pc_vscode\src\ui\fonts\ui_font_keyboard_small_18.c --size 18 --bpp 4 --lv-font-name ui_font_keyboard_small_18 --font E:\projects\lvgl\lv_port_pc_vscode\external\ttf\MapleMono-NF-CN-Medium.ttf --range 0x30-0x39 --symbols AS/% --font E:\projects\lvgl\lv_port_pc_vscode\lvgl\scripts\built_in_font\FontAwesome5-Solid+Brands+Regular.woff --range 0xF0E7-0xF0E7 --range 0xF240-0xF240 --range 0xF241-0xF241 --range 0xF242-0xF242 --range 0xF243-0xF243 --range 0xF244-0xF244 --range 0xF1E6-0xF1E6 --range 0xF287-0xF287 --range 0xF293-0xF293
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
#ifndef UI_FONT_KEYBOARD_SMALL_18
#define UI_FONT_KEYBOARD_SMALL_18 1
#endif
#if UI_FONT_KEYBOARD_SMALL_18
/*-----------------
* BITMAPS
*----------------*/
/*Store the image of the glyphs*/
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
/* U+0025 "%" */
0x6, 0xef, 0xb1, 0x0, 0x90, 0xe, 0x40, 0x92,
0xc0, 0x17, 0x63, 0xb0, 0xdd, 0x1a, 0x3, 0x22,
0xc, 0x3, 0xc5, 0x23, 0x41, 0x61, 0xb8, 0x69,
0x41, 0x42, 0x7, 0x20, 0x69, 0x71, 0xa8, 0x1,
0x37, 0x7d, 0x8b, 0xcc, 0x84, 0x2, 0x3d, 0x39,
0x84, 0x6d, 0x0, 0xb8, 0x9d, 0x8f, 0xd4, 0xcc,
0xe, 0x4a, 0xc0, 0x81, 0xe0, 0xc3, 0x1, 0x66,
0x4, 0x4, 0x2, 0xc1, 0x2, 0x23, 0x5c, 0x81,
0x47, 0xb7, 0x0, 0x69, 0x98, 0xb4, 0x0,
/* U+002F "/" */
0x0, 0xf4, 0x79, 0x0, 0x79, 0x80, 0x80, 0x39,
0x45, 0x40, 0x3d, 0xc1, 0xc0, 0x1c, 0x28, 0x48,
0x1, 0xcc, 0xa, 0x1, 0xea, 0xa, 0x0, 0xe3,
0x23, 0x20, 0xe, 0xa0, 0xa0, 0xf, 0x38, 0x30,
0x7, 0x28, 0xa8, 0x7, 0xb8, 0x38, 0x3, 0x85,
0x9, 0x0, 0x39, 0x81, 0x40, 0x3d, 0x41, 0x40,
0x1c, 0x64, 0x64, 0x1, 0xcc, 0x12, 0x1, 0xe0,
/* U+0030 "0" */
0x0, 0x46, 0xfe, 0xb0, 0x5, 0x4e, 0x45, 0x4c,
0x1, 0xa8, 0xf6, 0xe0, 0x38, 0xd0, 0x40, 0x81,
0x98, 0x1d, 0x41, 0x0, 0x14, 0x61, 0xe2, 0x4,
0x38, 0xa2, 0x6, 0x61, 0x2f, 0x33, 0x68, 0x9,
0x8e, 0x8b, 0x60, 0x80, 0x88, 0x1, 0x12, 0xe,
0x6, 0xa0, 0xee, 0x0, 0x78, 0x65, 0x3, 0x8,
0x1a, 0x82, 0x1a, 0x8f, 0x6e, 0x4, 0x88, 0x53,
0x89, 0x12, 0x58, 0x0,
/* U+0031 "1" */
0x0, 0x1e, 0x7c, 0x80, 0x66, 0xc3, 0x7, 0x0,
0x92, 0x42, 0x80, 0x40, 0x31, 0xea, 0x88, 0x6,
0x4c, 0x20, 0xf, 0xff, 0x82, 0x66, 0x42, 0x19,
0x89, 0x23, 0x38, 0x0, 0x66, 0x50,
/* U+0032 "2" */
0x7, 0xdf, 0xf6, 0xc8, 0x82, 0x41, 0x10, 0xc9,
0xb4, 0x12, 0x37, 0x59, 0xc4, 0x68, 0xe, 0x40,
0x1, 0xa0, 0x20, 0xf, 0x70, 0x10, 0x7, 0x13,
0xa, 0x0, 0x61, 0xf0, 0xb0, 0xc, 0x38, 0x50,
0xa0, 0x10, 0xe1, 0x3b, 0x80, 0x21, 0xc2, 0x78,
0x0, 0x87, 0x9, 0xe0, 0x3, 0x59, 0x2, 0xe6,
0x66, 0xd0, 0x1, 0x19, 0xef,
/* U+0033 "3" */
0x1a, 0xef, 0xf5, 0xa0, 0x1, 0x54, 0x6, 0x29,
0x6a, 0x9, 0x9d, 0x9d, 0x21, 0x20, 0x3, 0x0,
0x98, 0x4, 0x3, 0x86, 0x41, 0x0, 0x3, 0x9b,
0xcc, 0xb2, 0x1, 0x19, 0x0, 0x15, 0x80, 0x3,
0xff, 0x50, 0xc9, 0x0, 0x72, 0xc8, 0x20, 0x7,
0xbc, 0x0, 0x28, 0x1, 0x13, 0x2, 0xd5, 0xfe,
0x6e, 0x85, 0x4e, 0x14, 0x4c, 0x8e, 0x68, 0x0,
/* U+0034 "4" */
0x0, 0xed, 0xe1, 0x0, 0xf4, 0x90, 0xa8, 0x7,
0x1b, 0x0, 0x7e, 0xe1, 0x30, 0xf, 0x41, 0x43,
0x0, 0x71, 0xb9, 0xb0, 0x7, 0xbc, 0x3c, 0x3,
0xce, 0x6c, 0x60, 0x1e, 0x80, 0x4c, 0xc2, 0x4,
0xd0, 0x20, 0x11, 0x98, 0x40, 0x90, 0x2f, 0xff,
0x28, 0x5f, 0x0, 0x7f, 0xf1, 0x34, 0x18, 0x0,
/* U+0035 "5" */
0xb, 0xff, 0xf0, 0x82, 0x1, 0x19, 0xc8, 0x22,
0x2, 0xdc, 0xca, 0xc0, 0x21, 0x0, 0xfc, 0x20,
0x1e, 0x10, 0x2f, 0xf6, 0xb8, 0x5, 0x8, 0x62,
0x51, 0x60, 0x7, 0xbc, 0xec, 0x14, 0x30, 0xe,
0x39, 0x5, 0x0, 0xf7, 0x80, 0x5, 0x0, 0x22,
0x60, 0x5a, 0xbf, 0xcd, 0xd0, 0xa9, 0xc2, 0x89,
0x91, 0x22, 0x80,
/* U+0036 "6" */
0x0, 0x86, 0xbd, 0x0, 0x30, 0xe2, 0xc2, 0x0,
0x6d, 0x2b, 0x70, 0xc, 0xe7, 0x28, 0x1, 0xd0,
0x88, 0x78, 0x40, 0x3, 0x0, 0x36, 0x1e, 0xe8,
0x34, 0x1b, 0x3f, 0x9, 0x51, 0x83, 0x8c, 0xe,
0x3, 0x4c, 0xc, 0x2, 0x70, 0x23, 0x0, 0xe1,
0x1, 0x70, 0x90, 0xa, 0x3, 0x20, 0x1b, 0xb3,
0xd0, 0xd8, 0xb5, 0x40, 0xc5, 0xf0, 0x0,
/* U+0037 "7" */
0xdf, 0xff, 0x91, 0x6, 0x7c, 0x0, 0xc9, 0xcc,
0xe3, 0xb, 0x0, 0xe2, 0x33, 0x80, 0x3a, 0xc2,
0x80, 0x3c, 0xc0, 0xc0, 0x1c, 0xc0, 0xc0, 0x1e,
0xb0, 0xb0, 0xe, 0x42, 0x42, 0x0, 0xee, 0xe,
0x0, 0xe1, 0x51, 0x50, 0xe, 0x90, 0x60, 0xf,
0x10, 0xc8, 0x6,
/* U+0038 "8" */
0x1, 0xae, 0xfe, 0x80, 0x0, 0xfa, 0x81, 0x8b,
0xe0, 0x30, 0xaf, 0x67, 0x91, 0xa7, 0x87, 0x80,
0x48, 0x0, 0xb0, 0x92, 0x3, 0x71, 0x43, 0xb1,
0xdf, 0xc2, 0xd0, 0x6, 0x90, 0x98, 0x1a, 0x3,
0xb3, 0x3b, 0x39, 0x21, 0x28, 0x24, 0x0, 0x3e,
0x1a, 0x60, 0x1c, 0xe0, 0x68, 0x12, 0x1, 0x40,
0x6c, 0xb, 0x76, 0x7a, 0x13, 0x96, 0x28, 0x18,
0xb6, 0x0,
/* U+0039 "9" */
0x2, 0xae, 0xfe, 0x91, 0x2, 0xd5, 0x33, 0x3,
0x60, 0x40, 0x46, 0xe7, 0x21, 0x33, 0x83, 0x80,
0x50, 0x18, 0x1, 0xe1, 0x1, 0x60, 0x50, 0xa,
0x0, 0x7c, 0x2a, 0xe7, 0x50, 0x35, 0x28, 0x91,
0xd5, 0x1, 0x1, 0x77, 0xfa, 0x94, 0xcc, 0x1,
0xdc, 0x10, 0x1, 0xd4, 0x6c, 0x80, 0x10, 0xda,
0xa4, 0x80, 0x66, 0x47, 0xb0, 0x8,
/* U+0041 "A" */
0x0, 0xd7, 0xf0, 0x1, 0xf1, 0xa0, 0x38, 0x7,
0xc8, 0x8, 0x8, 0x1, 0xeb, 0x8, 0xd, 0x0,
0xe1, 0x34, 0x31, 0x40, 0xe, 0x40, 0xc0, 0x42,
0x20, 0x6, 0xc0, 0x40, 0xc0, 0x40, 0xc, 0xe2,
0x60, 0x81, 0x80, 0x11, 0x8, 0xbf, 0xda, 0xa,
0x1, 0x20, 0x23, 0xbc, 0x60, 0x60, 0xd, 0x9,
0x88, 0xa4, 0x2c, 0x0, 0x84, 0x40, 0x9, 0x1,
0x0, 0x41, 0x40, 0x30, 0xa0, 0x80,
/* U+0053 "S" */
0x1, 0x9e, 0xfd, 0x70, 0xb, 0x18, 0xc, 0xa2,
0x81, 0x49, 0x3b, 0x38, 0x94, 0x48, 0x34, 0x0,
0x3f, 0x22, 0x41, 0xa0, 0x11, 0x30, 0x29, 0xa7,
0x4a, 0x80, 0x6c, 0x83, 0x6a, 0xe4, 0x0, 0x9f,
0x75, 0x3, 0x64, 0x1, 0x89, 0xe4, 0x29, 0xa8,
0x3, 0x10, 0xf, 0xac, 0x80, 0x5c, 0x1f, 0x60,
0xdd, 0x9e, 0xa4, 0xc5, 0xaa, 0x6, 0x2d, 0x80,
/* U+F0E7 "" */
0x0, 0x57, 0xff, 0x50, 0x7, 0x94, 0x3, 0x84,
0x3, 0x84, 0x3, 0xda, 0x1, 0xc4, 0x1, 0xe7,
0x0, 0xe6, 0x0, 0xe4, 0x10, 0xe, 0xd0, 0xe,
0x15, 0x59, 0x0, 0x4, 0x1, 0xcb, 0x55, 0x59,
0x83, 0x0, 0x7f, 0x19, 0x80, 0x80, 0x3f, 0xbc,
0x3, 0xfe, 0x53, 0x0, 0x7e, 0xed, 0x80, 0x1a,
0xc0, 0x21, 0x22, 0x9c, 0x2, 0x81, 0x0, 0xfb,
0x0, 0x2, 0xe0, 0x1f, 0x90, 0x1, 0x60, 0x1f,
0xc6, 0x6, 0xa0, 0x1f, 0x8c, 0x1, 0xe0, 0x1f,
0xc8, 0xc, 0x60, 0x1f, 0xe1, 0x80, 0xf, 0xf3,
0xf0, 0x80, 0x78,
/* U+F1E6 "" */
0x0, 0x25, 0x10, 0x6, 0x79, 0x0, 0xef, 0x55,
0x0, 0x68, 0x63, 0x0, 0xce, 0x1e, 0x1, 0xe7,
0x0, 0xff, 0xe9, 0x9, 0x4c, 0x61, 0x16, 0x88,
0x38, 0x87, 0x6d, 0x45, 0xee, 0xe8, 0x86, 0x73,
0x80, 0x7f, 0xf0, 0xb8, 0x40, 0x3f, 0xd0, 0xe2,
0x60, 0x1f, 0xe2, 0x0, 0x30, 0x7, 0xf9, 0x80,
0x1a, 0x1, 0xfc, 0x26, 0x0, 0x63, 0x0, 0xfd,
0x60, 0x1b, 0x4c, 0x3, 0xd2, 0xa0, 0x18, 0x72,
0x4c, 0x0, 0xda, 0xc0, 0x1f, 0x35, 0x0, 0xc9,
0x0, 0x7f, 0xf6, 0x5e, 0xf4, 0x3, 0xc0,
/* U+F240 "" */
0x4, 0x4f, 0xfe, 0x38, 0x80, 0x2a, 0xef, 0xff,
0x8f, 0xe2, 0xa, 0x1, 0xff, 0xc7, 0x14, 0x0,
0xaf, 0xff, 0xff, 0x88, 0x0, 0xa3, 0x0, 0xab,
0xff, 0xff, 0x84, 0x1, 0x98, 0x3, 0xff, 0x8f,
0x80, 0x1f, 0xfc, 0xa3, 0x10, 0xf, 0xfe, 0x4c,
0x8, 0x7, 0x7b, 0xbf, 0xff, 0x8, 0x1c, 0x3,
0x8c, 0x73, 0x3f, 0xf8, 0x4a, 0x0, 0x34, 0x0,
0x45, 0xdf, 0xff, 0xe, 0x80, 0x12, 0x32, 0x20,
0x1f, 0xfc, 0x64, 0x50,
/* U+F241 "" */
0x4, 0x4f, 0xfe, 0x38, 0x80, 0x2a, 0xef, 0xff,
0x8f, 0xe2, 0xa, 0x1, 0xff, 0xc7, 0x14, 0x0,
0xaf, 0xff, 0xff, 0x88, 0x0, 0xa3, 0x0, 0xb7,
0xff, 0xfc, 0xc0, 0x1f, 0x30, 0x7, 0xff, 0x1f,
0x0, 0x3f, 0xf9, 0x46, 0x20, 0x1f, 0xfc, 0x98,
0x10, 0xe, 0xb7, 0x7f, 0xf2, 0x80, 0x67, 0x0,
0xe3, 0x2c, 0xcf, 0xf3, 0xa2, 0x4a, 0x0, 0x34,
0x0, 0x45, 0xdf, 0xff, 0xe, 0x80, 0x12, 0x32,
0x20, 0x1f, 0xfc, 0x64, 0x50,
/* U+F242 "" */
0x4, 0x4f, 0xfe, 0x38, 0x80, 0x2a, 0xef, 0xff,
0x8f, 0xe2, 0xa, 0x1, 0xff, 0xc7, 0x14, 0x0,
0xaf, 0xff, 0xff, 0x88, 0x0, 0xa3, 0x0, 0xb3,
0xff, 0xe1, 0x0, 0xff, 0x30, 0x7, 0xff, 0x1f,
0x0, 0x3f, 0xf9, 0x46, 0x20, 0x1f, 0xfc, 0x98,
0x10, 0xe, 0xa7, 0x7f, 0x84, 0x3, 0xe7, 0x0,
0xe3, 0x2c, 0xcf, 0x91, 0x3e, 0x50, 0x1, 0xa0,
0x2, 0x2e, 0xff, 0xf8, 0x74, 0x0, 0x91, 0x91,
0x0, 0xff, 0xe3, 0x22, 0x80,
/* U+F243 "" */
0x4, 0x4f, 0xfe, 0x38, 0x80, 0x2a, 0xef, 0xff,
0x8f, 0xe2, 0xa, 0x1, 0xff, 0xc7, 0x14, 0x0,
0xaf, 0xff, 0xff, 0x88, 0x0, 0xa3, 0x0, 0xa7,
0xfe, 0xe0, 0xf, 0xfe, 0x13, 0x0, 0x7f, 0xf1,
0xf0, 0x3, 0xff, 0x94, 0x62, 0x1, 0xff, 0xc9,
0x81, 0x0, 0xed, 0x77, 0xa4, 0x3, 0xfe, 0x70,
0xe, 0x30, 0xcc, 0x8d, 0x13, 0xfc, 0xa0, 0x3,
0x40, 0x4, 0x5d, 0xff, 0xf0, 0xe8, 0x1, 0x23,
0x22, 0x1, 0xff, 0xc6, 0x45, 0x0,
/* U+F244 "" */
0x4, 0x4f, 0xfe, 0x38, 0x80, 0x2a, 0xef, 0xff,
0x8f, 0xe2, 0xa, 0x1, 0xff, 0xc7, 0x14, 0x0,
0xaf, 0xff, 0xff, 0x88, 0x0, 0xa3, 0x0, 0xff,
0xe5, 0x30, 0x7, 0xff, 0x1f, 0x0, 0x3f, 0xf9,
0x46, 0x20, 0x1f, 0xfc, 0x98, 0x10, 0xf, 0xfe,
0x4b, 0x80, 0x71, 0xa2, 0x7f, 0xf0, 0xd4, 0x0,
0x68, 0x0, 0x8b, 0xbf, 0xfe, 0x1d, 0x0, 0x24,
0x64, 0x40, 0x3f, 0xf8, 0xc8, 0xa0,
/* U+F287 "" */
0x0, 0xff, 0xe0, 0x3c, 0x8, 0x7, 0xff, 0x14,
0x9a, 0x61, 0xf0, 0x3, 0xff, 0x88, 0x9b, 0xc,
0x0, 0x20, 0xf, 0xfe, 0x25, 0x3f, 0x68, 0x84,
0x0, 0x7f, 0x88, 0x3, 0x30, 0x40, 0x17, 0x7b,
0x80, 0x7e, 0x3e, 0xde, 0x30, 0x5, 0xc8, 0x7,
0xf1, 0x50, 0x80, 0x3c, 0x40, 0x79, 0x24, 0x88,
0xab, 0xff, 0x73, 0x81, 0x80, 0x45, 0x6c, 0x15,
0x5f, 0xe8, 0x0, 0x42, 0xa8, 0x2, 0x5f, 0xfc,
0x88, 0xff, 0xfb, 0x1, 0x35, 0x69, 0x8d, 0xa8,
0x3, 0x42, 0x18, 0x7, 0x8a, 0xac, 0x80, 0x13,
0x92, 0x1, 0xc6, 0x90, 0x5, 0x77, 0x28, 0xa8,
0x7, 0xff, 0x6, 0x52, 0x6d, 0x12, 0xd0, 0xf,
0xfe, 0x19, 0x61, 0x20, 0x7, 0xff, 0x20, 0xef,
0x80, 0x3f, 0xf9, 0x47, 0xff, 0x40, 0x7, 0x0,
/* U+F293 "" */
0x0, 0x85, 0xef, 0x75, 0x70, 0x40, 0x1c, 0xbd,
0x8, 0x4, 0x8f, 0xac, 0x1, 0x25, 0x0, 0x56,
0xe0, 0x14, 0xa0, 0x2, 0x80, 0x3a, 0x1c, 0x2,
0x90, 0x41, 0x0, 0xf4, 0x30, 0x0, 0x8f, 0x0,
0x12, 0x80, 0x1, 0xc9, 0x60, 0x2, 0x18, 0x5,
0x68, 0x2, 0x44, 0xc0, 0x7, 0x30, 0x2, 0x56,
0xd0, 0x34, 0x68, 0x0, 0x46, 0x1, 0x52, 0x90,
0xe, 0x80, 0x4e, 0x1, 0xd2, 0x0, 0x51, 0x0,
0xc6, 0x1, 0x16, 0x80, 0x1d, 0x80, 0x30, 0x80,
0xf, 0x9, 0x42, 0x64, 0xa0, 0x7, 0x60, 0x7,
0x2e, 0x18, 0x31, 0xc0, 0x0, 0xb0, 0x1, 0xf6,
0x2, 0x14, 0x38, 0x0, 0xf7, 0x0, 0x10, 0x6,
0x7c, 0x10, 0x3, 0x8b, 0x0, 0x71, 0x60, 0x80,
0x18, 0x42, 0x50, 0x2, 0xdc, 0x10, 0x1, 0x40,
0x5, 0x74, 0x60, 0xa2, 0x4, 0xfa, 0x20, 0x19,
0x73, 0xfb, 0x9f, 0xb0, 0x1, 0x0
};
/*---------------------
* GLYPH DESCRIPTION
*--------------------*/
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
{.bitmap_index = 0, .adv_w = 173, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 71, .adv_w = 173, .box_w = 9, .box_h = 17, .ofs_x = 1, .ofs_y = -2},
{.bitmap_index = 127, .adv_w = 173, .box_w = 9, .box_h = 13, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 187, .adv_w = 173, .box_w = 9, .box_h = 13, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 217, .adv_w = 173, .box_w = 9, .box_h = 13, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 270, .adv_w = 173, .box_w = 9, .box_h = 13, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 326, .adv_w = 173, .box_w = 10, .box_h = 13, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 374, .adv_w = 173, .box_w = 9, .box_h = 13, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 425, .adv_w = 173, .box_w = 9, .box_h = 13, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 480, .adv_w = 173, .box_w = 9, .box_h = 13, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 523, .adv_w = 173, .box_w = 9, .box_h = 13, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 581, .adv_w = 173, .box_w = 9, .box_h = 13, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 635, .adv_w = 173, .box_w = 11, .box_h = 13, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 697, .adv_w = 173, .box_w = 9, .box_h = 13, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 753, .adv_w = 180, .box_w = 13, .box_h = 19, .ofs_x = -1, .ofs_y = -3},
{.bitmap_index = 836, .adv_w = 216, .box_w = 14, .box_h = 19, .ofs_x = 0, .ofs_y = -3},
{.bitmap_index = 915, .adv_w = 360, .box_w = 23, .box_h = 12, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 983, .adv_w = 360, .box_w = 23, .box_h = 12, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 1052, .adv_w = 360, .box_w = 23, .box_h = 12, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 1121, .adv_w = 360, .box_w = 23, .box_h = 12, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 1191, .adv_w = 360, .box_w = 23, .box_h = 12, .ofs_x = 0, .ofs_y = 1},
{.bitmap_index = 1253, .adv_w = 360, .box_w = 23, .box_h = 15, .ofs_x = 0, .ofs_y = -1},
{.bitmap_index = 1365, .adv_w = 252, .box_w = 14, .box_h = 19, .ofs_x = 1, .ofs_y = -3}
};
/*---------------------
* CHARACTER MAPPING
*--------------------*/
static const uint16_t unicode_list_0[] = {
0x0, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10,
0x11, 0x12, 0x13, 0x14, 0x1c, 0x2e, 0xf0c2, 0xf1c1,
0xf21b, 0xf21c, 0xf21d, 0xf21e, 0xf21f, 0xf262, 0xf26e
};
/*Collect the unicode lists and glyph_id offsets*/
static const lv_font_fmt_txt_cmap_t cmaps[] =
{
{
.range_start = 37, .range_length = 62063, .glyph_id_start = 1,
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 23, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
}
};
/*--------------------
* ALL CUSTOM DATA
*--------------------*/
#if LVGL_VERSION_MAJOR == 8
/*Store all the custom data of the font*/
static lv_font_fmt_txt_glyph_cache_t cache;
#endif
#if LVGL_VERSION_MAJOR >= 8
static const lv_font_fmt_txt_dsc_t font_dsc = {
#else
static lv_font_fmt_txt_dsc_t font_dsc = {
#endif
.glyph_bitmap = glyph_bitmap,
.glyph_dsc = glyph_dsc,
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 1,
.bpp = 4,
.kern_classes = 0,
.bitmap_format = 1,
#if LVGL_VERSION_MAJOR == 8
.cache = &cache
#endif
};
/*-----------------
* PUBLIC FONT
*----------------*/
/*Initialize a public general font descriptor*/
#if LVGL_VERSION_MAJOR >= 8
const lv_font_t ui_font_keyboard_small_18 = {
#else
lv_font_t ui_font_keyboard_small_18 = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
.line_height = 19, /*The maximum line height required by the font*/
.base_line = 3, /*Baseline measured from the bottom of the line*/
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
.subpx = LV_FONT_SUBPX_NONE,
#endif
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
.underline_position = -3,
.underline_thickness = 1,
#endif
.dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
.fallback = NULL,
#endif
.user_data = NULL,
};
#endif /*#if UI_FONT_KEYBOARD_SMALL_18*/

View File

@@ -0,0 +1,455 @@
/*******************************************************************************
* Size: 48 px
* Bpp: 4
* Opts: --format lvgl --output E:\projects\lvgl\lv_port_pc_vscode\src\ui\fonts\ui_font_keyboard_time_48.c --size 48 --bpp 4 --lv-font-name ui_font_keyboard_time_48 --font E:\projects\lvgl\lv_port_pc_vscode\external\ttf\MapleMono-NF-CN-SemiBold.ttf --range 0x30-0x39 --symbols :
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
#ifndef UI_FONT_KEYBOARD_TIME_48
#define UI_FONT_KEYBOARD_TIME_48 1
#endif
#if UI_FONT_KEYBOARD_TIME_48
/*-----------------
* BITMAPS
*----------------*/
/*Store the image of the glyphs*/
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
/* U+0030 "0" */
0x0, 0xfc, 0xb5, 0xbf, 0xf6, 0xd2, 0x80, 0x7f,
0xf0, 0x97, 0xa9, 0x48, 0x2, 0x25, 0xae, 0x40,
0xf, 0xfa, 0x28, 0x40, 0x3f, 0x86, 0xdc, 0x3,
0xfa, 0x1c, 0x3, 0xff, 0x83, 0xe, 0x1, 0xf2,
0x38, 0x7, 0xff, 0xe, 0xc, 0x3, 0xd2, 0x1,
0xe5, 0xab, 0x94, 0x0, 0xf7, 0x80, 0x73, 0x10,
0x7, 0x5d, 0x2a, 0x35, 0xc8, 0x7, 0x1a, 0x0,
0x6a, 0x0, 0xe8, 0x40, 0xe, 0x65, 0x0, 0xee,
0x0, 0x88, 0xc0, 0x31, 0x38, 0x7, 0xd6, 0x1,
0xca, 0x20, 0x4, 0x0, 0xeb, 0x0, 0xfc, 0x2a,
0x1, 0xca, 0x0, 0xc0, 0xe, 0x70, 0xf, 0xce,
0xa0, 0x1c, 0x60, 0x7, 0x0, 0xc6, 0x20, 0x1f,
0x4c, 0x0, 0x7b, 0x0, 0x4, 0x1, 0x94, 0x3,
0xc3, 0x6c, 0x1, 0xf3, 0x0, 0x80, 0x77, 0x0,
0x71, 0x62, 0x0, 0x7e, 0x30, 0x20, 0xe, 0x20,
0xc, 0x98, 0x40, 0x1f, 0xc2, 0xe, 0x1, 0xc2,
0x1, 0x35, 0x88, 0x7, 0xfc, 0x22, 0x0, 0xe7,
0x0, 0x44, 0x80, 0x79, 0x60, 0x3, 0x8c, 0x3,
0xf5, 0xb8, 0x7, 0xa2, 0x84, 0x3, 0xff, 0x80,
0xd8, 0x80, 0x1e, 0xa7, 0x1, 0x0, 0xe3, 0x10,
0xe, 0xa3, 0x0, 0xe1, 0xc5, 0x0, 0xfe, 0x70,
0xf, 0xf8, 0xfc, 0xc0, 0x2e, 0x0, 0xe1, 0x20,
0xf, 0xf2, 0xe0, 0x80, 0x63, 0x0, 0xc2, 0x2,
0x1, 0xfc, 0xf4, 0x1, 0xe7, 0x0, 0xc4, 0x0,
0x20, 0xf, 0xa6, 0x0, 0x3e, 0x20, 0xc, 0xc0,
0x7, 0x0, 0xe1, 0xc6, 0x0, 0xf9, 0x0, 0x3b,
0x0, 0x18, 0x1, 0xd0, 0x60, 0x1f, 0xb4, 0x3,
0x94, 0x0, 0x80, 0x1d, 0x60, 0x1f, 0x85, 0x80,
0x30, 0x98, 0x0, 0x8c, 0x3, 0x13, 0x80, 0x7d,
0x60, 0x1c, 0xc0, 0x1a, 0x80, 0x3a, 0x10, 0x3,
0x9d, 0x40, 0x3a, 0x80, 0x33, 0x10, 0x7, 0x5d,
0x2a, 0x35, 0xc0, 0x7, 0x29, 0x0, 0x77, 0x0,
0x79, 0x6a, 0xe5, 0x0, 0x3d, 0x0, 0x1e, 0x38,
0x0, 0xff, 0xe1, 0xd1, 0x0, 0x7c, 0xf0, 0x1,
0xff, 0xc1, 0xa5, 0x0, 0xfe, 0x7a, 0x10, 0xf,
0xe2, 0xc5, 0x0, 0xff, 0x97, 0xa5, 0x48, 0x0,
0x24, 0xd7, 0xa6, 0x1, 0xe0,
/* U+0031 "1" */
0x0, 0xfc, 0xb7, 0xdf, 0xd6, 0x60, 0x1f, 0xfc,
0x31, 0xca, 0x41, 0x1, 0x4c, 0x30, 0xf, 0xfe,
0xa, 0x79, 0x80, 0x7d, 0x20, 0x1f, 0xfc, 0x7,
0xb1, 0x0, 0xfc, 0xe0, 0x1f, 0xe1, 0xb8, 0x0,
0xff, 0xe4, 0xe, 0x20, 0x7, 0xff, 0x26, 0x8,
0x3, 0xca, 0x1, 0xff, 0xc3, 0x40, 0xe, 0x1b,
0xa4, 0x0, 0xff, 0xe1, 0x18, 0x6, 0x3f, 0x40,
0x10, 0xf, 0xfe, 0x11, 0x0, 0x4b, 0x82, 0x1,
0xff, 0xc5, 0x90, 0x2a, 0xa0, 0x7, 0xff, 0x1d,
0x7f, 0x54, 0x3, 0xff, 0xfe, 0x1, 0xff, 0xff,
0x0, 0xff, 0xff, 0x80, 0x7f, 0xff, 0xc0, 0x3f,
0xff, 0xe0, 0x1f, 0xfe, 0xa4, 0x55, 0xf9, 0x80,
0x31, 0xaa, 0xf8, 0x40, 0x19, 0x75, 0x5f, 0x18,
0x6, 0x1a, 0xaf, 0x71, 0x9c, 0x1, 0xff, 0xc9,
0x96, 0x0, 0xff, 0xe5, 0xb, 0x80, 0x7f, 0xf2,
0x88, 0x90, 0x20, 0x1f, 0xfc, 0x74, 0x80,
/* U+0032 "2" */
0x0, 0xc2, 0xd5, 0x9d, 0xff, 0x76, 0xd3, 0x90,
0x7, 0xf0, 0xcf, 0x4a, 0x98, 0x80, 0x42, 0x4b,
0x1b, 0x64, 0x1, 0xe1, 0xf6, 0x0, 0xff, 0xe0,
0xa6, 0xa8, 0x7, 0x58, 0x80, 0x7f, 0xf1, 0x29,
0x0, 0x32, 0x0, 0x7f, 0xf1, 0xa8, 0x40, 0x24,
0x0, 0xe5, 0x9c, 0xdc, 0xa6, 0x0, 0xf0, 0xb0,
0x5, 0x66, 0x0, 0x7e, 0xa6, 0x32, 0x35, 0x9e,
0x30, 0xe, 0xa0, 0x8, 0x73, 0x1d, 0x2, 0x1,
0xf0, 0xe8, 0x7, 0x10, 0x7, 0x18, 0x80, 0x7f,
0x85, 0x80, 0x38, 0xc0, 0x3f, 0xf8, 0xb8, 0x1,
0xff, 0xcb, 0x10, 0xf, 0xfe, 0x59, 0x0, 0x71,
0x0, 0x7f, 0xf1, 0x74, 0x3, 0x8, 0x80, 0x3f,
0xf8, 0x86, 0x80, 0x19, 0xc0, 0x3f, 0xf8, 0xbe,
0x1, 0xd4, 0x1, 0xff, 0xc4, 0x83, 0x0, 0xc8,
0x60, 0x1f, 0xfc, 0x35, 0x70, 0xe, 0x80, 0xf,
0xfe, 0x1a, 0x50, 0x7, 0x39, 0x80, 0x7f, 0xf0,
0x8e, 0xc0, 0x38, 0xe0, 0x3, 0xff, 0x84, 0x78,
0x1, 0xc5, 0xa0, 0x1f, 0xfc, 0x23, 0xc0, 0xe,
0x1f, 0x10, 0xf, 0xfe, 0x9, 0xe0, 0x7, 0xe,
0x90, 0x7, 0xff, 0x4, 0xf0, 0x3, 0x87, 0x4c,
0x3, 0xff, 0x82, 0x78, 0x1, 0xc3, 0xa6, 0x1,
0xff, 0xc1, 0x3c, 0x0, 0xe1, 0xd3, 0x0, 0xff,
0xe0, 0x9e, 0x0, 0x70, 0xe9, 0x80, 0x7f, 0xf0,
0x4f, 0x0, 0x38, 0x74, 0xc0, 0x3f, 0xf8, 0x27,
0x80, 0x1c, 0x3a, 0x60, 0x1f, 0xfc, 0x13, 0xc0,
0xe, 0x1d, 0x30, 0xf, 0xfe, 0x9, 0x60, 0x7,
0x95, 0xd1, 0x3f, 0xc6, 0x20, 0xa, 0x0, 0xf9,
0x2e, 0xff, 0xec, 0xe8, 0x2, 0x0, 0xff, 0xe4,
0xb8, 0x98, 0x7, 0xff, 0x28, 0xe8, 0x3, 0xff,
0x94, 0x47, 0x66, 0x1, 0xff, 0xc6, 0x2b, 0x0,
/* U+0033 "3" */
0x0, 0xcb, 0x39, 0xdf, 0xf7, 0x64, 0xa8, 0x7,
0xf9, 0xfe, 0x98, 0xc4, 0x2, 0x13, 0x6a, 0xf8,
0x0, 0xf9, 0x60, 0x3, 0xff, 0x82, 0xfa, 0x40,
0x1d, 0xc0, 0x1f, 0xfc, 0x32, 0xc2, 0x0, 0xc4,
0x1, 0xff, 0xc4, 0x1e, 0x0, 0xd6, 0x1, 0x96,
0x2e, 0xd5, 0x6, 0x1, 0xe3, 0x30, 0x4, 0x58,
0xcf, 0xd4, 0xe8, 0x85, 0x7c, 0xb1, 0x0, 0xeb,
0x0, 0xc7, 0x30, 0x20, 0x1f, 0x26, 0x80, 0x71,
0x80, 0x7f, 0xf1, 0xc, 0x80, 0x33, 0x80, 0x7f,
0xf1, 0x44, 0x3, 0x38, 0x7, 0xff, 0x10, 0x4c,
0x3, 0x10, 0x7, 0xff, 0x12, 0xc0, 0x3b, 0x0,
0x3f, 0xf8, 0x47, 0x8a, 0x1, 0x85, 0x40, 0x3f,
0x12, 0x21, 0xa3, 0x30, 0x60, 0x1d, 0x60, 0x1f,
0xe, 0xea, 0xed, 0x2e, 0x60, 0x1e, 0x85, 0x0,
0xfa, 0x48, 0x3, 0xfc, 0xb8, 0xe0, 0x1f, 0x84,
0x3, 0xfe, 0x49, 0x20, 0xf, 0xde, 0x1, 0xff,
0xa, 0xec, 0x0, 0x7c, 0xcc, 0x0, 0xff, 0xe0,
0xbc, 0x0, 0x7d, 0x3f, 0xf7, 0x6d, 0xc2, 0x0,
0x79, 0xcc, 0x3, 0xfc, 0x24, 0x8f, 0x78, 0x40,
0x1d, 0x20, 0x1f, 0xfc, 0x33, 0xc2, 0x0, 0xca,
0x1, 0xff, 0xc4, 0x1b, 0x0, 0xe3, 0x0, 0xff,
0xe2, 0x28, 0x7, 0x38, 0x7, 0xff, 0x11, 0xc0,
0x38, 0x40, 0x3f, 0xf8, 0x84, 0x1, 0xc2, 0x1,
0xff, 0xc4, 0xd0, 0xe, 0x60, 0xf, 0xfe, 0x1a,
0xa0, 0x6, 0x12, 0x4, 0xba, 0x50, 0xf, 0xe6,
0xa0, 0xe, 0x70, 0x4b, 0x45, 0xaf, 0xb8, 0x64,
0x42, 0xc6, 0xc8, 0x7, 0xac, 0x38, 0x3, 0x91,
0xe6, 0xed, 0x4e, 0x40, 0x1e, 0x82, 0x1, 0x0,
0xff, 0xe3, 0x2b, 0x80, 0x3c, 0x3, 0xff, 0x8a,
0xf4, 0x1, 0x26, 0x28, 0x7, 0xff, 0x5, 0x32,
0x0, 0x38, 0xeb, 0xed, 0xd0, 0x84, 0x2, 0x24,
0x8d, 0xb3, 0x0, 0xe0,
/* U+0034 "4" */
0x0, 0xff, 0xe1, 0x26, 0x7f, 0xa8, 0x40, 0x3f,
0xf8, 0xe7, 0x66, 0x0, 0x5d, 0x0, 0xff, 0xe3,
0xf0, 0x7, 0x19, 0x0, 0x7f, 0xf1, 0x5c, 0x80,
0x3c, 0xc0, 0x1f, 0xfc, 0x42, 0x80, 0xf, 0xfe,
0x67, 0x0, 0x7f, 0xf3, 0x18, 0xc0, 0x3f, 0xf9,
0x63, 0x20, 0x1f, 0xfc, 0xca, 0x0, 0xff, 0xe6,
0x2a, 0x0, 0x61, 0x0, 0xff, 0xe3, 0xd, 0x80,
0x66, 0xa0, 0xf, 0xfe, 0x35, 0x88, 0x4, 0x30,
0x1, 0xff, 0xc6, 0x45, 0x0, 0xd0, 0x20, 0x1f,
0xfc, 0x69, 0x0, 0xc4, 0xc0, 0x1f, 0xfc, 0x68,
0x20, 0xd, 0xc0, 0x1f, 0xfc, 0x63, 0x70, 0xc,
0xa6, 0x1, 0xff, 0xc6, 0xe0, 0xe, 0xb0, 0xf,
0xfe, 0x33, 0x90, 0x6, 0x81, 0x0, 0xff, 0xe2,
0x94, 0x0, 0x62, 0x70, 0xf, 0xfe, 0x37, 0x0,
0x74, 0x80, 0x7f, 0xf1, 0x98, 0xc0, 0x32, 0x20,
0x3, 0xff, 0x8a, 0x32, 0x1, 0xc2, 0x3f, 0x0,
0x78, 0x46, 0x0, 0x9c, 0x3, 0xcb, 0xdd, 0xeb,
0x0, 0xea, 0xee, 0x94, 0x4, 0x3, 0xff, 0x94,
0x36, 0x2, 0x1, 0xff, 0xcb, 0x10, 0x71, 0x0,
0xff, 0xe5, 0x8, 0xe, 0xa0, 0x7, 0xff, 0x20,
0xa8, 0x0, 0x77, 0xff, 0xff, 0x58, 0x7, 0x5f,
0xfb, 0x50, 0x3, 0xff, 0xfe, 0x1, 0xff, 0xe0,
0x70, 0xe, 0x70, 0xf, 0xfe, 0x39, 0x0, 0x71,
0x0, 0x7f, 0xf1, 0xc6, 0x88, 0x6, 0x84, 0x3,
0x80,
/* U+0035 "5" */
0x0, 0x93, 0x3f, 0xff, 0xf8, 0x74, 0x1, 0x92,
0xcc, 0x3, 0xff, 0x86, 0xae, 0x1, 0x78, 0x7,
0xff, 0x1b, 0x40, 0x25, 0x0, 0xff, 0xe3, 0x10,
0x7, 0xff, 0x24, 0x68, 0x3, 0xf9, 0xee, 0xff,
0xd9, 0xc4, 0x1, 0xf9, 0x21, 0x13, 0xfc, 0x60,
0x1f, 0xe2, 0x0, 0xff, 0xff, 0x80, 0x7f, 0xf1,
0x8, 0x3, 0xff, 0x94, 0x8a, 0x20, 0x1f, 0xfc,
0x9a, 0xef, 0xf7, 0x64, 0xa8, 0x7, 0xf1, 0x80,
0x7f, 0x9, 0xb5, 0x7c, 0x0, 0x7c, 0xc0, 0x1f,
0xfc, 0x27, 0xd2, 0x0, 0xef, 0x0, 0xff, 0xe1,
0x96, 0x8, 0x6, 0x4b, 0x30, 0xf, 0xfe, 0x10,
0xd0, 0x7, 0x26, 0x7f, 0xee, 0xca, 0x61, 0x0,
0xf2, 0x20, 0x3, 0xfc, 0x26, 0xb3, 0xd0, 0x1,
0xee, 0x0, 0xff, 0xe1, 0xbd, 0x0, 0x72, 0x80,
0x7f, 0xf1, 0x15, 0x40, 0x1c, 0x40, 0x1f, 0xfc,
0x4f, 0x0, 0xe6, 0x0, 0xff, 0xe2, 0x30, 0x7,
0x8, 0x7, 0xff, 0x11, 0xc0, 0x3f, 0xf9, 0x44,
0x1, 0xc2, 0x1, 0xff, 0xc4, 0xa0, 0xe, 0x60,
0xf, 0xfe, 0x1a, 0x98, 0x6, 0x12, 0x4, 0xba,
0x50, 0xf, 0xe6, 0xa0, 0xe, 0x60, 0x4b, 0x45,
0xaf, 0xb8, 0x64, 0x42, 0xc6, 0xc8, 0x7, 0xa4,
0x38, 0x3, 0x91, 0xe6, 0xed, 0x4e, 0x40, 0x1e,
0x81, 0x1, 0x0, 0xff, 0xe3, 0x33, 0x80, 0x3c,
0x3, 0xff, 0x8a, 0xf2, 0x1, 0x26, 0x28, 0x7,
0xff, 0x5, 0x32, 0x0, 0x38, 0xeb, 0xed, 0xd0,
0x84, 0x2, 0x25, 0x8d, 0xb3, 0x0, 0xe0,
/* U+0036 "6" */
0x0, 0xff, 0xe0, 0x14, 0xf7, 0xf2, 0x0, 0x7f,
0xf1, 0xa3, 0x58, 0x40, 0x60, 0x3, 0xff, 0x8b,
0x6e, 0x1, 0xc8, 0x1, 0xff, 0xc3, 0x1c, 0x40,
0xe, 0x88, 0x0, 0x7f, 0xf0, 0x87, 0xc, 0x3,
0x16, 0x38, 0x7, 0xff, 0xf, 0x48, 0x3, 0x26,
0x18, 0x7, 0xff, 0xe, 0x4c, 0x3, 0x2d, 0x88,
0x7, 0xff, 0xd, 0x58, 0x3, 0x2d, 0x0, 0x7f,
0xf1, 0x6, 0xc0, 0x31, 0xd0, 0x7, 0xff, 0x16,
0x4, 0x2, 0x1d, 0x0, 0xff, 0xe2, 0x93, 0x0,
0x6b, 0x10, 0xf, 0xfe, 0x2d, 0x80, 0x65, 0x50,
0x7, 0xff, 0x14, 0x58, 0x3, 0x40, 0x13, 0xe7,
0x7f, 0xb6, 0x50, 0x3, 0xf3, 0x0, 0x66, 0x29,
0xd8, 0x31, 0x0, 0x13, 0x5d, 0x84, 0x3, 0xd4,
0x1, 0xb7, 0x4c, 0x1, 0xfc, 0x9e, 0x40, 0x18,
0x88, 0x1, 0x14, 0x90, 0x7, 0xfc, 0x3e, 0x1,
0x90, 0x3, 0x11, 0x0, 0x6, 0xf0, 0xc6, 0x1,
0xe2, 0x80, 0xb, 0x0, 0x3c, 0x59, 0x88, 0x79,
0xcd, 0x30, 0xe, 0x60, 0x9, 0xc0, 0x38, 0xf4,
0xc0, 0x38, 0xb0, 0xc0, 0x30, 0xa8, 0x0, 0x80,
0x3b, 0x80, 0x3f, 0xb8, 0x3, 0xb0, 0x4, 0x3,
0x94, 0x80, 0x3f, 0x89, 0x0, 0x32, 0x81, 0x80,
0x77, 0x80, 0x7f, 0xd8, 0x1, 0x88, 0x4, 0x3,
0x9c, 0x3, 0xfe, 0x20, 0xc, 0x20, 0x1f, 0x18,
0x7, 0xff, 0x30, 0xc0, 0x3f, 0xf8, 0xc4, 0x1,
0xce, 0x1, 0xff, 0x68, 0x6, 0x20, 0x10, 0xe,
0xe0, 0xf, 0xf9, 0x40, 0x33, 0x80, 0x10, 0x3,
0x22, 0x0, 0x3f, 0x9c, 0x80, 0x36, 0x0, 0x34,
0x3, 0xad, 0x0, 0x3e, 0x68, 0x0, 0xe7, 0x0,
0x30, 0x80, 0x75, 0xd2, 0x90, 0x92, 0xdc, 0x80,
0x73, 0x8, 0x5, 0x60, 0x1e, 0x5a, 0xde, 0xda,
0x40, 0xe, 0x18, 0x0, 0xca, 0xc0, 0x1f, 0xfc,
0x5d, 0x10, 0xe, 0x97, 0x0, 0xff, 0xe1, 0xe,
0x18, 0x7, 0xd1, 0x86, 0x1, 0xff, 0x37, 0x98,
0x7, 0xf1, 0xe6, 0x1d, 0x4, 0x2, 0x25, 0x9e,
0x91, 0x0, 0xe0,
/* U+0037 "7" */
0x8, 0xff, 0xff, 0xe3, 0xd9, 0x82, 0x38, 0x7,
0xff, 0x1d, 0x30, 0x8c, 0x3, 0xff, 0x95, 0x44,
0x1, 0xff, 0xca, 0x35, 0x30, 0xf, 0xfe, 0x48,
0x86, 0x62, 0xef, 0xff, 0x83, 0x20, 0x1e, 0xd0,
0x1, 0xa2, 0x7f, 0xf0, 0x58, 0x40, 0x39, 0x80,
0x3f, 0xf8, 0x86, 0x20, 0x19, 0x44, 0x3, 0xff,
0x89, 0x20, 0x1d, 0x20, 0x1f, 0xfc, 0x41, 0x50,
0xc, 0x46, 0x1, 0xff, 0xc4, 0x90, 0xe, 0xb0,
0xf, 0xfe, 0x2b, 0x0, 0x73, 0x0, 0x7f, 0xf1,
0x14, 0x40, 0x33, 0x0, 0x7f, 0xf1, 0x64, 0x3,
0xac, 0x3, 0xff, 0x88, 0x66, 0x0, 0xc8, 0x40,
0x1f, 0xfc, 0x49, 0x0, 0xef, 0x0, 0xff, 0xe2,
0xa, 0x80, 0x62, 0x40, 0xf, 0xfe, 0x23, 0x0,
0x75, 0x0, 0x7f, 0xf1, 0x64, 0x3, 0x98, 0x3,
0xff, 0x88, 0xa2, 0x1, 0x94, 0x40, 0x3f, 0xf8,
0x92, 0x1, 0xd2, 0x1, 0xff, 0xc4, 0x23, 0x0,
0xc6, 0x60, 0xf, 0xfe, 0x25, 0x80, 0x74, 0x80,
0x7f, 0xf1, 0x5, 0x80, 0x30, 0xa8, 0x7, 0xff,
0x11, 0x80, 0x39, 0x80, 0x3f, 0xf8, 0xb4, 0x1,
0xd2, 0x1, 0xff, 0xc4, 0x42, 0x0, 0xc8, 0x20,
0x1f, 0xfc, 0x4f, 0x0, 0xef, 0x0, 0xff, 0xe2,
0x12, 0x0, 0x62, 0x40, 0xf, 0xfe, 0x25, 0x80,
0x75, 0x80, 0x7f, 0xf1, 0x58, 0x3, 0x98, 0x3,
0xff, 0x88, 0xc0, 0x1c, 0xc0, 0x1f, 0xfc, 0x5a,
0x0, 0xeb, 0x0, 0xff, 0xe5, 0x21, 0x0, 0x7f,
0xf1, 0x60, 0x80, 0xb, 0x40, 0x1f, 0xfc, 0x0,
/* U+0038 "8" */
0x0, 0xf8, 0xe2, 0xfb, 0xfe, 0xdb, 0x72, 0x0,
0xff, 0xe0, 0x1e, 0x61, 0xd0, 0x40, 0x22, 0x48,
0xda, 0x20, 0xf, 0xe8, 0xc3, 0x0, 0xff, 0x97,
0x54, 0x3, 0xe8, 0x70, 0xf, 0xfe, 0x1d, 0x28,
0x7, 0x1b, 0x80, 0x7f, 0xf1, 0x68, 0x40, 0x34,
0x0, 0x78, 0xe2, 0xae, 0x9c, 0x80, 0x3d, 0x20,
0x10, 0xa0, 0x7, 0x4e, 0x3a, 0xa2, 0xc6, 0xb0,
0x7, 0x28, 0x4, 0x40, 0x1c, 0xac, 0x1, 0xf4,
0x98, 0x6, 0x20, 0x9, 0xc0, 0x3b, 0xc0, 0x3f,
0xac, 0x3, 0x84, 0x0, 0xe0, 0x1c, 0x20, 0x1f,
0xfc, 0x31, 0x0, 0x18, 0x7, 0x18, 0x7, 0xf6,
0x80, 0x62, 0x0, 0xca, 0x1, 0xa0, 0x80, 0x3e,
0x45, 0x0, 0xd4, 0x1, 0xa0, 0x3, 0xb6, 0xc,
0x0, 0x29, 0x36, 0x1, 0x85, 0xc0, 0x31, 0x50,
0x7, 0x3e, 0x7f, 0xba, 0xd8, 0x3, 0xe, 0x80,
0x79, 0x70, 0x80, 0x3f, 0xf8, 0x29, 0x86, 0x1,
0xf1, 0xec, 0x88, 0x7, 0xf1, 0xe5, 0x90, 0x7,
0xe2, 0xb9, 0x10, 0xf, 0xe3, 0xca, 0x10, 0xf,
0x97, 0x50, 0x3, 0xff, 0x82, 0xbe, 0x60, 0x1c,
0x94, 0x1, 0xc5, 0x15, 0x74, 0xe2, 0x1, 0x87,
0x48, 0x3, 0x50, 0x7, 0x4e, 0xba, 0xa2, 0xc7,
0x38, 0x6, 0x19, 0x0, 0x9c, 0x40, 0x34, 0xb0,
0x7, 0xd0, 0xc0, 0x19, 0x10, 0x0, 0xb0, 0xc,
0x6c, 0x1, 0xfd, 0x0, 0x1d, 0xa0, 0x26, 0x1,
0xac, 0x3, 0xfc, 0x2a, 0x1, 0x90, 0x8, 0x3,
0x8c, 0x3, 0xfe, 0xd0, 0xc, 0x40, 0xe0, 0x1f,
0xfc, 0xc7, 0x0, 0xe2, 0x0, 0xff, 0xb4, 0x3,
0xc4, 0x1, 0xd4, 0x1, 0xff, 0x20, 0x6, 0x10,
0x12, 0x0, 0xc6, 0xa0, 0x1f, 0xd2, 0x20, 0x19,
0x0, 0xa, 0x1, 0xd4, 0xc0, 0x1f, 0x43, 0x0,
0x77, 0x80, 0x24, 0x3, 0xd3, 0xae, 0xa8, 0xb1,
0xce, 0x1, 0xc4, 0xa0, 0x1, 0x90, 0xf, 0x14,
0x55, 0xd3, 0x88, 0x7, 0xb8, 0x3, 0x33, 0x0,
0x3f, 0xf8, 0xb2, 0x60, 0x1d, 0x2e, 0x1, 0xff,
0xc3, 0xa6, 0x0, 0xfa, 0x31, 0x0, 0x3f, 0xe5,
0xe5, 0x0, 0xfe, 0x3b, 0xd8, 0x41, 0x0, 0x89,
0x67, 0xa8, 0x40, 0x38,
/* U+0039 "9" */
0x0, 0xf8, 0xa2, 0xfb, 0xfe, 0xda, 0x61, 0x0,
0xff, 0xe0, 0x1e, 0x6b, 0xa0, 0x80, 0x44, 0xb3,
0xd4, 0x20, 0x1f, 0xd1, 0x86, 0x1, 0xff, 0x2f,
0x28, 0x7, 0xd2, 0xe0, 0x1f, 0xfc, 0x3a, 0x50,
0xe, 0x66, 0x0, 0x7f, 0xf1, 0x68, 0x80, 0x34,
0x0, 0x79, 0xaf, 0xb9, 0xb4, 0x80, 0x1e, 0x90,
0x9, 0xc4, 0x3, 0x16, 0xca, 0x8, 0x89, 0x6e,
0xc0, 0x1c, 0x86, 0x0, 0xb0, 0xc, 0x38, 0x40,
0x1f, 0x25, 0x0, 0x75, 0x80, 0xc, 0x3, 0x30,
0x80, 0x7f, 0x29, 0x80, 0x64, 0x2, 0x0, 0xeb,
0x0, 0xff, 0xac, 0x3, 0x10, 0x8, 0x7, 0x10,
0x7, 0xfc, 0x40, 0x18, 0x40, 0x3e, 0x30, 0xf,
0xfe, 0x30, 0x80, 0x73, 0x0, 0x7f, 0xda, 0x1,
0xe2, 0x0, 0xed, 0x0, 0xff, 0x94, 0x3, 0xe3,
0x0, 0xcc, 0x20, 0x1f, 0xce, 0x40, 0x18, 0x80,
0x16, 0x1, 0xda, 0x20, 0x1f, 0x34, 0x0, 0x70,
0x80, 0x18, 0x3, 0x8f, 0xdc, 0xc0, 0x2, 0x97,
0x20, 0x1e, 0x60, 0x0, 0xb0, 0x7, 0xc, 0x67,
0xfb, 0xad, 0x0, 0x2, 0x1, 0xb4, 0x2, 0x93,
0x0, 0xff, 0xe0, 0xc8, 0x80, 0x64, 0x0, 0xda,
0x80, 0x1f, 0xe1, 0xc1, 0x0, 0xe3, 0x0, 0xc3,
0x72, 0x40, 0x1f, 0x3f, 0x9d, 0x0, 0x65, 0x0,
0xf9, 0xb7, 0x26, 0x21, 0x39, 0xf0, 0x2a, 0x40,
0x1b, 0x80, 0x3f, 0x8d, 0x9d, 0xcc, 0x60, 0x14,
0x0, 0x62, 0x40, 0xf, 0xfe, 0x2b, 0x90, 0x6,
0xb0, 0xf, 0xfe, 0x29, 0x40, 0x6, 0x16, 0x0,
0xff, 0xe2, 0xf0, 0x7, 0x58, 0x7, 0xff, 0x16,
0x4c, 0x3, 0x1a, 0x80, 0x7f, 0xf1, 0x1d, 0x80,
0x3b, 0x80, 0x3f, 0xf8, 0x8f, 0x0, 0x1d, 0x24,
0x1, 0xff, 0xc3, 0x88, 0x0, 0x73, 0x30, 0x3,
0xff, 0x84, 0x36, 0xe0, 0x1c, 0xb2, 0x1, 0xff,
0xc2, 0x2c, 0x40, 0xe, 0x5a, 0x0, 0xff, 0xe1,
0xf9, 0x0, 0x73, 0xd0, 0x7, 0xff, 0x10, 0x80,
0x3a, 0xa0, 0x3, 0xff, 0x8b, 0x22, 0x4, 0xfc,
0xa0, 0x1f, 0xf0,
/* U+003A ":" */
0x0, 0xff, 0xe0, 0xd, 0x77, 0xf4, 0x0, 0x43,
0xea, 0x20, 0x2f, 0x80, 0xb, 0x10, 0xe, 0x37,
0x5, 0x0, 0xfa, 0xc0, 0x3f, 0x84, 0x3, 0xf8,
0x41, 0x40, 0x3e, 0xb0, 0xb1, 0x0, 0xe3, 0x70,
0x1f, 0x61, 0x1, 0x7c, 0x0, 0x86, 0x7b, 0xfa,
0x0, 0x3f, 0xff, 0xc3, 0x5d, 0xfd, 0x0, 0x10,
0xfa, 0x88, 0xb, 0xe0, 0x2, 0xc4, 0x3, 0x8d,
0xc1, 0x40, 0x3e, 0xb0, 0xf, 0xe1, 0x0, 0xfe,
0x10, 0x50, 0xf, 0xa8, 0x2c, 0x40, 0x38, 0xd8,
0x7, 0xd8, 0x40, 0x5f, 0x0
};
/*---------------------
* GLYPH DESCRIPTION
*--------------------*/
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
{.bitmap_index = 0, .adv_w = 461, .box_w = 25, .box_h = 35, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 293, .adv_w = 461, .box_w = 24, .box_h = 35, .ofs_x = 3, .ofs_y = 0},
{.bitmap_index = 420, .adv_w = 461, .box_w = 24, .box_h = 35, .ofs_x = 3, .ofs_y = 0},
{.bitmap_index = 660, .adv_w = 461, .box_w = 23, .box_h = 35, .ofs_x = 3, .ofs_y = 0},
{.bitmap_index = 912, .adv_w = 461, .box_w = 26, .box_h = 35, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 1105, .adv_w = 461, .box_w = 23, .box_h = 35, .ofs_x = 3, .ofs_y = 0},
{.bitmap_index = 1312, .adv_w = 461, .box_w = 25, .box_h = 35, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 1587, .adv_w = 461, .box_w = 24, .box_h = 35, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 1795, .adv_w = 461, .box_w = 25, .box_h = 35, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 2087, .adv_w = 461, .box_w = 25, .box_h = 35, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 2362, .adv_w = 461, .box_w = 10, .box_h = 27, .ofs_x = 9, .ofs_y = 0}
};
/*---------------------
* CHARACTER MAPPING
*--------------------*/
/*Collect the unicode lists and glyph_id offsets*/
static const lv_font_fmt_txt_cmap_t cmaps[] =
{
{
.range_start = 48, .range_length = 11, .glyph_id_start = 1,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
}
};
/*--------------------
* ALL CUSTOM DATA
*--------------------*/
#if LVGL_VERSION_MAJOR == 8
/*Store all the custom data of the font*/
static lv_font_fmt_txt_glyph_cache_t cache;
#endif
#if LVGL_VERSION_MAJOR >= 8
static const lv_font_fmt_txt_dsc_t font_dsc = {
#else
static lv_font_fmt_txt_dsc_t font_dsc = {
#endif
.glyph_bitmap = glyph_bitmap,
.glyph_dsc = glyph_dsc,
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 1,
.bpp = 4,
.kern_classes = 0,
.bitmap_format = 1,
#if LVGL_VERSION_MAJOR == 8
.cache = &cache
#endif
};
/*-----------------
* PUBLIC FONT
*----------------*/
/*Initialize a public general font descriptor*/
#if LVGL_VERSION_MAJOR >= 8
const lv_font_t ui_font_keyboard_time_48 = {
#else
lv_font_t ui_font_keyboard_time_48 = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
.line_height = 35, /*The maximum line height required by the font*/
.base_line = 0, /*Baseline measured from the bottom of the line*/
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
.subpx = LV_FONT_SUBPX_NONE,
#endif
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
.underline_position = -7,
.underline_thickness = 2,
#endif
.dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
.fallback = NULL,
#endif
.user_data = NULL,
};
#endif /*#if UI_FONT_KEYBOARD_TIME_48*/