517 lines
15 KiB
C
517 lines
15 KiB
C
/******************************************************************************
|
||
* @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_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; /**< DP0:0=软关,电机/玩法停,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;
|
||
uint8_t no_disturb;
|
||
} 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;
|
||
s_func.no_disturb = !!gDevData.no_disturb_enable[0];
|
||
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);
|
||
}
|
||
|
||
void app_function_set_no_disturb(uint8_t enable)
|
||
{
|
||
s_func.no_disturb = !!enable;
|
||
app_shake_wake_reset();
|
||
}
|
||
|
||
/**
|
||
* @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.no_disturb != !!gDevData.no_disturb_enable[0]) {
|
||
app_function_set_no_disturb(gDevData.no_disturb_enable[0]);
|
||
}
|
||
|
||
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;
|
||
|
||
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);
|
||
}
|
||
if (mode != old_mode) {
|
||
app_led_request_mode_blink(); /* 模式真变了才闪两下 */
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @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(); /* 自动/空闲切到手动,闪两下提示 */
|
||
log_info("hand cmd=%d %s max=%ums\n", cmd,
|
||
(cmd == HAND_MODE_1) ? "upper" : "lower",
|
||
(unsigned)BOARD_HAND_PULSE_MS);
|
||
} 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 &&
|
||
!s_func.no_disturb &&
|
||
!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");
|
||
}
|
||
}
|