Files
JBao_Magic_Box_FW/apps/usr_app/app_function.c
T

519 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/******************************************************************************
* @file app_function.c
* @brief 设备功能开关 + 自动玩法 + 手动点动 + 按键动作实现
* @author cyWu <1917507415@qq.com>
* @date 2026.08.26
* @version V1.1.0
* @history
* - V1.0.0, 2026.08.25, cyWu, 首次发布,实现 DP0 开关、自动玩法、手动点动与按键动作
* - V1.1.0, 2026.08.26, cyWu, 切模式闪灯改到 function_play_mode_apply 统一
* 触发(自动互切/手动切自动都会闪);手动点动
* 启动也补上闪灯;DP2 停转(cmd=3)改边沿触发,
* 避免残留的 03 一直把刚启动的自动模式打断;
* 自动模式新增单次最长运行时长,到点回待机
******************************************************************************/
#include "app_function.h"
#include "app_power.h"
#include "app_pair.h"
#include "app_mode.h"
#include "app_led.h"
#include "app_shake_wake.h"
#include "app_no_disturb.h"
#include "car_audio_test.h"
#include "app_audio_debug.h"
#include "app_time_util.h"
#include "bsp_hw.h"
#include "board_pin.h"
#include "jb_product.h"
#include "usr_le_api.h"
#include "system/includes.h"
#define LOG_TAG_CONST APP
#define LOG_TAG "[APP_FUNC]"
#define LOG_INFO_ENABLE
#include "debug.h"
/** 功能模块总状态,替代原来一堆散落的 static 变量 */
typedef struct {
uint8_t app_power; /**< DP00=软关,电机/玩法停,BLE 仍在 */
uint8_t play_mode; /**< 当前自动模式 0~5 */
uint32_t play_ms; /**< 当前自动模式已运行时长,超时回待机 */
uint8_t hand_busy; /**< 手动点动进行中 */
uint8_t hand_cmd_last; /**< 最近一次手动命令 */
uint16_t hand_ms;
volatile uint8_t hand_write_pending; /**< DP2 刚写入(03 停转要靠事件) */
volatile uint8_t hand_write_cmd;
volatile uint8_t power_write_pending;
volatile uint8_t power_write_value;
volatile uint8_t play_write_pending;
volatile uint8_t play_write_mode;
uint8_t shake_wake;
} AppFunctionCtx_t;
static AppFunctionCtx_t s_func;
static void function_hand_stop(void);
static void function_play_mode_apply(uint8_t mode);
static void function_play_mode_poll(void);
static void function_play_mode_timeout(uint16_t dt);
static void function_hand_poll(void);
static void function_on_click(void);
static uint8_t function_power_on(uint8_t show_hint);
static uint8_t function_shake_wake_allowed(void);
static void function_shake_wake_trigger(void);
/**
* @brief 初始化功能模块状态
* @return 无
*/
void app_function_init(void)
{
memset(&s_func, 0, sizeof(s_func));
app_shake_wake_init(function_shake_wake_allowed,
function_shake_wake_trigger);
s_func.shake_wake = !!gDevData.shake_wake;
app_shake_wake_set_enable(s_func.shake_wake);
}
void app_function_dp_power_write(uint8_t enable)
{
s_func.power_write_value = !!enable;
s_func.power_write_pending = 1;
}
void app_function_dp_play_write(uint8_t mode)
{
s_func.play_write_mode = mode;
s_func.play_write_pending = 1;
}
/**
* @brief DP2 写入钩子:仅记录第 1 字节,真正执行在 app_function_poll
* @param cmd 0=停止,1=上部电机探出,2=下部电机探出
* @return 无
*/
void app_function_dp_hand_write(uint8_t cmd)
{
s_func.hand_write_cmd = cmd;
s_func.hand_write_pending = 1;
}
void app_function_set_shake_wake(uint8_t enable)
{
s_func.shake_wake = !!enable;
gDevData.shake_wake = s_func.shake_wake;
app_shake_wake_set_enable(s_func.shake_wake);
}
/**
* @brief 跟随 APP 下发的 DP0/DP1,并处理 DP2 手动点动
* @return 无
*/
void app_function_poll(void)
{
if (s_func.shake_wake != !!gDevData.shake_wake) {
app_function_set_shake_wake(gDevData.shake_wake);
}
if (s_func.power_write_pending) {
uint8_t enable = s_func.power_write_value;
s_func.power_write_pending = 0;
if (enable) {
if (function_power_on(1)) {
log_info("APP power on\n");
} else {
gDevData.power = s_func.app_power;
}
} else if (!enable && s_func.app_power) {
app_function_soft_off();
} else {
gDevData.power = s_func.app_power;
}
}
function_play_mode_poll();
function_hand_poll();
}
/**
* @brief 推进当前玩法节拍或手动刹车
* @param dt 距上次调用的毫秒数
* @return 无
*/
void app_function_tick_1ms(uint16_t dt)
{
/* 与业务节拍共用一次唤醒,避免震动检测另开 10ms sys_timer。 */
app_shake_wake_poll();
if (s_func.hand_busy) {
bsp_motor_switch_tick(); /* 手动点动期间也要走换向死区 */
s_func.hand_ms = app_add_ms(s_func.hand_ms, dt);
if (s_func.hand_ms >= BOARD_HAND_PULSE_MS) {
function_hand_stop();
gDevData.hand_mode = HAND_MODE_0;
app_led_request_mode_blink();
log_info("manual timeout %ums\n", (unsigned)BOARD_HAND_PULSE_MS);
}
} else {
app_mode_run();
function_play_mode_timeout(dt); /* 自动模式跑满 10 分钟回待机 */
}
}
/**
* @brief 停手动点动(走电机反转刹车)
* @return 无
*/
static void function_hand_stop(void)
{
s_func.hand_busy = 0;
s_func.hand_ms = 0;
gDevData.hand_mode = HAND_MODE_0;
bsp_drive_stop();
}
/**
* @brief 停手动 + 自动模式,并清 DP1
* @return 无
*/
void app_function_stop_all(void)
{
s_func.play_mode = 0;
s_func.play_ms = 0;
gDevData.play_mode = 0;
gDevData.hand_mode = HAND_MODE_0;
app_mode_stop();
function_hand_stop();
}
/**
* @brief 软关:电机停、DP0=0,等 APP/短按再开。不断 BLE
* @return 无
*/
void app_function_soft_off(void)
{
s_func.app_power = 0;
gDevData.power = 0;
s_func.play_mode = 0;
s_func.play_ms = 0;
gDevData.play_mode = 0;
app_mode_stop();
function_hand_stop();
/* 不依赖系统下一次轻睡回调,软关生效后立刻关四路 MCPWM。 */
bsp_hw_sleep_pwm_off();
app_shake_wake_reset();
usr_ble_set_standby(1);
log_info("soft off, wait APP power on\n");
}
/**
* @brief 上电默认开启功能(DP0=1),不带开机灯效提示
* @return 无
*/
void app_function_power_on_silent(void)
{
(void)function_power_on(0);
}
/**
* @brief 查询功能开关状态(DP0)
* @return 1=开,0=关
*/
uint8_t app_function_is_power_on(void)
{
return s_func.app_power;
}
/**
* @brief 查询手动点动是否进行中
* @return 1=进行中,0=空闲
*/
uint8_t app_function_is_hand_busy(void)
{
return s_func.hand_busy;
}
/**
* @brief 查询当前自动玩法编号
* @return 0=停转,1~5=对应玩法
*/
uint8_t app_function_play_mode(void)
{
return s_func.play_mode;
}
/**
* @brief 切换自动模式 0~5,并统一在这里判定要不要闪灯提示
* @note 闪灯规则:只要 play_mode 真的变了就闪两下。手动点动期间 play_mode
* 恒为 0(见 function_hand_poll),所以“手动切到自动”落到这里时
* old_mode 必是 0、新 mode 非 0,天然满足“变了”,不用额外判断;
* 由充电/软关/配对等条件被动摁回 0 且本来就是 0 的情况才不闪,
* 避免刷屏式误闪
* @param mode 0=停转,1~5=对应玩法
* @return 无
*/
static void function_play_mode_apply(uint8_t mode)
{
uint8_t old_mode = s_func.play_mode;
MAGIC_AUDIO_LOG("模式切换请求:旧=%u,请求=%u,DP4=%u,功能开关=%u,充电=%u,关机中=%uOTA=%u",
old_mode, mode, gDevData.beep_trig, s_func.app_power,
app_power_usb_online(), app_power_poweroff_pending(), app_led_is_ota());
if (mode > PLAY_MODE_5) {
mode = 0;
}
/* Pairing is an online overlay; only charging, OTA and power-off stop motion. */
if (app_power_usb_online() || !s_func.app_power || app_power_poweroff_pending()
|| app_led_is_ota()) {
mode = 0;
}
if (mode != 0 && s_func.hand_busy) {
function_hand_stop(); /* 手动切到自动:先停手动点动,再进自动模式 */
}
s_func.play_mode = mode;
s_func.play_ms = 0; /* 每次切模式都重新计 10 分钟 */
gDevData.play_mode = mode;
if (mode == 0) {
app_mode_stop();
} else {
app_mode_start(mode);
}
// MAGIC_AUDIO_LOG("模式切换结果:旧=%u,实际=%u;当前测试不拦截DP4", old_mode, mode);
if (mode != old_mode) {
app_led_request_mode_blink(); /* 模式真变了才闪两下 */
/* DP4:0=关闭模式切换鸟叫,1=允许播放 */
//&& gDevData.beep_trig
if ((mode != PLAY_MODE_0) && gDevData.beep_trig) {
Car_Audio_Test_Play();
}
}
}
/**
* @brief 跟随 APP 下发的 DP1 自动模式
* @note 手动点动进行中也允许响应,因为选中一个自动模式本身就代表要从
* 手动切回自动(function_play_mode_apply 内部会先停手动)
* @return 无
*/
static void function_play_mode_poll(void)
{
uint8_t mode;
if (!s_func.play_write_pending) {
return;
}
s_func.play_write_pending = 0;
if (app_power_usb_online() || !s_func.app_power || app_power_poweroff_pending()
|| app_led_is_ota()) {
gDevData.play_mode = s_func.play_mode;
return;
}
mode = s_func.play_write_mode;
if (mode > PLAY_MODE_5) {
mode = 0;
}
if (mode != PLAY_MODE_0 && mode == s_func.play_mode) {
mode = PLAY_MODE_0;
}
function_play_mode_apply(mode);
log_info("APP play_mode=%d\n", mode);
}
/**
* @brief 自动模式单次最长运行 BOARD_AUTO_MODE_MAX_MS,到点自动回待机
* @note 不走 function_play_mode_apply 的闪灯提示:这是保护性超时,不是
* 用户主动切模式,闪灯提示反而会让用户误以为是自己按到了什么
* @param dt 距上次调用的毫秒数
* @return 无
*/
static void function_play_mode_timeout(uint16_t dt)
{
if (s_func.play_mode == 0) {
return;
}
s_func.play_ms += dt;
if (s_func.play_ms < BOARD_AUTO_MODE_MAX_MS) {
return;
}
log_info("auto mode %d run %ums, timeout to standby\n",
s_func.play_mode, (unsigned)s_func.play_ms);
app_function_soft_off();
}
/**
* @brief DP2 手动:只看第 1 字节。0=空闲,1=上部探出,2=下部探出
* @return 无
*/
static void function_hand_poll(void)
{
uint8_t cmd;
uint8_t got_write = s_func.hand_write_pending;
/* 优先用 DP 写入钩子带来的边沿;没有新写入才读协议层当前值 */
if (got_write) {
s_func.hand_write_pending = 0;
cmd = s_func.hand_write_cmd;
} else {
cmd = gDevData.hand_mode;
}
if (app_power_usb_online() || !s_func.app_power || app_power_poweroff_pending()
|| app_led_is_ota()) {
if (s_func.hand_busy) {
app_function_stop_all();
}
s_func.hand_cmd_last = cmd;
return;
}
if (!got_write) {
s_func.hand_cmd_last = cmd;
return;
}
if (cmd == HAND_MODE_0) {
if (s_func.hand_busy) {
function_hand_stop();
app_led_request_mode_blink();
}
s_func.hand_cmd_last = cmd;
return;
}
if (cmd == HAND_MODE_1 || cmd == HAND_MODE_2) {
s_func.play_mode = 0;
s_func.play_ms = 0;
gDevData.play_mode = 0;
app_mode_stop();
s_func.hand_busy = 1;
s_func.hand_ms = 0;
gDevData.hand_mode = cmd;
if (cmd == HAND_MODE_1) {
bsp_drive_set(BSP_MOTOR_FORWARD, 5500,
BSP_MOTOR_STOP, 0);
} else {
bsp_drive_set(BSP_MOTOR_STOP, 0,
BSP_MOTOR_FORWARD, 5500);
}
app_led_request_mode_blink();
if (gDevData.beep_trig) {
Car_Audio_Test_Play();
}
} else {
gDevData.hand_mode = HAND_MODE_0;
if (s_func.hand_busy) {
function_hand_stop();
}
log_info("ignore unsupported hand cmd=%d\n", cmd);
}
s_func.hand_cmd_last = cmd;
}
/**
* @brief 短按:自动模式 0→1→…→5→0(闪灯提示交给 function_play_mode_apply
* @return 无
*/
static void function_on_click(void)
{
if (s_func.hand_busy) {
log_info("key click ignored, hand busy\n");
return;
}
function_play_mode_apply((s_func.play_mode >= PLAY_MODE_5) ?
PLAY_MODE_1 : (uint8_t)(s_func.play_mode + 1));
log_info("key click play_mode=%d\n", s_func.play_mode);
}
/**
* @brief 短按回调:充电/配对忽略;软关时唤醒;否则切模式
* @return 无
*/
void usr_on_key_short(void)
{
if (app_power_poweroff_pending()) {
return;
}
if (app_power_usb_online()) {
log_info("key click ignored, charging\n");
return;
}
if (app_led_is_ota()) {
log_info("key click ignored, ota\n");
return;
}
if (!s_func.app_power) {
/* 软关状态下短按:只唤醒功能,不切模式 */
if (function_power_on(1)) {
log_info("key click cancel soft off\n");
}
return;
}
function_on_click();
}
/**
* @brief 长按 3s:运行中深睡(开机 3s/5s 不走这里)
* @return 无
*/
void usr_on_key_long(void)
{
if (app_power_poweroff_pending()) {
return;
}
if (app_power_usb_online()) {
log_info("key hold 3s ignored, charging\n");
return;
}
if (app_led_is_ota()) {
log_info("key hold 3s, exit ota and poweroff\n");
usr_app_ota_exit();
app_led_exit_ota();
app_power_request_poweroff();
return;
}
log_info("key hold 3s, poweroff\n");
app_power_request_poweroff();
}
/** Shared standby-to-on transition for APP, key and vibration wake. */
static uint8_t function_power_on(uint8_t show_hint)
{
if (app_power_usb_online() || app_power_poweroff_pending() ||
app_led_is_ota()) {
return 0;
}
if (s_func.app_power) {
gDevData.power = 1;
return 1;
}
app_function_stop_all();
s_func.app_power = 1;
gDevData.power = 1;
app_shake_wake_reset();
usr_ble_set_standby(0);
if (show_hint) {
app_led_hint_power_on();
}
return 1;
}
static uint8_t function_shake_wake_allowed(void)
{
return s_func.shake_wake &&
!app_no_disturb_is_active() &&
!s_func.app_power &&
!app_power_usb_online() &&
!app_power_poweroff_pending() &&
!app_led_is_ota();
}
static void function_shake_wake_trigger(void)
{
if (function_power_on(1)) {
log_info("shake wake power on\n");
}
}