feat: 更新键盘固件的事件系统和模块配置
- 在CMakeLists.txt中添加hid_boot_event.c、keyboard_led_event.c和ble_slot_ctrl_module.c源文件 - 新增Kconfig配置项NEW_KBD_BLE_BOND_ENABLE用于启用应用特定的BLE绑定支持 - 修改prj.conf配置,禁用配对模式下的设备名称广播功能 - 重构电池状态事件结构,将charging和full布尔字段改为flags位域,并提供相应的访问函数 - 添加hid_boot_event事件类型,用于处理HID Boot协议输入报告 - 重命名keyboard_led_state_event为keyboard_led_event并改进LED状态处理逻辑 - 移除hid_protocol_event中的transport字段,简化协议事件处理 - 分离hid_report_event和hid_boot_event,明确区分Report和Boot协议报文处理 - 重构battery_module.c代码结构,改用上下文结构体管理电池模块状态 - 更新ble_battery_module.c使用新的电池状态事件访问接口
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include <zephyr/bluetooth/bluetooth.h>
|
||||
#include <zephyr/bluetooth/conn.h>
|
||||
#include <zephyr/settings/settings.h>
|
||||
#include <errno.h>
|
||||
|
||||
@@ -28,33 +29,45 @@ enum ble_bond_cfg_opt {
|
||||
#define PEER_ID_KEY "peer_id"
|
||||
#define BT_LUT_KEY "bt_lut"
|
||||
|
||||
enum state {
|
||||
STATE_DISABLED,
|
||||
STATE_IDLE,
|
||||
STATE_STANDBY,
|
||||
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
|
||||
|
||||
static enum state state = STATE_DISABLED;
|
||||
BUILD_ASSERT(BLE_BOND_SLOT_COUNT <= APP_PEER_COUNT,
|
||||
"BLE slot count exceeds available Bluetooth identities");
|
||||
|
||||
/* app peer id -> bt stack id mapping (default identity 0 is not used). */
|
||||
static uint8_t bt_stack_id_lut[APP_PEER_COUNT];
|
||||
static bool bt_stack_id_lut_valid;
|
||||
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;
|
||||
};
|
||||
|
||||
static uint8_t cur_ble_peer_id;
|
||||
static 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 const char *state_name(enum state s)
|
||||
static struct ble_bond_ctx bond = {
|
||||
.state = BLE_BOND_STATE_DISABLED,
|
||||
};
|
||||
|
||||
static const char *state_name(enum ble_bond_state s)
|
||||
{
|
||||
switch (s) {
|
||||
case STATE_DISABLED:
|
||||
case BLE_BOND_STATE_DISABLED:
|
||||
return "DISABLED";
|
||||
case STATE_IDLE:
|
||||
case BLE_BOND_STATE_IDLE:
|
||||
return "IDLE";
|
||||
case STATE_STANDBY:
|
||||
case BLE_BOND_STATE_STANDBY:
|
||||
return "STANDBY";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
@@ -63,8 +76,8 @@ static const char *state_name(enum state s)
|
||||
|
||||
static uint8_t get_bt_stack_peer_id(uint8_t app_id)
|
||||
{
|
||||
__ASSERT_NO_MSG(app_id < APP_PEER_COUNT);
|
||||
return bt_stack_id_lut[app_id];
|
||||
__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)
|
||||
@@ -78,7 +91,9 @@ static int store_bt_stack_id_lut(void)
|
||||
{
|
||||
char key[] = MODULE_NAME "/" BT_LUT_KEY;
|
||||
|
||||
return settings_save_one(key, bt_stack_id_lut, sizeof(bt_stack_id_lut));
|
||||
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)
|
||||
@@ -93,31 +108,31 @@ static void submit_peer_op_event(enum peer_operation op, uint8_t app_id)
|
||||
|
||||
static void init_bt_stack_id_lut(void)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(bt_stack_id_lut); i++) {
|
||||
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. */
|
||||
bt_stack_id_lut[i] = i + 1;
|
||||
bond.storage.bt_stack_id_lut[i] = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static bool storage_data_is_valid(void)
|
||||
{
|
||||
if (!cur_peer_id_valid || !bt_stack_id_lut_valid) {
|
||||
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",
|
||||
cur_peer_id_valid, bt_stack_id_lut_valid);
|
||||
bond.storage.cur_peer_id_valid, bond.storage.bt_stack_id_lut_valid);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cur_ble_peer_id >= APP_PEER_COUNT) {
|
||||
if (bond.storage.cur_peer_id >= BLE_BOND_SLOT_COUNT) {
|
||||
LOG_WRN("Stored peer id out of range: peer_id=%u max=%u",
|
||||
cur_ble_peer_id, APP_PEER_COUNT - 1);
|
||||
bond.storage.cur_peer_id, BLE_BOND_SLOT_COUNT - 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(bt_stack_id_lut); i++) {
|
||||
if ((bt_stack_id_lut[i] == BT_ID_DEFAULT) ||
|
||||
(bt_stack_id_lut[i] >= CONFIG_BT_ID_MAX)) {
|
||||
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, bt_stack_id_lut[i]);
|
||||
(uint32_t)i, bond.storage.bt_stack_id_lut[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -131,29 +146,31 @@ static int settings_set(const char *key, size_t len_rd,
|
||||
ssize_t rc;
|
||||
|
||||
if (!strcmp(key, PEER_ID_KEY)) {
|
||||
if (len_rd != sizeof(cur_ble_peer_id)) {
|
||||
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(cur_ble_peer_id));
|
||||
cur_peer_id_valid = false;
|
||||
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, &cur_ble_peer_id, sizeof(cur_ble_peer_id));
|
||||
cur_peer_id_valid = (rc == sizeof(cur_ble_peer_id));
|
||||
if (!cur_peer_id_valid) {
|
||||
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(bt_stack_id_lut)) {
|
||||
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(bt_stack_id_lut));
|
||||
bt_stack_id_lut_valid = false;
|
||||
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, bt_stack_id_lut, sizeof(bt_stack_id_lut));
|
||||
bt_stack_id_lut_valid = (rc == sizeof(bt_stack_id_lut));
|
||||
if (!bt_stack_id_lut_valid) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -183,6 +200,164 @@ static int load_identities(void)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -209,7 +384,7 @@ static int erase_peer(uint8_t app_id)
|
||||
|
||||
static int erase_all_peers(void)
|
||||
{
|
||||
for (uint8_t i = 0; i < APP_PEER_COUNT; i++) {
|
||||
for (uint8_t i = 0; i < BLE_BOND_SLOT_COUNT; i++) {
|
||||
int err = erase_peer(i);
|
||||
if (err) {
|
||||
return err;
|
||||
@@ -244,18 +419,14 @@ static bool handle_config_event(const struct config_event *event)
|
||||
case BLE_BOND_CFG_PEER_SELECT:
|
||||
if (event->dyndata.size >= 1) {
|
||||
uint8_t peer_id = event->dyndata.data[0];
|
||||
if (peer_id < APP_PEER_COUNT) {
|
||||
cur_ble_peer_id = peer_id;
|
||||
if (!store_peer_id(cur_ble_peer_id)) {
|
||||
submit_peer_op_event(PEER_OPERATION_SELECTED, cur_ble_peer_id);
|
||||
rsp->status = CONFIG_STATUS_SUCCESS;
|
||||
}
|
||||
if (!select_peer(peer_id)) {
|
||||
rsp->status = CONFIG_STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_BOND_CFG_PEER_ERASE:
|
||||
if (!erase_peer(cur_ble_peer_id)) {
|
||||
if (!erase_peer(bond.storage.cur_peer_id)) {
|
||||
rsp->status = CONFIG_STATUS_SUCCESS;
|
||||
}
|
||||
break;
|
||||
@@ -280,7 +451,7 @@ static bool handle_config_event(const struct config_event *event)
|
||||
rsp_data->event_id = event->event_id;
|
||||
rsp_data->recipient = event->recipient;
|
||||
rsp_data->status = CONFIG_STATUS_SUCCESS;
|
||||
rsp_data->dyndata.data[0] = cur_ble_peer_id;
|
||||
rsp_data->dyndata.data[0] = bond.storage.cur_peer_id;
|
||||
APP_EVENT_SUBMIT(rsp_data);
|
||||
return true;
|
||||
}
|
||||
@@ -300,12 +471,15 @@ static int init_after_settings_loaded(void)
|
||||
|
||||
if (!storage_data_is_valid()) {
|
||||
LOG_WRN("Stored BLE bond data invalid, reinitializing defaults");
|
||||
cur_ble_peer_id = 0;
|
||||
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(cur_ble_peer_id);
|
||||
err = store_peer_id(bond.storage.cur_peer_id);
|
||||
if (err) {
|
||||
LOG_ERR("Failed to store peer_id=%u (err:%d)", cur_ble_peer_id, err);
|
||||
LOG_ERR("Failed to store peer_id=%u (err:%d)",
|
||||
bond.storage.cur_peer_id, err);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
@@ -316,10 +490,12 @@ static int init_after_settings_loaded(void)
|
||||
}
|
||||
}
|
||||
|
||||
state = STATE_IDLE;
|
||||
bond.state = BLE_BOND_STATE_IDLE;
|
||||
LOG_INF("ble_bond init done: state=%s peer_id=%u stack_id=%u",
|
||||
state_name(state), cur_ble_peer_id, get_bt_stack_peer_id(cur_ble_peer_id));
|
||||
submit_peer_op_event(PEER_OPERATION_SELECTED, cur_ble_peer_id);
|
||||
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;
|
||||
@@ -331,12 +507,12 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
const struct module_state_event *event = cast_module_state_event(aeh);
|
||||
|
||||
if (check_state(event, MODULE_ID(settings_loader), MODULE_STATE_READY) &&
|
||||
(state == STATE_DISABLED)) {
|
||||
(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(state));
|
||||
err, state_name(bond.state));
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -345,22 +521,26 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
}
|
||||
|
||||
if (is_power_down_event(aeh)) {
|
||||
if (state == STATE_IDLE) {
|
||||
state = STATE_STANDBY;
|
||||
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 (state == STATE_STANDBY) {
|
||||
state = STATE_IDLE;
|
||||
if (bond.state == BLE_BOND_STATE_STANDBY) {
|
||||
bond.state = BLE_BOND_STATE_IDLE;
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
submit_peer_op_event(PEER_OPERATION_SELECTED, cur_ble_peer_id);
|
||||
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));
|
||||
}
|
||||
@@ -373,4 +553,5 @@ 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);
|
||||
|
||||
Reference in New Issue
Block a user