STM32F03C8T6通过AT指令获取天气API

浏览器控制台输出strlen

function calcHttpLength(apiKey, location) {
    const req = `GET /v3/weather/now.json?key=${apiKey}&location=${location}&language=en&unit=c HTTP/1.1\r\nHost: api.seniverse.com\r\nConnection: close\r\n\r\n`;
    console.log("Length:", req.length, "bytes");
    console.log("AT Command: AT+CIPSEND=" + req.length);
    return req.length;
}

STM32 天气API接口调试总结

问题现象

连续多次尝试获取天气数据失败,表现为:

  • JSON 解析失败
  • 数据被截断
  • cJSON_Parse 返回错误

失败原因分析

1. 过度复杂的 JSON 解析逻辑

问题:

  • 使用了复杂的嵌套查找逻辑
  • 尝试从 "results" 往前查找 {
  • 多层指针操作容易出错

表现:

'now' not found
JSON parse failed

2. cJSON 库的内存和字符串处理问题

问题:

  • cJSON 对输入字符串要求严格,必须完整且格式正确
  • 接收缓冲区中可能存在 \0 字符导致字符串提前终止
  • HTTP 响应头和 JSON 数据混在一起

表现:

cJSON_Parse failed
Error before: path":"Beijing...
JSON length: 260 (实际应该更长)

3. 字符串截断问题

问题:

  • 使用 strlen() 计算长度,遇到 \0 就停止
  • HTTP 响应中可能包含二进制数据或控制字符
  • JSON 数据被截断到 260-270 字节

表现:

Received 720 bytes
JSON length: 260 bytes  // 不匹配!

4. 过多的调试输出干扰

问题:

  • 大量的 printf 调试信息
  • 打印完整的 JSON 数据(可能很长)
  • 影响程序执行时序

成功的关键改进

✅ 1. 简化 JSON 解析逻辑

改进:

// 之前:复杂的嵌套查找
p_results = strstr(json, "\"results\"");
p_location = strstr(p_results, "\"location\"");
p_now = strstr(p_results, "\"now\"");
// 然后从各个位置解析...

// 现在:直接在整个缓冲区中查找
if(strstr(ESP8266_RxBuffer, "\"results\""))
{
    Weather_ParseJSON(ESP8266_RxBuffer);
}

优势:

  • 不需要精确定位 JSON 起始位置
  • 直接在整个缓冲区中搜索关键字
  • 容错性更强

✅ 2. 使用简单的字符串查找函数

改进:

// 自定义的 GetStringBetween 函数
static uint8_t GetStringBetween(char *src, char *start, char *end, 
                                 char *output, uint16_t maxLen)
{
    char *p_start = strstr(src, start);
    if(p_start == NULL) return 0;
    
    p_start += strlen(start);
    char *p_end = strstr(p_start, end);
    if(p_end == NULL) return 0;
    
    // 复制数据
    uint16_t len = p_end - p_start;
    if(len >= maxLen) len = maxLen - 1;
    strncpy(output, p_start, len);
    output[len] = '\0';
    
    return 1;
}

// 使用示例
GetStringBetween(json, "\"name\":\"", "\"", temp, sizeof(temp));
GetStringBetween(json, "\"text\":\"", "\"", temp, sizeof(temp));
GetStringBetween(json, "\"temperature\":\"", "\"", temp, sizeof(temp));

优势:

  • 不依赖第三方库(cJSON)
  • 直接处理原始字符串,不受 \0 影响
  • 代码简单易懂
  • 参考了成功示例的实现方式

✅ 3. 移除 cJSON 依赖

原因:

  • cJSON 对输入要求严格
  • 需要完整、格式正确的 JSON 字符串
  • 在嵌入式环境中可能有内存碎片问题

改进:

  • 使用简单的字符串查找
  • 只提取需要的字段
  • 不需要解析完整的 JSON 结构

✅ 4. 优化等待机制

改进:

// 之前:固定延时
Delay_ms(3000);

// 现在:超时循环等待
uint16_t TimeOut = 0;
while(TimeOut < 100 && !strstr(ESP8266_RxBuffer, "OK"))
{
    Delay_ms(10);
    TimeOut++;
}

优势:

  • 响应更快(收到数据立即处理)
  • 有超时保护
  • 参考了成功示例的实现

✅ 5. 精简调试输出

改进:

// 之前:大量详细的调试信息
DEBUG_PRINTF("========== 开始获取天气信息 ==========\r\n");
DEBUG_PRINTF("城市: %s\r\n", city);
DEBUG_PRINTF("API Key: %s\r\n", api_key);
DEBUG_PRINTF("关闭旧连接...\r\n");
// ... 还有很多

// 现在:简洁的关键信息
printf("[Weather] Getting weather for %s...\r\n", city);
printf("[Weather] Connecting to server...\r\n");
printf("[Weather] Request sent, waiting response...\r\n");
printf("[Weather] Success!\r\n");

优势:

  • 减少串口输出时间
  • 不影响程序时序
  • 信息更清晰易读

核心经验教训

🎯 1. 简单就是美

  • 不要过度设计
  • 能用简单方法解决的,不要用复杂方案
  • 字符串查找比完整 JSON 解析更适合嵌入式环境

🎯 2. 参考成功案例

  • 看到别人成功的代码,直接参考其实现方式
  • 不要重复造轮子
  • 成功的示例已经验证过可行性

🎯 3. 嵌入式环境的特殊性

  • 内存有限,避免使用复杂的库
  • 字符串处理要小心 \0 字符
  • HTTP 响应包含头部和数据,需要容错处理

🎯 4. 调试要适度

  • 过多的调试输出反而影响程序运行
  • 关键节点输出即可
  • 成功后可以进一步精简

最终成功的代码特点

简洁:150 行代码(之前 300+ 行)
可靠:基于成功示例的实现
高效:超时等待机制,响应快
易维护:逻辑清晰,易于理解
无依赖:不需要 cJSON 等第三方库

成功输出示例

[Weather] Getting weather for beijing...
[Weather] Connecting to server...
[Weather] Request sent, waiting response...
[Weather] Received 720 bytes
[Weather] Parsing JSON...

========================================
         Weather Information
========================================
City:        Beijing
Weather:     Clear
Temperature: -1 C
Temp Range:  -4 ~ 4 C
========================================

[Weather] Success!

总结

失败的根本原因: 过度复杂化,使用了不适合嵌入式环境的方案(cJSON + 复杂解析逻辑)

成功的关键: 回归简单,参考成功示例,使用基础的字符串查找函数

教训: 在嵌入式开发中,简单、可靠、易维护比功能强大更重要!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐