Files
JBao_Door_Clamp_FW/apps/usr_app/app_function.c
T
wuchuyuan fdb43011b7 1、低电 3.7V 只闪红灯不再 1 分钟后关机;电量低于约 5% 自动关机,未充电开机先闪 5 下红灯再睡回,只能充电后才能开机
2、自动/手动互相切换都闪 2 下灯;APP 发停止后再发自动模式可正常启动,手动残留停止不再挡住自动
3、自动模式 1~6 单次最长跑 10 分钟后回待机;关机时长按 12 秒进 OTA,OTA 期间红绿交替闪
2026-08-27 10:20:48 +08:00

399 lines
12 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_time_util.h"
#include "bsp_hw.h"
#include "board_pin.h"
#include "jb_product.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~6 */
uint32_t play_ms; /**< 当前自动模式已运行时长,超时回待机 */
uint8_t hand_busy; /**< 手动点动进行中 */
uint8_t hand_cmd_last; /**< 边沿:同一 1/2/3 不重复触发 */
uint16_t hand_ms;
volatile uint8_t hand_write_pending; /**< DP2 刚写入(03 停转要靠事件) */
volatile uint8_t hand_write_cmd;
} 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);
/**
* @brief 初始化功能模块状态
* @return 无
*/
void app_function_init(void)
{
memset(&s_func, 0, sizeof(s_func));
}
/**
* @brief DP2 写入钩子:仅记录第 1 字节,真正执行在 app_function_poll
* @param cmd 0=空闲,1=顺时针,2=逆时针,3=停
* @return 无
*/
void app_function_dp_hand_write(uint8_t cmd)
{
s_func.hand_write_cmd = cmd;
s_func.hand_write_pending = 1;
}
/**
* @brief 跟随 APP 下发的 DP0/DP1,并处理 DP2 手动点动
* @return 无
*/
void app_function_poll(void)
{
/* DP0:APP 下发开关。充电中 / 正在关机时不跟,避免和 USB 软关抢状态 */
if (!app_power_usb_online() && !app_power_poweroff_pending()) {
if (gDevData.power && !s_func.app_power) {
s_func.app_power = 1;
app_led_hint_power_on();
log_info("APP power on\n");
} else if (!gDevData.power && s_func.app_power) {
app_function_soft_off();
}
}
/* 先跟自动模式,再处理手动点动:同一拍里 APP 发自动模式时,能先把手动停掉 */
function_play_mode_poll();
function_hand_poll();
}
/**
* @brief 推进当前玩法节拍或手动刹车
* @param dt 距上次调用的毫秒数
* @return 无
*/
void app_function_tick_1ms(uint16_t dt)
{
if (s_func.hand_busy) {
bsp_motor_switch_tick(); /* 手动点动期间也要走换向死区 */
s_func.hand_ms = app_add_ms(s_func.hand_ms, dt);
} 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;
bsp_motor_set(BSP_MOTOR_STOP);
}
/**
* @brief 停手动 + 自动模式,并清 DP1
* @return 无
*/
void app_function_stop_all(void)
{
s_func.play_mode = 0;
s_func.play_ms = 0;
gDevData.play_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();
log_info("soft off, wait APP power on\n");
}
/**
* @brief 上电默认开启功能(DP0=1),不带开机灯效提示
* @return 无
*/
void app_function_power_on_silent(void)
{
s_func.app_power = 1;
gDevData.power = 1;
}
/**
* @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~6=对应玩法
*/
uint8_t app_function_play_mode(void)
{
return s_func.play_mode;
}
/**
* @brief 切换自动模式 0~6,并统一在这里判定要不要闪灯提示
* @note 闪灯规则:只要 play_mode 真的变了就闪两下。手动点动期间 play_mode
* 恒为 0(见 function_hand_poll),所以“手动切到自动”落到这里时
* old_mode 必是 0、新 mode 非 0,天然满足“变了”,不用额外判断;
* 由充电/软关/配对等条件被动摁回 0 且本来就是 0 的情况才不闪,
* 避免刷屏式误闪
* @param mode 0=停转,1~6=对应玩法
* @return 无
*/
static void function_play_mode_apply(uint8_t mode)
{
uint8_t old_mode = s_func.play_mode;
if (mode > 6) {
mode = 0;
}
/* 充电 / 软关 / 关机中 / 配对中:不允许跑自动,强制回到待机 */
if (app_power_usb_online() || !s_func.app_power || app_power_poweroff_pending()
|| app_pair_is_active()) {
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 (app_power_usb_online() || !s_func.app_power || app_power_poweroff_pending()
|| app_pair_is_active()) {
return;
}
mode = gDevData.play_mode;
if (mode > 6) {
mode = 0;
gDevData.play_mode = 0;
}
if (mode == s_func.play_mode) {
return; /* 没变化,不重复 start */
}
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);
s_func.play_mode = 0;
s_func.play_ms = 0;
gDevData.play_mode = 0;
app_mode_stop();
}
/**
* @brief DP2 手动:只看第 1 字节。0=空闲,1=顺时针,2=逆时针,3=停
* @note 00 00 是 DP 默认值,不当停止。百分比/力度、DP3、DP4 预留不接。
* @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[0];
}
if (app_power_usb_online() || !s_func.app_power || app_power_poweroff_pending()
|| app_pair_is_active()) {
if (s_func.hand_busy || (got_write && (cmd == 3))) {
app_function_stop_all();
}
s_func.hand_cmd_last = cmd;
return;
}
if (cmd == 0) {
s_func.hand_cmd_last = 0; /* 00 是 DP 默认值,不当停止命令 */
return;
}
/* cmd==3 改边沿触发:只在“刚收到停止”这一拍才可能真的停一次,避免
* DP2 停留在 03(APP 没有再写别的值)时,后面随便什么原因一进自动/
* 手动就被这个陈旧的 03 立刻打断——这正是之前“自动运行中,APP 发
* 停止,再发运行自动模式却启动不了”的根因 */
if (cmd == 3) {
if ((got_write || (cmd != s_func.hand_cmd_last))
&& (s_func.hand_busy || app_mode_get_active())) {
app_function_stop_all();
log_info("hand stop cmd=3\n");
}
s_func.hand_cmd_last = 3;
return;
}
if ((cmd == 1 || cmd == 2) && (got_write || (cmd != s_func.hand_cmd_last))) {
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;
/* 1=顺时针收绳,2=逆时针放绳(BOARD_MOTOR_IN_INA_HIGH=1 */
bsp_motor_set((cmd == 1) ? BSP_MOTOR_IN : BSP_MOTOR_OUT);
app_led_request_mode_blink(); /* 自动/空闲切到手动,闪两下提示 */
log_info("hand cmd=%d %s until 03\n", cmd, (cmd == 1) ? "cw" : "ccw");
}
s_func.hand_cmd_last = cmd;
}
/**
* @brief 短按:自动模式 0→1→…→6→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((uint8_t)((s_func.play_mode + 1) % 7));
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_pair_is_active()) {
log_info("key click ignored, pairing\n");
return;
}
if (!s_func.app_power) {
/* 软关状态下短按:只唤醒功能,不切模式 */
s_func.app_power = 1;
gDevData.power = 1;
app_led_hint_power_on();
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_pair_is_active()) {
log_info("key hold 3s ignored, pairing\n");
return;
}
log_info("key hold 3s, poweroff\n");
app_power_request_poweroff();
}