毕设拯救计划(一)基于FreeRTOS的智能家居(STM32+Onenet云)
云计算在智能家居中负责数据传输和设备协调 #生活知识# #科技生活# #科技改变生活# #云计算#
系列文章目录
毕设拯救计划(一)基于FreeRTOS的智能家居(STM32+Onenet云)
毕设拯救计划(二)基于QT的智能家居(泰山派+Onenet云)
前言
这期算是补档,笔者之前出过STM32做的智能家具,当时利用的是EMQX,但是现在好像不是很好用,然后现在更新成ONENET云,并增加了Free RTOS。这部分算是第二节的下位机部分,大家可以自行扩展。
一、设计思路
这部分的效果展示网上的公开资料已经很多了,具体的功能代码之前也已经给出了来了课设拯救计划之基于MQTT云的智能家居,这里主要是说一些具体的规划和实现细节,至于什么外设怎么使用将不再赘述。
1.1 Free RTOS的控制思路
具体的框架思路如下图所示,在各部分模块初始化之后,创建所有需要的任务,将不需要的外设提前挂起以免占用资源。这里要主要内存的大小和优先的设置。这里我的优先级为:语音控制/网络控制>屏幕刷新>RTC>温湿度>监视器/灯光/电机>测距,遵循的原则是控制部分优先级最高,然后按照使用频率设计优先级。
这里的大部分移植都比较简单,只需要把链接中的相关部分打包成任务就可以了,比较麻烦的会单独说一下。
这里是将监视器拆为三部分:测距任务、监视拍照任务及图像调度任务,这样的好处是仿真任务的处理时间过长导致占用时间过长。
// 图像捕获任务 void Monitor_task(void *pvParameters) {float length = 0;while(1){camera_refresh();if(Monitor_Status == 1 && sd_ok){length = Get_Length();if(length < 50){LED0=0;//点亮DS0,提示正在拍照camera_new_pathname(pname);//得到文件名if(bmp_encode(pname,(lcddev.width-240)/2,(lcddev.height-320)/2,240,320,0)){//拍照有误Show_Str(40,130,240,12,"read file error",12,0);printf("read file error\n\r");}else{Show_Str(40,130,240,12,"photo saved",12,0);printf("photo saved\n\r");Show_Str(40+42,150,240,12,pname,12,0);message++;}LED1=1;//关闭DS1LCD_Clear(BLACK);}}else vTaskDelay(pdMS_TO_TICKS(100));} } // 图片调度任务 void PhotoManangerTask(void *pvParameters) {u8 res;DIR picdir;// 图片目录FILINFO picfileinfo; // 文件信息u8 *fn;// 长文件名u16 totpicnum;// 图片文件总数u16 curindex;// 图片当前索引u8 key;// 键值u8 pause = 0;// 暂停标记u8 t;u16 temp;u16 *picindextbl;// 图片索引表// 打开图片文件夹while(f_opendir(&picdir, "0:/PHOTO")) {vTaskDelay(pdMS_TO_TICKS(100));Show_Str(30, 170, 240, 16, "PHOTO文件夹错误!", 16, 0);vTaskDelay(pdMS_TO_TICKS(100));LCD_Fill(30, 170, 240, 186, WHITE); // 清除显示}// 获取图片总数totpicnum = pic_get_tnum("0:/PHOTO");// 为长文件名、路径名、索引表分配内存picfileinfo.lfsize = _MAX_LFN * 2 + 1;// 长文件名最大长度picfileinfo.lfname = mymalloc(SRAMIN, picfileinfo.lfsize);// 为长文件缓存区分配内存pname = mymalloc(SRAMIN, picfileinfo.lfsize);// 为带路径的文件名分配内存picindextbl = mymalloc(SRAMIN, 2 * totpicnum);// 申请2 * totpicnum个字节的内存, 用于存放图片索引// 记录图片索引res = f_opendir(&picdir, "0:/PHOTO");if (res == FR_OK) {curindex = 0; // 当前索引为0while (1) { // 全部查询一遍temp = picdir.index; // 记录当前indexres = f_readdir(&picdir, &picfileinfo); // 读取目录下的一个文件if (res != FR_OK || picfileinfo.fname[0] == 0) break; // 错误了/到末尾了,退出fn = (u8*)(*picfileinfo.lfname ? picfileinfo.lfname : picfileinfo.fname);res = f_typetell(fn);if ((res & 0XF0) == 0X50) { // 判断是否为图片文件picindextbl[curindex] = temp; // 记录索引curindex++;}}}// 显示图片Show_Str(30, 170, 240, 16, "开始显示...", 16, 0);vTaskDelay(pdMS_TO_TICKS(1500));piclib_init(); // 初始化画图// 开始图片显示循环curindex = 0; // 从0开始显示res = f_opendir(&picdir, "0:/PHOTO");while (res == FR_OK) {dir_sdi(&picdir, picindextbl[curindex]); // 改变当前目录索引res = f_readdir(&picdir, &picfileinfo); // 读取目录下的一个文件if (res != FR_OK || picfileinfo.fname[0] == 0) break; // 错误了/到末尾了,退出fn = (u8*)(*picfileinfo.lfname ? picfileinfo.lfname : picfileinfo.fname);strcpy((char*)pname, "0:/PHOTO/"); // 复制路径strcat((char*)pname, (const char*)fn); // 将文件名接在后面// 打印路径进行调试printf("Loading image: %s\n", pname);LCD_Clear(WHITE);res = ai_load_picfile(pname, 0, 0, lcddev.width, lcddev.height, 1); // 显示图片if (res != 0) printf("Error displaying image: %s\n", pname);Show_Str(2, 2, 240, 16, pname, 16, 1); // 显示图片名字// 按键逻辑t = 0;while (1) {key = KEY_Scan(0); // 扫描按键if (t > 250) key = 1; // 模拟按下KEY0if ((t % 20) == 0) LED0 = !LED0; // LED0闪烁, 提示程序正在运行if (key == KEY1_PRES) { // 上一张if (curindex) curindex--;else curindex = totpicnum - 1;break;} else if (key == KEY0_PRES) { // 下一张curindex++;if (curindex >= totpicnum) curindex = 0; // 到末尾, 从头开始break;} else if (key == WKUP_PRES) { // 暂停pause = !pause;LED1 = !pause;// 暂停时LED1亮}if (pause == 0) t++;vTaskDelay(pdMS_TO_TICKS(100));}}// 释放内存myfree(SRAMIN, picfileinfo.lfname);myfree(SRAMIN, pname);myfree(SRAMIN, picindextbl); } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 1.3 时钟刷新任务
这里部分也是由三部分组成:屏幕刷新任务、RTC时钟及闹钟(这里用的是软件定时器的方法,电机部分也是这个原理),大家可以回顾一下相关部分内容。
二、 联网功能
这里简单提一下,笔者主要是拿STM32作为下位机使用,实际上可以直接使用TCP进行双机互通(你的上位机是电脑、单片机、Arm平台都可以,而且工作量会小很多),感兴趣的可以看一下毕设拯救计划(二)基于QT的智能家居(泰山派+Onenet云)。具体的STM32的实现TCP的思路可以参考STM32 esp8266 TCP,这里讲了一下怎么移植,大家可以学习一下,个人感觉这里没必要深究,会用就可以了,而且确实很省事情!!
2.1 新版ONENET的实现思路
其实这部分也是很简单的,大家具体流程可以参考智能家居,我们这里利用的esp8266进行联网功能。主要是分为三部分:联网、订阅、发布,唯一值得注意的是上传的数据包结构,如果结构出错会直接断开连接。这部分的代码我这边是直接给出了,其实算是很固定的用法了,大家可以按照上面的视频自己再写写熟练一下。
这部分是联网功能,主要是针对8266来写的,代码如下:
// esp8266.c //========================================================== //函数名称:ESP8266_Clear // //函数功能:清空缓存 // //入口参数:无 // //返回参数:无 // //说明: //========================================================== void ESP8266_Clear(void) {memset(esp8266_buf, 0, sizeof(esp8266_buf));esp8266_cnt = 0; } //========================================================== //函数名称:ESP8266_WaitRecive // //函数功能:等待接收完成 // //入口参数:无 // //返回参数:REV_OK-接收完成REV_WAIT-接收超时未完成 // //说明:循环调用检测是否接收完成 //========================================================== _Bool ESP8266_WaitRecive(void) {if(esp8266_cnt == 0)//如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数return REV_WAIT;if(esp8266_cnt == esp8266_cntPre)//如果上一次的值和这次相同,则说明接收完毕{esp8266_cnt = 0;//清0接收计数return REV_OK;//返回接收完成标志}esp8266_cntPre = esp8266_cnt;//置为相同return REV_WAIT;//返回接收未完成标志 } //========================================================== //函数名称:ESP8266_SendCmd // //函数功能:发送命令 // //入口参数:cmd:命令 //res:需要检查的返回指令 // //返回参数:0-成功1-失败 // //说明: //========================================================== _Bool ESP8266_SendCmd(char *cmd, char *res) {unsigned char timeOut = 255;Usart_SendString(USART3, (unsigned char *)cmd, strlen((const char *)cmd));while(timeOut--){if(ESP8266_WaitRecive() == REV_OK)//如果收到数据{if(strstr((const char *)esp8266_buf, res) != NULL)//如果检索到关键词{ESP8266_Clear();//清空缓存return 0;}}delay_ms(10);}return 1; } //========================================================== //函数名称:ESP8266_SendData // //函数功能:发送数据 // //入口参数:data:数据 //len:长度 // //返回参数:无 // //说明: //========================================================== void ESP8266_SendData(unsigned char *data, unsigned short len) {char cmdBuf[32];ESP8266_Clear();//清空接收缓存sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len);//发送命令if(!ESP8266_SendCmd(cmdBuf, ">"))//收到‘>’时可以发送数据{Usart_SendString(USART3, data, len);//发送设备连接请求数据} } //========================================================== //函数名称:ESP8266_GetIPD // //函数功能:获取平台返回的数据 // //入口参数:等待的时间(乘以10ms) // //返回参数:平台返回的原始数据 // //说明:不同网络设备返回的格式不同,需要去调试 //如ESP8266的返回格式为"+IPD,x:yyy"x代表数据长度,yyy是数据内容 //========================================================== unsigned char *ESP8266_GetIPD(unsigned short timeOut) {char *ptrIPD = NULL;do{if(ESP8266_WaitRecive() == REV_OK)//如果接收完成{ptrIPD = strstr((char *)esp8266_buf, "IPD,");//搜索“IPD”头if(ptrIPD == NULL)//如果没找到,可能是IPD头的延迟,还是需要等待一会,但不会超过设定的时间{//UsartPrintf(USART_DEBUG, "\"IPD\" not found\r\n");}else{ptrIPD = strchr(ptrIPD, ':');//找到':'if(ptrIPD != NULL){ptrIPD++;return (unsigned char *)(ptrIPD);}elsereturn NULL;}}delay_ms(5);//延时等待} while(timeOut--);return NULL;//超时还未找到,返回空指针 } //========================================================== //函数名称:ESP8266_Init // //函数功能:初始化ESP8266 // //入口参数:无 // //返回参数:无 // //说明: //========================================================== void ESP8266_Init(void) {ESP8266_Clear(); while(ESP8266_SendCmd("+++", "")); while(ESP8266_SendCmd("AT+RESTORE\r\n", "OK")); while(ESP8266_SendCmd("AT\r\n", "OK")); ESP8266_SendCmd("AT+RST\r\n", ""); delay_ms(500); ESP8266_SendCmd("AT+CIPCLOSE\r\n", ""); delay_ms(500); while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK")); while(ESP8266_SendCmd("AT+CIPMUX=0\r\n", "OK")); while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "WIFI GOT IP")); } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
接下来就是对Onenet云操作的一些函数了:
// onenet.c //========================================================== //函数名称:OneNet_DevLink // //函数功能:与onenet创建连接 // //入口参数:无 // //返回参数:1-成功0-失败 // //说明:与onenet平台建立连接 //========================================================== _Bool OneNet_DevLink(void) {MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};//协议包unsigned char *dataPtr;char authorization_buf[160];_Bool status = 1;OneNET_Authorization("2018-10-31", PROID, 1956499200, ACCESS_KEY, DEVICE_NAME,authorization_buf, sizeof(authorization_buf), 0);UsartPrintf(USART_DEBUG, "OneNET_DevLink\r\n""NAME: %s,PROID: %s,KEY:%s\r\n" , DEVICE_NAME, PROID, authorization_buf);if(MQTT_PacketConnect(PROID, authorization_buf, DEVICE_NAME, 256, 1, MQTT_QOS_LEVEL0, NULL, NULL, 0, &mqttPacket) == 0){ESP8266_SendData(mqttPacket._data, mqttPacket._len);//上传平台dataPtr = ESP8266_GetIPD(250);//等待平台响应if(dataPtr != NULL){if(MQTT_UnPacketRecv(dataPtr) == MQTT_PKT_CONNACK){switch(MQTT_UnPacketConnectAck(dataPtr)){case 0:UsartPrintf(USART_DEBUG, "Tips:连接成功\r\n");status = 0;break;case 1:UsartPrintf(USART_DEBUG, "WARN:连接失败:协议错误\r\n");break;case 2:UsartPrintf(USART_DEBUG, "WARN:连接失败:非法的clientid\r\n");break;case 3:UsartPrintf(USART_DEBUG, "WARN:连接失败:服务器失败\r\n");break;case 4:UsartPrintf(USART_DEBUG, "WARN:连接失败:用户名或密码错误\r\n");break;case 5:UsartPrintf(USART_DEBUG, "WARN:连接失败:非法链接(比如token非法)\r\n");break;default:UsartPrintf(USART_DEBUG, "ERR:连接失败:未知错误\r\n");break;}}}MQTT_DeleteBuffer(&mqttPacket);//删包}elseUsartPrintf(USART_DEBUG, "WARN:MQTT_PacketConnect Failed\r\n");return status; } extern u8 temp,humi,message; unsigned char OneNet_FillBuf(char *buf) {char text[256];memset(text, 0, sizeof(text));strcpy(buf, "{\"id\":\"1\",\"params\":{");memset(text, 0, sizeof(text));sprintf(text, "\"tem\":{\"value\":%d},", temp);strcat(buf, text);memset(text, 0, sizeof(text));sprintf(text, "\"hmi\":{\"value\":%d},", humi);strcat(buf, text);memset(text, 0, sizeof(text));sprintf(text, "\"message\":{\"value\":%d},", message);strcat(buf, text);memset(text, 0, sizeof(text));sprintf(text, "\"monitor\":{\"value\":%s},", Monitor_info.Monitor_Status ? "true" : "false");strcat(buf, text); //printf("Buf after monitor: %s\n", buf); // 打印拼接后的bufmemset(text, 0, sizeof(text));sprintf(text, "\"led\":{\"value\":%s}", led_info.Led_Status ? "true" : "false");strcat(buf, text); //printf("Buf after led: %s\n", buf); // 打印拼接后的bufstrcat(buf, "}}");return strlen(buf); } //========================================================== //函数名称:OneNet_SendData // //函数功能:上传数据到平台 // //入口参数:type:发送数据的格式 // //返回参数:无 // //说明: //========================================================== void OneNet_SendData(void) {MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};//协议包char buf[256];short body_len = 0, i = 0; //UsartPrintf(USART_DEBUG, "Tips:OneNet_SendData-MQTT\r\n");memset(buf, 0, sizeof(buf));body_len = OneNet_FillBuf(buf);//获取当前需要发送的数据流的总长度if(body_len){if(MQTT_PacketSaveData(PROID, DEVICE_NAME, body_len, NULL, &mqttPacket) == 0)//封包{for(; i < body_len; i++)mqttPacket._data[mqttPacket._len++] = buf[i];ESP8266_SendData(mqttPacket._data, mqttPacket._len);//上传数据到平台 //UsartPrintf(USART_DEBUG, "Send %d Bytes\r\n", mqttPacket._len);MQTT_DeleteBuffer(&mqttPacket);//删包}elseUsartPrintf(USART_DEBUG, "WARN:EDP_NewBuffer Failed\r\n");} } //========================================================== //函数名称:OneNET_Publish // //函数功能:发布消息 // //入口参数:topic:发布的主题 //msg:消息内容 // //返回参数:无 // //说明: //========================================================== void OneNET_Publish(const char *topic, const char *msg) {MQTT_PACKET_STRUCTURE mqtt_packet = {NULL, 0, 0, 0};//协议包UsartPrintf(USART_DEBUG, "Publish Topic: %s, Msg: %s\r\n", topic, msg);if(MQTT_PacketPublish(MQTT_PUBLISH_ID, topic, msg, strlen(msg), MQTT_QOS_LEVEL0, 0, 1, &mqtt_packet) == 0){ESP8266_SendData(mqtt_packet._data, mqtt_packet._len);//向平台发送订阅请求MQTT_DeleteBuffer(&mqtt_packet);//删包} } //========================================================== //函数名称:OneNET_Subscribe // //函数功能:订阅 // //入口参数:无 // //返回参数:无 // //说明: //========================================================== void OneNET_Subscribe(void) {MQTT_PACKET_STRUCTURE mqtt_packet = {NULL, 0, 0, 0};//协议包char topic_buf[56];const char *topic = topic_buf;snprintf(topic_buf, sizeof(topic_buf), "$sys/%s/%s/thing/property/set", PROID, DEVICE_NAME);UsartPrintf(USART_DEBUG, "Subscribe Topic: %s\r\n", topic_buf);if(MQTT_PacketSubscribe(MQTT_SUBSCRIBE_ID, MQTT_QOS_LEVEL0, &topic, 1, &mqtt_packet) == 0){ESP8266_SendData(mqtt_packet._data, mqtt_packet._len);//向平台发送订阅请求MQTT_DeleteBuffer(&mqtt_packet);//删包} } //========================================================== //函数名称:OneNet_RevPro // //函数功能:平台返回数据检测 // //入口参数:dataPtr:平台返回的数据 // //返回参数:无 // //说明: //========================================================== void OneNet_RevPro(unsigned char *cmd) {char *req_payload = NULL;char *cmdid_topic = NULL;unsigned short topic_len = 0;unsigned short req_len = 0;unsigned char qos = 0;static unsigned short pkt_id = 0;unsigned char type = 0;short result = 0;cJSON *raw_json, *params_json, *led_json, *monitor_json;type = MQTT_UnPacketRecv(cmd);switch(type){case MQTT_PKT_PUBLISH://接收的Publish消息result = MQTT_UnPacketPublish(cmd, &cmdid_topic, &topic_len, &req_payload, &req_len, &qos, &pkt_id);if(result == 0){UsartPrintf(USART_DEBUG, "topic: %s, topic_len: %d, payload: %s, payload_len: %d\r\n",cmdid_topic, topic_len, req_payload, req_len);raw_json = cJSON_Parse(req_payload);params_json = cJSON_GetObjectItem(raw_json,"params");led_json = cJSON_GetObjectItem(params_json,"led");if(led_json != NULL){if(led_json->type == cJSON_True) control = 1;else if(led_json->type == cJSON_False) control = 2;}monitor_json = cJSON_GetObjectItem(params_json,"monitor");if(monitor_json !=NULL){if(monitor_json->type == cJSON_True) control = 4;else if(monitor_json->type == cJSON_False) control = 3;}cJSON_Delete(raw_json);}case MQTT_PKT_PUBACK://发送Publish消息,平台回复的Ackif(MQTT_UnPacketPublishAck(cmd) == 0)UsartPrintf(USART_DEBUG, "Tips:MQTT Publish Send OK\r\n");break;case MQTT_PKT_SUBACK://发送Subscribe消息的Ackif(MQTT_UnPacketSubscribe(cmd) == 0)UsartPrintf(USART_DEBUG, "Tips:MQTT Subscribe OK\r\n");elseUsartPrintf(USART_DEBUG, "Tips:MQTT Subscribe Err\r\n");break;default:result = -1;break;}ESP8266_Clear();//清空缓存if(result == -1)return;if(type == MQTT_PKT_CMD || type == MQTT_PKT_PUBLISH){MQTT_FreeBuffer(cmdid_topic);MQTT_FreeBuffer(req_payload);} } 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
2.2 获取当地时间及天气预报
这部分内容就更简单了,主要是调用了一下相关的API,获取了包括时间戳、天气情况等进行预测。考虑这部分不会经常使用,我们这里是在软件初始化之后就立即获取,得到所需的数据后先存储下来,定时对RTC进行时间校准。而对于天气情况等信息则是由语音下达命令之后,再打印出来,以免重复调度占用资源。接下来是我所用的几个API接口:
//心知天气API #define Xinzhi_TCP"AT+CIPSTART=\"TCP\",\"api.seniverse.com\",80\r\n" //拼多多API #define Time_TCP"AT+CIPSTART=\"TCP\",\"qapi.pinduoduo.com\",80\r\n" //获取当天天气 #define Now_GET"GET https://api.seniverse.com/v3/weather/now.json?key=[你申请的密钥]=shenyang&language=en&unit=c\r\n" //获取天气预报 #define Forcast_GET"GET https://api.seniverse.com/v3/weather/daily.json?key=[你申请的密钥]=shenyang&language=en&unit=c&start=0&days=3\r\n" //获取拼多多时间戳 #define Time_GET"GET https://api.pinduoduo.com/api/server/_stm\r\n" 123456789101112
之后就可以得到相对于的数据包,大家按照需求进行解包即可。
免责声明:本次项目的部分代码也是参考了很多优秀作者开源的项目,再次感谢,如有侵权可联系笔者。
网址:毕设拯救计划(一)基于FreeRTOS的智能家居(STM32+Onenet云) https://www.yuejiaxmz.com/news/view/137076
相关内容
STM32毕业设计——基于STM32+MQTT+WiFi技术的智能家居系统设计与实现(毕业论文+程序源码)——智能家居系统基于STM32的智能家居环境监测与控制系统毕业设计
基于STM32的智能节能风扇的设计与实现
基于STM32的智能扫地机器人设计
基于STM32的语音控制智能家居系统设计
一种基于STM32的智能家居控制系统
基于STM32的智能家居安防AI系统:OpenCV、TCP/HTTP、RFID、UART技术设计思路
基于 STM32 的语音识别智能家居控制系统的设计(LD3320语音识别芯片+ESP8266 WIFI模块+DHT11温湿度采集+MQ系列 烟雾及可燃气体+蜂鸣器+步进电机模拟窗帘+OLED液晶显示+
基于STM32的智能家居语音控制系统:集成LD3320、ESP8266设计流程
基于STM32的智能家居系统:MQTT、AT指令、TCP\HTTP、IIC技术