【STM32学习笔记-03】ESP8266 访问心知天气API获取实时天气信息

目录

一、心知天气

1.1 注册账号 

1.2 申请产品获取专属密钥

1.3 查看专属密钥

1.4 阅读数据产品手册,了解相关信息

1.5 获取"天气实况"API接口地址

1.5.1 “私钥” 直接请求方式     

1.5.2 “公钥 + 私钥” 签名验证方式

         1.6 “公钥 + 私钥” 签名验证方式复现

1.6.1 构造验证参数字符串

1.6.2 对上一步得到的字符串使用 HMAC-SHA1 的方式做哈希运算得到二进制结果,并用 Base64 的方式编码,得到一串哈希字符串

1.6.3 使用 URLEncode 上一步的结果进行编码,得到签名 sig

1.6.4 将sig作为第一步得到的参数字符串的最后一个参数

1.6.5 组合替换得出API接口地址

1.6.6 在线测试

二、使用ESP8266进行访问测试

2.1 串口配置

2.2 EPS8266 STA模式配置

2.3 主程序

三、 总结


一、心知天气

        HyperData 是心知天气的高精度气象数据产品,通过标准的 Restful API 接口,提供标准化的数据访问。无论是 APP、智能硬件还是企业级系统都可以轻松接入心知的精细化天气数据。最重要的是一些基础功能免费。

1.1 注册账号 

传送门:心知天气

1.2 申请产品获取专属密钥

具体步骤:进入控制台--->产品管理--->添加产品(天气数据API)--->申请免费版

1.3 查看专属密钥

具体步骤:进入控制台--->产品管理--->找到你的产品--->基本信息--->API密钥(事关安全,请勿轻易向他人透露)

1.4 阅读数据产品手册,了解相关信息

传送门:HyperData 数据产品简介

1.5 获取"天气实况"API接口地址

传送门:天气实况使用说明

  • 请求参数说明:(我们需要填充的是等号=后面的参数,例如:your_api_key

参数名称

类型

默认值

必填

备注

key

String

你的API密钥

location

Location

所查询的位置

language

Language

zh-Hans

语言

unit

Unit

c

单位

  • 返回结果示例:
{
  "results": [
    {
      "location": {
        "id": "C23NB62W20TF",
        "name": "西雅图",
        "country": "US",
        "path": "西雅图,华盛顿州,美国",
        "timezone": "America/Los_Angeles",
        "timezone_offset": "-07:00"
      },
      "now": {
        "text": "多云", //天气现象文字
        "code": "4", //天气现象代码
        "temperature": "14", //温度,单位为c摄氏度或f华氏度
        "feels_like": "14", //体感温度,单位为c摄氏度或f华氏度
        "pressure": "1018", //气压,单位为mb百帕或in英寸
        "humidity": "76", //相对湿度,0~100,单位为百分比
        "visibility": "16.09", //能见度,单位为km公里或mi英里
        "wind_direction": "西北", //风向文字
        "wind_direction_degree": "340", //风向角度,范围0~360,0为正北,90为正东,180为正南,270为正西
        "wind_speed": "8.05", //风速,单位为km/h公里每小时或mph英里每小时
        "wind_scale": "2", //风力等级,请参考:http://baike.baidu.com/view/465076.htm
        "clouds": "90", //云量,单位%,范围0~100,天空被云覆盖的百分比 #目前不支持中国城市#
        "dew_point": "-12" //露点温度,请参考:http://baike.baidu.com/view/118348.htm #目前不支持中国城市#
      },
      "last_update": "2015-09-25T22:45:00-07:00" //数据更新时间(该城市的本地时间)
    }
  ]
}

        那么,我们的重点工作就在于获取API密钥;心知天气支持两种 API 安全验证方式:

我的公钥:Pnqrgerh6idkcIs6U              我的私钥:STNnlCcI3dgZn5NLW

1.5.1 “私钥” 直接请求方式     

         这种方式比较简单粗暴,只需要将 API 密钥中的“私钥”作为 API 请求中的 key 参数值:(说明此方式较为方便,但请注意不要泄漏你的“私钥”)

API接口地址格式:https://api.seniverse.com/v3/weather/now.json?key=your_private_key&location=beijing&language=zh-Hans&unit=c

        这里我们只需要将your_private_key替换为我们的私钥即可,替换后的API接口地址如下:

https://api.seniverse.com/v3/weather/now.json?key=STNnlCcI3dgZn5NLW&location=beijing&language=zh-Hans&unit=c

1.5.2 “公钥 + 私钥” 签名验证方式

        “公钥 + 私钥” 验证方式更加安全。请求地址中只包含你的“公钥”以及用你的“私钥”制作的签名,因此不会在请求地址中泄露你的私钥。具体使用方式可以查看官方的详细介绍如何使用签名验证方式

        下面我将按照官方的步骤进行复现。

1.6 “公钥 + 私钥” 签名验证方式复现

1.6.1 构造验证参数字符串

数据格式要求:ts=UNIX时间戳&ttl=签名失效时间&uid=用户的公钥

①UNIX时间戳:

传送门:在线时间戳转换

②签名失效时间:签名失效时间参数 ttl 是可选参数。如果忽略这个参数,生成的签名有效期默认为 1800 秒(30 分钟)。较短的有效期可以使签名更难被盗用,提高安全性,但请务必保证请求到达心知服务器的时间在签名有效期内,否则会鉴权失败;在这里我以24小时为例(24*60*60)s。

构造的结果:ts=1655945206&ttl=86400&uid=Pnqrgerh6idkcIs6U

1.6.2 对上一步得到的字符串使用 HMAC-SHA1 的方式做哈希运算得到二进制结果,并用 Base64 的方式编码,得到一串哈希字符串

传送门:HMAC-SHA1计算

HMAC-SHA1计算+Base64编码后的结果:0nmfAICtezPCqE417SM0oiZTQ68=

1.6.3 使用 URLEncode 上一步的结果进行编码,得到签名 sig

传送门:URLEncode编码

URLEncode编码后的结果:0nmfAICtezPCqE417SM0oiZTQ68%3D

1.6.4 将sig作为第一步得到的参数字符串的最后一个参数

拼接格式:ts=1655945206&ttl=86400&uid=Pnqrgerh6idkcIs6U&sig=签名sig

拼接后的结果:ts=1655945206&ttl=86400&uid=Pnqrgerh6idkcIs6U&sig=0nmfAICtezPCqE417SM0oiZTQ68%3D

1.6.5 组合替换得出API接口地址

API接口格式:https://api.seniverse.com/v3/weather/now.json?your_api_key&location=Nanchang&language=en&unit=c

        这里我们只需要将your_api_key替换为上一步(2.6.4节)得出的结果即可,下面是替换后的最终结果:

https://api.seniverse.com/v3/weather/now.json?ts=1655945206&ttl=86400&uid=Pnqrgerh6idkcIs6U&sig=0nmfAICtezPCqE417SM0oiZTQ68%3D&location=Nanchang&language=en&unit=c

1.6.6 在线测试

         至此,整个签名验证方式全部执行完毕;在正式使用ESP8266进行访问之前,可以先对API接口地址进行在线Get测试,以免上述配置出现问题!传送门:在线GET请求测试

二、使用ESP8266进行访问测试

2.1 串口配置

/******************************************************************************\
 函数功能:串口3初始化配置
 硬件连接:USART3_TX - PB10 - 复用推挽输出
           USART3_RX - PB11 - 上下拉输入
 形参说明:
 返 回 值:
\******************************************************************************/
void USART3_Init(u32 baud)
{
    //1,打相应的外设时钟
    RCC->APB2ENR |= 1<<3; //IOPB外设时钟使能
    RCC->APB1ENR |= 1<<18;//USART3外设时钟使能
    
    //2,配置GPIO工作模式: 通用推挽-3 复用推挽-B 上下拉输入-8 模拟输入-0
    GPIOB->CRH &= 0xFFFF00FF;
    GPIOB->CRH |= 0x00008B00;
    
    //3,配置串口3的相应功能
    RCC->APB1RSTR |= 1<<18;    //开启USART3复位
    RCC->APB1RSTR &= ~(1<<18); //关闭USART3复位
    
    USART3->CR1 |= 1<<13;   //USART模块使能
    USART3->CR1 &= ~(1<<12);//字长:一个起始位,8个数据位,n个停止位
    USART3->CR2 &= ~(3<<12);//1个停止位
    //brr = clk / baud;
    USART3->BRR = (36*1000000) / baud;  //设置通信波特率
    
    #ifdef USART3_IRQ_EN
    USART3->CR1 |= 1<<5;    //接收缓冲区非空中断使能
    CM3_NVIC_SetPriority(USART3_IRQn, 0, 2);  //设置中断优先级
    #endif
    
    USART3->CR1 |= 1<<3;    //发送器使能
    USART3->CR1 |= 1<<2;    //接收器使能
}

/******************************************************************************\
 函数功能:串口3发送字符串
 形参说明:
 返 回 值:
\******************************************************************************/
void USART3_Send_String(char *pstr)
{
    while(*pstr != '\0')
    {
        USART3->DR = *pstr++;   //1/72us
        while(!(USART3->SR & 1<<7)){}    //等待TDR发送数据缓冲器空
    }    
}

//串口3中断服务函数
u8 USART3_RX_BUFF[USART_RX_MAX];
u16 USART3_RX_CNT;
u8 USART3_RX_FLAG;
void USART3_IRQHandler(void)
{
    if(USART3->SR & 1<<5)  //判断是否是接收中断触发
    {
        TIM3->CNT = 0;
        TIM3->CR1 |= 1<<0;  //启动计数器
        u8 tmp = USART3->DR; //1,保存数据 2,清除标志位
        if(USART3_RX_CNT < USART_RX_MAX)
        {
            USART3_RX_BUFF[USART3_RX_CNT] = tmp;
            USART3_RX_CNT++;
        }
        else USART3_RX_FLAG = 1; //缓冲区满了
    }
}

2.2 EPS8266 STA模式配置

char buff[50];

/******************************************************************************\
 函数功能:核心函数-发送AT指令(自动判断是否成功,具备失败自动重发的能力)
 形参说明:
 返 回 值:0 - 成功 / 1 - 失败
\******************************************************************************/
u8 ESP8266_Send_AtCmd(char *cmd, char *ack)
{
    u8 i;
    u16 j;
    for(i=0; i<5; i++)
    {
        USART3_Send_String(cmd); //1,把命令发出去
        for(j=0; j<10*1000; j++)  //60s
        {
            if(j%50==0) printf(">");
            if(USART3_RX_FLAG) //2,确认是否收到回复
            {
                USART3_RX_CNT = 0;
                USART3_RX_FLAG = 0;
                if(strstr((char *)USART3_RX_BUFF, ack))  //3,确认收到的回复是否正确
                {
                    printf("\n");
                    return 0;
                }
            }
            Delay_Ms(1);
        }
    }
    return 1;
}

/******************************************************************************\
 函数功能:STA模式配置
 形参说明:ssid - 热点名称
           pwd - 热点密码
           ip - 服务器ip
           port 服务器端口号
 返 回 值:
\******************************************************************************/
u8 ESP8266_STA_TCPClientMode_Init(const char *ssid, const char *pwd, const char *ip, const u16 port)
{
    USART3_Send_String("AT+RST\r\n");
    Delay_Ms(1000);
    Delay_Ms(1000);

    //1,发送AT测试指令
    printf("1\n");
    if(ESP8266_Send_AtCmd("AT\r\n", "OK")) return 0xE1;

    //2,配置WIFI工作模式:STA模式
    printf("2\n");
    if(ESP8266_Send_AtCmd("AT+CWMODE=1\r\n", "OK")) return 0xE2;
    
    //3,连接热点
    printf("3\n");
    snprintf(buff, sizeof(buff), "AT+CWJAP_DEF=\"%s\",\"%s\"\r\n", ssid, pwd);
    if(ESP8266_Send_AtCmd(buff, "OK")) return 0xE3;
    
    //4,查询本机IP
    printf("4\n");
    if(ESP8266_Send_AtCmd("AT+CIFSR\r\n", "OK")) return 0xE4;
    
    //5,开启单连接
    printf("5\n");
    if(ESP8266_Send_AtCmd("AT+CIPMUX=0\r\n", "OK")) return 0xE5;
    
    //6,连接服务器
    printf("6\n");
    snprintf(buff, sizeof(buff), "AT+CIPSTART=\"TCP\",\"%s\",%d\r\n", ip, port);
    if(ESP8266_Send_AtCmd(buff, "OK")) return 0xE6;
    
    printf("热点名称:%s\n", ssid);
    printf("热点密码:%s\n", pwd);
    printf("服务器IP:%s\n", ip);
    printf("服务器端口号:%d\n", port);
    
    return 0;
}

/******************************************************************************\
 函数功能:客户端向服务器发送数据
 形参说明:txData - 要发送的数据
 返 回 值:0 - 成功 / 1 - 失败
\******************************************************************************/
u8 ESP8266_STA_TCPClient_SendData(char *txData)
{
    snprintf(buff, sizeof(buff), "AT+CIPSEND=%d\r\n", strlen(txData));
    if(ESP8266_Send_AtCmd(buff, ">")) return 1;
    if(ESP8266_Send_AtCmd(txData, "SEND OK")) return 2; 
    
    return 0;    
}

2.3 主程序

//连接服务器所需信息
const char *ssid       = "oooh";                //热点名称
const char *password   = "12345678";            //热点密码
const char *ServerHost = "api.seniverse.com";   //服务器域名/服务器IP
const int ServerPort   = 80;                    //服务器端口号
 
//心知天气HTTP请求所需信息
#define SigMode 0
#if SigMode //签名验证方式
const char *reqUserAPIKey = "ts=1655945206&ttl=86400&uid=Pnqrgerh6idkcIs6U&sig=0nmfAICtezPCqE417SM0oiZTQ68%3D"; //你的API密钥(3.2.5步的结果)  
#else 
const char *reqUserAPIKey = "&key=STNnlCcI3dgZn5NLW";  //私钥直接请求
#endif
const char *reqLocation   = "NanChang";     //所查询的位置
const char *reqLanguage   = "en";           //语言
const char reqUnit        = 'c';            //单位   

//用于存放最终组合出来的请求地址
char reqURL[512];       
char keywords[3][20] = {"text", "code", "temperature"};
char result[3][20];   

int main()
{
    LED_Init(); 
    BEEP_Init();
    KEY_Init();
    
    USART1_Init(115200);     //串口1调试接口 - 串口调试助手
    TIM1_Init(72, 1000*10);  //辅助串口1接收数据

    USART3_Init(115200);     //串口3 - WIFI
    TIM3_Init(72, 1000*10);  //辅助串口3接收数据
    
    //1,关键函数 - ESP8266 STA模式配置
    do{}while(ESP8266_STA_TCPClientMode_Init(ssid, password, ServerHost, ServerPort));
    //2,拼接请求地址
    sprintf(reqURL, "GET https://api.seniverse.com/v3/weather/now.json?%s&location=%s&language=%s&unit=%c\n", reqUserAPIKey, reqLocation, reqLanguage, reqUnit);
    
    BEEP = 1;
    Delay_Ms(30);
    BEEP = 0;
         
    while(1)     //轮询
    {
        if(KEY_Scan(0))
        {
            //3,在这里我选择通过按键的方式触发请求
            printf("reqURL:%s, %d\n", reqURL, strlen((char *)reqURL));
            ESP8266_STA_TCPClient_SendData(reqURL);
        }
    
        if(USART3_RX_FLAG)
        {
            printf("USART3_RX_BUFF:%s, %d\n", USART3_RX_BUFF, strlen((char *)USART3_RX_BUFF));
            //解析天气
            ESP8266_ReqRes_DataParsing((char *)USART3_RX_BUFF, keywords[0], result[0]);
            ESP8266_ReqRes_DataParsing((char *)USART3_RX_BUFF, keywords[1], result[1]);
            ESP8266_ReqRes_DataParsing((char *)USART3_RX_BUFF, keywords[2], result[2]);
            printf("解析结果:\n");
            for(u8 i=0; i<3; i++)
            {
                printf("%s : %s\n", keywords[i], result[i]);
            }
            USART3_RX_CNT = 0;
            USART3_RX_FLAG = 0;
        }
        if(USART1_RX_FLAG)
        {
            printf("USART3_TX_BUFF:%s, %d\n", USART1_RX_BUFF, strlen((char *)USART1_RX_BUFF));
            USART3_Send_String((char *)USART1_RX_BUFF);
            USART1_RX_CNT = 0;
            USART1_RX_FLAG = 0;
        }
    }
}

/******************************************************************************\
 函数功能:解析请求响应的数据
 形参说明:reqRes - 响应数据
           keywords - 要解析的信息
           result - 解析后的结果
 返 回 值:
\******************************************************************************/
u8 ESP8266_ReqRes_DataParsing(char *reqRes, char *keywords, char *result)
{
    char *p1 = NULL;
    char *p2 = NULL;
    
    if(strstr(reqRes, "last_update"))   //判断请求响应的数据是否正确
    {
        p1 = strstr(reqRes, keywords);  //查找关键词
        if(p1)
        {
            p1 += strlen(keywords) + 3;
            p2 = strstr(p1, "\"");      //查找末端的 " 
            strncpy(result, p1, p2 - p1); //拷贝数据
            return 0;
        }
    }
    return 1;
}

具体的测试结果如下:

注:大家如果也是使用ESP8266进行访问,那这里的访问链接是有细微的不一样的。具体如下:(主要是新加入了GET命令)

三、 总结

        这篇文章前前后后写了很久,中间有很多次次因为手贱使用了"Ctrl+Z",导致码了很久的文字一夜回到解放前;本想摆烂,但是还是坚持了下来。毕竟做任何事情要有始有终。在此也非常感谢心知天气的技术人员提供的技术支持与帮助,万分感谢!

        文章中出现的密钥,在大家看到这篇文章的时候,应该已经被我废除了;但是考虑到有些情况下只是想快速的访问一下天气,所以在此我也给大家留了一个做好的,有效期比较长(3年)的API接口地址,提供给能耐心看到这里的有缘人使用:

https://api.seniverse.com/v3/weather/now.json?ts=1656147796&ttl=94867200&uid=PepU6vrMa6uGoj-zf&sig=0iYKi3lf83bmQZSimvxd55OK23c%3D&location=Nanchang&language=en&unit=c


版权声明:本文为qq_43417608原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。