1. 解决PWM 更新导致周期混乱继而亮度抖动的问题

2. 增加无论何种状态都能强制复位的机制
3. 添加设备注册检查机制,如果设备已经处于配网但是还是注册的状态,强制进行一次复位
4. 添加串口控制协议
This commit is contained in:
2025-10-26 17:18:59 +08:00
parent a16c05db7b
commit 21c6d05bad
18 changed files with 2378 additions and 81 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
.cache/
.spec-workflow/
.vscode/
*.a
*.so
*.bin

1056
LPT262_PWM_user_guide.md Normal file

File diff suppressed because it is too large Load Diff

320
PWM参数计算详解.md Normal file
View File

@@ -0,0 +1,320 @@
# PWM参数计算详解通俗易懂版
## 文档说明
本文档专门解释PWM的各个参数计算原理用通俗的语言和图表帮助理解复杂的PWM概念。适合只熟悉0-255亮度控制的开发者快速理解PWM参数。
---
## 1. 什么是PWM
PWM就像开关灯一样快速地开关来控制亮度。想象你手里有个开关如果一直开着灯就是100%亮如果一直关着灯就是0%亮。但如果你快速地开开关关,开的时间长一点,灯就亮一点;关的时间长一点,灯就暗一点。
### PWM波形图解
```
100%亮度 (一直开着):
████████████████████████████████████████████████████
50%亮度 (一半时间开,一半时间关):
████████ ████████ ████████ ████
25%亮度 (1/4时间开3/4时间关):
████ ████ ████ ████ ████ ████ ████
0%亮度 (一直关着):
________________________________________________
```
### 关键概念图解
```mermaid
graph LR
A[频率<br/>每秒开关多少次] --> B[周期<br/>一次开关的总时间]
B --> C[占空比<br/>开的时间占比例]
C --> D[亮度效果<br/>人眼感知的亮度]
style A fill:#e3f2fd
style B fill:#f1f8e9
style C fill:#fff3e0
style D fill:#fce4ec
```
---
## 2. 从0-255到实际参数的转换
### 2.1 对应关系表
| 你熟悉的数值 | 百分比 | PWM术语 | 实际效果 |
|-------------|--------|---------|----------|
| 0 | 0% | 0%占空比 | 完全不亮 |
| 64 | 25% | 25%占空比 | 1/4亮度 |
| 128 | 50% | 50%占空比 | 一半亮度 |
| 192 | 75% | 75%占空比 | 3/4亮度 |
| 255 | 100% | 100%占空比 | 最亮 |
### 2.2 转换公式
```c
// 从你熟悉的0-255转换到百分比
uint8_t brightness = 128; // 你设置的亮度值
float duty_percent = (brightness / 255.0f) * 100.0f; // 转换成百分比
// 例子:
// brightness = 0 → duty_percent = 0% (完全不亮)
// brightness = 64 → duty_percent = 25% (1/4亮度)
// brightness = 128 → duty_percent = 50% (一半亮度)
// brightness = 192 → duty_percent = 75% (3/4亮度)
// brightness = 255 → duty_percent = 100% (最亮)
```
---
## 3. PWM参数计算原理
### 3.1 基础概念图解
```
系统就像一个超快的时钟(80MHZ),每秒"滴答(cycle)"8000万次每个滴答节奏所对应的时间是固定的。
也就是这个PWM的最高精度就是(1/80*1000*1000)s =
如果我们要让PWM 1000HZ也就是每秒开关1000次
那么每次开关需要80M ÷ 1000 = 80000 cycle所对应的时间
所以我们控制PWM本质上是控制这个PWM一个周期的cycle总个数以及比例分配。
比如我要 PWM 是1KHZ
首先计算1KHZ 一个PWM周期就是1/1000s 也就是1ms
然后计算1ms 对应多少个cycle: 0.001 / (1/80*1000*1000) = 80,000
然后再根据占空比去分配这 80,000 个数字就行了
只要确保high_time low_time加起来是8000就行
```
### 3.2 时间轴图解
```
一个PWM周期 (1000Hz = 1ms):
0 20000 40000 60000 80000
|---------|---------|---------|---------|
25% 50% 75% 100%
如果要50%亮度:
████████████████████████████████████████
|←----- 开40000-----→|←---- 关40000 ----|
如果要75%亮度:
████████████████████████████████████████████████████████
|←-------- 开 (60000滴答) ---------→|←- 关 (20000滴答) -|
```
### 3.3 计算步骤详解
```c
// 第1步确定系统时钟这是硬件固定的不用改
uint32_t system_clock = 80000000; // 80MHz = 每秒8000万次计数
// 第2步设定PWM频率就是每秒开关多少次
uint32_t pwm_frequency = 1000; // 1000Hz = 每秒开关1000次
// 第3步计算一个开关周期需要多少个时钟计数
// 打个比方如果系统每秒有8000万下你要让它每秒开关1000次
// 那么每次开关就需要8000万 ÷ 1000 = 80000个计数
uint32_t period_count = system_clock / pwm_frequency;
// 第4步根据亮度(占空比)计算开和关的时间
// 如果要60%亮度那么80000个计数中48000个用来"开"32000个用来"关"
float brightness_percent = 60.0; // 60%亮度
uint32_t on_time = (uint32_t)(period_count * brightness_percent / 100.0);
uint32_t off_time = period_count - on_time;
// 结果:
// on_time = 48000 (开的时间)
// off_time = 32000 (关的时间)
```
---
## 4. PWM参数详解图表
### 4.1 pwm_config_t结构体参数图解
```c
typedef struct pwm_config {
uint32_t high_time; // 高电平时间("开"的时间,用计数表示)
uint32_t low_time; // 低电平时间("关"的时间,用计数表示)
uint32_t offset_time; // 相位偏移时间(延迟启动时间)
uint16_t cycles; // 重复次数0=无限重复)
bool repeat; // 连续输出标志true=一直输出)
} pwm_config_t;
```
### 4.2 参数关系图
```mermaid
graph TB
A[一个PWM周期] --> B[high_time<br/>开的时间]
A --> C[low_time<br/>关的时间]
B --> D[占空比 = high_time / high_time + low_time]
C --> D
D --> E[最终亮度效果]
F[offset_time<br/>延迟启动] --> G[多通道同步控制]
H[cycles<br/>重复次数] --> I[输出控制]
J[repeat<br/>连续标志] --> I
style A fill:#e3f2fd
style D fill:#f1f8e9
style E fill:#fff3e0
```
### 4.3 各参数的通俗解释
#### **high_time高电平时间**
- **通俗理解**:就是"开关"中"开"的时间,用时钟计数表示
- **计算方法**`high_time = 周期计数 × 亮度百分比`
- **例子**如果high_time = 40000系统时钟80MHz那么"开"的时间 = 40000 ÷ 80000000 = 0.5ms
#### **low_time低电平时间**
- **通俗理解**:就是"开关"中"关"的时间,用时钟计数表示
- **计算方法**`low_time = 周期计数 - high_time`
- **例子**如果low_time = 40000那么"关"的时间也是0.5ms
#### **offset_time相位偏移**
- **通俗理解**延迟多长时间再开始PWM输出
- **使用场景**多个LED需要错开时间启动避免同时启动造成电流冲击
- **大多数情况**设为0即可
#### **cycles重复次数**
- **通俗理解**PWM波形重复多少次后停止
- **常用设置**
- `0`:无限重复(最常用)
- `具体数字`:输出指定次数后停止
#### **repeat连续输出标志**
- **通俗理解**是否持续输出PWM波形
- **常用设置**
- `true`:持续输出(最常用)
- `false`:只输出指定次数后停止
---
## 5. 实际计算示例
### 5.1 示例1设置50%亮度
```c
// 目标PWM频率1000Hz50%亮度
// 第1步计算周期计数
uint32_t period_cnt = 80000000 / 1000; // = 80000
// 第2步计算高低电平时间
uint32_t high_time = 80000 * 50 / 100; // = 40000
uint32_t low_time = 80000 - 40000; // = 40000
// 第3步配置PWM
pwm_config_t cfg = {
.high_time = 40000, // "开" 0.5ms
.low_time = 40000, // "关" 0.5ms
.offset_time = 0, // 不延迟
.repeat = true, // 持续输出
.cycles = 0 // 无限重复
};
```
### 5.2 示例2设置75%亮度
```c
// 目标PWM频率1000Hz75%亮度
uint32_t period_cnt = 80000;
uint32_t high_time = 80000 * 75 / 100; // = 60000
uint32_t low_time = 80000 - 60000; // = 20000
pwm_config_t cfg = {
.high_time = 60000, // "开" 0.75ms (更长)
.low_time = 20000, // "关" 0.25ms (更短)
.offset_time = 0,
.repeat = true,
.cycles = 0
};
```
### 5.3 波形对比图
```
50%占空比 (high_time=40000, low_time=40000):
████████████████████████████████████████ ████████████████████████████████████████
|←-------- 0.5ms ------→|←-- 0.5ms --→| |←-------- 0.5ms ------→|←-- 0.5ms --→|
75%占空比 (high_time=60000, low_time=20000):
████████████████████████████████████████████████████████ ████████████████████████████████████████████████████████
|←-------------- 0.75ms ------------→|0.25ms| |←-------------- 0.75ms ------------→|0.25ms|
25%占空比 (high_time=20000, low_time=60000):
████████████████████ ████████████████████
|←-- 0.25ms --→|←---- 0.75ms ----→| |←-- 0.25ms --→|←---- 0.75ms ----→|
```
---
## 7. 简化使用函数
### 7.1 推荐的简化函数
```c
// 这个函数让PWM像以前一样简单
errcode_t set_pwm_brightness_simple(uint8_t channel, uint8_t brightness_0_255)
{
// 固定使用1000Hz频率适合LED调光
uint32_t frequency = 1000;
// 将0-255转换为百分比
float duty_percent = (brightness_0_255 / 255.0f) * 100.0f;
// 计算周期计数80MHz ÷ 1000Hz = 80000
uint32_t period_cnt = 80000000 / frequency;
// 计算高低电平时间
uint32_t high_time = (uint32_t)(period_cnt * duty_percent / 100.0f);
uint32_t low_time = period_cnt - high_time;
// 配置PWM
pwm_config_t cfg = {
.high_time = high_time, // "开"的时间(计数值)
.low_time = low_time, // "关"的时间(计数值)
.offset_time = 0, // 不需要相位偏移
.repeat = true, // 持续输出
.cycles = 0 // 无限循环
};
return uapi_pwm_open(channel, &cfg);
}
```
---
## 8. 常见问题解答
### Q1: 为什么不能直接用0-255要用这些复杂参数
**A**: PWM硬件需要知道具体的时钟计数不是百分比。就像你告诉司机"开快点",司机需要知道具体开多少码一样。
### Q2: high_time和low_time的单位是什么
**A**: 单位是时钟计数次数不是时间。1个计数 = 1/80000000秒 = 12.5纳秒。
### Q3: 为什么推荐1000Hz频率
**A**:
- 高于100Hz人眼看不到闪烁
- 低于20kHz不会产生高频噪音
- 1000Hz刚好在这个范围内且计算简单
### Q4: offset_time什么时候用
**A**: 主要用于多通道同步比如RGB灯的三个颜色错开启动避免电流冲击。
### Q5: 如何实现渐变效果?
**A**: 在循环中逐步改变brightness_0_255的值每次改变后调用set_pwm_brightness_simple()。
---

View File

@@ -54,11 +54,15 @@ errcode_t SsapsAddDescriptorSync(uint8_t serverId, uint16_t serviceHandle, uint1
return ssaps_add_descriptor_sync(serverId, serviceHandle, propertyHandle, (ssaps_desc_info_t *)descriptor);
}
#define DEFAULT_SLE_SPEED_MTU_SIZE 1500
errcode_t SsapsStartService(uint8_t serverId, uint16_t serviceHandle)
{
SsapcExchangeInfo info = {0};
info.mtuSize = DEFAULT_SLE_SPEED_MTU_SIZE;
info.version = 1;
SsapsSetInfo(serverId, &info);
return ssaps_start_service(serverId, serviceHandle);
}
errcode_t SsapsDeleteAllServices(uint8_t serverId)
{
return ssaps_delete_all_services(serverId);

View File

@@ -294,7 +294,7 @@ static void HILINK_BT_StateChangeHandler(HILINK_BT_SdkStatus event, const void *
HILINK_SAL_ERROR("set addr err\n");
}
/* 设置蓝牙广播格式,包括靠近发现、碰一碰等,下一次发送广播生效 */
BLE_SetAdvType(BLE_ADV_NEARBY_V0);
BLE_SetAdvType(BLE_ADV_LOCAL_NAME);
/* BLE配网广播控制参数代表广播时间0:停止0xFFFFFFFF:一直广播,其他:广播指定时间后停止,单位秒 */
if(ble_adv_time) {
@@ -617,7 +617,7 @@ int hilink_ble_main(void)
}
/* 设置广播方式为靠近发现 */
BLE_SetAdvType(BLE_ADV_NEARBY_V0);
BLE_SetAdvType(BLE_ADV_LOCAL_NAME);
/* 初始化ble sdk */
ret = BLE_CfgNetInit(&g_bleInitParam, &g_bleCfgNetCb);

View File

@@ -30,7 +30,7 @@ extern "C" {
#define DEVICE_HIVERSION "1.0.0"
/* 设备固件版本号 */
#define FIRMWARE_VER "1.0.13"
#define FIRMWARE_VER "1.0.6"
/* 设备硬件版本号 */
#define HARDWARE_VER "1.0.0"
/* 设备软件版本号 */

View File

@@ -112,7 +112,7 @@ int HILINK_GetDevInfo(HILINK_DevInfo *devinfo)
return -1;
}
HILINK_SAL_DEBUG("HILINK_GetDevInfo manuName: [%s]\r\n", devinfo->manuName);
#ifdef CONFIG_SUPPORT_HILINK_INDIE_UPGRADE
#if 0 //def CONFIG_SUPPORT_HILINK_INDIE_UPGRADE
err = sprintf_s(devinfo->fwv, sizeof(devinfo->fwv), "%s_%s_%s", "103","1.312", FIRMWARE_VER);
if (err <= 0) {
HILINK_SAL_ERROR("sprintf_s err:%d\r\n", err);
@@ -632,6 +632,7 @@ void HILINK_NotifyDevStatus(int status)
case HILINK_LINK_CONNECTED_WIFI:
/* 设备已经连上路由器,请在此处添加实现 */
hf_set_wifi_state(1);
handle_device_online();
break;
case HILINK_M2M_CONNECTTING_CLOUD:
/* 设备正在连接云端,请在此处添加实现 */

View File

@@ -57,6 +57,10 @@ int myhandle_put_brightness(const char* svc_id, const char* payload, unsigned in
// 处理色温控制
int myhandle_put_cct(const char* svc_id, const char* payload, unsigned int len)
{
// 🔧 添加调试信息:显示调用前的状态
e_printf("[put_cct] 调用开始: g_device_control.cct_local=%d, duty_cw=%d, duty_ww=%d\n",
g_device_control.cct_local, g_device_control.duty_cw, g_device_control.duty_ww);
int ret = -1;
cJSON *json = cJSON_Parse(payload);
if (json == NULL) {

View File

@@ -100,8 +100,14 @@ typedef struct __attribute__((packed, aligned(1))) {
#define BRIGHTNESS_MAX 100 // 最大亮度
#define BRIGHTNESS_LOCAL_MIN 0 // 最小亮度
#define BRIGHTNESS_LOCAL_MAX 10000 // 最大亮度
#define BRIGHTNESS_REMOTE2LOCAL(x) (x * 100) //变化范围0 -10000
#define BRIGHTNESS_LOCAL2REMOTE(x) (x / 100)
// 亮度映射函数仅对1-10%范围进行映射到2-10%11-100%保持不变
// 映射公式:
// - 1-10%: 实际亮度 = 2 + (APP亮度 - 1) * 8 / 9
// - 11-100%: 实际亮度 = APP亮度
// 当APP亮度为1%时实际亮度为2%当APP亮度为10%时实际亮度为10%11%及以上保持不变
#define BRIGHTNESS_REMOTE2LOCAL(x) (((x) <= 0) ? 0 : ((x) <= 10 ? (200 + (((x) - 1) * 800) / 9) : ((x) * 100)))
#define BRIGHTNESS_LOCAL2REMOTE(x) (((x) <= 0) ? 0 : ((x) <= 1000 ? (1 + (((x) - 200) * 9) / 800) : ((x) / 100)))
#define BRIGHTNESS_LITME_RANGE(x) do { \
if (x > BRIGHTNESS_LOCAL_MAX) x = BRIGHTNESS_LOCAL_MAX; \
@@ -113,9 +119,9 @@ typedef struct __attribute__((packed, aligned(1))) {
#define INIT_STA__BRIGHTNESS 50
#define INIT_STA__CCT 4000
#define INIT_NET_CFG_PWOER_ON_KEEP_TIME (5 * 1000) // 统计进入配网每次打开状态的保持时间
#define INIT_NET_CFG_PWOER_ON_KEEP_TIME (5 * 1000) // 上电保持超过该时间则清零计数(防误触)
#define NET_CFG_ENTRY_CNT 8 // 配网进入的上电次数
#define NET_CFG_ENTRY_CNT 8 // 快速上电计数阈值:达到即强制解绑并恢复出厂
#define NET_CFG_BREATH_DURATION 1*60*1000 // 配网呼吸灯持续时间(ms)
#define NET_CFG_TOTAL_TIMEOUT 10*60*1000 // 配网总超时时间(ms)
@@ -195,6 +201,10 @@ typedef enum {
#define CCT_PIN GPIO_00 // 暖白LED (WW)
#define SWITCH_PIN GPIO_13
// PWM 更新标志输出引脚(用于逻辑分析仪观察控制时序)
// 选取 GPIO10每次调用 update_pwm_output() 时翻转一次电平
#define PWM_UPDATE_FLAG_PIN GPIO_10
#define CONFIG_PWM_GROUP_ID 2
#define OPEN_LIGHT GPIO_LEVEL_HIGH
@@ -213,6 +223,7 @@ typedef enum {
.is_net_configured = false, \
.duty_cw = 0, \
.duty_ww = 0, \
.power_on_cnt = 0, \
};
int spotlight_main(void);
@@ -228,4 +239,3 @@ void stop_net_config(void);
extern int fast_report(const char* svc_id);
#endif

View File

@@ -64,6 +64,8 @@ bool g_reset_factory_flag = false;
static uint32_t pwm_period_cnt = 0; // PWM周期 40us
static uint8_t channel_id_cw = 0; // 冷白LED通道ID
static uint8_t channel_id_ww = 0; // 暖白LED通道ID
// PWM参数更新调试标志引脚是否已初始化
static bool g_pwm_update_flag_inited = false;
void save_device_data(void);
void read_device_data(void);
@@ -112,6 +114,7 @@ static struct fade_ctx_t {
uint16_t fade_time; //s
uint32_t smooth_time_us; // 渐变总时长(us)
uint32_t update_interval; // 更新间隔(us)
volatile bool timer_active; // 渐变定时器是否允许自重启(竞态保护)
// 打印限制器
print_limiter_t print_limiter;
};
@@ -208,7 +211,7 @@ static void *fade_task(const char *arg)
fade_ctx.current_cct, fade_ctx.target_cct, fade_ctx.step_cct, fade_ctx.is_closing_fade);
}
// 检查是否达到目标值(恢复原始逻辑)
// 检查是否达到目标值
bool brightness_reached = (fade_ctx.step_brightness > 0) ?
(fade_ctx.current_brightness >= fade_ctx.target_brightness) :
(fade_ctx.current_brightness <= fade_ctx.target_brightness);
@@ -216,14 +219,14 @@ static void *fade_task(const char *arg)
bool cct_reached = (fade_ctx.step_cct > 0) ?
(fade_ctx.current_cct >= fade_ctx.target_cct) :
(fade_ctx.current_cct <= fade_ctx.target_cct);
// 更新当前值(恢复原始逻辑)
// 更新当前值
if (!brightness_reached) {
fade_ctx.current_brightness += fade_ctx.step_brightness;
}
if (!cct_reached) {
fade_ctx.current_cct += fade_ctx.step_cct;
}
// 如果达到目标,停止渐变(恢复原始逻辑)
// 达到目标,停止渐变
if (brightness_reached && cct_reached) {
e_printf("[fade_task] Fade completed, Final brightness: %d, CCT: %d, closing_fade: %d\r\n",
fade_ctx.target_brightness, fade_ctx.target_cct, fade_ctx.is_closing_fade);
@@ -231,6 +234,7 @@ static void *fade_task(const char *arg)
fade_ctx.fade_completed = true;
fade_ctx.current_brightness = fade_ctx.target_brightness;
fade_ctx.current_cct = fade_ctx.target_cct;
fade_ctx.timer_active = false; // 防止回调自重启
uapi_timer_stop(fade_ctx.timer_handle);
}
@@ -335,6 +339,10 @@ static void *breath_task(const char *arg)
// 渐变定时器回调函数
static void fade_timer_callback(uintptr_t data)
{
// 若已被取消或未处于渐变态,禁止回调重入自重启,避免双定时器抖动
if (!fade_ctx.timer_active || !fade_ctx.is_fading) {
return;
}
osal_sem_up(&fade_ctx.sem); // 唤醒渐变任务 fade_task
uapi_timer_start(fade_ctx.timer_handle, fade_ctx.update_interval, fade_timer_callback, 0);
}
@@ -364,6 +372,27 @@ static void init_fade_ctx(void)
// 初始化打印限制器
init_print_limiter(&fade_ctx.print_limiter, "[fade_task]");
// 🔧 智能初始化:根据设备状态决定初始值
if (g_device_control.read_done) {
// 数据已读取完成(正常启动)
if (g_device_control.on) {
// 开机渐变从0开始渐变到目标亮度
fade_ctx.current_brightness = 0;
fade_ctx.current_cct = g_device_control.cct_local; // 色温保持目标值
e_printf("[init_fade_ctx] 开机渐变模式: brightness=0->%d, cct=%d\n",
g_device_control.brightness_local, fade_ctx.current_cct);
} else {
// 设备关闭状态
fade_ctx.current_brightness = 0;
fade_ctx.current_cct = g_device_control.cct_local;
}
} else {
// 数据尚未读取(初始化阶段)- 保持原有逻辑
fade_ctx.current_brightness = 0;
fade_ctx.current_cct = 0;
e_printf("[init_fade_ctx] 初始化阶段: brightness=0, cct=0\n");
}
// 创建渐变任务
osal_kthread_lock();
fade_ctx.task_handle = osal_kthread_create((osal_kthread_handler)fade_task, NULL, "FadeTask",
@@ -380,8 +409,7 @@ static void init_fade_ctx(void)
// 设置默认更新间隔
fade_ctx.update_interval = FADE_INTERVAL_MIN;
// fade_ctx.current_brightness = g_device_control.brightness_local;
// fade_ctx.current_cct = g_device_control.cct_local;
fade_ctx.timer_active = false; // 默认禁用,只有开始渐变时启用
}
// 初始化呼吸灯控制
@@ -491,8 +519,12 @@ void calculate_pwm_duty(device_control_t* pdevice_control)
// 更新PWM输出
void update_pwm_output(bool on_state, uint16_t duty_cw_val, uint16_t duty_ww_val)
{
pwm_config_t cfg_repeat = {0};
cfg_repeat.repeat = true;
// 说明:为了避免每步“重相位 + 动态拼接”导致的低亮抖动,
// 这里改为固定相位offset_time=0并避免每次重复start_group。
// 若平台支持 uapi_pwm_update_duty_ratio可切换为更新接口否则退回 open 配置。
pwm_config_t cfg = {0};
cfg.repeat = true;
uint16_t current_duty_cw = duty_cw_val;
uint16_t current_duty_ww = duty_ww_val;
@@ -512,29 +544,53 @@ void update_pwm_output(bool on_state, uint16_t duty_cw_val, uint16_t duty_ww_val
if (!on_state) {
high_cnt_cw = 0;
low_cnt_cw = pwm_period_cnt; // Ensure low_cnt is full period if off
low_cnt_cw = pwm_period_cnt; // 关灯时保持低电平整周期
high_cnt_ww = 0;
low_cnt_ww = pwm_period_cnt; // Ensure low_cnt is full period if off
low_cnt_ww = pwm_period_cnt;
}
// uapi_pwm_stop_group(CONFIG_PWM_GROUP_ID);
cfg_repeat.high_time = high_cnt_cw;
cfg_repeat.low_time = low_cnt_cw;
uapi_pwm_open(channel_id_cw, &cfg_repeat);
// uapi_pwm_update_duty_ratio(channel_id_cw, cfg_repeat.low_time, cfg_repeat.high_time);
#if defined(CONFIG_PWM_USING_V150)
// V150: 先更新占空,再通过 start_group 触发加载
(void)uapi_pwm_update_duty_ratio(channel_id_cw, low_cnt_cw, high_cnt_cw);
(void)uapi_pwm_update_duty_ratio(channel_id_ww, low_cnt_ww, high_cnt_ww);
// 需要显式触发加载
(void)uapi_pwm_start_group(CONFIG_PWM_GROUP_ID);
#else
// V151 场景
cfg.offset_time = 0; // 固定相位,避免相位不断重置造成抖动
#if defined(CONFIG_PWM_PRELOAD)
// 使用预加载:配置写入影子寄存器,等当前周期结束后自动无缝切换。
cfg.high_time = high_cnt_cw;
cfg.low_time = low_cnt_cw;
(void)uapi_pwm_config_preload(CONFIG_PWM_GROUP_ID, channel_id_cw, &cfg);
cfg_repeat.high_time = high_cnt_ww;
cfg_repeat.low_time = low_cnt_ww;
cfg_repeat.offset_time = high_cnt_cw; // WW PWM starts after CW PWM high time
cfg.high_time = high_cnt_ww;
cfg.low_time = low_cnt_ww;
(void)uapi_pwm_config_preload(CONFIG_PWM_GROUP_ID, channel_id_ww, &cfg);
// 预加载模式下不重复调用 start_group避免打乱当前计数器与周期
#else
// 无预加载能力时的回退:直接 open + start_group可能导致轻微周期扰动
cfg.high_time = high_cnt_cw;
cfg.low_time = low_cnt_cw;
(void)uapi_pwm_open(channel_id_cw, &cfg);
uapi_pwm_open(channel_id_ww, &cfg_repeat);
// uapi_pwm_update_duty_ratio(channel_id_ww, cfg_repeat.low_time, cfg_repeat.high_time);
uapi_pwm_start_group(CONFIG_PWM_GROUP_ID);
cfg.high_time = high_cnt_ww;
cfg.low_time = low_cnt_ww;
(void)uapi_pwm_open(channel_id_ww, &cfg);
(void)uapi_pwm_start_group(CONFIG_PWM_GROUP_ID);
#endif
#endif
if (should_print(&pwm_limiter)) {
e_printf("on:%d, cw:high:%u, low:%u, duty:%u/%u, ww:high:%u, low:%u, duty:%u/%u, offset_time:%u\r\n",
on_state, high_cnt_cw, low_cnt_cw, current_duty_cw, PWM_DUTY_RATIO_MAX, high_cnt_ww, low_cnt_ww, current_duty_ww, PWM_DUTY_RATIO_MAX, cfg_repeat.offset_time);
e_printf("on:%d, cw(H/L):%u/%u, duty:%u, ww(H/L):%u/%u, duty:%u\r\n",
on_state, high_cnt_cw, low_cnt_cw, current_duty_cw,
high_cnt_ww, low_cnt_ww, current_duty_ww);
}
// 调试每次更新PWM参数后翻转一次 GPIO10PWM_UPDATE_FLAG_PIN
// 便于使用逻辑分析仪观察控制发生的时间点
if (g_pwm_update_flag_inited) {
(void)uapi_gpio_toggle(PWM_UPDATE_FLAG_PIN);
}
}
@@ -608,6 +664,8 @@ lab_exit:
static void cancel_current_light_fade(void)
{
// 竞态保护:先拉低标志再停计时器,避免回调自重启
fade_ctx.timer_active = false;
uapi_timer_stop(fade_ctx.timer_handle);
// 如果我打断了渐变,则将当前值更新到目标值不然下次计算会异常
// g_device_control.cct_local = fade_ctx.current_cct;
@@ -887,10 +945,17 @@ int set_light(light_ctrl_source_e source,
// 如果打开灯,则需要手动将前置状态设置为关闭的样子,这样子后面计算渐变才能正常计算
if (APP_OPEN_LIGHT == source || DEV_POWER_ON == source) {
g_device_control.on = true;
// fade_ctx.current_brightness = 0;
if (DEV_POWER_ON == source) {
// 🔧 开机渐变明确设置从0开始
fade_ctx.current_brightness = 0; // 从0开始渐变
fade_ctx.current_cct = g_device_control.cct_local; // 色温保持目标值
e_printf("[set_light] 开机渐变设置: brightness=0->%d, cct=%d\n",
g_device_control.brightness_local, fade_ctx.current_cct);
}
brightness_local_target = g_device_control.brightness_local;
// 色温不进行变化,只改变亮度
fade_ctx.current_cct = g_device_control.cct_local;
cct_local_target = g_device_control.cct_local;
if (g_device_control.colourMode != COLOUR_MODE_DUAL) {
@@ -930,6 +995,7 @@ int set_light(light_ctrl_source_e source,
calculate_fade_steps(&tmp_fade_ctx); // 复用原有计算流程
memcpy(&fade_ctx, &tmp_fade_ctx, FADE_CTRL_DATA_SIZE(tmp_fade_ctx));
e_printf("start close light fade\r\n");
fade_ctx.timer_active = true;
uapi_timer_start(fade_ctx.timer_handle, fade_ctx.update_interval, fade_timer_callback, 0);
start_report_task(REPORT_SWITCH | REPORT_LIGHT_MODE);
req_save_device_data();
@@ -953,6 +1019,7 @@ int set_light(light_ctrl_source_e source,
calculate_fade_steps(&tmp_fade_ctx);
memcpy(&fade_ctx, &tmp_fade_ctx, FADE_CTRL_DATA_SIZE(tmp_fade_ctx));
e_printf("start fade\r\n");
fade_ctx.timer_active = true;
uapi_timer_start(fade_ctx.timer_handle, fade_ctx.update_interval, fade_timer_callback, 0);
start_report_task(REPORT_SWITCH | REPORT_BRIGHTNESS | REPORT_CCT | REPORT_LIGHT_MODE | REPORT_COLOUR_MODE);
req_save_device_data();
@@ -972,6 +1039,7 @@ int set_light(light_ctrl_source_e source,
calculate_fade_steps(&tmp_fade_ctx);
memcpy(&fade_ctx, &tmp_fade_ctx, FADE_CTRL_DATA_SIZE(tmp_fade_ctx));
e_printf("start fade\r\n");
fade_ctx.timer_active = true;
uapi_timer_start(fade_ctx.timer_handle, fade_ctx.update_interval, fade_timer_callback, 0);
start_report_task(REPORT_BRIGHTNESS | REPORT_CCT | REPORT_LIGHT_MODE | REPORT_COLOUR_MODE);
req_save_device_data();
@@ -1025,27 +1093,34 @@ static void pwm_init(pin_t pin, pin_t pin1)
uapi_pin_set_mode(pin, PIN_MODE_1);
uapi_pin_set_mode(pin1, PIN_MODE_1);
uapi_pwm_init();
uint32_t frequency = uapi_pwm_get_frequency(pin%8);
// uint32_t pwm_base_period_ns = 1000 * 1000 * 1000 / frequency;
// uint32_t pwm_base_period_ns = 1000 * 1000 * 1000 / (80 * 1000 * 1000);// 80MHZ
// uint32_t pwm_target_period_ns = 1000 * 1000 * 1000 / PWM_FREQUENCY;
pwm_period_cnt = ((80 * 1000 * 1000) / PWM_FREQUENCY);
// 基准时钟固定 80MHz客户指定。不要使用 uapi_pwm_get_frequency 返回值。
channel_id_cw = pin % 8;
channel_id_ww = pin1 % 8;
const uint32_t base_clk = 80 * 1000 * 1000; // 80MHz 固定
pwm_period_cnt = (base_clk / PWM_FREQUENCY);
// 初始为关灯高电平0低电平整周期固定相位
cfg_repeat.high_time = 0;
cfg_repeat.low_time = pwm_period_cnt;
// 设置PWM组
channel_id_cw = pin%8;
channel_id_ww = pin1%8;
cfg_repeat.offset_time = 0;
// 先打开两个通道
(void)uapi_pwm_open(channel_id_cw, &cfg_repeat);
(void)uapi_pwm_open(channel_id_ww, &cfg_repeat);
// 设置分组并启动(分组只需启动一次)
uint8_t channel_ids[2] = {channel_id_cw, channel_id_ww};
// uapi_pwm_open(channel_id_cw, &cfg_repeat);
// uapi_pwm_open(channel_id_ww, &cfg_repeat);
uapi_pwm_set_group(CONFIG_PWM_GROUP_ID, channel_ids, sizeof(channel_ids));
// uapi_pwm_start_group(CONFIG_PWM_GROUP_ID);
e_printf("PWM基础时钟频率: %dHz, 目标时钟频率: %dHz, 周期计数: %d\r\n", frequency, PWM_FREQUENCY, pwm_period_cnt);
(void)uapi_pwm_set_group(CONFIG_PWM_GROUP_ID, channel_ids, sizeof(channel_ids));
(void)uapi_pwm_start_group(CONFIG_PWM_GROUP_ID);
e_printf("PWM基础时钟频率(固定): %uHz, 目标时钟频率: %dHz, 周期计数: %u\r\n", base_clk, PWM_FREQUENCY, pwm_period_cnt);
}
extern int start_hilink_ble_net_config(int32_t net_cfg_time_s);
void start_net_config(void) {
e_printf("进入配网状态,上电次数:%d, is_new_device:%d\r\n", g_device_control.power_on_cnt, !g_device_control.is_net_configured);
g_device_control.power_on_cnt = 0;//
e_printf("进入配网状态,上电次数:%d, is_new设备:%d\r\n", g_device_control.power_on_cnt, !g_device_control.is_net_configured);
// 按新规则上电计数仅能在上电超过5秒后自然清零
// 配网流程不再主动清除 power_on_cnt。
int ret = start_hilink_ble_net_config(NET_CFG_TOTAL_TIMEOUT/1000);
if (ret) {
// FIXME: 这里简单恢复之前等待配网的灯效即可
@@ -1075,23 +1150,16 @@ static int handle_network_status(void)
// 检查是否需要进入配网状态或恢复出厂设置
if (g_device_control.power_on_cnt >= NET_CFG_ENTRY_CNT) {
// 任何状态下达到阈值都强制解绑并恢复出厂设置
e_printf("快速上电达到 %d 次,强制恢复出厂设置(解绑)\r\n", NET_CFG_ENTRY_CNT);
g_reset_factory_flag = true;
g_device_control.power_on_cnt = 0;
if (g_device_control.is_net_configured) {
// 已出厂设备上下电6次后恢复出厂设置并重启
e_printf("已到达出厂设备上电次,执行恢复出厂设置\r\n");
g_reset_factory_flag = true;
extern int HILINK_RestoreFactorySettings(void);
while (!hf_hilink_main_is_runing()) {
msleep(10);
}
HILINK_RestoreFactorySettings();
return start_net_cfg;
} else {
// 未出厂设备:进入配网模式
e_printf("未出厂设备上电6次进入配网模式\r\n");
start_net_config();
start_net_cfg = 1;
extern int HILINK_RestoreFactorySettings(void);
while (!hf_hilink_main_is_runing()) {
msleep(10);
}
HILINK_RestoreFactorySettings();
return start_net_cfg;
} else if (!g_device_control.is_net_configured) {
// 未出厂设备直接进入配网
start_net_config();
@@ -1274,18 +1342,17 @@ void handle_device_offline(void)
// 处理设备解绑
void handle_device_unbind(void)
{
e_printf("设备被解绑,重置配网状态\r\n");
e_printf("设备被解绑,重置配网状态:%d\r\n", g_device_control.is_bound);
if (g_device_control.is_bound) {
g_device_control.is_bound = false;
g_device_control.power_on_cnt = 0; // 重置上电计数
// stop_spotlight_main_task();
device_control_t tmp = DEFAULT_DEVICE_DATA;//恢复默认
// if (!g_reset_factory_flag) {
// tmp.is_net_configured = g_device_control.is_net_configured;
// }
g_device_control = tmp;
save_device_data();
}
save_device_data();
}
uint64_t startup_time = 0;
@@ -1312,10 +1379,27 @@ static void *spotlight_main_task(const char *arg)
unused(arg);
e_printf("[spotlight_main_task] Task started\r\n");
uint32_t current_time = 0; //uapi_systick_get_ms();
// 自愈在HiLink Main运行后做一次绑定状态一致性检查
static bool s_bind_consistency_checked = false;
while (spotlight_main_task_running) {
// 检查配网超时
check_net_cfg_timeout();
check_net_cfg_power_on_keep_time();
if (!s_bind_consistency_checked && hf_hilink_main_is_runing()) {
// 仅执行一次检查应用层与HiLink SDK的绑定状态是否一致
extern int HILINK_IsRegister(void);
extern int HILINK_RestoreFactorySettings(void);
int reg = HILINK_IsRegister();
bool sdk_bound = (reg != 0);
bool app_bound = g_device_control.is_bound;
if (sdk_bound != app_bound) {
e_printf("[consistency] app_bound=%d, sdk_bound=%d, do factory reset\r\n", app_bound, sdk_bound);
// 触发完整流程由HiLink框架内部清理持久化并按注册的重启回调执行
HILINK_RestoreFactorySettings();
}
s_bind_consistency_checked = true;
}
osal_msleep(20);
}
@@ -1402,6 +1486,10 @@ int spotlight_main(void) {
read_device_data();
// 初始化GPIO并将灯关闭
gpio_init(SWITCH_PIN);
// 初始化 PWM 更新标志引脚GPIO10为普通GPIO输出默认拉低
gpio_init(PWM_UPDATE_FLAG_PIN);
g_pwm_update_flag_inited = true;
// 初始化PWM系统
pwm_sys_init();
pwm_init(BRIGHTNESS_PIN, CCT_PIN);
@@ -1476,12 +1564,23 @@ static void set_light2net_cfg_done(void)
g_device_control.brightness_local = BRIGHTNESS_REMOTE2LOCAL(NET_CFG_DEFAULT_BRIGHTNESS);
g_device_control.cct_local = CCT_REMOTE2LOCAL(NET_CFG_DEFAULT_CCT);
g_device_control.fade_time = NET_CFG_DEFAULT_FADE_TIME;
// 🔧 关键修复配网完成后直接使用目标色温不从PWM反推
// PWM反推逻辑是错误的因为呼吸灯PWM状态不代表用户期望的色温
fade_ctx.current_brightness = g_device_control.brightness_local;
fade_ctx.current_cct = g_device_control.cct_local;
fade_ctx.current_cct = g_device_control.cct_local; // 直接使用目标色温
e_printf("[set_light2net_cfg_done] 配网完成,同步色温: brightness=%d, cct=%d\n",
fade_ctx.current_brightness, fade_ctx.current_cct);
// 设置目标值(与当前值一致,避免不必要的渐变)
fade_ctx.target_brightness = g_device_control.brightness_local;
fade_ctx.target_cct = g_device_control.cct_local;
calculate_pwm_duty(&g_device_control);
update_pwm_output(g_device_control.on, g_device_control.duty_cw, g_device_control.duty_ww);
e_printf("[set_light2net_cfg_done] 配网完成状态同步: current_cct=%d, target_cct=%d\n",
fade_ctx.current_cct, fade_ctx.target_cct);
}
// 启动呼吸灯
@@ -1502,4 +1601,3 @@ lightMode_e convert_mode_for_report(lightMode_e current_mode)
// 否则上报基础模式
return LIGHT_MODE_GET_BASE(current_mode);
}

View File

@@ -130,7 +130,7 @@ target = {
'-:hal_systick', 'partition', 'partition_ws63','pmp_cfg_ws63', 'nonos_malloc', 'nonos_malloc_port',
'update_common', 'update_local', 'update_local_ws63', 'lzma_22.00', 'update_storage', 'update_common_ws63', 'update_ab_ws63', 'factory_ws63',
'efuse', 'hal_efuse_v151', 'efuse_port', 'soc_port',
'pwm', 'hal_pwm', 'pwm_port', # ADD
# 'pwm', 'hal_pwm', 'pwm_port', # ADD
],
'ram_component_set': ['uart', "time_set", "cpu", "pinctrl", "watchdog", "security_unified",'pmp_set'],
'os': 'non-os',
@@ -288,7 +288,7 @@ target = {
},
'ws63-liteos-app-iot': {
'base_target_name': 'target_ws63_app_rom_template',
'liteos_kconfig': 'ws63_iot', #ekko add for remove indie upgrade
'liteos_kconfig': 'ws63_iot', # EKKO add for remove indie upgrade
'os': 'liteos',
'defines': [
"USE_CMSIS_OS",
@@ -336,7 +336,7 @@ target = {
"CONFIG_USE_CUSTOMER_SVC_INFO", #EKKO ADD
"CONFIG_DHCPS_GW",
"_HSF_",
"CONFIG_PWM_USING_V150", #EKKO ADD
"CONFIG_PWM_PRELOAD", # Enable PWM preload (period-boundary update)
# "ENABLE_BLE_SCAN" #open ble scan
],
'ram_component': [
@@ -379,11 +379,10 @@ target = {
'cjson',
'xo_trim_port',
'hilink',
#'app_addr_map', #ekko remove for indie upgrade
'hilinkdevicesdk', #ekko add for remove indie upgrade
'hilinkota', #ekko add for remove indie upgrade
'hilinkbtsdk', #ekko add for remove indie upgrade
#'hilinkquickcfg', #ekko add for remove indie upgrade
# 'app_addr_map', # ekko remove for remove indie upgrade
'hilinkdevicesdk', # ekko add for remove indie upgrade
'hilinkota', # ekko add for remove indie upgrade
'hilinkbtsdk', # ekko add for remove indie upgrade
'huks_sdk',
'deviceauth',
'little_fs', 'littlefs_adapt_ws63',

View File

@@ -0,0 +1,361 @@
# 串口控制协议
## 目录
- [1. 指令格式](#1-指令格式)
- [1.1 串口参数](#11-串口参数)
- [2. JSON 结构](#2-json-结构)
- [2.1 字段说明](#21-字段说明)
- [2.2 发送提示](#22-发送提示)
- [2.3 查询命令AT+QUERY](#23-查询命令atquery)
- [3. 支持服务与字段(详解)](#3-支持服务与字段详解)
- [3.1 switch开关](#31-switch开关)
- [3.2 brightness亮度](#32-brightness亮度)
- [3.3 cct色温](#33-cct色温)
- [3.4 lightMode场景模式](#34-lightmode场景模式)
- [3.5 progressSwitch渐变时长](#35-progressswitch渐变时长)
- [3.6 colourMode色温模式](#36-colourmode色温模式)
- [4. 约束](#4-约束)
- [5. 错误码](#5-错误码)
- [5.1 NotControllable112用法建议](#51-notcontrollable112用法建议)
- [6. 时序图示Mermaid](#6-时序图示mermaid)
- [7. 示例](#7-示例)
## 1. 指令格式
- 下发:`AT+CTRL={JSON}\r\n`
- 查询:`AT+QUERY={JSON}\r\n`
- 回执:`OK,<id>\r\n``ERROR,<code>[,<message>]\r\n`
- 异步结果:`AT+RESP={JSON}\r\n`
- 规则:
- 单行 JSON内部换行请写成 `\n`
- 行结束统一为 CRLF `\r\n`Windows 兼容)
- 接收端应兼容仅 LF `\n`
### 1.1 串口参数
- 波特率:`9600`
- 数据位:`8`
- 校验位:`None`
- 停止位:`1`
- 流控:`None`
- 编码:`UTF-8`(无 BOM
- 建议:行缓冲读取,按 CRLF `\r\n` 作为帧结束。
## 2. JSON 结构
- 单服务控制(必须携带 id
- 下发:`{"id": <u32>, "sid":"<serviceId>", "data": {<fields>}}`
- 返回:`{"id": <u32>, "sid":"<serviceId>", "data": {<fields>}, "error": <u32>, "message": "<可选>"}``error=0` 表示成功)
### 2.1 字段说明
- `id`number, u32>0必填
- 会话标识,用于将异步结果与请求一一对应、便于定位问题。
- 由发起方分配与维护,建议使用一个全局自增计数(溢出后回绕到 1
- 受控方不做去重与顺序检查,仅在 RESP 中原样回显。
- `sid`string必填
- 服务功能 ID。合法值`switch``brightness``cct``lightMode``progressSwitch``colourMode`
- 区分大小写;不在集合内的值视为不支持服务。
- `data`object必填
- 具体服务的数据负载。各服务的字段与范围见“支持服务与字段(详解)”。
- 缺字段或类型不符为业务错误RESP `error=105``message` 可携带原因)。
- `error`number, u32RESP 必带)
- 业务执行结果码:`0` 成功;非 0 失败。常见值见“错误码”。
- `message`string可选RESP 可带)
- 人类可读的错误或提示信息,用于调试。
### 2.2 发送提示
- 多次控制:请按需多次发送 `AT+CTRL=...`,每次使用不同的 `id`
- ACK 超时与重发建议:
- 发送后等待 `OK,<id>`,超时建议 `200500 ms`
- 未收到 ACK 则按原 `id` 与完全相同的 JSON 重发,重发 `23 次`,间隔 `200300 ms`
- 注意:设备不做去重,重复下发可能重复执行;建议尽量使用幂等设置(如重复设置相同亮度)。
- 结果等待与超时:
- 收到 `OK,<id>` 后等待 `AT+RESP`,建议超时阈值 `12 s`(视业务而定)。
- 超时未收到可按失败处理并在上层重试(使用新的 `id`)。
- 换行约定:所有发送与回执均以 CRLF `\r\n` 结束;示例为单行展示,实际发送需包含 CRLF。
### 2.3 查询命令AT+QUERY
- 作用:查询指定 `sid` 的当前状态(只读,不改变设备状态)。
- 下发:`AT+QUERY={"id": <u32>, "sid":"<serviceId>"}`\r\n
- 回执:`OK,<id>`\r\n
- 异步结果:`AT+RESP={"id": <u32>, "sid":"<serviceId>", "data": {<current-fields>}, "error": <u32>, "message": "<可选>"}`\r\n
- 说明:
- `data` 与“支持服务与字段(详解)”中各 `sid` 的状态格式一致(等同于 Get 接口的输出)。
-`sid` 不支持,返回 `ERROR,104,UnsupportedSid`(协议级错误)。
## 3. 支持服务与字段(详解)
### 3.1 switch开关
- 功能:控制灯具开/关。
- 下发:`{"id":<u32>,"sid":"switch","data":{"on":0|1}}`
- 返回:`{"id":<u32>,"sid":"switch","data":{"on":0|1},"error":<u32>,"message":"<可选>"}`
- 参数:`on` 为 int取值 `0` 关、`1` 开。
- 行为当处于“离家模式”mode=7收到 `on=1` 会先退出离家模式再开灯。
- 示例:
- 开灯
- 【发起方发送】`AT+CTRL={"id":101,"sid":"switch","data":{"on":1}}`
- 【受控方返回】`OK,101``AT+RESP={"id":101,"sid":"switch","data":{"on":1},"error":0}`
- 关灯
- 【发起方发送】`AT+CTRL={"id":102,"sid":"switch","data":{"on":0}}`
- 【受控方返回】`OK,102``AT+RESP={"id":102,"sid":"switch","data":{"on":0},"error":0}`
### 3.2 brightness亮度
- 功能:设置亮度百分比。
- 下发:`{"id":<u32>,"sid":"brightness","data":{"brightness":0..100}}`
- 返回:`{"id":<u32>,"sid":"brightness","data":{"brightness":<0..100>},"error":<u32>,"message":"<可选>"}`
- 参数:`brightness` 为 int范围 `0..100`;超出范围按边界钳制。
- 示例:
- 设为 60%`AT+CTRL={"id":201,"sid":"brightness","data":{"brightness":60}}`
- 最小值:`AT+CTRL={"id":202,"sid":"brightness","data":{"brightness":0}}`
- 最大值:`AT+CTRL={"id":203,"sid":"brightness","data":{"brightness":100}}`
### 3.3 cct色温
- 功能:设置色温(单位 K
- 下发:`{"id":<u32>,"sid":"cct","data":{"colorTemperature":2700..6000}}`
- 返回:`{"id":<u32>,"sid":"cct","data":{"colorTemperature":<2700..6000>},"error":<u32>,"message":"<可选>"}`
- 参数:`colorTemperature` 为 int范围 `2700..6000`;超出范围按边界钳制。
- 示例:
- 暖色 3000K`AT+CTRL={"id":301,"sid":"cct","data":{"colorTemperature":3000}}`
- 中性 4000K`AT+CTRL={"id":302,"sid":"cct","data":{"colorTemperature":4000}}`
- 冷色 6000K`AT+CTRL={"id":303,"sid":"cct","data":{"colorTemperature":6000}}`
### 3.4 lightMode场景模式
- 功能:按预设场景设置亮度与色温,或进入离家模式。
- 下发:`{"id":<u32>,"sid":"lightMode","data":{"mode":0..7}}`
- 返回:`{"id":<u32>,"sid":"lightMode","data":{"mode":<0..7>},"error":<u32>,"message":"<可选>"}`
- 参数:`mode` 为 int
- 0 customer自定义模式
- 1 relax (休闲模式)(约 50%4000K
- 2 movie观影模式约 10%3000K
- 3 dining用餐模式约 100%4000K
- 4 home回家模式约 80%3500K
- 5 winter冬天模式约 100%2700K
- 6 summer夏天模式约 100%6000K
- 7 leave离家模式直接关灯
- 示例:
- 观影模式:`AT+CTRL={"id":401,"sid":"lightMode","data":{"mode":2}}`
- 离家模式:`AT+CTRL={"id":402,"sid":"lightMode","data":{"mode":7}}`
- 回家模式:`AT+CTRL={"id":403,"sid":"lightMode","data":{"mode":4}}`
### 3.5 progressSwitch渐变时长
- 功能:设置亮度/色温变化的平滑过渡时间(秒)。
- 下发:`{"id":<u32>,"sid":"progressSwitch","data":{"fadeTime":0..30}}`
- 返回:`{"id":<u32>,"sid":"progressSwitch","data":{"fadeTime":<0..30>},"error":<u32>,"message":"<可选>"}`
- 参数:`fadeTime` 为 int范围 `0..30` 秒;`0` 表示尽快变化。
- 示例:
- 设为 5 秒:`AT+CTRL={"id":501,"sid":"progressSwitch","data":{"fadeTime":5}}`
- 立即变化:`AT+CTRL={"id":502,"sid":"progressSwitch","data":{"fadeTime":0}}`
### 3.6 colourMode色温模式
- 功能:设置色温工作模式。
- 下发:`{"id":<u32>,"sid":"colourMode","data":{"mode":0|1}}`
- 返回:`{"id":<u32>,"sid":"colourMode","data":{"mode":0|1},"error":<u32>,"message":"<可选>"}`
- 参数:`mode` 为 int`0` 单色温、`1` 双色温。
- 行为单色温0设备固定到预设色温6000K双色温1使用/保持当前色温。
- 示例:
- 单色温:`AT+CTRL={"id":601,"sid":"colourMode","data":{"mode":0}}`
- 双色温:`AT+CTRL={"id":602,"sid":"colourMode","data":{"mode":1}}`
## 4. 约束
- `{JSON}` 最大长度:≤ 1024 字节(不含前缀与行尾)。
- 单行承载,不允许真实换行;内部换行使用 `\n` 字符串。
- 行结束采用 CRLF `\r\n`;接收端应兼容仅 LF `\n`
- `id` 必须存在且为 u32>0缺失/类型不符即返回 `ERROR,105,TypeError`
## 5. 错误码
- `100` BadPrefix未以 `AT+CTRL=` 开头)
- `101` MalformedJSONJSON 语法错误)
- `102` EmptyJSON空 JSON
- `103` PayloadTooLong负载超长
- `104` UnsupportedSid不支持的 `sid`
- `105` TypeError字段缺失或类型不符
- `106` Busy设备忙
- `107` ApplyTimeout内部执行超时
- `112` NotControllable非受控状态设备处于不可被控制的模式例如升级/维护/工厂测试/安全锁定等)
- `111` RateLimited限频
### 5.1 NotControllable112用法建议
- 触发场景(示例):
- 升级进行中OTA/独立升级/固件校验)
- 维护/自检/烧录/工厂测试模式
- 安全锁定/童锁/设备被上级网关临时锁定
- 热保护/过温降额/低电量保护等安全策略生效
- 正在配网/关键迁移流程,不允许外部控制
- 返回规范:
- 收到控制后先回 ACK`OK,<id>`
- 业务结束后返回:`AT+RESP={"id":<id>,"sid":"<sid>","data":{...},"error":112,"message":"<原因>"}`
- `message` 建议取值(便于前端统一展示与日志聚合):
- `upgrading`(升级中)
- `maintenance`(维护/自检)
- `factory_test`(工厂测试)
- `locked`(安全/家长锁定)
- `thermal_protect`(热保护)
- `low_power`(低电量保护)
- `net_config`(正在配网)
- 查询建议:
- `AT+QUERY` 通常仍应返回当前状态并 `error=0`;若状态不可读,再返回 `error=112` 与合适的 `message`
## 6. 时序图示Mermaid
1) 多次控制(均成功,异步返回 `error=0`
```mermaid
sequenceDiagram
participant 发起方
participant 受控方
发起方->>受控方: AT+CTRL={"id":7001,"sid":"switch","data":{"on":1}}\r\n
受控方-->>发起方: OK,7001\r\n
note over 受控方: 执行开关控制
受控方-->>发起方: AT+RESP={"id":7001,"sid":"switch","data":{"on":1},"error":0}\r\n
发起方->>受控方: AT+CTRL={"id":7201,"sid":"lightMode","data":{"mode":2}}\r\n
受控方-->>发起方: OK,7201\r\n
note over 受控方: 执行场景模式切换
受控方-->>发起方: AT+RESP={"id":7201,"sid":"lightMode","data":{"mode":2},"error":0}\r\n
发起方->>受控方: AT+CTRL={"id":7301,"sid":"progressSwitch","data":{"fadeTime":5}}\r\n
受控方-->>发起方: OK,7301\r\n
note over 受控方: 设置渐变时长
受控方-->>发起方: AT+RESP={"id":7301,"sid":"progressSwitch","data":{"fadeTime":5},"error":0}\r\n
```
2) 协议级错误JSON 语法错误,直接 `ERROR`,不产生 RESP
```mermaid
sequenceDiagram
participant 发起方
participant 受控方
发起方->>受控方: AT+CTRL={"sid":"switch","data":{"on":1}
受控方-->>发起方: ERROR,101,MalformedJSON
note over 受控方: 帧被丢弃,无 AT+RESP
```
3) 业务错误(收到并受理,但参数类型错误)
```mermaid
sequenceDiagram
participant 发起方
participant 受控方
发起方->>受控方: AT+CTRL={"id":7501,"sid":"brightness","data":{"brightness":"sixty"}}\r\n
受控方-->>发起方: OK,7501\r\n
note over 受控方: 参数校验失败(类型错误)
受控方-->>发起方: AT+RESP={"id":7501,"sid":"brightness","data":{"brightness":50},"error":105,"message":"TypeError"}\r\n
```
4) 查询状态OK + RESP
```mermaid
sequenceDiagram
participant 发起方
participant 受控方
发起方->>受控方: AT+QUERY={"id":7101,"sid":"brightness"}\r\n
受控方-->>发起方: OK,7101\r\n
note over 受控方: 读取并打包当前亮度状态
受控方-->>发起方: AT+RESP={"id":7101,"sid":"brightness","data":{"brightness":60},"error":0}\r\n
```
## 7. 示例
1) 开关:打开
【发起方发送】
```
AT+CTRL={"id":7001,"sid":"switch","data":{"on":1}}
```
【受控方返回】
```
OK,7001
```
【受控方返回】
```
AT+RESP={"id":7001,"sid":"switch","data":{"on":1},"error":0}
```
2) 查询当前亮度
【发起方发送】
```
AT+QUERY={"id":7101,"sid":"brightness"}
```
【受控方返回】
```
OK,7101
```
【受控方返回】
```
AT+RESP={"id":7101,"sid":"brightness","data":{"brightness":60},"error":0}
```
3) 场景模式movie
【发起方发送】
```
AT+CTRL={"id":7201,"sid":"lightMode","data":{"mode":2}}
```
【受控方返回】
```
OK,7201
```
【受控方返回】
```
AT+RESP={"id":7201,"sid":"lightMode","data":{"mode":2},"error":0}
```
4) 渐变时长5 秒
【发起方发送】
```
AT+CTRL={"id":7301,"sid":"progressSwitch","data":{"fadeTime":5}}
```
【受控方返回】
```
OK,7301
```
【受控方返回】
```
AT+RESP={"id":7301,"sid":"progressSwitch","data":{"fadeTime":5},"error":0}
```
5) 色温模式:单色温
【发起方发送】
```
AT+CTRL={"id":7401,"sid":"colourMode","data":{"mode":0}}
```
【受控方返回】
```
OK,7401
```
【受控方返回】
```
AT+RESP={"id":7401,"sid":"colourMode","data":{"mode":0},"error":0}
```
6) 业务失败(类型错误)
【发起方发送】
```
AT+CTRL={"id":7501,"sid":"brightness","data":{"brightness":"sixty"}}
```
【受控方返回】
```
OK,7501
```
【受控方返回】
```
AT+RESP={"id":7501,"sid":"brightness","data":{"brightness":50},"error":105,"message":"TypeError"}
```
7) 非法 JSON协议级错误
【发起方发送】
```
AT+CTRL={"sid":"switch","data":{"on":1}
```
【受控方返回】
```
ERROR,101,MalformedJSON
```
8) 不支持的服务(协议级错误)
【发起方发送】
```
AT+CTRL={"id":7601,"sid":"foo","data":{"bar":1}}
```
【受控方返回】
```
ERROR,104,UnsupportedSid
```

BIN
docs/serial_control_protocol.pdf Executable file

Binary file not shown.

View File

@@ -124,6 +124,7 @@ def all_build():
os.makedirs(temp_dir)
build_update_package(os.path.join(info.upg_output, "update.fwpkg"), temp_dir, info.upg_output)
print("gen package.zip")
file_dir = os.path.dirname(os.path.abspath(__file__))
os.system("cd " + file_dir + " && python3 package.py")

Binary file not shown.

440
ws63_check_json_analysis.md Normal file
View File

@@ -0,0 +1,440 @@
# WS63 Check.json 文件分析完整文档
## 概述
本文档详细分析了 `ws63-liteos-hilink-check.json` 文件的生成机制、校验内容和对应关系。该文件是WS63项目独立升级功能的核心组件用于验证HiLink固件与App-IoT固件之间的接口兼容性。
## 1. JSON文件基本信息
**文件路径**: `./output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink-check.json`
**文件内容结构**:
```json
{
"ver": 1,
"app": "3A23309AAF0B692188B9C56755C084522354143382C5309A2D3258C3E6C834A500B53089C7A65C43392D51DFEF32BBC125B613C6B37656F59E142F0D713AD6115955749E72C53C3BA8CD96D2C69CB534231411DEAE5C4F1782DA2760BB04E0199813CAC4CBA339BEBF3D734FBF0955235DD160C0387CA4AD28BFC8766508F627D0B46BE48319EAC371921D27D806C9F3464BF4DFBAE09ADB95232386B021EEFBD3FAD2C6B405A1E9C13BB0420CAB5EF02D3D543090162EB8E51C2E1D10F3EE4EE5FE71A0BA8DF1E95E4A36694908DDB16E08F54B606E13CACB530F71320CC9DF6C3DCB70DC6E54BDA7B3C1720E7ECCB54E88950CFC3D4D700224AB40D9E568A89571387053E9BB3D77889DAA102DB1FC4CE38751F2E6A0BD27B0EC51B8EA015CE0E475ADF3E3CDB49580E84E228803A691F39D958D3F28B0E4D53C670A27B5AB436D11F83A8AAEB6538628D32F674C398B6CF1C295BAB9499E19EA993B11D07524E025EF6FF88F42331C3BE36689468DAF74C6B811187E2D7953251B9228B8A8C32422F97C0122332BB295C110F36291A845CD2FA2D6BC34E37B7CBE23083067AE8AF9346DEEEFCC168D2D4658A94AB5049858C11E8BE4672F492DE9B4046E4B0EEEA5E3D6FE8809743B2B6111718F2038742D7C48EE81BBD5D485373D34C70868622D3233179ADED3E8C5026A2217F6938E85AA7A6754CE869A8127656D364C00F05E95FE7738AD05E79CACA11368FAE6717C71C469FC934FF221953E9F7CA301D52266EA04E8184A6D5AF66621F90139D0642D0350FA3F28ED49CF23AC20D03710992FBE7E3DBCEBD737385AE906D8D885A6118F2416A45FFA93EC3483EDADE5864419C451689382274A6DEC6FD6734D3975199C9FFB561202FEAB34F195D595E791C43315669E430FB9E7288EF87BC4498A4DF1A93918FA7C7805F7131CCF092F762F28C0A502EC955661D19C624CD0B61E88F81F9D57108A971D268273CEABEE8013FF69661C1A29BBD995111C6A64314013FBA647F77B736AE8E330F3D7157E6C3774AFBCC38D643C7108363127CB03B902C6D32C202A73EE7099A65902A14959BA60D70D055A89A6C3E489489F355E418A02EF8A543D8DF826C59AD8FABD1564AF4FEE6224FCA67974F6F63E17D3036A50120E5449669A4D792E919F88C4BBEEC64E542216D35ED1DE5A21434FC521776D37F5C50257B639AA3F47D1CC1888A19961CD6F5D7C9BEF30D5EACE785DB24268E360581533F98458376DD1E770BF3612B6D1F9C62395F48BEDE23AE1514E24C79360CE44291140AFEC5576503E6EBBC1D453DC9ED772F6AA506112D26C5B806EC4E0EAEC6A98F6F1683AC86494FA565B7CE2A0540F45AC6A8A556CDEE96B315EF45ED456EF46F1E5C234ABF92B121CCE688D2163BDF57FB6098B4742FBC504AC5E03230ADF90428D53F024F9C20964CA8AEA8A9042D1CEF0A2BA9C88A011C1004A9B8F7F9EBEDA365056947E9426D868829904D70DA74B7276D93829D151333F3506E83032A8CF68630BD5D0A39237FDF0BADD131AB77B6092987F07139588081D140EB2A0CA25BF9267FBC809457528A7C5DFA1729552A8D76B3362B9915E84795CD770EB84A3AE73DE78F04D37E8FC2F53E2F72853D57F170FC2551F7CBBB9B63D119AC4775120418F3865792FEFAAFF69038BFD3AA3C2C476B26DFB2B48F3B3904203C394782FE1C40F718001C31504539827DDC80900966524DCE965006F86EBDA9C46226BC1DA2387E2AE9565DFEE621F0DA936298371D1AD83860B4E607BB473E4103ADF872D1A3CA9EB171CF73077B4AFC2D7FC3737FAE5337290D8116CE0826F347C621502BA313EEF20F1F67D00A7EB1BB555DE7B0FCDD05C747D7615136B35079DA4BF9A6EED5F0C5448B8364FB03476275BD823C8C7F3CBCC1D0BD41C5B49538D65C00C85F9C8D50201EDA6266D7A2A375141F7F5E69E1AD06932C3A6C044EAE908D43657EA22C5FA797FE8D04592541477B362753A05",
"hilink": "BEC62B3A1B90717BABD8C9AD15D3BE1560977111A022C59CEF399A97E8B1D1D2D981EE972F4C881D0D3D30D8165FA46BEB1F07B4B8F1AECEA4335DFC7388E1234641EDD1AF31DFEDB616EBF0A9900778C03C4467D3CBCD6540F51D66CBE0FF998506BA06448D8BEB6CDA8E9E79BE4C8BF15449A4DEFEB22F342B09F03683E17D699DA475D08A2F079EDA59B99A96CD0E468B089224120EE96B86A8BB4B250B35E0E8B8A1E77FE8A13F50CF8B018FA5A91475DDA932141DAF1F5C4D9C5D7F8DC8E3CB582D0DB0EA8823396BF6BC0A30D68B968BEE77AB6014036385A68BB35701D274404A9BFA24348E7321F63FEA98B7"
}
```
## 2. 文件生成机制
### 2.1 生成触发条件
**触发时机**: 在构建过程的 `build_post` 阶段(构建完成后)
**配置条件**: 编译配置中必须包含 `CONFIG_SUPPORT_HILINK_INDIE_UPGRADE`
- 当前项目状态:该配置**已被注释**config.py:334行
- 因此只有HiLink固件生成了check.jsonApp-IoT固件未生成
### 2.2 生成调用链
```
构建系统 (build.py)
build_post 钩子
entry.py:153 → dump_indie_upg_check_file()
indie_upgrade_utils.py:324 → 生成JSON文件
```
### 2.3 核心生成文件
**主要脚本文件**:
- `build/config/target_config/ws63/script/entry.py:153` - 构建钩子调用点
- `build/script/utils/indie_upgrade_utils.py:324` - JSON文件生成函数
- `build/script/utils/indie_upgrade_utils.py:292-320` - 接口校验码生成核心逻辑
## 3. 校验内容详解
### 3.1 JSON字段含义
| 字段 | 含义 | 数据格式 |
|------|------|----------|
| `ver` | 校验文件版本号 | 整数当前为1 |
| `app` | App侧接口校验码串联 | CRC32十六进制字符串 |
| `hilink` | HiLink侧接口校验码串联 | CRC32十六进制字符串 |
### 3.2 校验码生成原理
1. **接口函数扫描**: 通过正则表达式扫描源文件中的函数原型
```python
rgl = r"((?:const )?(?:unsigned )?(?:struct )?(?:enum )?\w+[ \*]*)" + \
r"(\w+)[\s\n]*" + \
r"(\()" + \
r"([\s\*,\w\[\]]*?)" + \
r"(\))" + \
r"\s*\{"
```
2. **原型标准化**: 去除参数名,保留参数类型,生成标准化函数原型字符串
3. **CRC32计算**: 对每个函数原型字符串计算CRC32校验码
```python
checksum = "{:08X}".format(zlib.crc32(prototype.encode("utf8")))
```
4. **校验码串联**: 将所有接口的8位十六进制CRC32码按顺序串联
### 3.3 校验码对应的编译文件
| JSON字段 | 编译目标 | 二进制文件 | 签名文件 |
|----------|----------|------------|----------|
| `"app"` | `ws63-liteos-app-iot` | `output/ws63/acore/ws63-liteos-app-iot/ws63-liteos-app-iot.bin` | `ws63-liteos-app-iot-sign.bin` |
| `"hilink"` | `ws63-liteos-hilink` | `output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink.bin` | `ws63-liteos-hilink-sign.bin` |
## 4. 接口映射文件体系
### 4.1 App侧接口配置
**源文件路径**: `application/samples/wifi/hilink_indie_upgrade/address_mapping/hilinksdk/app_uapi/`
**主要接口文件**:
- `uapi_hilink_kv_adapter.c` - KV存储适配器接口
- `uapi_hilink_mem_adapter.c` - 内存管理适配器接口
- `uapi_hilink_socket_adapter.c` - Socket网络适配器接口
- `uapi_hilink_stdio_adapter.c` - 标准IO适配器接口
- `uapi_hilink_thread_adapter.c` - 线程管理适配器接口
- `uapi_hilink_time_adapter.c` - 时间管理适配器接口
- `uapi_hilink_network_adapter.c` - 网络适配器接口
- `uapi_hilink_device.c` - 设备管理接口
- `uapi_hichain.c` - 设备认证接口
- `uapi_hilink_ble_adapter.c` - BLE适配器接口
- 等共52个接口适配文件
**生成的映射表**: `application/samples/wifi/hilink_indie_upgrade/address_mapping/application/app_function_mapping.c`
### 4.2 HiLink侧接口配置
**源文件路径**: `application/samples/wifi/hilink_indie_upgrade/address_mapping/application/hilink_uapi/`
**主要接口文件**:
- `uapi_hilink.c` - HiLink核心接口
- `uapi_hilink_log_manage.c` - 日志管理接口
- `uapi_hilink_device_ext.c` - 设备扩展接口
- `uapi_ble_cfg_net_api.c` - BLE配网接口
- `uapi_hilink_bt_function.c` - 蓝牙功能接口
- `uapi_hilink_network_adapter.c` - 网络适配器接口
- `uapi_hilink_socket_adapter.c` - Socket适配器接口
- `uapi_hilink_custom.c` - 自定义功能接口
- `uapi_hilink_sle_api.c` - SLE接口
- `uapi_hilink_quick_netcfg_api.c` - 快速配网接口
**生成的映射表**: `application/samples/wifi/hilink_indie_upgrade/address_mapping/hilinksdk/hilink_function_mapping.c`
### 4.3 接口头文件映射
**App侧头文件路径**:
- `application/samples/wifi/ohos_connect/hilink_adapt/adapter/include/` - 适配器接口声明
- `application/samples/wifi/ohos_connect/hilink_adapt/product/` - 产品相关接口
- `application/samples/wifi/ohos_connect/hilink_adapt/include/` - 通用接口声明
- `open_source/deviceauth/interfaces/innerkits/deviceauth_lite/` - 设备认证接口
- `kernel/liteos/liteos_v208.5.0/Huawei_LiteOS/open_source/CMSIS/CMSIS/RTOS2/Include/` - RTOS接口
- `open_source/cjson/cjson/` - JSON库接口
- `open_source/mbedtls/mbedtls_v3.1.0/include/` - 加密库接口
**HiLink侧头文件路径**:
- `application/samples/wifi/ohos_connect/hilink_adapt/include/` - HiLink核心接口声明
- `application/samples/wifi/ohos_connect/hilink_adapt/product/` - 产品相关接口
- `application/samples/wifi/ohos_connect/hilink_adapt/adapter/include/` - 适配器接口声明
## 5. 校验机制详解
### 5.1 接口兼容性检查函数
**函数位置**: `build/script/utils/indie_upgrade_utils.py:353-370`
**检查逻辑**:
```python
def check_indie_upg_match(hilink_check_file, app_check_file):
# 读取两个check.json文件
f = open(hilink_check_file, "r", encoding="utf-8")
hilink_check = json.loads(f.read())
f.close()
f = open(app_check_file, "r", encoding="utf-8")
app_check = json.loads(f.read())
f.close()
# 版本号检查
if hilink_check["ver"] != 1 or app_check["ver"] != 1:
print("check file ver[h:%d,a:%d] not match" % (hilink_check["ver"], app_check["ver"]))
return False
# HiLink侧接口向后兼容检查
if not hilink_check["hilink"].startswith(app_check["hilink"]):
prt_not_match_info("hilink", hilink_check["hilink"], app_check["hilink"], True)
return False
# App侧接口向后兼容检查
if not app_check["app"].startswith(hilink_check["app"]):
prt_not_match_info("app", app_check["app"], hilink_check["app"], True)
return False
return True
```
### 5.2 校验失败信息输出
**函数位置**: `indie_upgrade_utils.py:330-350`
校验失败时会输出具体的不匹配接口信息:
- 显示接口序号和对应的CRC32校验码
- 帮助定位具体是哪个接口发生了变更
- 每个CRC32码长度为8个十六进制字符
### 5.3 升级包构建时的校验
**调用位置**: `build/config/target_config/ws63/build_ws63_update.py:74`
```python
if not check_indie_upg_match(info.hilink_check, info.app_iot_check):
print("indie upg hilink and app not match")
return False
```
在生成升级包时会自动进行兼容性检查,如果接口不兼容,构建过程会失败。
## 6. 独立升级工作流程
### 6.1 独立升级概述
独立升级允许在不更新App-IoT固件的情况下单独升级HiLink SDK固件。这种机制可以
- 快速修复HiLink SDK的bug
- 更新云端协议和功能
- 减少升级包大小和升级时间
- 降低升级风险
### 6.2 配置开启流程
根据 `indie_upg.md` 文档,开启独立升级需要:
1. **修改配置** (`build/config/target_config/ws63/config.py`):
- 删除 `'liteos_kconfig': 'ws63_iot'`
- 在 `'defines'` 中增加 `CONFIG_SUPPORT_HILINK_INDIE_UPGRADE`
- 在 `'ram_component'` 中增加 `'app_addr_map'`
- 在 `'ram_component'` 中删除 `'hilinkdevicesdk'`、`'hilinkota'`、`'hilinkbtsdk'`、`'hilinkquickcfg'`
2. **分别编译两个固件**:
```bash
# 先编译HiLink固件
python3 build.py -c ws63-liteos-hilink
# 再编译App-IoT固件
python3 build.py -c ws63-liteos-app-iot
# 生成升级包
python3 build/config/target_config/ws63/build_ws63_update.py --pkt=app_iot
```
3. **使用简化编译工具**:
```bash
# 完整编译
python3 indie_build.py all
# 仅编译SDK用于独立升级
python3 indie_build.py sdk
```
### 6.3 独立升级流程
1. **准备已认证的App-IoT固件**:
- `ws63-liteos-app-iot-sign.bin`
- `ws63-liteos-app-iot-check.json`
2. **更新HiLink SDK库文件**:
- 替换 `application/samples/wifi/libhilink/` 中的库文件
3. **执行独立编译**:
```bash
python3 indie_build.py sdk
```
4. **接口兼容性校验**:
- 自动比较新HiLink固件与已认证App-IoT固件的接口
- 如果接口不匹配,构建失败
- 校验通过后生成完整升级包
### 6.4 约束条件
1. **分区兼容性**: 非独立升级版本与独立升级版本不可交叉升级
2. **接口兼容性**: 模组App程序不变时HiLink接口不能发生变更
3. **版本号格式**:
- 非独立升级:`1.0.0`
- 独立升级:`1.0.0_14.2.0.304` (拼接SDK版本号)
## 7. 新增接口适配指南
### 7.1 接口调用机制
独立升级模式下模组App和HiLink分别编译成两个固件
- 各自维护接口地址表
- 通过查表机制获取对方接口函数指针
- 实现跨固件的函数调用
### 7.2 新增HiLink接口步骤
**示例接口原型**:
```c
retType TestFuncName(type1 param1, type2 param2, type3 param3);
```
1. **接口声明** (`application/samples/wifi/ohos_connect/hilink_adapt/include/hilink_test.h`):
```c
retType TestFuncName(type1 param1, type2 param2, type3 param3);
```
2. **查表适配实现** (`application/samples/wifi/hilink_indie_upgrade/address_mapping/application/hilink_uapi/uapi_hilink_test.c`):
```c
retType TestFuncName(type1 param1, type2 param2, type3 param3)
{
hilink_call3(HILINK_CALL_TEST_FUNC_NAME, TestFuncName, retType, type1, param1, type2, param2, type3, param3);
return (retType)0;
}
```
3. **添加到编译系统** (`application/samples/wifi/hilink_indie_upgrade/address_mapping/application/CMakeLists.txt`):
```cmake
uapi_hilink_test.c
```
4. **配置脚本识别** (`build/script/utils/indie_upgrade_utils.py`):
```python
default_config["hilink"]["src_file"] += ("uapi_hilink_test.c",)
default_config["hilink"]["mapping_header"]["application/samples/wifi/ohos_connect/hilink_adapt/include/"] += ("hilink_test.h",)
```
5. **白名单配置**(可选):
```python
default_config["hilink"]["white_list"] += ("TestFuncName",)
```
### 7.3 宏说明
**查表宏定义**:
- `hilink_call0` / `hilink_call0_ret_void` - 无参数函数
- `hilink_call1` / `hilink_call1_ret_void` - 1个参数函数
- `hilink_call2` / `hilink_call2_ret_void` - 2个参数函数
- `hilink_call3` / `hilink_call3_ret_void` - 3个参数函数
- `hilink_callx` / `hilink_callx_ret_void` - x个参数函数
**参数说明**:
1. 枚举值(如 `HILINK_CALL_TEST_FUNC_NAME`
2. 函数名
3. 返回值类型
4. 参数类型和参数名交替列出
## 8. 相关文件清单
### 8.1 核心脚本文件
| 文件路径 | 功能描述 |
|----------|----------|
| `build/script/utils/indie_upgrade_utils.py` | 独立升级工具核心逻辑 |
| `build/config/target_config/ws63/script/entry.py` | 构建钩子入口 |
| `build/config/target_config/ws63/build_ws63_update.py` | 升级包构建脚本 |
| `build/config/target_config/ws63/config.py` | 编译配置文件 |
| `indie_build.py` | 独立升级编译工具 |
| `package.py` | 升级包生成工具 |
### 8.2 接口映射相关文件
| 目录/文件 | 描述 |
|-----------|------|
| `application/samples/wifi/hilink_indie_upgrade/address_mapping/` | 接口映射根目录 |
| `application/samples/wifi/hilink_indie_upgrade/address_mapping/include/func_call_list.h` | 函数调用枚举定义(自动生成) |
| `application/samples/wifi/hilink_indie_upgrade/address_mapping/application/` | App侧接口适配 |
| `application/samples/wifi/hilink_indie_upgrade/address_mapping/hilinksdk/` | HiLink侧接口适配 |
| `application/samples/wifi/ohos_connect/hilink_adapt/` | 接口声明头文件目录 |
### 8.3 输出文件
| 文件路径 | 描述 |
|----------|------|
| `output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink-check.json` | HiLink固件接口校验文件 |
| `output/ws63/acore/ws63-liteos-app-iot/ws63-liteos-app-iot-check.json` | App-IoT固件接口校验文件需开启独立升级 |
| `output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink.bin` | HiLink固件二进制文件 |
| `output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink-sign.bin` | HiLink固件签名文件 |
| `output/ws63/acore/ws63-liteos-app-iot/ws63-liteos-app-iot.bin` | App-IoT固件二进制文件 |
| `output/ws63/acore/ws63-liteos-app-iot/ws63-liteos-app-iot-sign.bin` | App-IoT固件签名文件 |
| `output/ws63/fwpkg/ws63-liteos-app-iot/ws63-liteos-app-iot_all.fwpkg` | 完整烧录包 |
| `output/ws63/upgrade/update.fwpkg` | 升级包 |
| `output/ws63/ws63-liteos_all.zip` | 完整产物压缩包 |
## 9. 当前项目状态
### 9.1 独立升级配置状态
**当前状态**: 独立升级功能**已关闭**
**证据**:
- `config.py:334` 行:`"CONFIG_SUPPORT_HILINK_INDIE_UPGRADE"` 被注释
- `config.py:291` 行:`'liteos_kconfig': 'ws63_iot'` 仍然存在
- 只有HiLink固件生成了check.json文件
- App-IoT固件目录下没有对应的check.json文件
### 9.2 编译产物状态
**已生成的文件**:
- ✅ `ws63-liteos-hilink.bin` (278K)
- ✅ `ws63-liteos-hilink-sign.bin` (279K)
- ✅ `ws63-liteos-hilink-check.json` (3.4K)
- ✅ `ws63-liteos-app-iot.bin` (1.7M)
- ✅ `ws63-liteos-app-iot-sign.bin` (1.7M)
- ❌ `ws63-liteos-app-iot-check.json` (未生成,因为独立升级未开启)
## 10. 故障排查指南
### 10.1 check.json文件未生成
**可能原因**:
1. `CONFIG_SUPPORT_HILINK_INDIE_UPGRADE` 未在配置中启用
2. 构建过程中 `build_post` 钩子未执行
3. 接口映射文件生成失败
**排查步骤**:
1. 检查 `config.py` 中的配置项
2. 查看构建日志中是否有 "create indie upg mapping files succ" 消息
3. 检查接口源文件和头文件是否存在语法错误
### 10.2 接口兼容性校验失败
**错误信息示例**:
```
indie upg hilink and app not match
hilink checksum not match
func 5 [A1B2C3D4:E5F6A7B8] not match
```
**排查步骤**:
1. 确定是哪个接口发生了变更根据func序号
2. 检查接口原型是否发生改变
3. 检查参数类型、顺序、返回值类型
4. 如果是合理的接口升级需要重新生成App-IoT的check.json
### 10.3 接口映射表生成失败
**可能原因**:
1. 源文件中函数原型格式不规范
2. 头文件中接口声明与源文件实现不匹配
3. 正则表达式匹配失败
**排查步骤**:
1. 检查函数原型是否符合标准C语法
2. 确保头文件声明与源文件实现一致
3. 查看构建日志中的正则匹配错误信息
## 11. 总结
`ws63-liteos-hilink-check.json` 文件是WS63项目独立升级功能的核心组件通过CRC32校验码机制确保固件间接口兼容性。虽然当前项目未启用独立升级功能但完整的技术框架已经就绪可以通过简单的配置修改来启用该功能。
该机制的核心价值在于:
- **风险控制**: 通过接口校验防止不兼容升级
- **灵活部署**: 支持HiLink SDK的独立快速升级
- **开发效率**: 减少完整固件的编译和测试周期
- **维护便利**: 清晰的接口约定和自动化校验流程
---
**文档创建时间**: 2025-09-10
**项目版本**: SR_Light_Hilink_14.2.1.312_20250714
**分析对象**: output/ws63/acore/ws63-liteos-hilink/ws63-liteos-hilink-check.json