Files
JBao_Door_Clamp_FW/apps/usr_app/app_cbc2.c
T

622 lines
16 KiB
C

/******************************************************************************
* @file app_cbc2.c
* @brief CBC2 电源 / 充电 / 按键 / 配对 / DP 协调
* @author cyWu <1917507415@qq.com>
* @date 2026.08.24
* @version V1.0.2
* @history
* - V1.0.0, 2026.08.21, cyWu, 首次发布
* - V1.0.2, 2026.08.24, cyWu, 上电即绑定广播,与 81bd2f7 行为对齐
******************************************************************************/
#include "system/includes.h"
#include "app_main.h"
#include "app_cbc2.h"
#include "app_nv.h"
#include "app_led.h"
#include "app_rope.h"
#include "app_mode.h"
#include "app_dnd.h"
#include "bsp_hw.h"
#include "usr_le_api.h"
#include "asm/power/power_api.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[APP_CBC2]"
#define LOG_ERROR_ENABLE
#define LOG_DEBUG_ENABLE
#define LOG_INFO_ENABLE
#include "debug.h"
extern void sys_enter_soft_poweroff(void *priv);
/*============================================================================*/
/* 类型 */
/*============================================================================*/
typedef enum {
APP_LIFE_CHARGE_ONLY = 0,
APP_LIFE_ON,
APP_LIFE_PAIR
} AppLife_t;
typedef struct {
uint8_t inited;
AppLife_t life;
uint8_t power; /* DP0 APP 功能开关 */
uint8_t play_mode;
uint8_t battery;
uint8_t charge_dp; /* 1 充电中 2 未充电 */
uint8_t alm_low;
uint8_t charge_led; /* 0 无 1 充 2 满 */
uint8_t usb_raw;
uint8_t usb_debounced;
uint8_t usb_cnt;
uint8_t restore_on; /* 插电前是否开机 */
uint8_t full_latched;
uint8_t from_deep_boot;
uint8_t boot_key_lock; /* 开机按键未松开前,忽略短按/关机 */
uint8_t key_raw;
uint8_t key_stable;
uint8_t key_cnt;
uint8_t key_pressed;
uint16_t key_hold_ms;
uint8_t req_click;
uint8_t req_poweroff;
uint8_t req_pair;
uint32_t pair_ms;
uint32_t lowbat_ms;
uint8_t lowbat_run;
uint16_t vbat_mv;
uint8_t pause_mode;
uint8_t pause_cal;
uint8_t pause_percent;
uint8_t pause_percent_valid;
uint8_t run_timer;
} AppCbc2_t;
/*============================================================================*/
/* 私有变量 */
/*============================================================================*/
static AppCbc2_t s_app;
/*============================================================================*/
/* 私有函数 */
/*============================================================================*/
static uint8_t app_cbc2_ctrl_allowed(void)
{
return (uint8_t)(s_app.life != APP_LIFE_CHARGE_ONLY);
}
static void app_cbc2_apply_stop_reset(void)
{
app_mode_stop();
app_rope_stop();
s_app.play_mode = 0;
}
static void app_cbc2_apply_play_mode(uint8_t mode, uint8_t blink)
{
if (mode > 6) {
mode = 0;
}
app_rope_stop();
if (mode == 0) {
app_mode_stop();
} else {
app_mode_start(mode);
}
s_app.play_mode = mode;
if (blink) {
app_led_request_mode_blink();
}
log_info("play_mode=%d\n", mode);
}
static void app_cbc2_do_poweroff(void)
{
log_info("enter deep off\n");
app_mode_stop();
bsp_hw_enter_sleep_io();
app_nv_save_if_dirty();
sys_enter_soft_poweroff(NULL);
}
static void app_cbc2_boot_poweroff(void)
{
log_info("boot deep off\n");
bsp_hw_enter_sleep_io();
app_nv_save_if_dirty();
power_set_soft_poweroff();
}
static void app_cbc2_enter_pair(void)
{
log_info("enter pair mode\n");
app_cbc2_apply_stop_reset();
s_app.life = APP_LIFE_PAIR;
s_app.power = 1;
s_app.pair_ms = 0;
s_app.from_deep_boot = 0;
usr_var.usr_goto_pair = 1;
usr_goto_pair_mode();
}
static void app_cbc2_pair_end(uint8_t ok)
{
log_info("pair %s\n", ok ? "ok" : "fail");
app_led_start_pair_result(ok);
s_app.life = APP_LIFE_ON;
s_app.power = 1;
s_app.play_mode = 0;
usr_var.usr_goto_pair = 0;
}
static void app_cbc2_pause_functions(void)
{
s_app.pause_mode = s_app.play_mode;
s_app.pause_cal = app_rope_is_calibrating();
s_app.pause_percent_valid = 0;
if (app_rope_is_busy() && !s_app.pause_cal && app_rope_is_calibrated()
&& app_rope_get_turns()) {
int32_t max_x = (int32_t)app_rope_get_turns() * 100;
if (max_x > 0) {
int32_t pct = app_rope_remaining_x100() * 100 / max_x;
if (pct < 0) {
pct = 0;
}
if (pct > 100) {
pct = 100;
}
s_app.pause_percent = (uint8_t)pct;
s_app.pause_percent_valid = 1;
}
}
app_mode_stop();
app_rope_stop();
log_info("app power pause mode=%d cal=%d\n", s_app.pause_mode, s_app.pause_cal);
}
static void app_cbc2_resume_functions(void)
{
log_info("app power resume mode=%d cal=%d\n", s_app.pause_mode, s_app.pause_cal);
if (s_app.pause_cal) {
app_rope_cal_start();
} else if (s_app.pause_percent_valid) {
app_rope_goto_percent(s_app.pause_percent);
} else if (s_app.pause_mode >= 1 && s_app.pause_mode <= 6) {
app_cbc2_apply_play_mode(s_app.pause_mode, 0);
}
s_app.pause_cal = 0;
s_app.pause_percent_valid = 0;
}
static uint8_t app_cbc2_bat_percent(uint16_t mv)
{
int32_t span = BOARD_VBAT_FULL_MV - BOARD_VBAT_EMPTY_MV;
int32_t pct;
if (mv <= BOARD_VBAT_EMPTY_MV) {
return 0;
}
if (mv >= BOARD_VBAT_FULL_MV) {
return 100;
}
pct = ((int32_t)mv - BOARD_VBAT_EMPTY_MV) * 100 / span;
if (pct < 0) {
pct = 0;
}
if (pct > 100) {
pct = 100;
}
return (uint8_t)pct;
}
static void app_cbc2_sample_charge(void)
{
uint8_t usb = bsp_vpwr_is_online();
if (usb == s_app.usb_raw) {
if (s_app.usb_cnt < 5) {
s_app.usb_cnt++;
}
} else {
s_app.usb_raw = usb;
s_app.usb_cnt = 0;
}
if (s_app.usb_cnt >= 5 && s_app.usb_debounced != s_app.usb_raw) {
s_app.usb_debounced = s_app.usb_raw;
if (s_app.usb_debounced) {
s_app.restore_on = (uint8_t)(s_app.life != APP_LIFE_CHARGE_ONLY);
s_app.full_latched = 0;
app_cbc2_apply_stop_reset();
s_app.life = APP_LIFE_CHARGE_ONLY;
log_info("usb in, charge-only (restore_on=%d)\n", s_app.restore_on);
} else {
log_info("usb out full_latched=%d restore_on=%d\n",
s_app.full_latched, s_app.restore_on);
if (s_app.full_latched || !s_app.restore_on) {
s_app.req_poweroff = 1;
} else {
s_app.life = APP_LIFE_ON;
app_cbc2_apply_stop_reset();
}
}
}
if (s_app.usb_debounced) {
if (bsp_chg_is_low()) {
s_app.charge_led = 1;
s_app.charge_dp = 1;
s_app.full_latched = 0;
} else {
s_app.charge_led = 2;
s_app.charge_dp = 2; /* 充满:DP 报未充电 */
s_app.full_latched = 1;
}
usr_var.usr_power_charge_flag = 1;
} else {
s_app.charge_led = 0;
s_app.charge_dp = 2;
usr_var.usr_power_charge_flag = 2;
}
}
static void app_cbc2_sample_battery(void)
{
uint16_t mv = bsp_vbat_mv();
uint8_t alarm_on;
uint8_t alarm_off;
s_app.vbat_mv = mv;
s_app.battery = app_cbc2_bat_percent(mv);
alarm_on = BOARD_VBAT_LOW_MV;
alarm_off = BOARD_VBAT_LOW_MV + BOARD_VBAT_LOW_HYST_MV;
if (mv <= BOARD_VBAT_EMPTY_MV && !s_app.usb_debounced) {
if (s_app.life != APP_LIFE_CHARGE_ONLY) {
log_info("vbat %d mV lock\n", mv);
s_app.req_poweroff = 1;
}
s_app.alm_low = 1;
return;
}
if (!s_app.alm_low && mv < alarm_on) {
s_app.alm_low = 1;
s_app.lowbat_ms = 0;
s_app.lowbat_run = 1;
log_info("low battery warn %d mV\n", mv);
} else if (s_app.alm_low && mv > alarm_off) {
s_app.alm_low = 0;
s_app.lowbat_run = 0;
s_app.lowbat_ms = 0;
}
if (s_app.usb_debounced || s_app.life == APP_LIFE_CHARGE_ONLY) {
return;
}
if (s_app.lowbat_run && s_app.alm_low) {
s_app.lowbat_ms += 50;
if (s_app.lowbat_ms >= BOARD_LOWBAT_SLEEP_MS) {
log_info("low battery 1min, deep off\n");
s_app.req_poweroff = 1;
}
}
}
static void app_cbc2_handle_click(void)
{
uint8_t next;
if (s_app.life != APP_LIFE_ON) {
return;
}
if (s_app.power == 0) {
return;
}
if (app_rope_is_calibrating()) {
return;
}
next = (uint8_t)((s_app.play_mode + 1) % 7);
app_cbc2_apply_play_mode(next, 1);
}
static void app_cbc2_key_hold_check(void)
{
if (!s_app.key_pressed || s_app.boot_key_lock) {
return;
}
s_app.key_hold_ms++;
if (s_app.life == APP_LIFE_CHARGE_ONLY || s_app.life == APP_LIFE_PAIR) {
return;
}
if (s_app.life == APP_LIFE_ON && s_app.key_hold_ms == BOARD_KEY_POWER_MS) {
s_app.req_poweroff = 1;
}
}
static void app_cbc2_key_tick_1ms(void)
{
uint8_t raw = bsp_key_is_pressed();
if (raw == s_app.key_raw) {
if (s_app.key_cnt < BOARD_KEY_DEBOUNCE_MS) {
s_app.key_cnt++;
}
} else {
s_app.key_raw = raw;
s_app.key_cnt = 0;
}
if (s_app.key_cnt != BOARD_KEY_DEBOUNCE_MS) {
app_cbc2_key_hold_check();
return;
}
if (raw && !s_app.key_pressed) {
s_app.key_pressed = 1;
s_app.key_hold_ms = 0;
} else if (!raw && s_app.key_pressed) {
if (s_app.boot_key_lock) {
s_app.boot_key_lock = 0;
s_app.key_pressed = 0;
s_app.key_hold_ms = 0;
log_info("boot key released\n");
return;
}
if (s_app.life == APP_LIFE_ON &&
s_app.power &&
s_app.key_hold_ms < BOARD_KEY_CLICK_MS &&
s_app.key_hold_ms >= 10) {
s_app.req_click = 1;
}
s_app.key_pressed = 0;
s_app.key_hold_ms = 0;
}
app_cbc2_key_hold_check();
}
static void app_cbc2_boot_decide(void)
{
s_app.usb_raw = bsp_vpwr_is_online();
s_app.usb_debounced = s_app.usb_raw;
s_app.usb_cnt = 5;
s_app.vbat_mv = bsp_vbat_mv();
if (s_app.usb_debounced) {
s_app.life = APP_LIFE_CHARGE_ONLY;
s_app.restore_on = 0;
s_app.power = 0;
log_info("boot CHARGE_ONLY vbat=%d\n", s_app.vbat_mv);
return;
}
if (s_app.vbat_mv <= BOARD_VBAT_EMPTY_MV) {
log_info("boot vbat %d too low\n", s_app.vbat_mv);
app_cbc2_boot_poweroff();
return;
}
/* 与 81bd2f7 一致:上电即绑定广播,不等按键、不 deep off */
s_app.power = 1;
s_app.play_mode = 0;
s_app.from_deep_boot = 0;
s_app.boot_key_lock = bsp_key_is_pressed() ? 1 : 0;
s_app.key_pressed = s_app.boot_key_lock;
s_app.key_hold_ms = 0;
s_app.key_raw = s_app.key_pressed;
s_app.key_stable = s_app.key_pressed;
s_app.key_cnt = BOARD_KEY_DEBOUNCE_MS;
usr_var.usr_goto_pair = 1;
s_app.life = APP_LIFE_PAIR;
s_app.pair_ms = 0;
log_info("boot PAIR power-on bind adv\n");
}
/*============================================================================*/
/* 公有函数 */
/*============================================================================*/
AppCbc2_Ret_t app_cbc2_init(void)
{
if (s_app.inited) {
return APP_CBC2_OK;
}
memset(&s_app, 0, sizeof(s_app));
s_app.charge_dp = 2;
s_app.power = 0;
bsp_hw_init();
app_nv_init();
app_led_init();
app_rope_init();
app_mode_init();
app_dnd_init();
app_cbc2_boot_decide();
s_app.inited = 1;
sys_timer_add(NULL, app_cbc2_run, 50);
log_info("cbc2 init life=%d power=%d\n", s_app.life, s_app.power);
return APP_CBC2_OK;
}
AppCbc2_Ret_t app_cbc2_get_status(void)
{
return s_app.inited ? APP_CBC2_OK : APP_CBC2_ERR_NOT_INIT;
}
void app_cbc2_tick_1ms(void)
{
AppLedCtx_t led;
if (!s_app.inited) {
return;
}
app_rope_tick_1ms();
app_cbc2_key_tick_1ms();
(void)bsp_aloft_is_active();
memset(&led, 0, sizeof(led));
led.charge_led = s_app.charge_led;
led.pairing = (uint8_t)(s_app.life == APP_LIFE_PAIR);
led.app_power = s_app.power;
led.low_bat = s_app.alm_low;
led.life_on = (uint8_t)(s_app.life != APP_LIFE_CHARGE_ONLY);
app_led_tick_1ms(&led, 1);
}
void app_cbc2_run(void *priv)
{
(void)priv;
if (!s_app.inited) {
return;
}
app_cbc2_sample_charge();
app_cbc2_sample_battery();
app_dnd_run();
app_mode_run();
if (s_app.life == APP_LIFE_PAIR) {
s_app.pair_ms += 50;
if (usr_get_pair_flag()) {
app_cbc2_pair_end(1);
} else if (s_app.pair_ms >= BOARD_PAIR_TIMEOUT_MS) {
app_cbc2_pair_end(0);
}
}
/* 仅 BLE 已起来后才允许再调 usr_goto_pair_mode,避免和 bt_ble_init 抢广播 */
if (s_app.req_pair) {
s_app.req_pair = 0;
if (s_app.life != APP_LIFE_CHARGE_ONLY) {
app_cbc2_enter_pair();
}
}
if (s_app.req_click) {
s_app.req_click = 0;
app_cbc2_handle_click();
}
if (s_app.req_poweroff) {
s_app.req_poweroff = 0;
app_cbc2_do_poweroff();
}
app_nv_save_if_dirty();
}
void app_cbc2_fill_report(AppCbc2Report_t *out)
{
if (!out) {
return;
}
memset(out, 0, sizeof(*out));
out->power = s_app.power;
out->play_mode = s_app.play_mode;
out->calibration = app_rope_is_calibrating();
out->numberturns = app_rope_get_turns();
out->alm_low_battery = s_app.alm_low;
out->battery = s_app.battery;
out->charge = s_app.charge_dp;
app_dnd_get_raw(out->dnd);
}
AppCbc2_Ret_t app_cbc2_ctrl_power(uint8_t on)
{
if (!app_cbc2_ctrl_allowed()) {
log_info("ctrl power ignored (charge)\n");
return APP_CBC2_ERR_DENIED;
}
if (s_app.life == APP_LIFE_PAIR) {
return APP_CBC2_ERR_BUSY;
}
on = on ? 1 : 0;
if (on == s_app.power) {
return APP_CBC2_OK;
}
if (on) {
s_app.power = 1;
app_cbc2_resume_functions();
} else {
app_cbc2_pause_functions();
s_app.power = 0;
}
return APP_CBC2_OK;
}
AppCbc2_Ret_t app_cbc2_ctrl_play_mode(uint8_t mode)
{
if (!app_cbc2_ctrl_allowed() || s_app.power == 0) {
return APP_CBC2_ERR_DENIED;
}
if (app_rope_is_calibrating()) {
return APP_CBC2_ERR_BUSY;
}
app_cbc2_apply_play_mode(mode, (uint8_t)(mode != s_app.play_mode));
return APP_CBC2_OK;
}
AppCbc2_Ret_t app_cbc2_ctrl_hand(uint8_t reserved, uint8_t percent)
{
(void)reserved;
if (!app_cbc2_ctrl_allowed() || s_app.power == 0) {
return APP_CBC2_ERR_DENIED;
}
if (app_rope_is_calibrating()) {
return APP_CBC2_ERR_BUSY;
}
app_cbc2_apply_stop_reset();
if (app_rope_goto_percent(percent) != APP_ROPE_OK) {
return APP_CBC2_ERR_DENIED;
}
return APP_CBC2_OK;
}
AppCbc2_Ret_t app_cbc2_ctrl_cal(uint8_t on)
{
if (!app_cbc2_ctrl_allowed() || s_app.power == 0) {
return APP_CBC2_ERR_DENIED;
}
if (on) {
app_mode_stop();
s_app.play_mode = 0;
if (app_rope_cal_start() != APP_ROPE_OK) {
return APP_CBC2_ERR_DENIED;
}
} else {
app_rope_cal_stop();
}
return APP_CBC2_OK;
}
AppCbc2_Ret_t app_cbc2_ctrl_turns(uint8_t turns)
{
if (!app_cbc2_ctrl_allowed()) {
return APP_CBC2_ERR_DENIED;
}
app_rope_set_turns(turns);
return APP_CBC2_OK;
}
AppCbc2_Ret_t app_cbc2_ctrl_dnd(const uint8_t raw[APP_CBC2_DND_LEN])
{
if (!raw) {
return APP_CBC2_ERR_PARAM;
}
if (!app_cbc2_ctrl_allowed()) {
return APP_CBC2_ERR_DENIED;
}
app_dnd_set_raw(raw);
return APP_CBC2_OK;
}