feat: 完成弹力魔盒初版固件

This commit is contained in:
2026-08-31 20:15:19 +08:00
parent 026d6d5da7
commit b63a928b92
34 changed files with 1320 additions and 781 deletions
+122 -100
View File
@@ -1,6 +1,6 @@
/* ================================================================
* jb_product.c — 用户层实现(请填补空白处)
* 设备: 门夹弹力绳 | 生成时间: 2026-08-21 18:01:47
* 设备: 猫猫捕猎罩 | 生成时间: 2026-08-18 16:24:47
*
* ══ 开发流程 ══
* 1. 实现硬件抽象: jbGetTimerCount(定时器由 SOC SDK 提供)
@@ -13,6 +13,8 @@
*/
#include "jb_product.h"
#include "app_function.h"
#include "usr_rtc.h"
/* ════════════════════════════════════════════════════════════════
* 全局变量
@@ -21,6 +23,79 @@
jb_data_point_t gDevData;
static volatile uint32_t timerMs;
#define JB_DND_LEN 13
#define JB_DND_ENABLE_OFFSET 0
#define JB_DND_WEEK_OFFSET 2
#define JB_DND_START_HOUR_OFFSET 9
#define JB_DND_START_MIN_OFFSET 10
#define JB_DND_END_HOUR_OFFSET 11
#define JB_DND_END_MIN_OFFSET 12
static uint8_t jbLastNoDisturbActive;
/* Return weekday with Monday=0, matching DP7 bytes 2..8. */
static uint8_t jbWeekdayMonday0(const struct sys_time *time)
{
static const uint8_t monthOffset[12] = {
0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4
};
uint16_t year = time->year;
uint8_t sunday0;
if (time->month < 3) {
year--;
}
sunday0 = (uint8_t)((year + year / 4U - year / 100U + year / 400U +
monthOffset[time->month - 1U] + time->day) % 7U);
return (uint8_t)((sunday0 + 6U) % 7U);
}
static void jbNoDisturbNormalize(uint8_t *cfg)
{
uint8_t i;
cfg[JB_DND_ENABLE_OFFSET] = !!cfg[JB_DND_ENABLE_OFFSET];
for (i = 0; i < 7; i++) {
cfg[JB_DND_WEEK_OFFSET + i] = !!cfg[JB_DND_WEEK_OFFSET + i];
}
}
static uint8_t jbNoDisturbScheduleValid(const uint8_t *cfg)
{
uint8_t i;
if (cfg[JB_DND_START_HOUR_OFFSET] >= 24 ||
cfg[JB_DND_END_HOUR_OFFSET] >= 24 ||
cfg[JB_DND_START_MIN_OFFSET] >= 60 ||
cfg[JB_DND_END_MIN_OFFSET] >= 60) {
return 0;
}
for (i = 0; i < 7; i++) {
if (cfg[JB_DND_WEEK_OFFSET + i]) {
return 1;
}
}
return 0;
}
static uint8_t jbNoDisturbActive(void)
{
/* Match the verified product behavior: DP7 byte 0 is the global
* no-disturb switch. Schedule bytes are retained and reported, but do
* not gate the local vibration wake path. */
return !!gDevData.no_disturb_enable[JB_DND_ENABLE_OFFSET];
}
static void jbNoDisturbUpdate(void)
{
uint8_t active = jbNoDisturbActive();
if (active == jbLastNoDisturbActive) {
return;
}
jbLastNoDisturbActive = active;
app_function_set_no_disturb(active);
}
/* ════════════════════════════════════════════════════════════════
* userInit — DP 默认值
* ════════════════════════════════════════════════════════════════ */
@@ -30,16 +105,16 @@ void userInit(void)
gDevData.power = 0; /* DP0 开关 0=关 1=开 */
gDevData.play_mode = 0; /* PLAY_MODE_0 (停止复位) */
memset(gDevData.hand_mode, 0, 2); /* DP2 手动模式 */
gDevData.calibration = 0; /* DP3 绳索校准 0=关 1=开 */
gDevData.numberturns = 0; /* DP4 绳索圈数 */
gDevData.alm_low_battery = 0; /* DP5 电量报警 0=关 1=开 */
gDevData.battery = 0; /* DP6 电量上报 */
gDevData.charge = 1; /* CHARGE_1 (充电中) */
memset(gDevData.no_disturb_enable, 0, 13); /* DP8 勿扰模式开关 */
gDevData.reserva_dp1 = 0; /* DP9 预留DP1 */
gDevData.reserva_dp2 = 0; /* DP10 预留DP2 */
gDevData.reserva_dp3 = 0; /* DP11 预留DP3 */
gDevData.hand_mode = 0; /* HAND_MODE_0 (忽略) */
gDevData.alm_low_battery = 0; /* DP3 低电量报警 0=关 1=开 */
gDevData.beep_trig = 0; /* DP4 声音开关 0=关 1=开 */
gDevData.battery = 0; /* DP5 电量报 */
gDevData.shake_wake = 0; /* DP6 震动触发开机 0=关 1=开 */
memset(gDevData.no_disturb_enable, 0, 13); /* DP7 勿扰模式开关 */
gDevData.charge = 0; /* CHARGE_0 (不支持充电) */
gDevData.reserva_dp3 = 0; /* DP10 预留DP3 */
jbLastNoDisturbActive = 0xff;
jbNoDisturbUpdate();
}
/* ════════════════════════════════════════════════════════════════
@@ -48,16 +123,18 @@ void userInit(void)
* ════════════════════════════════════════════════════════════════ */
void userHandle(void)
{
jbNoDisturbUpdate();
/* ── 可上报 DP (RO / RW): 用户数据采集 ── */
// gDevData.power = read_sensor(); /* DP0 开关 */
// gDevData.play_mode = read_sensor(); /* DP1 自动模式 */
// gDevData.alm_low_battery = read_sensor(); /* DP5 低电量报警 */
// gDevData.battery = read_sensor(); /* DP6 电量上报 */
// gDevData.charge = read_sensor(); /* DP7 充电状态 */
// memcpy(gDevData.no_disturb_enable, sensor_read(), 13); /* DP8 勿扰模式开关 */
// gDevData.reserva_dp1 = read_sensor(); /* DP9 预留DP1 */
// gDevData.reserva_dp2 = read_sensor(); /* DP10 预留DP2 */
// gDevData.reserva_dp3 = read_sensor(); /* DP11 预留DP3 */
// gDevData.alm_low_battery = read_sensor(); /* DP3 低电量报警 */
// gDevData.beep_trig = read_sensor(); /* DP4 声音开关 */
// gDevData.battery = read_sensor(); /* DP5 电量上报 */
// gDevData.shake_wake = read_sensor(); /* DP6 震动触发开机 */
// memcpy(gDevData.no_disturb_enable, sensor_read(), 13); /* DP7 勿扰模式开关 */
// gDevData.charge = read_sensor(); /* DP8 充电状态 */
// gDevData.reserva_dp3 = read_sensor(); /* DP10 预留DP3 */
}
/* ════════════════════════════════════════════════════════════════
@@ -69,6 +146,8 @@ void userHandle(void)
* ════════════════════════════════════════════════════════════════ */
int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
{
int8_t result = JB_OK;
if (!info || !ctrl)
{
return -1;
@@ -81,98 +160,47 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
/* ── DP0 开关 (bool) ── */
case DP_ID_POWER:
if (ctrl->power)
{
gDevData.power = 1;
// TODO: 开 — 执行动作
}
else
{
gDevData.power = 0;
// TODO: 关 — 执行动作
}
app_function_dp_power_write(ctrl->power);
break;
/* ── DP1 自动模式 (enum) ── */
case DP_ID_PLAY_MODE:
gDevData.play_mode = ctrl->play_mode;
switch (ctrl->play_mode)
{
case PLAY_MODE_0: /* 停止复位 */
// TODO: 处理 停止复位
break;
case PLAY_MODE_1: /* 弹力伪装 */
// TODO: 处理 弹力伪装
break;
case PLAY_MODE_2: /* 飞鸟俯冲 */
// TODO: 处理 飞鸟俯冲
break;
case PLAY_MODE_3: /* 云阶小陀螺 */
// TODO: 处理 云阶小陀螺
break;
case PLAY_MODE_4: /* 起伏攻防战 */
// TODO: 处理 起伏攻防战
break;
case PLAY_MODE_5: /* 微风拂动 */
// TODO: 处理 微风拂动
break;
case PLAY_MODE_6: /* 闪电快闪 */
// TODO: 处理 闪电快闪
break;
default:
break;
}
app_function_dp_play_write(ctrl->play_mode);
break;
/* ── DP2 手动模式 (raw) ── */
/* ── DP2 手动模式 (enum) ── */
case DP_ID_HAND_MODE:
memcpy(gDevData.hand_mode, ctrl->hand_mode, 2);
/* 薄适配:通知业务层;03=停,00=空闲 */
{
extern void usr_dp_hand_write(uint8_t cmd);
usr_dp_hand_write(ctrl->hand_mode[0]);
}
app_function_dp_hand_write(ctrl->hand_mode);
break;
/* ── DP3 绳索校准 (bool) ── */
case DP_ID_CALIBRATION:
if (ctrl->calibration)
/* ── DP4 声音开关 (bool) ── */
case DP_ID_BEEP_TRIG:
if (ctrl->beep_trig)
{
gDevData.calibration = 1;
gDevData.beep_trig = 1;
// TODO: 开 — 执行动作
}
else
{
gDevData.calibration = 0;
gDevData.beep_trig = 0;
// TODO: 关 — 执行动作
}
break;
/* ── DP4 绳索圈数 (uint8) ── */
case DP_ID_NUMBERTURNS:
gDevData.numberturns = ctrl->numberturns;
// TODO: 根据 ctrl->numberturns 执行动作
/* ── DP6 震动触发开机 (bool) ── */
case DP_ID_SHAKE_WAKE:
app_function_set_shake_wake(ctrl->shake_wake);
break;
/* ── DP8 勿扰模式开关 (raw) ── */
/* ── DP7 勿扰模式开关 (raw) ── */
case DP_ID_NO_DISTURB_ENABLE:
memcpy(gDevData.no_disturb_enable, ctrl->no_disturb_enable, 13);
// TODO: 根据 gDevData.no_disturb_enable 执行动作 (长度 13 字节)
memcpy(gDevData.no_disturb_enable, ctrl->no_disturb_enable,
JB_DND_LEN);
jbNoDisturbNormalize(gDevData.no_disturb_enable);
jbNoDisturbUpdate();
break;
/* ── DP9 预留DP1 (uint32) ── */
case DP_ID_RESERVA_DP1:
gDevData.reserva_dp1 = ctrl->reserva_dp1;
// TODO: 根据 ctrl->reserva_dp1 执行动作
break;
/* ── DP10 预留DP2 (uint32) ── */
case DP_ID_RESERVA_DP2:
gDevData.reserva_dp2 = ctrl->reserva_dp2;
// TODO: 根据 ctrl->reserva_dp2 执行动作
break;
/* ── DP11 预留DP3 (uint32) ── */
/* ── DP10 预留DP3 (uint32) ── */
case DP_ID_RESERVA_DP3:
gDevData.reserva_dp3 = ctrl->reserva_dp3;
// TODO: 根据 ctrl->reserva_dp3 执行动作
@@ -182,21 +210,16 @@ int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
break;
}
}
return 0;
return result;
}
/* 0x30 OTA 升级请求
* 返回 0=接受升级(协议层应答 JB_OK + MaxDataLen
* 非 0=拒绝,返回值即错误码(JB_OTA_*)
*
* ⚠ SOC 侧 OTA 尚未实现:当前仅应答,不执行任何升级动作。 */
int8_t jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32)
/* 0x30 OTA 升级请求 */
void jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32)
{
(void)fwVersion; (void)fwLength; (void)fwCrc32;
// TODO: 检查 Flash 空间与版本,决定是否接收升级
// 允许: 返回 JB_OK(协议层应答 JB_OK + MaxDataLen
// 拒绝: 返回对应 JB_OTA_* 错误码(如 JB_OTA_VER_LOW / JB_OTA_SPACE_ERR
return JB_OK;
// 允许: 直接返回(协议层默认应答 JB_OK + MaxDataLen=128
// 拒绝: 在 jbHandleOtaNotify 中改为 jbSendFrame(CMD_OTA_NOTIFY_ACK, sn, 错误码, 3)
}
/* 0x32 OTA 数据(含分片序号)*/
@@ -219,8 +242,7 @@ void jbOnOtaStop(void)
- 可在本回调中设置"XX 完成/失败"标志,供主循环判断"确认成功才继续" */
void jbOnAck(uint8_t cmd, uint8_t result)
{
(void)cmd;
(void)result;
(void)cmd; (void)result;
// TODO: switch(cmd){ case CMD_OTA_COMPLETE_ACK: gOtaDone = (result==JB_OK); break; case CMD_OTA_STOP_MCU_ACK: ... }
}
@@ -232,7 +254,7 @@ void jbOnAck(uint8_t cmd, uint8_t result)
void jbTimerIrq(void)
{
timerMs++;
} /* 在 1ms 中断中调用本函数 */
} /* 在 1ms 中断中调用本函数 */
uint32_t jbGetTimerCount(void)
{
+2 -2
View File
@@ -1,6 +1,6 @@
/* ================================================================
* jb_product.h — 用户层头文件(自动生成)
* 设备: 门夹弹力绳 | 生成时间: 2026-08-21 18:01:47
* 设备: 猫猫捕猎罩 | 生成时间: 2026-08-18 16:24:47
*
* 声明用户需实现的函数。数据结构定义见 jb_protocol.h
* ================================================================
@@ -43,7 +43,7 @@ void jbTimerIrq(void); /* Call from 1ms timer ISR */
/* =========== 协议回调(声明见 jb_protocol.h================== */
/* int8_t jbEventProcess(...); // 0x03 控制事件 */
/* int8_t jbOnOtaNotify(...); // 0x30 OTA 请求 */
/* void jbOnOtaNotify(...); // 0x30 OTA 请求 */
/* void jbOnOtaData(...); // 0x32 OTA 数据 */
/* void jbOnOtaStop(); // 0x36/0x38 停止 OTA */
/* void jbOnAck(...); // 任意请求 ACK(结果) */
+72 -92
View File
@@ -1,14 +1,13 @@
/* ================================================================
* jb_protocol.c — 见宝 IOT 协议实现(自动生成)
* 设备: 门夹弹力绳 | 生成时间: 2026-08-21 18:01:47
* 设备: 猫猫捕猎罩 | 生成时间: 2026-08-18 16:24:47
* ================================================================
*/
#include "jb_protocol.h"
#include "jb_common.h"
#include "jb_product.h"
#include "jb_ringbuffer.h"
#include "stdio.h"
#include "jb_common.h"
/* ════════════════════════════════════════════════════════════════
* 全局实例
@@ -168,46 +167,44 @@ static uint16_t jbPackDps(const jb_data_point_t *dp,
bitmap[DP_ID_PLAY_MODE / 8] |= (1 << (DP_ID_PLAY_MODE % 8));
out[valIdx++] = (uint8_t)dp->play_mode;
}
/* DP5 低电量报警 (bool, 1B, RO) */
/* DP3 低电量报警 (bool, 1B, RO) */
if (mask[DP_ID_ALM_LOW_BATTERY / 8] & (1 << (DP_ID_ALM_LOW_BATTERY % 8)))
{
bitmap[DP_ID_ALM_LOW_BATTERY / 8] |= (1 << (DP_ID_ALM_LOW_BATTERY % 8));
out[valIdx++] = dp->alm_low_battery ? 1 : 0;
}
/* DP6 电量上报 (uint8, 1B, RO) */
/* DP4 声音开关 (bool, 1B, RW) */
if (mask[DP_ID_BEEP_TRIG / 8] & (1 << (DP_ID_BEEP_TRIG % 8)))
{
bitmap[DP_ID_BEEP_TRIG / 8] |= (1 << (DP_ID_BEEP_TRIG % 8));
out[valIdx++] = dp->beep_trig ? 1 : 0;
}
/* DP5 电量上报 (uint8, 1B, RO) */
if (mask[DP_ID_BATTERY / 8] & (1 << (DP_ID_BATTERY % 8)))
{
bitmap[DP_ID_BATTERY / 8] |= (1 << (DP_ID_BATTERY % 8));
out[valIdx++] = (uint8_t)dp->battery;
}
/* DP7 充电状态 (enum, 1B, RO) */
if (mask[DP_ID_CHARGE / 8] & (1 << (DP_ID_CHARGE % 8)))
/* DP6 震动触发开机 (bool, 1B, RW) */
if (mask[DP_ID_SHAKE_WAKE / 8] & (1 << (DP_ID_SHAKE_WAKE % 8)))
{
bitmap[DP_ID_CHARGE / 8] |= (1 << (DP_ID_CHARGE % 8));
out[valIdx++] = (uint8_t)dp->charge;
bitmap[DP_ID_SHAKE_WAKE / 8] |= (1 << (DP_ID_SHAKE_WAKE % 8));
out[valIdx++] = dp->shake_wake ? 1 : 0;
}
/* DP8 勿扰模式开关 (raw, 13B, RW) */
/* DP7 勿扰模式开关 (raw, 13B, RW) */
if (mask[DP_ID_NO_DISTURB_ENABLE / 8] & (1 << (DP_ID_NO_DISTURB_ENABLE % 8)))
{
bitmap[DP_ID_NO_DISTURB_ENABLE / 8] |= (1 << (DP_ID_NO_DISTURB_ENABLE % 8));
memcpy(&out[valIdx], dp->no_disturb_enable, 13);
valIdx += 13;
}
/* DP9 预留DP1 (uint32, 4B, RW) */
if (mask[DP_ID_RESERVA_DP1 / 8] & (1 << (DP_ID_RESERVA_DP1 % 8)))
/* DP8 充电状态 (enum, 1B, RO) */
if (mask[DP_ID_CHARGE / 8] & (1 << (DP_ID_CHARGE % 8)))
{
bitmap[DP_ID_RESERVA_DP1 / 8] |= (1 << (DP_ID_RESERVA_DP1 % 8));
jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp1);
valIdx += 4;
bitmap[DP_ID_CHARGE / 8] |= (1 << (DP_ID_CHARGE % 8));
out[valIdx++] = (uint8_t)dp->charge;
}
/* DP10 预留DP2 (uint32, 4B, RW) */
if (mask[DP_ID_RESERVA_DP2 / 8] & (1 << (DP_ID_RESERVA_DP2 % 8)))
{
bitmap[DP_ID_RESERVA_DP2 / 8] |= (1 << (DP_ID_RESERVA_DP2 % 8));
jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp2);
valIdx += 4;
}
/* DP11 预留DP3 (uint32, 4B, RW) */
/* DP10 预留DP3 (uint32, 4B, RW) */
if (mask[DP_ID_RESERVA_DP3 / 8] & (1 << (DP_ID_RESERVA_DP3 % 8)))
{
bitmap[DP_ID_RESERVA_DP3 / 8] |= (1 << (DP_ID_RESERVA_DP3 % 8));
@@ -256,44 +253,43 @@ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
info->dp_ids[info->count++] = DP_ID_PLAY_MODE;
}
}
/* DP2 手动模式 (raw, 2B, WO) */
/* DP2 手动模式 (enum, 1B, WO) */
if (bitmap[DP_ID_HAND_MODE / 8] & (1 << (DP_ID_HAND_MODE % 8)))
{
if (valIdx + 2 <= valLen)
if (valIdx < valLen)
{
memcpy(dp->hand_mode, &values[valIdx], 2);
valIdx += 2;
dp->hand_mode = values[valIdx++];
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_HAND_MODE;
}
}
/* DP3 绳索校准 (bool, 1B, WO) */
if (bitmap[DP_ID_CALIBRATION / 8] & (1 << (DP_ID_CALIBRATION % 8)))
/* DP4 声音开关 (bool, 1B, RW) */
if (bitmap[DP_ID_BEEP_TRIG / 8] & (1 << (DP_ID_BEEP_TRIG % 8)))
{
if (valIdx < valLen)
{
dp->calibration = (values[valIdx++] != 0);
dp->beep_trig = (values[valIdx++] != 0);
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_CALIBRATION;
info->dp_ids[info->count++] = DP_ID_BEEP_TRIG;
}
}
/* DP4 绳索圈数 (uint8, 1B, WO) */
if (bitmap[DP_ID_NUMBERTURNS / 8] & (1 << (DP_ID_NUMBERTURNS % 8)))
/* DP6 震动触发开机 (bool, 1B, RW) */
if (bitmap[DP_ID_SHAKE_WAKE / 8] & (1 << (DP_ID_SHAKE_WAKE % 8)))
{
if (valIdx < valLen)
{
dp->numberturns = values[valIdx++];
dp->shake_wake = (values[valIdx++] != 0);
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_NUMBERTURNS;
info->dp_ids[info->count++] = DP_ID_SHAKE_WAKE;
}
}
/* DP8 勿扰模式开关 (raw, 13B, RW) */
/* DP7 勿扰模式开关 (raw, 13B, RW) */
if (bitmap[DP_ID_NO_DISTURB_ENABLE / 8] & (1 << (DP_ID_NO_DISTURB_ENABLE % 8)))
{
if (valIdx + 13 <= valLen)
@@ -306,33 +302,7 @@ static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
info->dp_ids[info->count++] = DP_ID_NO_DISTURB_ENABLE;
}
}
/* DP9 预留DP1 (uint32, 4B, RW) */
if (bitmap[DP_ID_RESERVA_DP1 / 8] & (1 << (DP_ID_RESERVA_DP1 % 8)))
{
if (valIdx + 4 <= valLen)
{
dp->reserva_dp1 = (uint32_t)jbReadU32BE(&values[valIdx]);
valIdx += 4;
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_RESERVA_DP1;
}
}
/* DP10 预留DP2 (uint32, 4B, RW) */
if (bitmap[DP_ID_RESERVA_DP2 / 8] & (1 << (DP_ID_RESERVA_DP2 % 8)))
{
if (valIdx + 4 <= valLen)
{
dp->reserva_dp2 = (uint32_t)jbReadU32BE(&values[valIdx]);
valIdx += 4;
}
if (info && info->count < DP_ID_MAX)
{
info->dp_ids[info->count++] = DP_ID_RESERVA_DP2;
}
}
/* DP11 预留DP3 (uint32, 4B, RW) */
/* DP10 预留DP3 (uint32, 4B, RW) */
if (bitmap[DP_ID_RESERVA_DP3 / 8] & (1 << (DP_ID_RESERVA_DP3 % 8)))
{
if (valIdx + 4 <= valLen)
@@ -370,43 +340,43 @@ static uint8_t jbBuildDiffMask(const jb_data_point_t *last,
mask[DP_ID_PLAY_MODE / 8] |= (1 << (DP_ID_PLAY_MODE % 8));
hasChange = 1;
}
/* DP5 低电量报警 */
/* DP3 低电量报警 */
if (last->alm_low_battery != cur->alm_low_battery)
{
mask[DP_ID_ALM_LOW_BATTERY / 8] |= (1 << (DP_ID_ALM_LOW_BATTERY % 8));
hasChange = 1;
}
/* DP6 电量上报 */
/* DP4 声音开关 */
if (last->beep_trig != cur->beep_trig)
{
mask[DP_ID_BEEP_TRIG / 8] |= (1 << (DP_ID_BEEP_TRIG % 8));
hasChange = 1;
}
/* DP5 电量上报 */
if (last->battery != cur->battery)
{
mask[DP_ID_BATTERY / 8] |= (1 << (DP_ID_BATTERY % 8));
hasChange = 1;
}
/* DP7 充电状态 */
if (last->charge != cur->charge)
/* DP6 震动触发开机 */
if (last->shake_wake != cur->shake_wake)
{
mask[DP_ID_CHARGE / 8] |= (1 << (DP_ID_CHARGE % 8));
mask[DP_ID_SHAKE_WAKE / 8] |= (1 << (DP_ID_SHAKE_WAKE % 8));
hasChange = 1;
}
/* DP8 勿扰模式开关 */
/* DP7 勿扰模式开关 */
if (memcmp(last->no_disturb_enable, cur->no_disturb_enable, 13))
{
mask[DP_ID_NO_DISTURB_ENABLE / 8] |= (1 << (DP_ID_NO_DISTURB_ENABLE % 8));
hasChange = 1;
}
/* DP9 预留DP1 */
if (last->reserva_dp1 != cur->reserva_dp1)
/* DP8 充电状态 */
if (last->charge != cur->charge)
{
mask[DP_ID_RESERVA_DP1 / 8] |= (1 << (DP_ID_RESERVA_DP1 % 8));
mask[DP_ID_CHARGE / 8] |= (1 << (DP_ID_CHARGE % 8));
hasChange = 1;
}
/* DP10 预留DP2 */
if (last->reserva_dp2 != cur->reserva_dp2)
{
mask[DP_ID_RESERVA_DP2 / 8] |= (1 << (DP_ID_RESERVA_DP2 % 8));
hasChange = 1;
}
/* DP11 预留DP3 */
/* DP10 预留DP3 */
if (last->reserva_dp3 != cur->reserva_dp3)
{
mask[DP_ID_RESERVA_DP3 / 8] |= (1 << (DP_ID_RESERVA_DP3 % 8));
@@ -419,11 +389,9 @@ static uint8_t jbBuildDiffMask(const jb_data_point_t *last,
/* 置全量掩码(所有可上报 DP)*/
static void jbSetFullMask(uint8_t *mask)
{
memset(mask, 0xFF, DP_BITMAP_SIZE);
if (DP_COUNT % 8)
{
mask[DP_BITMAP_SIZE - 1] &= (1 << (DP_COUNT % 8)) - 1;
}
/* DP IDs are sparse; list only the fields that can be reported/read. */
mask[0] = 0xFB; /* 0, 1, 3, 4, 5, 6, 7 */
mask[1] = 0x05; /* 8, 10 */
}
/* ════════════════════════════════════════════════════════════════
@@ -582,14 +550,25 @@ static void jbHandleControl(uint8_t *frame, uint16_t len)
uint8_t *values = &frame[6 + DP_BITMAP_SIZE];
uint16_t valLen = len - 7 - DP_BITMAP_SIZE;
/* Only writable fields are legal in a control frame. */
if ((bitmap[0] & (uint8_t)~0xD7U) ||
(bitmap[1] & (uint8_t)~0x04U))
{
jbSendAck(CMD_CONTROL_ACK, sn, JB_NOT_SUPPORT);
return;
}
jb_event_info_t info;
jb_data_point_t ctrlData;
memset(&ctrlData, 0, sizeof(ctrlData));
jbUnpackDps(bitmap, values, valLen, &ctrlData, &info);
/* 分发给用户层;用户在回调中更新 gDevData */
jbEventProcess(&info, &ctrlData);
jbSendAck(CMD_CONTROL_ACK, sn, JB_OK);
/* 分发给用户层;用户在回调中更新 gDevData */
{
int8_t result = jbEventProcess(&info, &ctrlData);
jbSendAck(CMD_CONTROL_ACK, sn,
(result < 0) ? JB_PARAM_ERR : (uint8_t)result);
}
}
/* ── 0x05 APP 读取请求 ── */
@@ -632,10 +611,12 @@ static void jbHandleOtaNotify(uint8_t *frame, uint16_t len)
uint32_t fwLength = jbReadU32BE(&frame[9]);
uint32_t fwCrc32 = jbReadU32BE(&frame[13]);
/* 应答: 结果码 + 最大单包长度(2B)(协议原格式,3 字节) */
int8_t otaRet = jbOnOtaNotify(fwVersion, fwLength, fwCrc32);
jbOnOtaNotify(fwVersion, fwLength, fwCrc32);
/* 默认应答: 结果码 + 最大单包长度(2B) */
/* maxDataLen 由 MCU 自身能力决定,填入 0x31 应答,BLE 据此分片 */
uint8_t ack[3];
ack[0] = (otaRet == 0) ? JB_OK : (uint8_t)otaRet;
ack[0] = JB_OK;
ack[1] = (uint8_t)(JB_OTA_MAX_PAYLOAD >> 8);
ack[2] = (uint8_t)(JB_OTA_MAX_PAYLOAD & 0xFF);
jbSendFrame(CMD_OTA_NOTIFY_ACK, sn, ack, 3);
@@ -649,9 +630,8 @@ static void jbHandleOtaData(uint8_t *frame, uint16_t len)
uint16_t totalShards = jbReadU16BE(&frame[8]);
const uint8_t *pData = &frame[10];
uint16_t dataLen = (len > 11) ? (len - 11) : 0;
/* 先发本包 0x33 应答,再调用户回调满足「先回复、再重启」 */
jbSendAck(CMD_OTA_DATA_ACK, sn, JB_OK);
jbOnOtaData(pData, dataLen, shardIdx, totalShards);
jbSendAck(CMD_OTA_DATA_ACK, sn, JB_OK);
}
/* ── 0x36 BLE 通知停止 OTA ── */
+45 -44
View File
@@ -1,7 +1,7 @@
/* ================================================================
* jb_protocol.h — 见宝 IOT 协议层(自动生成)
* 设备: 门夹弹力绳 | DP 数: 12 | Bitmap: 2B
* 协议版本: V1.0 | 生成时间: 2026-08-21 18:01:47
* 设备: 猫猫捕猎罩 | DP 数: 10 | Bitmap: 2B
* 协议版本: V1.0 | 生成时间: 2026-08-18 16:24:47
*
* 帧格式: [55 AA] [LEN_H LEN_L] [CMD] [SN] [payload...] [CKSUM]
* LEN = CMD(1) + SN(1) + payload + CKSUM(1) = payload + 3
@@ -26,7 +26,7 @@
#define MAX_PACKAGE_LEN 128
#define JB_OTA_MAX_PAYLOAD 96 /* OTA 单包 Payload 上限(Byte); 须 <= MAX_PACKAGE_LEN - 11(0x32 帧开销) */
#define DP_COUNT 12
#define DP_COUNT 10
#define DP_BITMAP_SIZE 2
#define SEND_MAX_TIME 2500 /* ACK 重传超时(ms)BLE Indication往返常需数百ms~1s,过短会误重传导致双应答 */
@@ -34,14 +34,13 @@
#define REPORT_DEBOUNCE 2000 /* 变化上报防抖时间(ms) */
#define REPORT_PERIOD 600000 /* 定时全量上报周期(ms) */
/* Device identity: 取自 Excel 设备信息表的 ProdtCode / productCode。
/* Device identity: change these values for the target product.
* JB_CATEGORY_CODE: 必须 4 字符 ASCII,供 BLE 广播 prodcode 使用
* JB_PRODUCT_CODE : 必须 6 字符 ASCII,供 BLE 广播 pcode 使用
* 模板未填时回退占位 XXXX / XXXXXX(仍需用户修改)。
*/
#define JB_PROTOCOL_VERSION 0x0100 /* V1.0 */
#define JB_CATEGORY_CODE "CBC2"
#define JB_PRODUCT_CODE "PICBC2"
#define JB_CATEGORY_CODE "CBC4"
#define JB_PRODUCT_CODE "PICBC4"
#define JB_DEVICE_SN 0x00
#define JB_DEVICE_CAPABILITY 0ULL
@@ -93,14 +92,14 @@ typedef enum
/* ════════════════════════════════════════════════════════════════
* BLE 工作状态位 (0x14)
* ════════════════════════════════════════════════════════════════ */
#define WS_CONNECTING (1 << 0) /* 配网中 */
#define WS_PAIRING (1 << 0) /* 配网中 */
#define WS_CONFIGURED (1 << 1) /* 已配网 */
#define WS_RECONNECTING (1 << 2) /* 断线重连中 */
#define WS_CLOUD_CONNECTED (1 << 3) /* 已连接到云 */
#define WS_APP_ONLINE (1 << 4) /* App 在线 */
#define WS_APP_DIRECT (1 << 5) /* App 直连 */
#define WS_APP_ONLINE (1 << 4) /* APP 在线 */
#define WS_APP_DIRECT (1 << 5) /* APP 直连 */
#define WS_OTA_UPGRADING (1 << 6) /* 模组 OTA 升级中 */
#define WS_RESERVED (1 << 7) /* 保留 */
#define WS_RESERVED (1 << 7) /* Reserved */
/* ════════════════════════════════════════════════════════════════
* 设备能力位图 (0x02 Capability Bitmap, 8B 大端)
@@ -120,31 +119,27 @@ typedef enum
{
DP_ID_POWER = 0, /* 开关 (bool, RW) */
DP_ID_PLAY_MODE = 1, /* 自动模式 (enum, RW) */
DP_ID_HAND_MODE = 2, /* 手动模式 (raw, WO) */
DP_ID_CALIBRATION = 3, /* 绳索校准 (bool, WO) */
DP_ID_NUMBERTURNS = 4, /* 绳索圈数 (uint8, WO) */
DP_ID_ALM_LOW_BATTERY = 5, /* 电量报警 (bool, RO) */
DP_ID_BATTERY = 6, /* 电量上报 (uint8, RO) */
DP_ID_CHARGE = 7, /* 充电状态 (enum, RO) */
DP_ID_NO_DISTURB_ENABLE = 8, /* 勿扰模式开关 (raw, RW) */
DP_ID_RESERVA_DP1 = 9, /* 预留DP1 (uint32, RW) */
DP_ID_RESERVA_DP2 = 10, /* 预留DP2 (uint32, RW) */
DP_ID_RESERVA_DP3 = 11, /* 预留DP3 (uint32, RW) */
DP_ID_HAND_MODE = 2, /* 手动模式 (enum, WO) */
DP_ID_ALM_LOW_BATTERY = 3, /* 低电量报警 (bool, RO) */
DP_ID_BEEP_TRIG = 4, /* 声音开关 (bool, RW) */
DP_ID_BATTERY = 5, /* 电量上报 (uint8, RO) */
DP_ID_SHAKE_WAKE = 6, /* 震动触发开机 (bool, RW) */
DP_ID_NO_DISTURB_ENABLE = 7, /* 勿扰模式开关 (raw, RW) */
DP_ID_CHARGE = 8, /* 充电状态 (enum, RO) */
DP_ID_RESERVA_DP3 = 10, /* 预留DP3 (uint32, RW) */
DP_ID_MAX
} dp_id_t;
/* DP 数据长度(字节) */
#define DP_LEN_POWER 1
#define DP_LEN_PLAY_MODE 1
#define DP_LEN_HAND_MODE 2
#define DP_LEN_CALIBRATION 1
#define DP_LEN_NUMBERTURNS 1
#define DP_LEN_HAND_MODE 1
#define DP_LEN_ALM_LOW_BATTERY 1
#define DP_LEN_BEEP_TRIG 1
#define DP_LEN_BATTERY 1
#define DP_LEN_CHARGE 1
#define DP_LEN_SHAKE_WAKE 1
#define DP_LEN_NO_DISTURB_ENABLE 13
#define DP_LEN_RESERVA_DP1 4
#define DP_LEN_RESERVA_DP2 4
#define DP_LEN_CHARGE 1
#define DP_LEN_RESERVA_DP3 4
/* ════════════════════════════════════════════════════════════════
@@ -154,17 +149,25 @@ typedef enum
typedef enum
{
PLAY_MODE_0 = 0, /* 停止复位 */
PLAY_MODE_1 = 1, /* 弹力伪装 */
PLAY_MODE_2 = 2, /* 飞鸟俯冲 */
PLAY_MODE_3 = 3, /* 云阶小陀螺 */
PLAY_MODE_4 = 4, /* 起伏攻防战 */
PLAY_MODE_5 = 5, /* 微风拂动 */
PLAY_MODE_6 = 6, /* 闪电快闪 */
PLAY_MODE_1 = 1, /* 上部慢速往复 */
PLAY_MODE_2 = 2, /* 上部快速往复 */
PLAY_MODE_3 = 3, /* 上部间歇、下部均匀摇摆 */
PLAY_MODE_4 = 4, /* 上部慢探、下部随机摇摆 */
PLAY_MODE_5 = 5, /* 上下电机随机组合 */
} PLAY_MODE_t;
/* 手动模式 */
typedef enum
{
HAND_MODE_0 = 0, /* 停止 */
HAND_MODE_1 = 1, /* 上部电机探出 */
HAND_MODE_2 = 2, /* 下部电机探出 */
} HAND_MODE_t;
/* 充电状态 */
typedef enum
{
CHARGE_0 = 0, /* 不支持充电 */
CHARGE_1 = 1, /* 充电中 */
CHARGE_2 = 2, /* 未充电 */
} CHARGE_t;
@@ -178,16 +181,14 @@ typedef struct
{
uint8_t power; /* DP0 开关 */
uint8_t play_mode; /* DP1 自动模式 */
uint8_t hand_mode[2]; /* DP2 手动模式 */
uint8_t calibration; /* DP3 绳索校准 */
uint8_t numberturns; /* DP4 绳索圈数 */
uint8_t alm_low_battery;/* DP5 电量报 */
uint8_t battery; /* DP6 电量上报 */
uint8_t charge; /* DP7 充电状态 */
uint8_t no_disturb_enable[13];/* DP8 勿扰模式开关 */
uint32_t reserva_dp1; /* DP9 预留DP1 */
uint32_t reserva_dp2; /* DP10 预留DP2 */
uint32_t reserva_dp3; /* DP11 预留DP3 */
uint8_t hand_mode; /* DP2 手动模式 */
uint8_t alm_low_battery;/* DP3 低电量报警 */
uint8_t beep_trig; /* DP4 声音开关 */
uint8_t battery; /* DP5 电量报 */
uint8_t shake_wake; /* DP6 震动触发开机 */
uint8_t no_disturb_enable[13];/* DP7 勿扰模式开关 */
uint8_t charge; /* DP8 充电状态 */
uint32_t reserva_dp3; /* DP10 预留DP3 */
} jb_data_point_t;
/* 控制事件信息 */
@@ -260,7 +261,7 @@ extern void ble_send_data(uint8_t *data, uint16_t len);
int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *data);
/* BLE → MCU 事件回调 */
int8_t jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32); /* 0x30, 返回0=接受,非0=拒绝码 */
void jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32); /* 0x30 */
void jbOnOtaData(const uint8_t *data, uint16_t len,
uint16_t shardIdx, uint16_t totalShards); /* 0x32 */
void jbOnOtaStop(void); /* 0x36 / 0x38 停止 */
+24 -9
View File
@@ -1,9 +1,9 @@
/******************************************************************************
* @file usr_jb_main.c
* @brief CBC2 业务组合根:只做“先后调用谁、谁通知谁”,不含具体业务算法
* @brief CBC1 猫猫捕猎罩业务组合根:只做调用编排,不含具体业务算法
* @author cyWu <1917507415@qq.com>
* @date 2026.08.27
* @version V2.2.0
* @version V2.3.0
* @history
* - V1.0.0, 2026.08.21, cyWu, 首次发布
* - V1.1.0, 2026.08.24, cyWu, 收口未接线模块;DP2 仅第 1 字节点动 1s
@@ -16,11 +16,13 @@
* - V2.1.0, 2026.08.26, cyWu, 电机运转期间长期占用 1ms 硬件定时器中断,
* 配网连接后与 BLE 协议栈抢时序触发看门狗复位;
* 换向死区判定改用阈值检查而非精确计时,
* 取消专门的 1ms 高频档,全系统统一 20ms 节拍,
* 取消专门的 1ms 高频档,全系统统一 10ms 节拍,
* 只保留"能不能休眠"这一个维度
* - V2.2.0, 2026.08.27, cyWu, app_led_init() 提前到 app_boot_wait_key() 之前,
* 开机 3s/5s/拒绝开机低电闪直接用灯效事件表,
* 不用再各自摸裸 GPIO 手搓一份闪烁定时
* - V2.3.0, 2026.08.31, cyWu, DP0=0 待机允许系统轻睡;震动检测并入现有
* 10ms 业务节拍,避免重复定时器唤醒
*
* @note 协议生成文件 jb_product.c 不要填业务。APP 下行写 gDevData。
* usr_timer_loop 由 BLE 回调,勿再注册第二份。
@@ -45,6 +47,7 @@
#include "usr_le_product.h"
#include "usr_le_api.h"
#include "bsp_hw.h"
#include "board_pin.h"
#include "app_power.h"
#include "app_pair.h"
#include "app_boot.h"
@@ -69,7 +72,7 @@ const uint8_t g_usr_le_pcode[USR_LE_PCODE_LEN] = {
JB_PRODUCT_CODE[3], JB_PRODUCT_CODE[4], JB_PRODUCT_CODE[5]
};
#define USR_TIMER_IDLE_MS 20 /**< 全局统一节拍:按键消抖、换向死区判定都按它对齐 */
#define USR_TIMER_IDLE_MS BOARD_SHAKE_POLL_MS /**< 按键、换向和震动检测共用同一节拍 */
/**
* @brief 业务节拍档位
@@ -78,7 +81,7 @@ const uint8_t g_usr_le_pcode[USR_LE_PCODE_LEN] = {
* 单独开过 1ms 硬件定时器中断,配网连接后跟 BLE 协议栈自己的收发/ACK
* 处理抢时序,长时间叠加导致看门狗复位。
* 换向死区的判定是"距上次换向是否已过 switch_ms"这种阈值检查,不是
* 精确计时:用 20ms 节拍去查,查到时实际已经过了不少于 20ms,天然
* 精确计时:用 10ms 节拍去查,查到时实际已经过了不少于 10ms,天然
* 满足"至少 1ms"的死区要求,只会让保护更充分,不会失效。所以死区
* 不需要专门的 1ms 精度,可以和其它业务共用同一个节拍。
*/
@@ -108,11 +111,20 @@ static void usr_on_power_evt(AppPowerEvt_t evt)
{
switch (evt) {
case APP_POWER_EVT_USB_IN:
app_function_stop_all();
log_info("usb in, motor stop\n");
if (!app_led_is_ota()) {
app_pair_stop();
usr_var.usr_goto_pair = 0;
app_function_soft_off();
log_info("usb in, charge standby\n");
} else {
app_function_stop_all();
log_info("usb in, keep ota\n");
}
break;
case APP_POWER_EVT_USB_OUT:
app_function_soft_off();
if (!app_led_is_ota()) {
app_function_soft_off();
}
break;
default:
break;
@@ -161,6 +173,8 @@ static void usr_poweroff_finalize_hook(void)
*/
static UsrTickMode_t usr_timer_pick_mode(void)
{
/* DP0=0 仍保留低优先级业务节拍、BLE 和外部唤醒源,不需要禁止轻睡。
* 定时器到期会正常唤醒 CPU,按键/震动/USB 检测功能不会因此停掉。 */
if (bsp_key_is_pressed() || app_power_poweroff_pending()
|| app_mode_get_active() || bsp_motor_is_busy()
|| app_function_is_hand_busy()) {
@@ -194,7 +208,7 @@ static void usr_timer_rearm(UsrTickMode_t mode)
* 只有这一种,没有"任务线程 + 禁睡"的组合可选。
* 代价是这里执行的每一行代码都会占用中断时间,跟 BLE 协议栈自己的
* 收发中断抢时序;这次的看门狗复位就是因为这条路径长期以 1kHz
* 触发。现在虽然降到了 20ms 一拍,但仍要守住底线:
* 触发。现在虽然降到了 10ms 一拍,但仍要守住底线:
* - 禁止阻塞等待(信号量/循环等 ACK 之类)
* - 禁止耗时操作(flash 擦写、大块 memcpy、长循环)
* 按键短按/长按走到的 usr_on_key_short/long、jbSendReqWithAck
@@ -291,6 +305,7 @@ void usr_jb_init(void)
user_key_init();
user_key_lock_if_held();
app_function_power_on_silent();
usr_on_power_evt(app_power_tick_1ms(0));
usr_timer_rearm(USR_TICK_IDLE);
log_info("timer idle %ums, sleep ok\n", (unsigned)USR_TIMER_IDLE_MS);