Initial import
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
/* ================================================================
|
||||
* jb_product.c — 用户层实现(请填补空白处)
|
||||
* 设备: 弹力魔盒 | 生成时间: 2026-08-18 16:24:47
|
||||
*
|
||||
* ══ 开发流程 ══
|
||||
* 1. 实现硬件抽象: jbGetTimerCount(定时器由 SOC SDK 提供)
|
||||
* 发送由 SOC SDK 的 ble_send_data 承担,协议层已直接调用,无需 uartWrite
|
||||
* 2. userInit() 设置 DP 默认值与外设初始化
|
||||
* 3. userHandle() 用户数据采集
|
||||
* 4. jbEventProcess() 处理 APP 下发的控制命令
|
||||
* 5. 主动上报 / OTA 的网关应答在收到 REPORTCMD_V2 后调用 jbReportAckCheck 清除等待
|
||||
* ================================================================
|
||||
*/
|
||||
|
||||
#include "jb_product.h"
|
||||
#include "app_function.h"
|
||||
#include "usr_rtc.h"
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 全局变量
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
|
||||
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 默认值
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
void userInit(void)
|
||||
{
|
||||
memset(&gDevData, 0, sizeof(jb_data_point_t));
|
||||
|
||||
gDevData.power = 0; /* DP0 开关 0=关 1=开 */
|
||||
gDevData.play_mode = 0; /* PLAY_MODE_0 (停止复位) */
|
||||
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();
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* userHandle — 用户数据采集
|
||||
* 协议层自动检测变化并上报(注意:此处是上报,不是下发控制)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
void userHandle(void)
|
||||
{
|
||||
jbNoDisturbUpdate();
|
||||
|
||||
/* ── 可上报 DP (RO / RW): 用户数据采集 ── */
|
||||
// gDevData.power = read_sensor(); /* DP0 开关 */
|
||||
// gDevData.play_mode = read_sensor(); /* DP1 自动模式 */
|
||||
// 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 */
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbEventProcess — 0x03 APP 控制事件处理(下行 / DP 赋值方向)
|
||||
*
|
||||
* 协议层自动解包 0x03 帧后调用本函数。
|
||||
* info->dp_ids[] = 被控制的 DP ID 列表
|
||||
* ctrl 已填入下发的值;复制到 gDevData 并执行动作
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *ctrl)
|
||||
{
|
||||
int8_t result = JB_OK;
|
||||
|
||||
if (!info || !ctrl)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < info->count; i++)
|
||||
{
|
||||
switch (info->dp_ids[i])
|
||||
{
|
||||
|
||||
/* ── DP0 开关 (bool) ── */
|
||||
case DP_ID_POWER:
|
||||
app_function_dp_power_write(ctrl->power);
|
||||
break;
|
||||
|
||||
/* ── DP1 自动模式 (enum) ── */
|
||||
case DP_ID_PLAY_MODE:
|
||||
app_function_dp_play_write(ctrl->play_mode);
|
||||
break;
|
||||
|
||||
/* ── DP2 手动模式 (enum) ── */
|
||||
case DP_ID_HAND_MODE:
|
||||
app_function_dp_hand_write(ctrl->hand_mode);
|
||||
break;
|
||||
|
||||
/* ── DP4 声音开关 (bool) ── */
|
||||
case DP_ID_BEEP_TRIG:
|
||||
if (ctrl->beep_trig)
|
||||
{
|
||||
gDevData.beep_trig = 1;
|
||||
// TODO: 开 — 执行动作
|
||||
}
|
||||
else
|
||||
{
|
||||
gDevData.beep_trig = 0;
|
||||
// TODO: 关 — 执行动作
|
||||
}
|
||||
break;
|
||||
|
||||
/* ── DP6 震动触发开机 (bool) ── */
|
||||
case DP_ID_SHAKE_WAKE:
|
||||
app_function_set_shake_wake(ctrl->shake_wake);
|
||||
break;
|
||||
|
||||
/* ── DP7 勿扰模式开关 (raw) ── */
|
||||
case DP_ID_NO_DISTURB_ENABLE:
|
||||
memcpy(gDevData.no_disturb_enable, ctrl->no_disturb_enable,
|
||||
JB_DND_LEN);
|
||||
jbNoDisturbNormalize(gDevData.no_disturb_enable);
|
||||
jbNoDisturbUpdate();
|
||||
break;
|
||||
|
||||
/* ── DP10 预留DP3 (uint32) ── */
|
||||
case DP_ID_RESERVA_DP3:
|
||||
gDevData.reserva_dp3 = ctrl->reserva_dp3;
|
||||
// TODO: 根据 ctrl->reserva_dp3 执行动作
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* 0x30 OTA 升级请求 */
|
||||
void jbOnOtaNotify(uint32_t fwVersion, uint32_t fwLength, uint32_t fwCrc32)
|
||||
{
|
||||
(void)fwVersion; (void)fwLength; (void)fwCrc32;
|
||||
// TODO: 检查 Flash 空间与版本,决定是否接收升级
|
||||
// 允许: 直接返回(协议层默认应答 JB_OK + MaxDataLen=128)
|
||||
// 拒绝: 在 jbHandleOtaNotify 中改为 jbSendFrame(CMD_OTA_NOTIFY_ACK, sn, 错误码, 3)
|
||||
}
|
||||
|
||||
/* 0x32 OTA 数据(含分片序号)*/
|
||||
void jbOnOtaData(const uint8_t *data, uint16_t len,
|
||||
uint16_t shardIdx, uint16_t totalShards)
|
||||
{
|
||||
(void)data; (void)len; (void)shardIdx; (void)totalShards;
|
||||
// TODO: 写入 Flash,累计 CRC32
|
||||
// 全部数据接收完成后调用 jbSendOtaResult(JB_OK, mcuVersion)
|
||||
}
|
||||
|
||||
/* 0x36 / 0x38 停止 OTA */
|
||||
void jbOnOtaStop(void)
|
||||
{
|
||||
// TODO: 终止 OTA,释放资源
|
||||
}
|
||||
|
||||
/* 任意 MCU→BLE 请求的 ACK 回调(cmd 标识是哪条请求的应答,result 为结果码)
|
||||
- SOC 仅保留 OTA 主动请求应答: OTA完成 0x35 / 停止 0x39
|
||||
- 可在本回调中设置"XX 完成/失败"标志,供主循环判断"确认成功才继续" */
|
||||
void jbOnAck(uint8_t cmd, uint8_t result)
|
||||
{
|
||||
(void)cmd; (void)result;
|
||||
// TODO: switch(cmd){ case CMD_OTA_COMPLETE_ACK: gOtaDone = (result==JB_OK); break; case CMD_OTA_STOP_MCU_ACK: ... }
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 硬件抽象(按实际平台实现)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── 定时器 ── */
|
||||
void jbTimerIrq(void)
|
||||
{
|
||||
timerMs++;
|
||||
} /* 在 1ms 中断中调用本函数 */
|
||||
|
||||
uint32_t jbGetTimerCount(void)
|
||||
{
|
||||
return timerMs;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/* ================================================================
|
||||
* jb_product.h — 用户层头文件(自动生成)
|
||||
* 设备: 弹力魔盒 | 生成时间: 2026-08-18 16:24:47
|
||||
*
|
||||
* 声明用户需实现的函数。数据结构定义见 jb_protocol.h
|
||||
* ================================================================
|
||||
*/
|
||||
|
||||
#ifndef _JB_PRODUCT_H
|
||||
#define _JB_PRODUCT_H
|
||||
|
||||
#include "jb_protocol.h"
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 硬件配置(SOC SDK 已提供定时器,无需 UART 波特率配置)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
#define TIMER_PERIOD_MS 1
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 版本管理
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
/* MCU software version: Vmajor.minor.patch */
|
||||
#define JB_MCU_SW_VERSION_MAJOR 1
|
||||
#define JB_MCU_SW_VERSION_MINOR 0
|
||||
#define JB_MCU_SW_VERSION_PATCH 0
|
||||
|
||||
/* MCU hardware version: Vmajor.minor.patch */
|
||||
#define JB_MCU_HW_VERSION_MAJOR 1
|
||||
#define JB_MCU_HW_VERSION_MINOR 0
|
||||
#define JB_MCU_HW_VERSION_PATCH 0
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 用户 API
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* 全局设备数据 — 协议层直接读写该变量 */
|
||||
extern jb_data_point_t gDevData;
|
||||
|
||||
/* ==================== 用户必须实现 ============================== */
|
||||
void userInit(void); /* DP 默认值与外设初始化 */
|
||||
void userHandle(void); /* 用户数据采集 */
|
||||
void jbTimerIrq(void); /* Call from 1ms timer ISR */
|
||||
|
||||
/* =========== 协议回调(声明见 jb_protocol.h)================== */
|
||||
/* int8_t jbEventProcess(...); // 0x03 控制事件 */
|
||||
/* void jbOnOtaNotify(...); // 0x30 OTA 请求 */
|
||||
/* void jbOnOtaData(...); // 0x32 OTA 数据 */
|
||||
/* void jbOnOtaStop(); // 0x36/0x38 停止 OTA */
|
||||
/* void jbOnAck(...); // 任意请求 ACK(结果) */
|
||||
|
||||
/* ============= 主动请求(声明见 jb_protocol.h)================ */
|
||||
/* int32_t jbNotifyOtaStop(); // 0x38 通知 BLE 停止 OTA */
|
||||
/* int32_t jbSendOtaResult(...); // 0x34 OTA 结果 */
|
||||
|
||||
#endif /* _JB_PRODUCT_H */
|
||||
@@ -0,0 +1,718 @@
|
||||
/* ================================================================
|
||||
* jb_protocol.c — 见宝 IOT 协议实现(自动生成)
|
||||
* 设备: 弹力魔盒 | 生成时间: 2026-08-18 16:24:47
|
||||
* ================================================================
|
||||
*/
|
||||
|
||||
#include "jb_protocol.h"
|
||||
#include "jb_product.h"
|
||||
#include "jb_ringbuffer.h"
|
||||
#include "jb_common.h"
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 全局实例
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
jb_protocol_t jbProtocol;
|
||||
static jb_ringbuffer_t rb; /* UART 接收缓冲区 */
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbInit — 协议层初始化
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
void jbInit(void)
|
||||
{
|
||||
memset(&jbProtocol, 0, sizeof(jb_protocol_t));
|
||||
jbRbInit(&rb);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbPutData — UART 中断将数据压入环形缓冲区
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
int32_t jbPutData(uint8_t *buf, uint32_t len)
|
||||
{
|
||||
if (!buf)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
for (uint32_t i = 0; i < len; i++)
|
||||
{
|
||||
jbRbPush(&rb, buf[i]);
|
||||
}
|
||||
return (int32_t)len;
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbSendFrame — 组装协议帧并经 ble_send_data 发送(SOC 无串口)
|
||||
* 返回帧总长度,< 0 表示出错
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
static int32_t jbSendFrame(uint8_t cmd, uint8_t sn,
|
||||
const uint8_t *payload, uint16_t pLen)
|
||||
{
|
||||
uint8_t buf[MAX_PACKAGE_LEN];
|
||||
uint16_t lenField = pLen + 3; /* CMD + SN + payload + CKSUM */
|
||||
uint16_t total = lenField + 4;
|
||||
|
||||
if (total > MAX_PACKAGE_LEN)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 55 AA | LEN_H LEN_L | CMD | SN | payload | CKSUM */
|
||||
buf[0] = PKT_HEAD_0;
|
||||
buf[1] = PKT_HEAD_1;
|
||||
jbWriteU16BE(&buf[2], lenField);
|
||||
buf[4] = cmd;
|
||||
buf[5] = sn;
|
||||
if (payload && pLen > 0)
|
||||
{
|
||||
memcpy(&buf[6], payload, pLen);
|
||||
}
|
||||
buf[total - 1] = jbChecksum(buf, total);
|
||||
|
||||
ble_send_data(buf, total);
|
||||
return (int32_t)total;
|
||||
}
|
||||
|
||||
/* 发送单字节结果应答 */
|
||||
static void jbSendAck(uint8_t cmd, uint8_t sn, uint8_t result)
|
||||
{
|
||||
jbSendFrame(cmd, sn, &result, 1);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbGetOnePacket — 从环形缓冲区提取完整帧
|
||||
* 返回: 0 = 成功, 1 = 需要更多数据, -2 = 校验和错误
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
static int8_t jbGetOnePacket(uint8_t *out, uint16_t *outLen)
|
||||
{
|
||||
static uint8_t state = 0;
|
||||
static uint16_t expect = 0;
|
||||
static uint16_t idx = 0;
|
||||
static uint8_t prev = 0;
|
||||
|
||||
while (jbRbAvailable(&rb))
|
||||
{
|
||||
uint8_t b = jbRbPop(&rb);
|
||||
|
||||
if (state == 0)
|
||||
{
|
||||
/* 搜索同步头 55 AA */
|
||||
if (prev == PKT_HEAD_0 && b == PKT_HEAD_1)
|
||||
{
|
||||
out[0] = PKT_HEAD_0;
|
||||
out[1] = PKT_HEAD_1;
|
||||
idx = 2;
|
||||
state = 1;
|
||||
}
|
||||
}
|
||||
else if (state == 1)
|
||||
{
|
||||
/* 接收 LEN(2B) + CMD + SN */
|
||||
out[idx++] = b;
|
||||
if (idx == 6)
|
||||
{
|
||||
expect = 4 + jbReadU16BE(&out[2]);
|
||||
if (expect > MAX_PACKAGE_LEN || expect < 7)
|
||||
{
|
||||
state = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 接收剩余数据 */
|
||||
out[idx++] = b;
|
||||
if (idx >= expect)
|
||||
{
|
||||
state = 0;
|
||||
if (out[expect - 1] == jbChecksum(out, expect))
|
||||
{
|
||||
*outLen = expect;
|
||||
return 0;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
prev = b;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbPackDps — dataPoint → outBuf[bitmap | values]
|
||||
* mask 指定要打包的 DP(bit 序号 = DP ID)
|
||||
* 返回 outBuf 总字节数(bitmap + values)
|
||||
* 仅打包 RO / RW 方向的 DP
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
static uint16_t jbPackDps(const jb_data_point_t *dp,
|
||||
const uint8_t *mask, uint8_t *outBuf)
|
||||
{
|
||||
uint8_t *bitmap = outBuf;
|
||||
uint8_t *out = outBuf + DP_BITMAP_SIZE;
|
||||
uint16_t valIdx = 0;
|
||||
memset(bitmap, 0, DP_BITMAP_SIZE);
|
||||
|
||||
/* DP0 开关 (bool, 1B, RW) */
|
||||
if (mask[DP_ID_POWER / 8] & (1 << (DP_ID_POWER % 8)))
|
||||
{
|
||||
bitmap[DP_ID_POWER / 8] |= (1 << (DP_ID_POWER % 8));
|
||||
out[valIdx++] = dp->power ? 1 : 0;
|
||||
}
|
||||
/* DP1 自动模式 (enum, 1B, RW) */
|
||||
if (mask[DP_ID_PLAY_MODE / 8] & (1 << (DP_ID_PLAY_MODE % 8)))
|
||||
{
|
||||
bitmap[DP_ID_PLAY_MODE / 8] |= (1 << (DP_ID_PLAY_MODE % 8));
|
||||
out[valIdx++] = (uint8_t)dp->play_mode;
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
/* DP6 震动触发开机 (bool, 1B, RW) */
|
||||
if (mask[DP_ID_SHAKE_WAKE / 8] & (1 << (DP_ID_SHAKE_WAKE % 8)))
|
||||
{
|
||||
bitmap[DP_ID_SHAKE_WAKE / 8] |= (1 << (DP_ID_SHAKE_WAKE % 8));
|
||||
out[valIdx++] = dp->shake_wake ? 1 : 0;
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
/* DP8 充电状态 (enum, 1B, RO) */
|
||||
if (mask[DP_ID_CHARGE / 8] & (1 << (DP_ID_CHARGE % 8)))
|
||||
{
|
||||
bitmap[DP_ID_CHARGE / 8] |= (1 << (DP_ID_CHARGE % 8));
|
||||
out[valIdx++] = (uint8_t)dp->charge;
|
||||
}
|
||||
/* 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));
|
||||
jbWriteU32BE(&out[valIdx], (uint32_t)dp->reserva_dp3);
|
||||
valIdx += 4;
|
||||
}
|
||||
|
||||
return DP_BITMAP_SIZE + valIdx;
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbUnpackDps — bitmap+values → dataPoint + event_info
|
||||
* 仅解包 WO / RW 方向的 DP
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
static void jbUnpackDps(const uint8_t *bitmap, const uint8_t *values,
|
||||
uint16_t valLen, jb_data_point_t *dp,
|
||||
jb_event_info_t *info)
|
||||
{
|
||||
uint16_t valIdx = 0;
|
||||
if (info)
|
||||
{
|
||||
info->count = 0;
|
||||
}
|
||||
|
||||
/* DP0 开关 (bool, 1B, RW) */
|
||||
if (bitmap[DP_ID_POWER / 8] & (1 << (DP_ID_POWER % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
{
|
||||
dp->power = (values[valIdx++] != 0);
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_POWER;
|
||||
}
|
||||
}
|
||||
/* DP1 自动模式 (enum, 1B, RW) */
|
||||
if (bitmap[DP_ID_PLAY_MODE / 8] & (1 << (DP_ID_PLAY_MODE % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
{
|
||||
dp->play_mode = values[valIdx++];
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_PLAY_MODE;
|
||||
}
|
||||
}
|
||||
/* DP2 手动模式 (enum, 1B, WO) */
|
||||
if (bitmap[DP_ID_HAND_MODE / 8] & (1 << (DP_ID_HAND_MODE % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
{
|
||||
dp->hand_mode = values[valIdx++];
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_HAND_MODE;
|
||||
}
|
||||
}
|
||||
/* DP4 声音开关 (bool, 1B, RW) */
|
||||
if (bitmap[DP_ID_BEEP_TRIG / 8] & (1 << (DP_ID_BEEP_TRIG % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
{
|
||||
dp->beep_trig = (values[valIdx++] != 0);
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_BEEP_TRIG;
|
||||
}
|
||||
}
|
||||
/* DP6 震动触发开机 (bool, 1B, RW) */
|
||||
if (bitmap[DP_ID_SHAKE_WAKE / 8] & (1 << (DP_ID_SHAKE_WAKE % 8)))
|
||||
{
|
||||
if (valIdx < valLen)
|
||||
{
|
||||
dp->shake_wake = (values[valIdx++] != 0);
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_SHAKE_WAKE;
|
||||
}
|
||||
}
|
||||
/* DP7 勿扰模式开关 (raw, 13B, RW) */
|
||||
if (bitmap[DP_ID_NO_DISTURB_ENABLE / 8] & (1 << (DP_ID_NO_DISTURB_ENABLE % 8)))
|
||||
{
|
||||
if (valIdx + 13 <= valLen)
|
||||
{
|
||||
memcpy(dp->no_disturb_enable, &values[valIdx], 13);
|
||||
valIdx += 13;
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_NO_DISTURB_ENABLE;
|
||||
}
|
||||
}
|
||||
/* DP10 预留DP3 (uint32, 4B, RW) */
|
||||
if (bitmap[DP_ID_RESERVA_DP3 / 8] & (1 << (DP_ID_RESERVA_DP3 % 8)))
|
||||
{
|
||||
if (valIdx + 4 <= valLen)
|
||||
{
|
||||
dp->reserva_dp3 = (uint32_t)jbReadU32BE(&values[valIdx]);
|
||||
valIdx += 4;
|
||||
}
|
||||
if (info && info->count < DP_ID_MAX)
|
||||
{
|
||||
info->dp_ids[info->count++] = DP_ID_RESERVA_DP3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbBuildDiffMask — 比较上次与当前值,为变化的可上报 DP 置 mask 位
|
||||
* 返回: 1 = 有 DP 发生变化, 0 = 全部未变化
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
static uint8_t jbBuildDiffMask(const jb_data_point_t *last,
|
||||
const jb_data_point_t *cur,
|
||||
uint8_t *mask)
|
||||
{
|
||||
uint8_t hasChange = 0;
|
||||
memset(mask, 0, DP_BITMAP_SIZE);
|
||||
|
||||
/* DP0 开关 */
|
||||
if (last->power != cur->power)
|
||||
{
|
||||
mask[DP_ID_POWER / 8] |= (1 << (DP_ID_POWER % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP1 自动模式 */
|
||||
if (last->play_mode != cur->play_mode)
|
||||
{
|
||||
mask[DP_ID_PLAY_MODE / 8] |= (1 << (DP_ID_PLAY_MODE % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
/* DP6 震动触发开机 */
|
||||
if (last->shake_wake != cur->shake_wake)
|
||||
{
|
||||
mask[DP_ID_SHAKE_WAKE / 8] |= (1 << (DP_ID_SHAKE_WAKE % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
/* DP8 充电状态 */
|
||||
if (last->charge != cur->charge)
|
||||
{
|
||||
mask[DP_ID_CHARGE / 8] |= (1 << (DP_ID_CHARGE % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
/* DP10 预留DP3 */
|
||||
if (last->reserva_dp3 != cur->reserva_dp3)
|
||||
{
|
||||
mask[DP_ID_RESERVA_DP3 / 8] |= (1 << (DP_ID_RESERVA_DP3 % 8));
|
||||
hasChange = 1;
|
||||
}
|
||||
|
||||
return hasChange;
|
||||
}
|
||||
|
||||
/* 置全量掩码(所有可上报 DP)*/
|
||||
static void jbSetFullMask(uint8_t *mask)
|
||||
{
|
||||
/* 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 */
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbSendReqWithAck — 发送请求帧并登记 ACK 重传
|
||||
*
|
||||
* 与 jbSendFrame 不同,本函数把完整帧缓存到 waitAck.buf,
|
||||
* 并启动 ACK 等待。主动上报 / OTA 等网关应答均走 REPORTCMD_V2,
|
||||
* 由 jbReportAckCheck() 清除等待;若 SEND_MAX_TIME 内未收到,
|
||||
* jbAckRetry() 自动重传,最多 SEND_MAX_NUM 次。
|
||||
* 返回 ble_send_data 结果(<0 出错)。
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
static int32_t jbSendReqWithAck(uint8_t cmd, uint8_t sn,
|
||||
const uint8_t *payload, uint16_t pLen)
|
||||
{
|
||||
uint16_t lenField = pLen + 3; /* CMD + SN + payload + CKSUM */
|
||||
uint16_t total = lenField + 4; /* + 55 AA + LEN(2) */
|
||||
|
||||
if (total > MAX_PACKAGE_LEN)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t buf[MAX_PACKAGE_LEN];
|
||||
buf[0] = PKT_HEAD_0;
|
||||
buf[1] = PKT_HEAD_1;
|
||||
jbWriteU16BE(&buf[2], lenField);
|
||||
buf[4] = cmd;
|
||||
buf[5] = sn;
|
||||
if (payload && pLen > 0)
|
||||
{
|
||||
memcpy(&buf[6], payload, pLen);
|
||||
}
|
||||
buf[total - 1] = jbChecksum(buf, total);
|
||||
|
||||
/* 缓存整帧,供超时重传 */
|
||||
memcpy(jbProtocol.waitAck.buf, buf, total);
|
||||
jbProtocol.waitAck.len = total;
|
||||
jbProtocol.waitAck.active = 1;
|
||||
jbProtocol.waitAck.sn = sn;
|
||||
jbProtocol.waitAck.retry = 0;
|
||||
jbProtocol.waitAck.sendTime = jbGetTimerCount();
|
||||
|
||||
ble_send_data(buf, total);
|
||||
return (int32_t)total;
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbAckRetry — ACK 超时重传
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
static void jbAckRetry(void)
|
||||
{
|
||||
if (!jbProtocol.waitAck.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (jbProtocol.waitAck.retry >= SEND_MAX_NUM)
|
||||
{
|
||||
memset(&jbProtocol.waitAck, 0, sizeof(jb_wait_ack_t));
|
||||
return;
|
||||
}
|
||||
if (jbGetTimerCount() - jbProtocol.waitAck.sendTime > SEND_MAX_TIME)
|
||||
{
|
||||
ble_send_data(jbProtocol.waitAck.buf, jbProtocol.waitAck.len);
|
||||
jbProtocol.waitAck.retry++;
|
||||
jbProtocol.waitAck.sendTime = jbGetTimerCount();
|
||||
}
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbReportAckCheck — 网关应答确认(主动上报 / OTA 共用)
|
||||
*
|
||||
* 网关通过 REPORTCMD_V2 回传(非 55AA)。成功条件:
|
||||
* cmd == 0
|
||||
* gateway_int_cnt == 1
|
||||
* gateway_int0 == 0
|
||||
* 满足则清除 waitAck,停止重传,并回调 jbOnAck(wait_cmd, 0)。
|
||||
*
|
||||
* 返回: 1=成功并清除等待; 0=未命中/条件不符。
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
int8_t jbReportAckCheck(uint16_t cmd, uint8_t gateway_int_cnt, int32_t gateway_int0)
|
||||
{
|
||||
if (!jbProtocol.waitAck.active)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (cmd == 0 && gateway_int_cnt == 1 && gateway_int0 == 0)
|
||||
{
|
||||
uint8_t wait_cmd = jbProtocol.waitAck.buf[4];
|
||||
memset(&jbProtocol.waitAck, 0, sizeof(jb_wait_ack_t));
|
||||
jbOnAck(wait_cmd, 0); /* result=0 表示应答成功 */
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbSendReport — 发送主动上报 (0x07)
|
||||
*
|
||||
* mask 指定本次上报包含哪些 DP:
|
||||
* - 差分掩码(来自 jbBuildDiffMask) → 仅上报变化的 DP
|
||||
* - 全量掩码(来自 jbSetFullMask) → 定时全量同步
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
static void jbSendReport(jb_data_point_t *data, const uint8_t *mask)
|
||||
{
|
||||
uint8_t payload[MAX_PACKAGE_LEN];
|
||||
uint16_t plen = jbPackDps(data, mask, payload);
|
||||
uint8_t sn = jbProtocol.sn++;
|
||||
jbSendReqWithAck(CMD_REPORT, sn, payload, plen);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbReportPolicy — 变化上报(差分) + 定时上报(全量)
|
||||
*
|
||||
* 策略:
|
||||
* 1. DP 发生变化 → 仅上报变化的 DP(差分掩码)
|
||||
* 2. 每隔 REPORT_PERIOD ms → 上报全部可上报 DP(全量同步)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
static void jbReportPolicy(jb_data_point_t *data)
|
||||
{
|
||||
uint32_t now = jbGetTimerCount();
|
||||
uint8_t diffMask[DP_BITMAP_SIZE];
|
||||
|
||||
/* ── 1. 变化上报(差分) ── */
|
||||
uint8_t hasChange = jbBuildDiffMask(&jbProtocol.lastData, data, diffMask);
|
||||
if (hasChange && !jbProtocol.waitAck.active &&
|
||||
(now - jbProtocol.lastChangeTime > REPORT_DEBOUNCE))
|
||||
{
|
||||
jbSendReport(data, diffMask);
|
||||
memcpy(&jbProtocol.lastData, data, sizeof(jb_data_point_t));
|
||||
jbProtocol.lastChangeTime = now;
|
||||
jbProtocol.lastReportTime = now;
|
||||
return;
|
||||
}
|
||||
|
||||
/* ── 2. 定时全量上报 ── */
|
||||
if (!jbProtocol.waitAck.active &&
|
||||
(now - jbProtocol.lastReportTime > REPORT_PERIOD))
|
||||
{
|
||||
uint8_t fullMask[DP_BITMAP_SIZE];
|
||||
jbSetFullMask(fullMask);
|
||||
jbSendReport(data, fullMask);
|
||||
memcpy(&jbProtocol.lastData, data, sizeof(jb_data_point_t));
|
||||
jbProtocol.lastReportTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 命令处理函数(BLE → MCU 下行帧)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── 0x03 APP 下发控制 ── */
|
||||
static void jbHandleControl(uint8_t *frame, uint16_t len)
|
||||
{
|
||||
uint8_t sn = frame[5];
|
||||
uint8_t *bitmap = &frame[6];
|
||||
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。 */
|
||||
{
|
||||
int8_t result = jbEventProcess(&info, &ctrlData);
|
||||
jbSendAck(CMD_CONTROL_ACK, sn,
|
||||
(result < 0) ? JB_PARAM_ERR : (uint8_t)result);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 0x05 APP 读取请求 ── */
|
||||
static void jbHandleRead(uint8_t *frame, uint16_t len,
|
||||
jb_data_point_t *data)
|
||||
{
|
||||
uint8_t sn = frame[5];
|
||||
uint8_t *reqBitmap = &frame[6];
|
||||
|
||||
uint8_t mask[DP_BITMAP_SIZE];
|
||||
memcpy(mask, reqBitmap, DP_BITMAP_SIZE);
|
||||
|
||||
/* bitmap 全 1(=0xFF...) 表示读取全部 DP */
|
||||
uint8_t allOnes = 1;
|
||||
for (int i = 0; i < DP_BITMAP_SIZE; i++)
|
||||
{
|
||||
if (mask[i] != 0xFF)
|
||||
{
|
||||
allOnes = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allOnes)
|
||||
{
|
||||
jbSetFullMask(mask);
|
||||
}
|
||||
|
||||
uint8_t payload[MAX_PACKAGE_LEN];
|
||||
uint16_t plen = jbPackDps(data, mask, payload);
|
||||
jbSendFrame(CMD_READ_ACK, sn, payload, plen);
|
||||
}
|
||||
|
||||
/* ── 0x30 OTA 升级请求 ── */
|
||||
static void jbHandleOtaNotify(uint8_t *frame, uint16_t len)
|
||||
{
|
||||
uint8_t sn = frame[5];
|
||||
uint32_t fwVersion = ((uint32_t)frame[6] << 16)
|
||||
| ((uint32_t)frame[7] << 8)
|
||||
| frame[8];
|
||||
uint32_t fwLength = jbReadU32BE(&frame[9]);
|
||||
uint32_t fwCrc32 = jbReadU32BE(&frame[13]);
|
||||
|
||||
jbOnOtaNotify(fwVersion, fwLength, fwCrc32);
|
||||
|
||||
/* 默认应答: 结果码 + 最大单包长度(2B) */
|
||||
/* maxDataLen 由 MCU 自身能力决定,填入 0x31 应答,BLE 据此分片 */
|
||||
uint8_t ack[3];
|
||||
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);
|
||||
}
|
||||
|
||||
/* ── 0x32 OTA 数据(含分片序号)── */
|
||||
static void jbHandleOtaData(uint8_t *frame, uint16_t len)
|
||||
{
|
||||
uint8_t sn = frame[5];
|
||||
uint16_t shardIdx = jbReadU16BE(&frame[6]);
|
||||
uint16_t totalShards = jbReadU16BE(&frame[8]);
|
||||
const uint8_t *pData = &frame[10];
|
||||
uint16_t dataLen = (len > 11) ? (len - 11) : 0;
|
||||
jbOnOtaData(pData, dataLen, shardIdx, totalShards);
|
||||
jbSendAck(CMD_OTA_DATA_ACK, sn, JB_OK);
|
||||
}
|
||||
|
||||
/* ── 0x36 BLE 通知停止 OTA ── */
|
||||
static void jbHandleOtaStop(uint8_t *frame, uint16_t len)
|
||||
{
|
||||
uint8_t sn = frame[5];
|
||||
jbOnOtaStop();
|
||||
jbSendAck(CMD_OTA_STOP_BLE_ACK, sn, JB_OK);
|
||||
}
|
||||
|
||||
/* 0x38 通知 BLE 停止 OTA(MCU 主动,等待 ACK 重传)*/
|
||||
int32_t jbNotifyOtaStop(void)
|
||||
{
|
||||
uint8_t sn = jbProtocol.sn++;
|
||||
return jbSendReqWithAck(CMD_OTA_STOP_MCU, sn, NULL, 0);
|
||||
}
|
||||
|
||||
/* 0x34 OTA 升级完成(result + MCU 版本 3B,等待 ACK 重传)*/
|
||||
int32_t jbSendOtaResult(uint8_t result, const uint8_t *mcuVersion)
|
||||
{
|
||||
uint8_t sn = jbProtocol.sn++;
|
||||
uint8_t payload[4];
|
||||
payload[0] = result;
|
||||
if (mcuVersion)
|
||||
{
|
||||
memcpy(&payload[1], mcuVersion, 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(&payload[1], 0, 3);
|
||||
}
|
||||
return jbSendReqWithAck(CMD_OTA_COMPLETE, sn, payload, 4);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* jbProtocolHandle — 协议主循环(在 while(1) 中调用)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
int32_t jbProtocolHandle(jb_data_point_t *data)
|
||||
{
|
||||
if (!data)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ACK 超时重传 */
|
||||
jbAckRetry();
|
||||
|
||||
/* 提取一帧 */
|
||||
uint16_t pktLen = 0;
|
||||
int8_t ret = jbGetOnePacket(jbProtocol.rxBuf, &pktLen);
|
||||
uint8_t cmd;
|
||||
if (ret == 0)
|
||||
{
|
||||
cmd = jbProtocol.rxBuf[4];
|
||||
|
||||
/* 按命令码统一分发 BLE → MCU 的所有帧(含 MCU 主动请求的应答)*/
|
||||
switch (cmd)
|
||||
{
|
||||
/* ── APP-BLE 数据 ── */
|
||||
case CMD_CONTROL:
|
||||
jbHandleControl(jbProtocol.rxBuf, pktLen);
|
||||
break;
|
||||
case CMD_READ:
|
||||
jbHandleRead(jbProtocol.rxBuf, pktLen, data);
|
||||
break;
|
||||
/* ── BLE-MCU OTA ── */
|
||||
case CMD_OTA_NOTIFY:
|
||||
jbHandleOtaNotify(jbProtocol.rxBuf, pktLen);
|
||||
break;
|
||||
case CMD_OTA_DATA:
|
||||
jbHandleOtaData(jbProtocol.rxBuf, pktLen);
|
||||
break;
|
||||
case CMD_OTA_STOP_BLE:
|
||||
jbHandleOtaStop(jbProtocol.rxBuf, pktLen);
|
||||
break;
|
||||
/* 主动上报 / OTA 完成&停止 的网关应答均走 REPORTCMD_V2 → jbReportAckCheck */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
jbReportPolicy(data);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/* ================================================================
|
||||
* jb_protocol.h — 见宝 IOT 协议层(自动生成)
|
||||
* 设备: 弹力魔盒 | 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
|
||||
* CKSUM = sum(LEN_H .. payload_last) & 0xFF
|
||||
* Bitmap = 每个 DP 占 1 bit,bit 序号 = DP ID(0 起始)
|
||||
* Values = 按 bitmap 置位顺序排列,每个 DP 占完整 byte_length
|
||||
* ================================================================
|
||||
*/
|
||||
|
||||
#ifndef _JB_PROTOCOL_H
|
||||
#define _JB_PROTOCOL_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 协议常量
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
#define PKT_HEAD_0 0x55
|
||||
#define PKT_HEAD_1 0xAA
|
||||
#define MAX_PACKAGE_LEN 128
|
||||
#define JB_OTA_MAX_PAYLOAD 96 /* OTA 单包 Payload 上限(Byte); 须 <= MAX_PACKAGE_LEN - 11(0x32 帧开销) */
|
||||
|
||||
#define DP_COUNT 10
|
||||
#define DP_BITMAP_SIZE 2
|
||||
|
||||
#define SEND_MAX_TIME 2500 /* ACK 重传超时(ms);BLE Indication往返常需数百ms~1s,过短会误重传导致双应答 */
|
||||
#define SEND_MAX_NUM 3 /* 最大重传次数 */
|
||||
#define REPORT_DEBOUNCE 2000 /* 变化上报防抖时间(ms) */
|
||||
#define REPORT_PERIOD 600000 /* 定时全量上报周期(ms) */
|
||||
|
||||
/* Device identity: change these values for the target product.
|
||||
* JB_CATEGORY_CODE: 必须 4 字符 ASCII,供 BLE 广播 prodcode 使用
|
||||
* JB_PRODUCT_CODE : 必须 6 字符 ASCII,供 BLE 广播 pcode 使用
|
||||
*/
|
||||
#define JB_PROTOCOL_VERSION 0x0100 /* V1.0 */
|
||||
#define JB_CATEGORY_CODE "CBC4"
|
||||
#define JB_PRODUCT_CODE "PICBC4"
|
||||
#define JB_DEVICE_SN 0x00
|
||||
#define JB_DEVICE_CAPABILITY 0ULL
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 命令码(见《SOC方案接入协议通讯文档》)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
typedef enum
|
||||
{
|
||||
/* ── APP-BLE 数据(6) ── */
|
||||
CMD_CONTROL = 0x03, /* APP/BLE → MCU: 下发控制 */
|
||||
CMD_CONTROL_ACK = 0x04, /* MCU → APP/BLE: 控制应答 */
|
||||
CMD_READ = 0x05, /* APP/BLE → MCU: 读取请求 */
|
||||
CMD_READ_ACK = 0x06, /* MCU → APP/BLE: 读取应答 */
|
||||
CMD_REPORT = 0x07, /* MCU → APP/BLE: 主动上报 */
|
||||
CMD_REPORT_ACK = 0x08, /* APP/BLE → MCU: 上报应答 */
|
||||
/* ── OTA(10) ── */
|
||||
CMD_OTA_NOTIFY = 0x30, /* BLE → MCU: OTA 升级请求 */
|
||||
CMD_OTA_NOTIFY_ACK = 0x31, /* MCU → BLE: OTA 应答(含MaxLen) */
|
||||
CMD_OTA_DATA = 0x32, /* BLE → MCU: OTA 数据 */
|
||||
CMD_OTA_DATA_ACK = 0x33, /* MCU → BLE: OTA 数据应答 */
|
||||
CMD_OTA_COMPLETE = 0x34, /* MCU → BLE: OTA 完成 */
|
||||
CMD_OTA_COMPLETE_ACK = 0x35, /* BLE → MCU: OTA 完成应答 */
|
||||
CMD_OTA_STOP_BLE = 0x36, /* BLE → MCU: 停止 OTA */
|
||||
CMD_OTA_STOP_BLE_ACK = 0x37, /* MCU → BLE: 停止应答 */
|
||||
CMD_OTA_STOP_MCU = 0x38, /* MCU → BLE: 通知停止 OTA */
|
||||
CMD_OTA_STOP_MCU_ACK = 0x39, /* BLE → MCU: 停止应答 */
|
||||
} jb_cmd_t;
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 结果码
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
typedef enum
|
||||
{
|
||||
JB_OK = 0x00,
|
||||
JB_PARAM_ERR = 0x01,
|
||||
JB_BUSY = 0x02,
|
||||
JB_NOT_SUPPORT = 0x03,
|
||||
JB_CRC_ERR = 0x04,
|
||||
JB_FAIL = 0x05,
|
||||
/* ── OTA 专用错误码 ── */
|
||||
JB_OTA_NO_NEED = 0x20, /* 无需升级(版本一致) */
|
||||
JB_OTA_VER_LOW = 0x21, /* OTA 版本低于当前版本 */
|
||||
JB_OTA_SPACE_ERR = 0x22, /* Flash 空间不足 */
|
||||
JB_OTA_STATE_ERR = 0x23, /* OTA 状态错误 */
|
||||
JB_OTA_FILE_ERR = 0x24, /* OTA 文件检验失败 */
|
||||
JB_OTA_FAIL = 0x25, /* 升级失败 */
|
||||
} jb_result_t;
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* BLE 工作状态位 (0x14)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
#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_OTA_UPGRADING (1 << 6) /* 模组 OTA 升级中 */
|
||||
#define WS_RESERVED (1 << 7) /* Reserved */
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 设备能力位图 (0x02 Capability Bitmap, 8B 大端)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
#define CAP_BLE_OTA (1ULL << 0) /* BLE OTA */
|
||||
#define CAP_MCU_OTA (1ULL << 1) /* MCU OTA */
|
||||
#define CAP_APP_DIRECT (1ULL << 2) /* APP 直连 BLE */
|
||||
#define CAP_IPC_GATEWAY (1ULL << 3) /* IPC 网关连接 */
|
||||
#define CAP_PRODUCTION (1ULL << 4) /* 产测模式 */
|
||||
#define CAP_PARAM_CFG (1ULL << 5) /* 参数配置 */
|
||||
#define CAP_LOG (1ULL << 6) /* 日志功能 */
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* DP ID 枚举(从 Excel 自动生成)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
typedef enum
|
||||
{
|
||||
DP_ID_POWER = 0, /* 开关 (bool, RW) */
|
||||
DP_ID_PLAY_MODE = 1, /* 自动模式 (enum, 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 1
|
||||
#define DP_LEN_ALM_LOW_BATTERY 1
|
||||
#define DP_LEN_BEEP_TRIG 1
|
||||
#define DP_LEN_BATTERY 1
|
||||
#define DP_LEN_SHAKE_WAKE 1
|
||||
#define DP_LEN_NO_DISTURB_ENABLE 13
|
||||
#define DP_LEN_CHARGE 1
|
||||
#define DP_LEN_RESERVA_DP3 4
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* DP 枚举值定义
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
/* 自动模式 */
|
||||
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_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;
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 数据结构
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* 数据点 — 所有 DP 的当前值 */
|
||||
typedef struct
|
||||
{
|
||||
uint8_t power; /* DP0 开关 */
|
||||
uint8_t play_mode; /* DP1 自动模式 */
|
||||
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;
|
||||
|
||||
/* 控制事件信息 */
|
||||
typedef struct
|
||||
{
|
||||
uint8_t count;
|
||||
uint8_t dp_ids[DP_ID_MAX];
|
||||
} jb_event_info_t;
|
||||
|
||||
/* ACK 等待 / 重传管理器 */
|
||||
typedef struct
|
||||
{
|
||||
uint8_t active; /* 是否有等待中的请求 */
|
||||
uint8_t sn; /* 帧序列号 */
|
||||
uint8_t retry;
|
||||
uint8_t buf[MAX_PACKAGE_LEN];
|
||||
uint16_t len;
|
||||
uint32_t sendTime;
|
||||
} jb_wait_ack_t;
|
||||
|
||||
/* 协议全局状态 */
|
||||
typedef struct
|
||||
{
|
||||
uint8_t sn; /* 设备 SN 计数器 */
|
||||
uint8_t rxBuf[MAX_PACKAGE_LEN];
|
||||
jb_wait_ack_t waitAck; /* ACK 重传管理器 */
|
||||
jb_data_point_t lastData; /* 上次上报快照 */
|
||||
uint32_t lastReportTime; /* 上次上报时刻 */
|
||||
uint32_t lastChangeTime; /* 上次变化上报时刻 */
|
||||
} jb_protocol_t;
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 全局实例
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
extern jb_protocol_t jbProtocol;
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 协议层 API(自动生成)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
void jbInit(void);
|
||||
int32_t jbPutData(uint8_t *buf, uint32_t len);
|
||||
|
||||
int32_t jbNotifyOtaStop(void);
|
||||
int32_t jbSendOtaResult(uint8_t result, const uint8_t *mcuVersion);
|
||||
|
||||
int32_t jbProtocolHandle(jb_data_point_t *data);
|
||||
|
||||
/*
|
||||
* 网关应答确认(主动上报 / OTA 共用,REPORTCMD_V2,非 55AA)
|
||||
* cmd: ble_proto 包 cmd
|
||||
* gateway_int_cnt: gateway int 个数
|
||||
* gateway_int0: gateway_int[0]
|
||||
* 成功条件: cmd==0 && gateway_int_cnt==1 && gateway_int0==0
|
||||
* 返回: 1=成功并清除等待; 0=未命中/条件不符
|
||||
*/
|
||||
int8_t jbReportAckCheck(uint16_t cmd, uint8_t gateway_int_cnt, int32_t gateway_int0);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 硬件抽象(用户实现)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
uint32_t jbGetTimerCount(void); /* ms 计数器(定时器由 SOC SDK 提供) */
|
||||
|
||||
/* 发送接口:经 SOC SDK 提供的 BLE 发送封装(由用户/SDK 实现,非本生成代码范围)*/
|
||||
extern void ble_send_data(uint8_t *data, uint16_t len);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 用户回调(用户实现)
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
/* 0x03 APP 下发控制事件 */
|
||||
int8_t jbEventProcess(jb_event_info_t *info, jb_data_point_t *data);
|
||||
|
||||
/* BLE → MCU 事件回调 */
|
||||
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 停止 */
|
||||
|
||||
/* MCU → BLE 应答回调(收到 BLE 应答时触发)*/
|
||||
void jbOnAck(uint8_t cmd, uint8_t result); /* 任意请求 ACK(结果) */
|
||||
|
||||
#endif /* _JB_PROTOCOL_H */
|
||||
@@ -0,0 +1,69 @@
|
||||
/* ================================================================
|
||||
* jb_common.c — 工具函数实现
|
||||
* ================================================================
|
||||
*/
|
||||
#include "jb_common.h"
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 校验和
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
uint8_t jbChecksum(const uint8_t *frame, uint16_t totalLen)
|
||||
{
|
||||
if (!frame || totalLen < 4)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
uint16_t sum = 0;
|
||||
/* 从 LEN_H(buf[2]) 累加到最后一个 payload 字节(buf[totalLen-2]) */
|
||||
for (uint16_t i = 2; i < totalLen - 1; i++)
|
||||
{
|
||||
sum += frame[i];
|
||||
}
|
||||
return (uint8_t)(sum & 0xFF);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 大端序读写
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
uint16_t jbReadU16BE(const uint8_t *buf)
|
||||
{
|
||||
if (!buf)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ((uint16_t)buf[0] << 8) | buf[1];
|
||||
}
|
||||
|
||||
uint32_t jbReadU32BE(const uint8_t *buf)
|
||||
{
|
||||
if (!buf)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return ((uint32_t)buf[0] << 24)
|
||||
| ((uint32_t)buf[1] << 16)
|
||||
| ((uint32_t)buf[2] << 8)
|
||||
| buf[3];
|
||||
}
|
||||
|
||||
void jbWriteU16BE(uint8_t *buf, uint16_t val)
|
||||
{
|
||||
if (!buf)
|
||||
{
|
||||
return;
|
||||
}
|
||||
buf[0] = (uint8_t)(val >> 8);
|
||||
buf[1] = (uint8_t)(val & 0xFF);
|
||||
}
|
||||
|
||||
void jbWriteU32BE(uint8_t *buf, uint32_t val)
|
||||
{
|
||||
if (!buf)
|
||||
{
|
||||
return;
|
||||
}
|
||||
buf[0] = (uint8_t)(val >> 24);
|
||||
buf[1] = (uint8_t)(val >> 16);
|
||||
buf[2] = (uint8_t)(val >> 8);
|
||||
buf[3] = (uint8_t)(val & 0xFF);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/* ================================================================
|
||||
* jb_common.h — 工具函数(校验和、大端序转换)
|
||||
*
|
||||
* 说明: 见宝协议采用大端序(Big-Endian)。
|
||||
* 多字节值(uint16/uint32)按高位在前传输。
|
||||
* ================================================================
|
||||
*/
|
||||
#ifndef _JB_COMMON_H
|
||||
#define _JB_COMMON_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 校验和
|
||||
* sum(LEN_H, LEN_L, CMD, SN, ...payload_last) & 0xFF
|
||||
* buf[0]=55, buf[1]=AA,累加范围从 buf[2] 到 buf[totalLen-2]
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
uint8_t jbChecksum(const uint8_t *frame, uint16_t totalLen);
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
* 大端序转换
|
||||
* ════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* 从字节数组读取 uint16(大端序)*/
|
||||
uint16_t jbReadU16BE(const uint8_t *buf);
|
||||
|
||||
/* 从字节数组读取 uint32(大端序)*/
|
||||
uint32_t jbReadU32BE(const uint8_t *buf);
|
||||
|
||||
/* 将 uint16 写入字节数组(大端序)*/
|
||||
void jbWriteU16BE(uint8_t *buf, uint16_t val);
|
||||
|
||||
/* 将 uint32 写入字节数组(大端序)*/
|
||||
void jbWriteU32BE(uint8_t *buf, uint32_t val);
|
||||
|
||||
#endif /* _JB_COMMON_H */
|
||||
@@ -0,0 +1,75 @@
|
||||
/* ================================================================
|
||||
* jb_ringbuffer.c — 环形缓冲区实现
|
||||
* ================================================================
|
||||
*/
|
||||
#include "jb_ringbuffer.h"
|
||||
|
||||
/* ── 初始化 ── */
|
||||
void jbRbInit(jb_ringbuffer_t *rb)
|
||||
{
|
||||
if (!rb)
|
||||
{
|
||||
return;
|
||||
}
|
||||
rb->write = rb->read = rb->count = 0;
|
||||
}
|
||||
|
||||
/* ── 压入一个字节 ── */
|
||||
void jbRbPush(jb_ringbuffer_t *rb, uint8_t byte)
|
||||
{
|
||||
if (!rb)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (rb->count >= JB_RB_CAPACITY)
|
||||
{
|
||||
return; /* 已满 — 丢弃 */
|
||||
}
|
||||
rb->buf[rb->write] = byte;
|
||||
rb->write = (rb->write + 1) % JB_RB_CAPACITY;
|
||||
rb->count++;
|
||||
}
|
||||
|
||||
/* ── 弹出一个字节 ── */
|
||||
uint8_t jbRbPop(jb_ringbuffer_t *rb)
|
||||
{
|
||||
if (!rb || rb->count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
uint8_t byte = rb->buf[rb->read];
|
||||
rb->read = (rb->read + 1) % JB_RB_CAPACITY;
|
||||
rb->count--;
|
||||
return byte;
|
||||
}
|
||||
|
||||
/* ── 查看指定偏移字节(不消费)── */
|
||||
uint8_t jbRbPeek(const jb_ringbuffer_t *rb, uint16_t offset)
|
||||
{
|
||||
if (!rb || offset >= rb->count)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
uint16_t idx = (rb->read + offset) % JB_RB_CAPACITY;
|
||||
return rb->buf[idx];
|
||||
}
|
||||
|
||||
/* ── 可读字节数 ── */
|
||||
uint16_t jbRbAvailable(const jb_ringbuffer_t *rb)
|
||||
{
|
||||
if (!rb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return rb->count;
|
||||
}
|
||||
|
||||
/* ── 清空缓冲区 ── */
|
||||
void jbRbClear(jb_ringbuffer_t *rb)
|
||||
{
|
||||
if (!rb)
|
||||
{
|
||||
return;
|
||||
}
|
||||
rb->write = rb->read = rb->count = 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/* ================================================================
|
||||
* jb_ringbuffer.h — 环形缓冲区(UART 接收)
|
||||
* ================================================================
|
||||
*/
|
||||
#ifndef _JB_RINGBUFFER_H
|
||||
#define _JB_RINGBUFFER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* 缓冲区配置 */
|
||||
#define JB_RB_CAPACITY 256 /* 缓冲区容量(2 的幂可优化取模运算)*/
|
||||
|
||||
/* 环形缓冲区实例 */
|
||||
typedef struct
|
||||
{
|
||||
uint8_t buf[JB_RB_CAPACITY];
|
||||
uint16_t write; /* 写索引 */
|
||||
uint16_t read; /* 读索引 */
|
||||
uint16_t count; /* 已用字节数 */
|
||||
} jb_ringbuffer_t;
|
||||
|
||||
void jbRbInit(jb_ringbuffer_t *rb);
|
||||
void jbRbPush(jb_ringbuffer_t *rb, uint8_t byte);
|
||||
uint8_t jbRbPop(jb_ringbuffer_t *rb);
|
||||
uint8_t jbRbPeek(const jb_ringbuffer_t *rb, uint16_t offset);
|
||||
uint16_t jbRbAvailable(const jb_ringbuffer_t *rb);
|
||||
void jbRbClear(jb_ringbuffer_t *rb);
|
||||
|
||||
#endif /* _JB_RINGBUFFER_H */
|
||||
@@ -0,0 +1,343 @@
|
||||
/******************************************************************************
|
||||
* @file usr_jb_main.c
|
||||
* @brief CBC4 弹力魔盒业务组合根:只做调用编排,不含具体业务算法
|
||||
* @author cyWu <1917507415@qq.com>
|
||||
* @date 2026.08.27
|
||||
* @version V2.3.0
|
||||
* @history
|
||||
* - V1.0.0, 2026.08.21, cyWu, 首次发布
|
||||
* - V1.1.0, 2026.08.24, cyWu, 收口未接线模块;DP2 仅第 1 字节点动 1s
|
||||
* - V1.2.0, 2026.08.24, cyWu, 按键:深睡 3s 开机、5s 开机+配对;开机中 3s 深睡
|
||||
* - V1.3.0, 2026.08.24, cyWu, DP2:00=空闲,01/02 转,03 停
|
||||
* - V1.4.0, 2026.08.25, cyWu, 空闲改可休眠定时器,系统才能进 sleep
|
||||
* - V1.5.0, 2026.08.25, cyWu, LED/按键改事件库,红绿可同时亮
|
||||
* - V2.0.0, 2026.08.25, cyWu, 重构:电源/配对/开机/功能拆成独立模块,
|
||||
* 本文件只剩初始化顺序、1ms 调度、模块间接线
|
||||
* - V2.1.0, 2026.08.26, cyWu, 电机运转期间长期占用 1ms 硬件定时器中断,
|
||||
* 配网连接后与 BLE 协议栈抢时序触发看门狗复位;
|
||||
* 换向死区判定改用阈值检查而非精确计时,
|
||||
* 取消专门的 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 回调,勿再注册第二份。
|
||||
* 具体业务逻辑请去对应模块找:
|
||||
* app_power —— USB/充电/电量/深睡请求
|
||||
* app_pair —— 配对会话计时
|
||||
* app_boot —— 开机 3s/5s/12s 按键判定
|
||||
* app_function —— DP0/DP1/DP2 与按键动作
|
||||
* app_led —— 灯效
|
||||
******************************************************************************/
|
||||
|
||||
#include "system/includes.h"
|
||||
#include "app_config.h"
|
||||
#include "app_main.h"
|
||||
#include "app_task.h"
|
||||
#include "user_cfg.h"
|
||||
|
||||
#include "jb_protocol.h"
|
||||
#include "jb_product.h"
|
||||
#include "jb_ringbuffer.h"
|
||||
#include "jb_common.h"
|
||||
#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"
|
||||
#include "app_function.h"
|
||||
#include "app_mode.h"
|
||||
#include "app_led.h"
|
||||
#include "key_manager.h"
|
||||
#include "asm/power/power_api.h"
|
||||
|
||||
#define LOG_TAG_CONST APP
|
||||
#define LOG_TAG "[USR_JB]"
|
||||
#define LOG_INFO_ENABLE
|
||||
#include "debug.h"
|
||||
|
||||
/* 向 usr_le_code 提供产品身份(换产品只改 jb_protocol.h 宏即可) */
|
||||
const uint8_t g_usr_le_prodcode[USR_LE_PRODCODE_LEN] = {
|
||||
JB_CATEGORY_CODE[0], JB_CATEGORY_CODE[1],
|
||||
JB_CATEGORY_CODE[2], JB_CATEGORY_CODE[3]
|
||||
};
|
||||
const uint8_t g_usr_le_pcode[USR_LE_PCODE_LEN] = {
|
||||
JB_PRODUCT_CODE[0], JB_PRODUCT_CODE[1], JB_PRODUCT_CODE[2],
|
||||
JB_PRODUCT_CODE[3], JB_PRODUCT_CODE[4], JB_PRODUCT_CODE[5]
|
||||
};
|
||||
|
||||
#define USR_TIMER_IDLE_MS BOARD_SHAKE_POLL_MS /**< 按键、换向和震动检测共用同一节拍 */
|
||||
|
||||
/**
|
||||
* @brief 业务节拍档位
|
||||
* @note 全系统只用一种节拍周期(USR_TIMER_IDLE_MS),区别只在"能不能休眠",
|
||||
* 不存在专门的 1ms 高频档——之前为了电机换向死区(BOARD_MOTOR_REVERSE_MS)
|
||||
* 单独开过 1ms 硬件定时器中断,配网连接后跟 BLE 协议栈自己的收发/ACK
|
||||
* 处理抢时序,长时间叠加导致看门狗复位。
|
||||
* 换向死区的判定是"距上次换向是否已过 switch_ms"这种阈值检查,不是
|
||||
* 精确计时:用 10ms 节拍去查,查到时实际已经过了不少于 10ms,天然
|
||||
* 满足"至少 1ms"的死区要求,只会让保护更充分,不会失效。所以死区
|
||||
* 不需要专门的 1ms 精度,可以和其它业务共用同一个节拍。
|
||||
*/
|
||||
typedef enum {
|
||||
USR_TICK_IDLE = 0, /**< 可休眠 */
|
||||
USR_TICK_BLOCK_SLEEP /**< 禁止休眠(按键按住/玩法中/关机等待/电机运转,含换向死区)*/
|
||||
} UsrTickMode_t;
|
||||
|
||||
/** 调度器状态,替代原来 2 个散落的 static */
|
||||
typedef struct {
|
||||
int timer_id;
|
||||
UsrTickMode_t mode;
|
||||
} UsrSchedulerCtx_t;
|
||||
|
||||
static UsrSchedulerCtx_t s_sched;
|
||||
|
||||
static void usr_timer_tick(void *priv);
|
||||
static void usr_timer_rearm(UsrTickMode_t mode);
|
||||
static void usr_log_boot_version(void);
|
||||
|
||||
/**
|
||||
* @brief USB 插拔的联动:插入即停电机,拔出即软关并刷新系统电源图标
|
||||
* @param evt app_power_tick_1ms 上报的边沿事件
|
||||
* @return 无
|
||||
*/
|
||||
static void usr_on_power_evt(AppPowerEvt_t evt)
|
||||
{
|
||||
switch (evt) {
|
||||
case APP_POWER_EVT_USB_IN:
|
||||
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:
|
||||
if (!app_led_is_ota()) {
|
||||
app_function_soft_off();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 深睡请求钩子:立刻停电机;空电路径挂红闪,按键关机则灯全灭
|
||||
* @return 无
|
||||
*/
|
||||
static void usr_poweroff_request_hook(void)
|
||||
{
|
||||
app_function_stop_all();
|
||||
if (app_power_empty_off_pending()) {
|
||||
/* 电量到 5%:关绿灯,红灯闪 5 下,真正掉电等闪完 */
|
||||
app_led_request_empty_off();
|
||||
} else {
|
||||
app_led_clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 深睡请求钩子:50ms 到点,真正进入深睡
|
||||
* @note 不走 sys_enter_soft_poweroff():那是耳机 SDK 给"蓝牙耳机"设计的优雅关机流程
|
||||
* (BLE 断连握手 + 任务切换到 idle + 尝试放关机音效),实测哪怕已经砍掉了灯效
|
||||
* 动画和广播空转,走完还是要 2.5s 左右,供应商确认这对按键关机的玩具不正常。
|
||||
* 本产品不用 DAC/音频(没有 dac_power_on 过),跳过 dac_power_off()——
|
||||
* 它在从未打开过 DAC 的情况下会直接调 audio_dac_close() 关一个没开过的
|
||||
* 句柄,没必要也没好处。跳过 BLE 优雅断连的代价是手机 App 端不会收到
|
||||
* 断连通知,只能等连接超时(标准 BLE 行为,等同于直接拔电池,App 端会
|
||||
* 自行处理,不影响下次重连配对)。
|
||||
* @return 无
|
||||
*/
|
||||
static void usr_poweroff_finalize_hook(void)
|
||||
{
|
||||
bsp_hw_enter_sleep_io();
|
||||
power_set_soft_poweroff();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按当前业务状态判断能不能休眠
|
||||
* @note 换向死区(bsp_motor_is_busy 已把它算进"忙")、点动、玩法、按键按住、
|
||||
* 关机等待,统一都是"不能睡",节拍周期都一样用 USR_TIMER_IDLE_MS,
|
||||
* 不再区分要不要 1ms
|
||||
* @return 本次应使用的档位
|
||||
*/
|
||||
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()) {
|
||||
return USR_TICK_BLOCK_SLEEP;
|
||||
}
|
||||
return USR_TICK_IDLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 切换业务定时器。priority=1 时系统无法 sleep;节拍周期恒为 USR_TIMER_IDLE_MS
|
||||
* @param mode 目标档位
|
||||
* @return 无
|
||||
*/
|
||||
static void usr_timer_rearm(UsrTickMode_t mode)
|
||||
{
|
||||
if (s_sched.timer_id) {
|
||||
usr_timer_del((u16)s_sched.timer_id);
|
||||
s_sched.timer_id = 0;
|
||||
}
|
||||
s_sched.mode = mode;
|
||||
s_sched.timer_id = (int)usr_timer_add(NULL, usr_timer_tick, USR_TIMER_IDLE_MS,
|
||||
(mode == USR_TICK_BLOCK_SLEEP) ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 业务定时器:固定 USR_TIMER_IDLE_MS 一拍;忙时禁睡,空闲可睡
|
||||
* @param priv 未使用
|
||||
* @note ⚠️ 本函数运行在硬件定时器时基上下文(usr_timer_add 的 SDK 说明:
|
||||
* "扫描函数将在硬件定时器中时基到时候被调用"),不是普通任务线程。
|
||||
* priority=1 能禁止休眠正是因为走的这条路——SDK 里能"禁睡"的定时器
|
||||
* 只有这一种,没有"任务线程 + 禁睡"的组合可选。
|
||||
* 代价是这里执行的每一行代码都会占用中断时间,跟 BLE 协议栈自己的
|
||||
* 收发中断抢时序;这次的看门狗复位就是因为这条路径长期以 1kHz
|
||||
* 触发。现在虽然降到了 10ms 一拍,但仍要守住底线:
|
||||
* - 禁止阻塞等待(信号量/循环等 ACK 之类)
|
||||
* - 禁止耗时操作(flash 擦写、大块 memcpy、长循环)
|
||||
* 按键短按/长按走到的 usr_on_key_short/long、jbSendReqWithAck
|
||||
* 目前都只是改几个标志位 + 入队发送,没有阻塞,符合这个要求;
|
||||
* 以后往 user_key_handle / app_function_poll 这条链路上加新功能,
|
||||
* 必须先确认不违反以上两条,否则会重新引入同一类复位问题。
|
||||
* 另外,禁止在这里直接 sys_enter_soft_poweroff,一律走 app_power 的深睡钩子
|
||||
* @return 无
|
||||
*/
|
||||
static void usr_timer_tick(void *priv)
|
||||
{
|
||||
uint16_t dt = USR_TIMER_IDLE_MS;
|
||||
uint16_t i;
|
||||
UsrTickMode_t want;
|
||||
AppPowerEvt_t pwr_evt;
|
||||
|
||||
(void)priv;
|
||||
for (i = 0; i < dt; i++) {
|
||||
jbTimerIrq();
|
||||
}
|
||||
|
||||
if (!app_power_poweroff_pending()) {
|
||||
user_key_scan(dt);
|
||||
user_key_handle(); /* 长按 3s 会在这里同步触发 app_power_request_poweroff() */
|
||||
}
|
||||
|
||||
pwr_evt = app_power_tick_1ms(dt);
|
||||
usr_on_power_evt(pwr_evt);
|
||||
|
||||
app_function_poll();
|
||||
app_function_tick_1ms(dt);
|
||||
|
||||
/* 重新查询而非复用上面的判断:user_key_handle() 可能在本拍内刚请求关机,
|
||||
* 要用最新状态决定这一拍还要不要播灯效,否则会多播一帧才熄灭。
|
||||
* OTA / 空电红闪期间灯效优先:关机钩子不要把提示灯清掉。 */
|
||||
if (app_led_is_ota() || app_led_is_empty_off()) {
|
||||
app_led_run(dt, app_function_is_power_on());
|
||||
} else if (app_power_poweroff_pending()) {
|
||||
app_led_clear();
|
||||
} else {
|
||||
app_led_run(dt, app_function_is_power_on());
|
||||
}
|
||||
|
||||
want = usr_timer_pick_mode();//判断能不能休眠
|
||||
if (want != s_sched.mode) {
|
||||
usr_timer_rearm(want);//切换业务定时器
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 上电打印固件版本与编译时间,方便串口对应当前是哪一包
|
||||
* @return 无
|
||||
*/
|
||||
static void usr_log_boot_version(void)
|
||||
{
|
||||
log_info("sw V%d.%d.%d hw V%d.%d.%d build %s %s\n",
|
||||
JB_MCU_SW_VERSION_MAJOR, JB_MCU_SW_VERSION_MINOR, JB_MCU_SW_VERSION_PATCH,
|
||||
JB_MCU_HW_VERSION_MAJOR, JB_MCU_HW_VERSION_MINOR, JB_MCU_HW_VERSION_PATCH,
|
||||
__DATE__, __TIME__);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 业务初始化(在 BLE 栈起来之前由 app_main 调用)
|
||||
* @note 内部会等按键 3s/5s;不足 3s 直接深睡,不会启动业务定时器。
|
||||
* 按键判定只摸 bsp_hw 的裸 GPIO 和 led_manager 的事件表(3s 常亮 /
|
||||
* 5s 配对快闪 / 拒绝开机低电闪都要用灯),不经过协议栈/电机状态机,
|
||||
* 所以 app_led_init() 要排在它前面(跟 bsp_hw_init() 一样)——一旦
|
||||
* 判定要睡回去,boot_sleep() 原地喂狗不再返回,后面这些初始化不会
|
||||
* 被浪费。app_led_init() 内部有已初始化判断,这里提前调不会导致
|
||||
* 后面重复初始化出问题。
|
||||
* @return 无
|
||||
*/
|
||||
void usr_jb_init(void)
|
||||
{
|
||||
usr_log_boot_version(); /* UART 已在 app_main 起来,按键判定前就能看到版本 */
|
||||
|
||||
bsp_hw_init();
|
||||
app_led_init(); /* 提前到按键判定之前:3s/5s/拒绝开机低电闪都要直接用灯效事件表 */
|
||||
|
||||
app_boot_wait_key(); /* 深睡分支不会返回;返回即代表确定要开机 */
|
||||
|
||||
jbInit();
|
||||
userInit();
|
||||
if (usr_var.usr_goto_updata) {
|
||||
/* 开机 12s 已进 OTA:立刻挂红绿交替 */
|
||||
app_led_request_ota();
|
||||
}
|
||||
app_mode_init();
|
||||
|
||||
app_power_init();
|
||||
app_power_set_hooks(usr_poweroff_request_hook, usr_poweroff_finalize_hook);
|
||||
app_function_init();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief DP2 写入钩子(jb_product 薄适配调用,不在此驱电机)
|
||||
* @param cmd 第 1 字节:0=空闲,1=顺时针,2=逆时针,3=停
|
||||
* @return 无
|
||||
*/
|
||||
void usr_dp_hand_write(uint8_t cmd)
|
||||
{
|
||||
app_function_dp_hand_write(cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief BLE 收到见宝载荷,送入协议环形缓冲
|
||||
* @param data 载荷指针
|
||||
* @param len 载荷长度,单位字节
|
||||
* @return 无
|
||||
*/
|
||||
void usr_jb_le_recieve_data(u8 *data, u16 len)
|
||||
{
|
||||
jbPutData(data, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 约 500ms:协议收包与 DP 上报(由 usr_le_code 回调,不要再注册一份)
|
||||
* @return 无
|
||||
*/
|
||||
void usr_timer_loop(void)
|
||||
{
|
||||
userHandle();
|
||||
jbProtocolHandle(&gDevData);
|
||||
}
|
||||
Reference in New Issue
Block a user