天气预报小助手V1.2(软件+硬件结合)

STM大家好,我是杂烩君。

前两篇文章我们分享了:

这篇文章我们分享:天气预报小助手V1.2(软件+硬件结合)

1、作品介绍

首先,看一下作品的演示视频:

温馨提示:因为是进行人机对话演示,所以应打开音量观看哈。

这是一个智能的天气预报系统。显示屏上显示各种天气指标及实时显示时间日期等。可以使用触摸屏输入城市名称搜索天气,也可以使用语音搜索天气。

1.1 系统功能

作品包含的的功能有:

(1)实时天气显示,温湿度显示,日历显示;

(2)收音机功能;

(3)人机对话功能。

系统框图如下:

天气预报小助手V1.2(软件+硬件结合)

1.2 系统GUI界面

(1)主界面

天气预报小助手V1.2(软件+硬件结合)

你没有看错,就是99℃,就是星期八。但这不是系统出错,而是本人故意设置的初始值,每当开机收到天气数据之后就可以看出有明显的变化。

(2)菜单界面

天气预报小助手V1.2(软件+硬件结合)

(3)wifi设置界面

天气预报小助手V1.2(软件+硬件结合)

点击文本框会进入键盘界面,输入WiFi信息之后返回,再点击Add按钮即可发送WiFi名称与密码给控制器,控制器控制WiFi模块连接WiFi热点。

(4)收音机界面

天气预报小助手V1.2(软件+硬件结合)

通过点击下方频率点跳到相应频率,再通过左右按钮调节频率至所需频率。

2、作品实现

2.1 天气数据获取及解析

2.1.1 天气数据从哪来?

天气数据可以从一些专门做天气预报的网站获取,如心知天气、和风天气等。本人选择的是心知天气

https://www.seniverse.com/

网站首页如下:

天气预报小助手V1.2(软件+硬件结合)

我们是通过其API密钥才能获取得到其天气数据,而只有注册的用户才拥有API密钥,所以必须得注册,可以点击右上角进行注册。

2.1.2 天气数据是什么格式?

登录心知天气网站之后,点击菜单导航中的数据->常规数据即可查看API文档。在API文档页面的左侧可看到一些可查看的条目,如:

天气预报小助手V1.2(软件+硬件结合)

可点击天气实况查看其相关说明,可以看到其天气数据格式如下图所示:

天气预报小助手V1.2(软件+硬件结合)

这就是JSON格式的数据,不了解JSON的朋友可查看上一篇笔记:例说嵌入式实用知识之JSON数据

2.1.3 如何解析得到有用的数据?

从上图中的JSON格式天气数据包中我们可以看出:我们需要用到的数据就是冒号后面的字符串数据,这些数据是我们需要获取并显示到屏幕上的数据。

那么,我们该怎么从这一堆JSON格式数据中解析出冒号后面的字符串呢?并且,这个系统是基于单片机的天气预报系统。而单片机使用C语言进行编程开发的,所以我们得使用C语言对这些JSON天气数据包进行解析。

其实,有一个专门解析JSON数据包的第三方C语言库。我们可以使用这个库进行解析,这个CJSON库的下载链接为:

链接:https://pan.baidu.com/s/1DQynsdlNyIvsVXmf4W5b8Q 提取码:ww4z

只要把cJSON.ccJSON.h放到工程主程序所在目录,然后在主程序中包含头文件JSON.h即可引入该库。如:

天气预报小助手V1.2(软件+硬件结合)

下面给出一个实例:

测试代码:

/*----------------------------------------------------------------------------------------
    Program Explain:解析JSON天气数据包now.json(天气实况)
    Create Date:2017.12.6 by lzn
----------------------------------------------------------------------------------------*/

//1、数据来源:心知天气(api.seniverse.com)
//2、获取方法:GET https://api.seniverse.com/v3/weather/now.json?key=2owqvhhd2dd9o9f9&location=beijing&language=zh-Hans&unit=c
//3、返回的数据范例见文件test.txt

#include <stdio.h>#include <string.h>#include <stdlib.h>#include "cJSON.h"
//函数声明
int cJSON_WeatherParse(char *JSON);

/*********************************************************************************
* Function Name    : main主函数
* Parameter        : NULL
* Return Value     : 0 
* Function Explain : 
* Create Date      : 2017.12.6 by lzn
**********************************************************************************/
int main(int argc, char **argv)
{
    FILE *fp;
    char *data;
    int len;
    int i;
    
    if((fp = fopen("now.txt","rb")) == NULL)
    {
        printf("Open error!\n");
        return 1;
    } 
    fseek(fp, 0, SEEK_END);     //文件指针指向文件末尾
    len = ftell(fp);            //求文件长度 
    fseek(fp, 0, SEEK_SET);     //文件指针指向文件开头
    data = (char*)malloc(len+1);
    fread(data, len, 1, fp);
    fclose(fp);
    //printf("read file %s complete, len=%d.\n","now.txt",len);
    cJSON_WeatherParse(data);   //解析天气数据
    free(data);
    
    system("pause");
    
    return 0;
}

/*********************************************************************************
* Function Name    : cJSON_WeatherParse,解析天气数据
* Parameter        : JSON:天气数据包  results:保存解析后得到的有用的数据
* Return Value     : 0:成功 其他:错误
* Function Explain : 
* Create Date      : 2017.12.6 by lzn
**********************************************************************************/
int cJSON_WeatherParse(char *JSON)
{
    cJSON *json,*arrayItem,*object,*subobject,*item;
    
    json = cJSON_Parse(JSON); //解析JSON数据包
    if(json == NULL)          //检测JSON数据包是否存在语法上的错误,返回NULL表示数据包无效
    {
        printf("Error before: [%s]\n",cJSON_GetErrorPtr()); //打印数据包语法错误的位置
        return 1;
    }
    else
    {
        if((arrayItem = cJSON_GetObjectItem(json,"results")) != NULL); //匹配字符串"results",获取数组内容
        {
            int size = cJSON_GetArraySize(arrayItem);     //获取数组中对象个数
            //printf("cJSON_GetArraySize: size=%d\n",size); 
            
            if((object = cJSON_GetArrayItem(arrayItem,0)) != NULL)//获取父对象内容
            {
                /* 匹配子对象1 */
                if((subobject = cJSON_GetObjectItem(object,"location")) != NULL)
                {
                    printf("\n-------------------------------location-----------------------------\n");
                    //匹配子对象1成员"id"
                    if((item = cJSON_GetObjectItem(subobject,"id")) != NULL)   
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象1成员"name"
                    if((item = cJSON_GetObjectItem(subobject,"name")) != NULL) 
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象1成员"country"
                    if((item = cJSON_GetObjectItem(subobject,"country")) != NULL)
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象1成员"timezone"
                    if((item = cJSON_GetObjectItem(subobject,"timezone")) != NULL)
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象1成员"timezone_offset"
                    if((item = cJSON_GetObjectItem(subobject,"timezone_offset")) != NULL)
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                }
                /* 匹配子对象2 */
                if((subobject = cJSON_GetObjectItem(object,"now")) != NULL)
                {
                    printf("---------------------------------now-------------------------------\n");
                    //匹配子对象2成员"text"
                    if((item = cJSON_GetObjectItem(subobject,"text")) != NULL)
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象2成员"code"
                    if((item = cJSON_GetObjectItem(subobject,"code")) != NULL)
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象2成员"temperature"
                    if((item = cJSON_GetObjectItem(subobject,"temperature")) != NULL) 
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象2成员"feels_like"
                    if((item = cJSON_GetObjectItem(subobject,"feels_like")) != NULL) 
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象2成员"pressure"
                    if((item = cJSON_GetObjectItem(subobject,"pressure")) != NULL) 
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象2成员"humidity"
                    if((item = cJSON_GetObjectItem(subobject,"humidity")) != NULL) 
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象2成员"visibility"
                    if((item = cJSON_GetObjectItem(subobject,"visibility")) != NULL) 
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象2成员"wind_direction"
                    if((item = cJSON_GetObjectItem(subobject,"wind_direction")) != NULL) 
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象2成员"wind_speed"
                    if((item = cJSON_GetObjectItem(subobject,"wind_speed")) != NULL) 
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象2成员"wind_scale"
                    if((item = cJSON_GetObjectItem(subobject,"wind_scale")) != NULL) 
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象2成员"clouds"
                    if((item = cJSON_GetObjectItem(subobject,"clouds")) != NULL) 
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                    
                    //匹配子对象2成员"dew_point"
                    if((item = cJSON_GetObjectItem(subobject,"dew_point")) != NULL) 
                    {
                        printf("%s : %s\n",item->string,item->valuestring);
                    }
                }
                /* 匹配子对象3 */
                if((subobject = cJSON_GetObjectItem(object,"last_update")) != NULL)
                {
                    printf("----------------------------last_update----------------------------\n");
                    printf("%s : %s\n\n",subobject->string,subobject->valuestring);
                }
            } 
        }
    }
    
    cJSON_Delete(json); //释放cJSON_Parse()分配出来的内存空间
    
    return 0;
}

这个测试程序会去读取我们工程目录下的 now.txt件,所以事先我们需要把JSON格式的天气预报数据复制到该文件中:

天气预报小助手V1.2(软件+硬件结合)

now.txt里面的数据读出并保存到data指向的动态内存中。然后再把data中的数据传入我们事先编写好的解析天气数据的函数int cJSON_WeatherParse(char *JSON)中进行解析,最后把解析之后的数据给到该函数的返回值即可。

解析函数里主要用到以下函数: 1、cJSON_Parse函数

cJSON*cJSON_Parse(const char *value);

该函数用来解析JSON数据包,并按照cJSON结构体的结构序列化整个数据包。

2、cJSON_GetObjectItem函数

cJSON_GetObjectItem(cJSON *object,const char *string);

该函数可从cJSON结构体中查找某个子节点名称(键名称),如果查找成功可把该子节点序列化到cJSON结构体中。

3、cJSON_GetArraySize函数

cJSON_GetArraySize(const cJSON *array);

该函数可获取数组中元素个数。

4、cJSON_GetArrayItem函数

cJSON_GetArrayItem(const cJSON *array, int index);

该函数可获取数组中的内容。

5、cJSON_Delete函数

cJSON_Delete(cJSON *c); 

该函数用来释放cJSON_Parse函数内部申请的堆内存。

我们的解析函数主要运用多次cJSON_GetObjectItem来匹配各对象成员,然后取出各个键值对的值valuestring

该程序的运行结果如下:

天气预报小助手V1.2(软件+硬件结合)

怎么才能知道一个网站的IP呢?在DOS黑窗口下输入ping+域名即可得该域名对应的IP,如我们ping百度:

天气预报小助手V1.2(软件+硬件结合)

ping是Windows、Unix和Linux系统下的一个命令,利用ping命令可以检查网络是否连通,可以很好地帮助我们分析和判定网络故障,该命令还可以加许多参数使用,具体是键入Ping按回车即可看到详细说明。

(2)发送HTTP请求,向心知天气服务器请求天气数据。HTTP有几种请求方法,我们这里使用的是GET请求:

GET请求:从指定的资源请求数据。

具体的请求方法示例为:

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

其中,GET后面的URL地址可以上心知天气查看,如:

可以预存50条关键词(关键句),本人已经把关键词写死在程序里了,这显然就不能灵活的面对各种场景。其实可以通过代码编写一个学习功能,即识别之前首先进行学习一些即将要识别的关键词,然后在进行识别演示,这样就可以应对比较多的场景。

但是,这样还是不够智能,毕竟只能识别已经预存的关键词(关键句),要是没有预存就没办法识别了。所以真正的语音识别应该是在软件算法上下功夫,关于语音识别已然成为热门的一大研究专题,这就属于人工智能的范畴吧。希望以后可以有机会接触这一块,如有接触再做学习分享~

(2)开始识别,如:

static void Task_ASR(void)
{
 switch(nAsrStatus)
 {
   case LD_ASR_RUNING:    
   case LD_ASR_ERROR:    
     break;
   case LD_ASR_NONE:
     nAsrStatus=LD_ASR_RUNING; 
     if (RunASR()==0)  //  启动一次ASR识别流程:ASR初始化,ASR添加关键词语,启动ASR运算
     {    
       nAsrStatus = LD_ASR_ERROR;
     }
     break;

   case LD_ASR_FOUNDOK:
     nAsrRes = LD_GetResult( );  //  一次ASR识别流程结束,去取ASR识别结果  
     ASRSuccess_Handle(nAsrRes);
     nAsrStatus = LD_ASR_NONE;  
   break;
   
   case LD_ASR_FOUNDZERO:
   default:
     nAsrStatus = LD_ASR_NONE;  
     break;
 }
}

nAsrStatus是用来表示语音识别的状态,不是LD3320芯片内部的状态寄存器。nAsrStatus有几种情况。我们比较关注的是LD_ASR_FOUNDOK状态。LD_ASR_FOUNDOK状态为识别成功,识别成功后将调用ASRSuccess_Handle函数进行识别后的操作。

(3)识别成功则执行相应操作,如

void ASRSuccess_Handle(uint8 asr_code)
{
 printf("\r\n识别码:%d\n",asr_code);    
 if(0 == asr_code)
 {
   printf("我在,需要我的帮助吗?\n");
   TTSPlay(0, "[t3][2]我在,[2]需要[2]我的[3]帮助吗");
   RunFlag = TRUE;
 }
 else if(RunFlag)
 {
   RunFlag = FALSE;
   /* 识别码0-10为搜索天气识别码 */
   if(asr_code>=0&&asr_code<=10)
   {
     switch(asr_code)      
     {
       case CODE01:      
         printf("“福州”命令识别成功\r\n");
         TTSPlay(0, "[t3][2]小天正在为您搜索福州天气");
         memcpy(g_city,"fujianfuzhou",sizeof(g_place));
         break;
       case CODE02:   
         printf("“上海”命令识别成功\r\n");
         TTSPlay(0, "[t3][2]小天正在为您搜索上海天气");      
         memcpy(g_city,"shanghai",sizeof(g_place));
         break;
       case CODE03:    
         printf("“深圳”命令识别成功\r\n");
         TTSPlay(0, "[t3][2]小天正在为您搜索深圳天气");  
         memcpy(g_city,"shenzhen",sizeof(g_place));
         break;
       case CODE04:    
         printf("“北京”命令识别成功\r\n");
         TTSPlay(0, "[t3][2]小天正在为您搜索北京天气");  
         memcpy(g_city,"beijing",sizeof(g_place));
         break;
       case CODE05:    
         printf("“广州”命令识别成功\r\n");
         TTSPlay(0, "[t3][2]小天正在为您搜索广州天气");  
         memcpy(g_city,"guangzhou",sizeof(g_place));
         break;
       case CODE06:    
         printf("“南宁”命令识别成功\r\n");
         TTSPlay(0, "[t3][2]小天正在为您搜索南宁天气");  
         memcpy(g_city,"nanning",sizeof(g_place));
         break;
       case CODE07:    
         printf("“厦门”命令识别成功\r\n");
         TTSPlay(0, "[t3][2]小天正在为您搜索厦门天气");  
         memcpy(g_city,"xiamen",sizeof(g_place));
         break;
       case CODE08:    
         printf("“泉州”命令识别成功\r\n");
         TTSPlay(0, "[t3][2]小天正在为您搜索泉州天气");  
         memcpy(g_city,"quanzhou",sizeof(g_place));
         break;
       case CODE09:    
         printf("“莆田”命令识别成功\r\n");
         TTSPlay(0, "[t3][2]小天正在为您搜索莆田天气");  
         memcpy(g_city,"putian",sizeof(g_place));
         break;
       case CODE10:  
         printf("“南平”命令识别成功\r\n");
         TTSPlay(0, "[t3][2]小天正在为您搜索南平天气");  
         memcpy(g_city,"nanping",sizeof(g_place));
         break;
     }
     memset(&weather_data, 0, sizeof(weather_data));
     GET_NowWeather();
     GET_DailyWeather();
     GetWeatherTimer = TIMER1_HOUR;  
     DisplayWeather(weather_data);
     DisplayWeatherIcon(weather_data);
   }
   else
   {
     switch(asr_code)  
     {
       case CODE11:
         printf("“语音播报天气”命令识别成功\r\n");
         printf("%s\n",g_WeatherText);
//          TTSPlay(0, (uint8_t*)g_WeatherText);
         break;
       case CODE12:
         printf("“今天的气温是多少”命令识别成功\r\n");
         break;
       default:
         TTSPlay(0, "语音识别失败,请对准麦克风说话!");
         break;
     }
   }
   
 }
}

2.4.2 语音合成

本系统的语音合成模块采用SYN6288语音合成芯片,支持文本直接转化为语音。其与单片机的通信方式为串口通信。向该模块发送以下格式的数据包:

5字节帧头+文本+1字节校验,文本字节数小于等于200字节

即可合成语音。代码如:

void TTSPlay(uint8_t Music,uint8_t *Text)
{
 /****************需要发送的文本**********************************/ 
 uint8_t DataPacket[50];    //
 uint8_t Text_Len;  
 uint8_t ecc  = 0;        //定义校验字节
 uint8_t i=0;  
 Text_Len =strlen((const char*)Text);       //需要发送文本的长度

 /*****************帧固定配置信息**************************************/           
 DataPacket[0] = 0xFD ;         //构造帧头FD
 DataPacket[1] = 0x00 ;         //构造数据区长度的高字节
 DataPacket[2] = Text_Len + 3;     //构造数据区长度的低字节
 DataPacket[3] = 0x01 ;         //构造命令字:合成播放命令          
 DataPacket[4] = 0x01 | Music<<4 ;   //构造命令参数:背景音乐设定

 /*******************校验码计算***************************************/     
 for(i = 0; i<5; i++)           //依次发送构造好的5个帧头字节
 {  
   ecc=ecc^(DataPacket[i]);    //对发送的字节进行异或校验  
 }

 for(i= 0; i<Text_Len; i++)       //依次发送待合成的文本数据
 {  
   ecc=ecc^(Text[i]);         //对发送的字节进行异或校验    
 }     
 /*******************发送帧信息***************************************/      
 memcpy(&DataPacket[5], Text, Text_Len);
 DataPacket[5+Text_Len]=ecc;
 UART4_SendStr((char*)DataPacket,5+Text_Len+1);
}

调用方式如:

TTSPlay(0, "[t3][2]小天正在为您搜索福州天气");

3、最后

以上就是关于该作品比较重要的一些内容的分享,由于篇幅太长其他功能不做介绍,有兴趣的朋友可以查看源码。源码链接:

原创不易。如果文章对你有帮助,麻烦帮忙点赞、收藏、转发,谢谢!

分享一个我创建的实用开源项目汇总仓库:

(0)
上一篇 2022年10月6日 04:08
下一篇 2022年10月6日 04:19

相关推荐