工程与源代码下载地址
Gitee:源码点这里
Github: 源码点这里
简单分析一下
序号 | 名称 | 数量 |
---|---|---|
1 | STM32F103ZET6开发板 | 1 |
2 | 4.3寸电容屏 | 1 |
3 | DHT11模块 | 1 |
4 | MQ-2 气体检测模块 | 1 |
5 | BH1750 光照强度检测模块 | 1 |
6 | ESP8266 WIFI 模块 | 1 |
7 | 3.3V4路继电器模块 | 1 |
8 | L298N 驱动模块 | 1 |
9 | SG90舵机 | 2 |
10 | 电线、杜邦线 | 若干 |
11 | 12V电机 | 2 |
12 | 12V灯泡 | 4 |
UI界面使用图标均使用阿里图标库中的图案,具体请看UI界面设计。
基于STM32的物联网智能家居系统
定时器 | 任务 |
---|---|
TIM1 | 舵机1 |
TIM2 | L298N电机调速 |
TIM3 | 心跳包定时发送 |
TIM4 | 保存云端下发数据 |
TIM8 | 舵机2 |
UCOSIII软件定时器:蜂鸣器控制
STM32中,使用UCOS-III系统,将功能分成了8个小任务分别实现。具体每个模块如何使用网上教程很多,这里就不一一贴出,有需要可以自行搜索。详细代码可以参考开源连接
将每个模块读取数据单独封装一个函数,将函数放入任务中,再将数据读取至数组中,完毕后通过消息队列发送到需要数据的任务。对每个模块数据的大小要有了解,不然有可能会出现数组越界卡死的情况。考虑到居家情况,可以对数据做一个小范围的限幅。同时在这里通过全局变量更新数据显示界面的数据可以让数据显示更加流畅
这里注意模块读取数据有严格时序,使用delay函数时,会引起任务调度,所以数据读取函数前后需要给调度器上锁,防止调度器打乱时序,这里的MQ-2模块是使用ADC采集电压与DMA传输数据,不受时序影响,所以就没有加锁
void datacollection_task(void *p_arg)
{
int SensorData[5] = {0};
char temp_data[7];
char humi_data[7];
char lux_data[25];
char ppm_data[10];
OS_ERR err;
DHT11_Init();
iic_by30_init();
while(1)
{
OSIntEnter(); //调度器加锁
DHT11_Read_Data(&SensorData[0],&SensorData[1]);
get_sunlight_value(&SensorData[2]);
OSIntExit(); //调度器解锁
MQ135_GetValue(&SensorData[3]);
// 如果进入了数据显示界面则更新数据
if(windos_flag){
sprintf(temp_data,"%d C",SensorData[0]);
sprintf(humi_data,"%d%%",SensorData[1]);
sprintf(lux_data,"%d Lux",SensorData[2]);
sprintf(ppm_data,"%d PPM",SensorData[3]);
TEXT_SetText(hWin_temp_Edit,temp_data);
TEXT_SetText(hWin_humi_Edit,humi_data);
TEXT_SetText(hWin_lux_Edit,lux_data);
TEXT_SetText(hWin_ppm_Edit,ppm_data);
}
if(SensorData[2] >= 500)
SensorData[2] = 500;
/* 发送消息队列给OneNet上传任务 */
OSQPost((OS_Q *)&SensorDataMsg, //消息变量指针
(void *)SensorData, //要发送的数据的指针,将内存块首地址通过队列“发送出去”
(OS_MSG_SIZE )30, //数据字节大小
(OS_OPT )OS_OPT_POST_FIFO | OS_OPT_POST_ALL, //先进先出和发布给全部任务的形式
(OS_ERR *)&err); //返回错误类型
//printf("已经发送信号量给上云任务!\r\n");
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);//延时1ms
}
}
这里主要是接收采集任务发来的数据,然后根据数据的不同来控制不同的外设。简单判断一下即可,这里每个判断加一个标志位与手动控制区分开,这样就可以通过按钮来切换自动或者手动控制外设。
void Autocontrol_task(void *p_arg)
{
OS_ERR err;
OS_MSG_SIZE MsgSize;
while(1)
{
int *TEXT_Buffer;
TEXT_Buffer = OSQPend ((OS_Q *)&SensorDataMsg,
(OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING, //如果没有获取到信号量就等待
(OS_MSG_SIZE *)&MsgSize,
(CPU_TS *)0,
(OS_ERR *)&err);
/*
TEXT_Buffer[0] = 温度
TEXT_Buffer[1] = 湿度
TEXT_Buffer[2] = 光强
TEXT_Buffer[3] = 煤气含量检测
*/
/**********************************
场景1:出现煤气泄漏的情况
**********************************/
/* 可能出现煤气泄漏 */
if(TEXT_Buffer[3] >= 300 && control_flag){
printf("场景1\r\n");
/* 蜂鸣器报警 */
OSTmrStart(&BeepTmr,&err);
/* 开窗通风 */
TIM_SetCompare1(TIM8,175);
/* 开排气扇 */
MotorPWM = 1;
}else if(TEXT_Buffer[3] <= 300 && control_flag){
/* 关闭蜂鸣器报警 */
BEEP = 0;
OSTmrStop(&BeepTmr,OS_OPT_TMR_NONE,0,&err);
/* 关闭窗口 */
TIM_SetCompare1(TIM8,195);
/* 关闭排气扇 */
MotorPWM = 0;
}
/**********************************
场景2:根据光照强度调节窗帘的开关
**********************************/
if(TEXT_Buffer[2] <= 10 && control_flag){
/* 到了深夜 */
TIM_SetCompare1(TIM1,195);
}else if(TEXT_Buffer[2] <= 30 && TEXT_Buffer[2] >= 10 && control_flag){
/* 有一点光 */
TIM_SetCompare1(TIM1,190); // 45度
}else if(TEXT_Buffer[2] <= 120 && TEXT_Buffer[2] >= 30 && control_flag){
/* 有光照 */
TIM_SetCompare1(TIM1,185); // 90度
}else if (TEXT_Buffer[2] <= 400 && TEXT_Buffer[2] >= 120 && control_flag){
/* 日出 */
TIM_SetCompare1(TIM1,175); // 180度
}
/**********************************
场景3:根据温度开关风扇
**********************************/
if(TEXT_Buffer[1] >= 30 && TEXT_Buffer[1] <= 35 && control_flag ){
TIM_SetCompare2(TIM2,50);
}
else if(TEXT_Buffer[1] >= 35 && control_flag){
TIM_SetCompare2(TIM2,100);
}else if (TEXT_Buffer[1] <= 30 && control_flag)
{
TIM_SetCompare2(TIM2,0);
}
}
}
接下来就是要将数据发送到云端和接受云端数据了,这个可以选择的平台和教程也很多,这里贴几个给大家参考,我是选择使用电信部的OneNet平台。
连接OneNet平台
STM32连接OneNet
大致步骤就是上网站注册,拿到IP、端口号、产品ID、设备号、密码。通过TCP连接到服务器之后使用MQTT协议发送报文就可以建立连接了。具体参考开源代码
注意OneNet是选择多协议接入,不是选MQTT套件
这里OneNet的IP和端口号是固定的,直接复制即可
IP | 端口号 |
---|---|
183.230.40.39 | 6002 |
#define PRODUCTKEY "183.230.40.39" //OneNetIP
#define PRODUCTKEY_LEN strlen(PRODUCTKEY)
#define DEVICENAME "927136809" //设备号
#define DEVICENAME_LEN strlen(DEVICENAME) //设备名长度
#define P_TOPIC_NAME "$dp" //需要发布的主题
#define PRODUCTID "503697" //产品ID
#define AUTHENTICATION "123456" //密码
在任务中,首先初始化WIFI模块、MQTT堆栈、OneNet连接信息等,然后与OneNet发起TCP连接,连接成功后。向OneNet发送订阅报文,订阅成功后,启动定时器3,定时30S,每30S向OneNet发送一次心跳,保证我们时刻在线,同时启动上传数据任务和下发命令处理来上传我们采集的数据和处理云端对下位机的控制。连接任务具体见下代码,定时器中断、WIFI连接代码和TCP代码参考开源代码,这里就不一一贴出
//连接OneNet任务
void connectOneNet_task(void *p_arg)
{
OS_ERR err;
WiFi_ResetIO_Init(); //初始化WiFi的复位IO
MQTT_Buff_Init(); //初始化接收,发送,命令数据的 缓冲区 以及各状态参数
OneNetIoT_Parameter_Init(); //初始化连接OneNet平台MQTT服务器的参数
while(1) //主循环
{
/*--------------------------------------------------------------------*/
/* Connect_flag=1同服务器建立了连接,我们可以发布数据和接收推送了 */
/*--------------------------------------------------------------------*/
if(Connect_flag==1)
{
/*-------------------------------------------------------------*/
/* 处理发送缓冲区数据 */
/*-------------------------------------------------------------*/
if(MQTT_TxDataOutPtr != MQTT_TxDataInPtr)
{ //if成立的话,说明发送缓冲区有数据了
//3种情况可进入if
//第1种:0x10 连接报文
//第2种:0x82 订阅报文,且ConnectPack_flag置位,表示连接报文成功
//第3种:SubcribePack_flag置位,说明连接和订阅均成功,其他报文可发
if((MQTT_TxDataOutPtr[2]==0x10)||((MQTT_TxDataOutPtr[2]==0x82)&&(ConnectPack_flag==1))||(SubcribePack_flag==1))
{
printf("发送数据:0x%x\r\n",MQTT_TxDataOutPtr[2]); //串口提示信息
MQTT_TxData(MQTT_TxDataOutPtr); //发送数据
MQTT_TxDataOutPtr += BUFF_UNIT; //指针下移
if(MQTT_TxDataOutPtr==MQTT_TxDataEndPtr) //如果指针到缓冲区尾部了
MQTT_TxDataOutPtr = MQTT_TxDataBuf[0]; //指针归位到缓冲区开头
}
}//处理发送缓冲区数据的else if分支结尾
/*-------------------------------------------------------------*/
/* 处理接收缓冲区数据 */
/*-------------------------------------------------------------*/
if(MQTT_RxDataOutPtr != MQTT_RxDataInPtr){ //if成立的话,说明接收缓冲区有数据了
printf("接收到数据:");
/*-----------------------------------------------------*/
/* 处理CONNACK报文 */
/*-----------------------------------------------------*/
//if判断,如果第一个字节是0x20,表示收到的是CONNACK报文
//接着我们要判断第4个字节,看看CONNECT报文是否成功
if(MQTT_RxDataOutPtr[2]==0x20){
switch(MQTT_RxDataOutPtr[5]){
case 0x00 : printf("CONNECT报文成功\r\n"); //串口输出信息
ConnectPack_flag = 1; //CONNECT报文成功,订阅报文可发
break; //跳出分支case 0x00
case 0x01 : printf("连接已拒绝,不支持的协议版本,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case 0x01
case 0x02 : printf("连接已拒绝,不合格的客户端标识符,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case 0x02
case 0x03 : printf("连接已拒绝,服务端不可用,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case 0x03
case 0x04 : printf("连接已拒绝,无效的用户名或密码,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case 0x04
case 0x05 : printf("连接已拒绝,未授权,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case 0x05
default : printf("连接已拒绝,未知状态,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支case default
}
}
//if判断,第一个字节是0x90,表示收到的是SUBACK报文
//接着我们要判断订阅回复,看看是不是成功
else if(MQTT_RxDataOutPtr[2]==0x90){
switch(MQTT_RxDataOutPtr[6]){
case 0x00 :
case 0x01 : printf("订阅成功\r\n"); //串口输出信息
SubcribePack_flag = 1; //SubcribePack_flag置1,表示订阅报文成功,其他报文可发送
Ping_flag = 0; //Ping_flag清零
LED1 = 0; //连接指示灯
TIM3_ENABLE_30S(); //启动30s的PING定时器
OS_TaskResume(&TASK9_TCB,&err); //启动数据上传任务
OS_TaskResume(&TASK10_TCB,&err); //启动下发命令处理任务
break; //跳出分支
default : printf("订阅失败,准备重启\r\n"); //串口输出信息
Connect_flag = 0; //Connect_flag置零,重启连接
break; //跳出分支
}
}
//if判断,第一个字节是0xD0,表示收到的是PINGRESP报文
else if(MQTT_RxDataOutPtr[2]==0xD0)
{
printf("PING报文回复\r\n"); //串口输出信息
if(Ping_flag==1){ //如果Ping_flag=1,表示第一次发送
Ping_flag = 0; //要清除Ping_flag标志
}else if(Ping_flag>1){ //如果Ping_flag>1,表示是多次发送了,而且是2s间隔的快速发送
Ping_flag = 0; //要清除Ping_flag标志
TIM3_ENABLE_30S(); //PING定时器重回30s的时间
}
}
//if判断,如果第一个字节是0x30,表示收到的是服务器发来的推送数据
//我们要提取控制命令
else if((MQTT_RxDataOutPtr[2]==0x30))
{
printf("服务器等级0推送\r\n"); //串口输出信息
MQTT_DealPushdata_Qs0(MQTT_RxDataOutPtr); //处理等级0推送数据
}
MQTT_RxDataOutPtr += BUFF_UNIT; //指针下移
if(MQTT_RxDataOutPtr==MQTT_RxDataEndPtr) //如果指针到缓冲区尾部了
MQTT_RxDataOutPtr = MQTT_RxDataBuf[0]; //指针归位到缓冲区开头
}//处理接收缓冲区数据的else if分支结尾
}//Connect_flag=1的if分支的结尾
/*--------------------------------------------------------------------*/
/* Connect_flag=0同服务器断开了连接,我们要重启连接服务器 */
/*--------------------------------------------------------------------*/
else
{
printf("需要连接服务器\r\n"); //串口输出信息
TIM_Cmd(TIM4,DISABLE); //关闭TIM4
TIM_Cmd(TIM3,DISABLE); //关闭TIM3
WiFi_RxCounter=0; //WiFi接收数据量变量清零
memset(WiFi_RX_BUF,0,WiFi_RXBUFF_SIZE); //清空WiFi接收缓冲区
if(WiFi_Connect_IoTServer()==0)
{ //如果WiFi连接云服务器函数返回0,表示正确,进入if
printf("建立TCP连接成功\r\n"); //串口输出信息
Connect_flag = 1; //Connect_flag置1,表示连接成功
WiFi_RxCounter=0; //WiFi接收数据量变量清零
memset(WiFi_RX_BUF,0,WiFi_RXBUFF_SIZE); //清空WiFi接收缓冲区
MQTT_Buff_ReInit(); //重新初始化发送缓冲区
}
}
delay_ms(100);
}
}
成功连接上OneNet后,启动数据上传任务,接收传感器的数据,并将数据拼接成MQTT发布报文,发送到云端上,这里每5秒上传一次数据,因为前端显示那边是5s更新一次显示,这样刚好可以同步更新。
这里注意报文每个数据的拼接方式,平台不同,拼接方式会有不同,但基本上大同小异,按使用网站的手册修改即可
void dataupload_task(void *p_arg)
{
OS_ERR err;
OS_MSG_SIZE MsgSize;
while(1)
{
char head1[3];
char temp[50]; //定义一个临时缓冲区1,不包括报头
char tempAll[150]; //定义一个临时缓冲区2,包括所有数据
int dataLen = 0; //报文长度
int *TEXT_Buffer;
TEXT_Buffer = OSQPend ((OS_Q *)&SensorDataMsg,
(OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING, //如果没有获取到信号量就等待
(OS_MSG_SIZE *)&MsgSize,
(CPU_TS *)0,
(OS_ERR *)&err);
//printf("已经接受到信号量!\r\n");
//printf("接受到的温:%d,湿度:%d,光:%d,空:%d\r\n",TEXT_Buffer[0],TEXT_Buffer[1],TEXT_Buffer[2],TEXT_Buffer[3]);
memset(temp, 0, 50); //清空缓冲区1
memset(tempAll, 0, 100); //清空缓冲区2
memset(head1, 0, 3); //清空MQTT头
sprintf(temp,"{\"temperature\":\"%d\",\"humidity\":\"%d\",\"Lux\":\"%d\",\"ppm\":\"%d\"}",
TEXT_Buffer[0], TEXT_Buffer[1],TEXT_Buffer[2],TEXT_Buffer[3]);//构建报文
head1[0] = 0x03; //固定报头
head1[1] = 0x00; //固定报头
head1[2] = strlen(temp); //剩余长度
sprintf(tempAll, "%c%c%c%s", head1[0], head1[1], head1[2], temp);
dataLen = strlen(temp) + 3;
//printf("%s",tempAll);
MQTT_PublishQs0(P_TOPIC_NAME,tempAll, dataLen);//添加数据,发布给服务器
//printf("Seed Data OK!\r\n ");
OSTimeDlyHMSM(0,0,5,0,OS_OPT_TIME_PERIODIC,&err);//延时1ms
}
这里检测接收的数据是否与我们预先设定的命令相符,如果相符,则执行我们设定的动作。具体见下代码
/* 云端命令处理任务 */
void cmd_task(void *p_arg)
{
OS_ERR err;
char online_cmd[20];
char *cmd;
unsigned char OnlinePwmControl;
while(1)
{
/* 云端指令处理 */
if(MQTT_CMDOutPtr != MQTT_CMDInPtr) //if成立的话,说明命令缓冲区有数据了
{
printf("命令:%s\r\n",&MQTT_CMDOutPtr[2]); //串口输出信息
/* 接收到云控制信号之后关闭自动控制模式 */
control_flag = 0;
/**************************
接受到控制灯的指令
**************************/
if(!memcmp(&MQTT_CMDOutPtr[2],LED1_ON,strlen(LED1_ON)))//判断指令,如果是LED1_ON
{
Light1 = 1;
printf("已打开LED1!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED1_OFF,strlen(LED1_OFF)))//判断指令,如果是LED1_ON
{
Light1 = 0;
printf("已关闭LED1!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED2_ON,strlen(LED2_ON)))//判断指令,如果是LED1_ON
{
Light2 = 1;
printf("已打开LED2!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED2_OFF,strlen(LED2_OFF)))//判断指令,如果是LED1_ON
{
Light2 = 0;
printf("已关闭LED2!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED3_ON,strlen(LED3_ON)))//判断指令,如果是LED1_ON
{
Light3 = 1;
printf("已打开LED3!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED3_OFF,strlen(LED3_OFF)))//判断指令,如果是LED1_ON
{
Light3 = 0;
printf("已关闭LED3!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED4_ON,strlen(LED4_ON)))//判断指令,如果是LED1_ON
{
Light4 = 1;
printf("已打开LED4!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED4_OFF,strlen(LED4_OFF)))//判断指令,如果是LED1_ON
{
Light4 = 0;
printf("已关闭LED4!\r\n");
}
/**************************
接受到窗户和排气扇的指令
**************************/
if(!memcmp(&MQTT_CMDOutPtr[2],Paiqishan_ON,strlen(Paiqishan_ON)))//判断指令,如果是LED1_ON
{
MotorPWM = 1;
printf("已打开排气扇!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],Paiqishan_OFF,strlen(Paiqishan_OFF)))//判断指令,如果是LED1_ON
{
MotorPWM = 0;
printf("已关闭排气扇!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],Windos_ON,strlen(Windos_ON)))//判断指令,如果是LED1_ON
{
TIM_SetCompare1(TIM8,175); // 180度;
printf("已打开窗户!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],Windos_OFF,strlen(Windos_OFF)))//判断指令,如果是LED1_ON
{
TIM_SetCompare1(TIM8,195);
printf("已关闭窗户!\r\n");
}
/**************************
接受到控制电机的指令
**************************/
if(!memcmp(&MQTT_CMDOutPtr[2],dianji,strlen(dianji)))
{
/*
OneNet控制命令为:fangshan:控制数值
控制数值范围为:0 - 100
取出指令中的控制数值字符串并转化为u8类型
*/
strcpy(online_cmd,(char*)&MQTT_CMDOutPtr[2]);
cmd = &online_cmd[9];
OnlinePwmControl = atoi(cmd);
printf("%d",OnlinePwmControl);
TIM_SetCompare2(TIM2,OnlinePwmControl);
}
//不做处理,后面会发送状态
else printf("未知指令\r\n"); //串口输出信息
MQTT_CMDOutPtr += BUFF_UNIT; //指针下移
if(MQTT_CMDOutPtr==MQTT_CMDEndPtr) //如果指针到缓冲区尾部了
MQTT_CMDOutPtr = MQTT_CMDBuf[0]; //指针归位到缓冲区开头
}
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);//延时1ms
}
}
直接在循环中调用UI显示和触摸检测即可
//UI显示任务
void emwindemo_task(void *p_arg)
{
while(1)
{
Main_Ui();
}
}
//触摸检测任务
void touch_task(void *p_arg)
{
OS_ERR err;
while(1)
{
GUI_TOUCH_Exec();
OSTimeDlyHMSM(0,0,0,5,OS_OPT_TIME_PERIODIC,&err);//延时5ms
}
}
用指示灯闪烁来证明系统正在工作,堆栈检测是前期做UI时,监控UI任务是否有出现爆栈的情况
//LED心跳灯
void SystemLED_task(void *p_arg)
{
OS_ERR err;
CPU_STK_SIZE free,used;
while(1)
{
LED0 = !LED0;
OSTaskStkChk (&EmwindemoTaskTCB,&free,&used,&err);
//printf("EmwindemoTaskTCB used/free:%d/%d usage:%%%d\r\n",used,free,(used*100)/(used+free));
OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_PERIODIC,&err);//延时500ms
}
}
UI界面这边是使用STEMWIN来制作,因为F1的RAM比较小,在显示大量控件和图片时会出现卡顿的情况,所以UI整体只是将基本控件放置在屏幕中,没有做换肤和美化。
STEMWIN这个库,使用起来非常方便,在界面构建时,可以使用自带的GUIBuilder来构建,在完成基本布局之后可以自动生成C代码,在具体功能调试时,可以使用仿真文件进行仿真,功能完成之后,再移植到板子上。
UI界面代码中,样式占了大多数,具体的样式代码就不一一贴出,这里就只贴出关键功能代码,具体可以参考开源代码
主界面的布局如下图所示,使用Dialog为主窗口,中间使用IconView控件,在IconView中添加5个小图标作为5个二级界面的入口,右上角使用系统RTC来记录时间,使用STEMWIN的软件定时器更新到屏幕上显示。
这里偷了一个小赖,可以直接将图标转化成.c文件保存在ROM中,直接读取显示,中文也只是按照界面做了部分字库。
图片显示具体方式参考:STEMWIN显示BMP
中文显示具体方式参考:STEMWINC字库制作方式
向IconView添加图标
ICONVIEW_hItem = WM_GetDialogItem(pMsg->hWin, ID_ICONVIEW_0);
ICONVIEW_SetIconAlign(ICONVIEW_hItem, ICONVIEW_IA_TOP);
ICONVIEW_AddBitmapItem(ICONVIEW_hItem,&bmdengguang,"灯光控制");
ICONVIEW_AddBitmapItem(ICONVIEW_hItem,&bmdata_icon,"家庭数据");
ICONVIEW_AddBitmapItem(ICONVIEW_hItem,&bmdianqi_icon,"电器控制");
ICONVIEW_AddBitmapItem(ICONVIEW_hItem,&bmshezhi,"系统设置");
ICONVIEW_AddBitmapItem(ICONVIEW_hItem,&bmguanyu_icon,"关于");
ICONVIEW_SetFont(ICONVIEW_hItem,&GUI_Fontsongit24);
图标被按下
case WM_NOTIFY_PARENT:
Id = WM_GetId(pMsg->hWinSrc);
NCode = pMsg->Data.v;
switch(Id) {
case ID_ICONVIEW_0: // Notifications sent by 'Iconview'
switch(NCode) {
case WM_NOTIFICATION_CLICKED:
// USER START (Optionally insert code for reacting on notification message)
// USER END
break;
case WM_NOTIFICATION_RELEASED:
/* 当小图标按下且松开时 检测是哪个小图标被按下 */
indx = ICONVIEW_GetSel(WM_GetDialogItem(pMsg->hWin, ID_ICONVIEW_0));
/* 根据按下的小图标来切换界面 */
switch(indx){
/* 如果是灯光界面被按下 */
case Light:
GUI_EndDialog(pMsg->hWin,0);
CreateLightFramewin();
break;
/* 如果是关于界面被按下 */
case info:
GUI_EndDialog(pMsg->hWin,0);
CreatehuanyinFramewin();
break;
/* 如果是居家数据界面被按下 */
case data:
GUI_EndDialog(pMsg->hWin,0);
CreateDataUi();
break;
/* 如果是电气控制界面被按下 */
case control:
GUI_EndDialog(pMsg->hWin,0);
ElectricalControl();
break;
/* 如果是系统配置界面被按下 */
case config:
GUI_EndDialog(pMsg->hWin,0);
CreateConfig();
break;
}
break;
在创建界面时创建一个1s周期的软件定时器
WM_HWIN CreateFramewin(void) {
WM_HWIN hWin;
/* 定时器定义 */
WM_HTIMER hTimer;
WM_SetCreateFlags(WM_CF_MEMDEV_ON_REDRAW);
hWin = GUI_CreateDialogBox(_aDialogCreate, GUI_COUNTOF(_aDialogCreate), _cbDialog, WM_HBKWIN, 0, 0);
/* 设定定时器周期和应用窗体 */
hTimer = WM_CreateTimer(WM_GetClientWindow(hWin), 0, 1000, 0);
return hWin;
}
然后界面的定时器触发中,更新RTC时间,这里的RTC库是使用的正点原子的RTC历程
case WM_TIMER:
sprintf(date,"%0.2d/%0.2d/%0.2d %0.2d:%0.2d:%0.2d",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);
TEXT_SetText(WM_GetDialogItem(pMsg->hWin, ID_TEXT_1),date);
WM_RestartTimer(pMsg->Data.v, 1000);
break;
使用Dialog为主窗体,控制使用Button控制来控制GPIO的高低电平,通过IO电平来控制继电器的吸合从而控制12V的点灯。
4个Button的写法都是一样,这里只贴出一个,其余只需要改IO口和控件号就可以
case ID_BUTTON_0: // Notifications sent by 'Button'
switch(NCode) {
case WM_NOTIFICATION_CLICKED:
break;
case WM_NOTIFICATION_RELEASED:
/* 灯光控制按键松开 就打开或者关闭灯
* 先读取IO的电平信息来判断现在灯的状态
*/
LedFlag[0] = ReadLight1Status();
if(!LedFlag[0]){
Light1 = 1;
BUTTON_SetFont(WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0), &GUI_Fonticon24);
BUTTON_SetText(WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0), "关");
}
else{
Light1 = 0;
BUTTON_SetFont(WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0), &GUI_Fonticon24);
BUTTON_SetText(WM_GetDialogItem(pMsg->hWin, ID_BUTTON_0), "开");
}
break;
}
break;
使用Dialog为主窗体,显示4个图标来表示数据。
这里的数据更新并不是在UI里面完成,而是在外面的数据采集任务中完成,进入此界面置位标志位,让任务对这里的数据进行更新。具体可以看数据采集任务
使用Dialog为主窗体,2Slider滑条分别控制风扇的PWM和舵机的旋转角度,两个Button分别控制另一个风扇和舵机旋转角度。一个Checkbox来切换手动和自动模式的标志位,上电默认是自动模式。
这样做主要是考虑到在实际中,厨房排气扇一般开就满速,不需要做调节,同理窗户也是只有全开和全关。
Slider滑条控制舵机转动
case ID_SLIDER_1: // Notifications sent by 'Slider'
switch(NCode) {
case WM_NOTIFICATION_CLICKED:
break;
case WM_NOTIFICATION_RELEASED:
break;
case WM_NOTIFICATION_VALUE_CHANGED:
WindosValue = 25 * SLIDER_GetValue(WM_GetDialogItem(pMsg->hWin, ID_SLIDER_1));
if(WindosValue >= 0){
sprintf(dis_data2,"窗帘状态:全关");
TIM_SetCompare1(TIM1,195); // 0度
}
if(WindosValue >= 25){
sprintf(dis_data2,"窗帘状态:1/4开");
TIM_SetCompare1(TIM1,190); // 45度
}
if(WindosValue >= 50){
sprintf(dis_data2,"窗帘状态:半开");
TIM_SetCompare1(TIM1,185); // 90度
}
if(WindosValue >= 75){
sprintf(dis_data2,"窗帘状态:3/4开");
TIM_SetCompare1(TIM1,180); // 135度
}
if(WindosValue == 100){
sprintf(dis_data2,"窗帘状态:全开");
TIM_SetCompare1(TIM1,175); // 180度
}
TEXT_SetText(WM_GetDialogItem(pMsg->hWin, ID_TEXT_1), dis_data2);
break;
}
break;
Slider控制风扇转速
case ID_SLIDER_0: // Notifications sent by 'Slider'
switch(NCode) {
case WM_NOTIFICATION_CLICKED:
break;
case WM_NOTIFICATION_RELEASED:
break;
case WM_NOTIFICATION_VALUE_CHANGED:
dis_Value = SLIDER_GetValue(WM_GetDialogItem(pMsg->hWin, ID_SLIDER_0));
TEXT_SetFont(hItem, &GUI_Fontkongzhi_font24);
sprintf(fanshan_data,"风扇转速:%d%%",dis_Value);
TEXT_SetText(WM_GetDialogItem(pMsg->hWin, ID_TEXT_0), fanshan_data);
TIM_SetCompare2(TIM2,dis_Value);
sprintf(dis_data,"风扇转速:%d%%",dis_Value);
break;
}
break;
使用Dialog为主窗体,中间使用Multiedit来做多个子页面切换的效果。这里只做了系统时间设置,可以在后续的拓展中,做WIFI信号选择等。
Multiedit效果就是在本身这个界面中,再添加一个其他子界面到这个页面中,然后通过上面的横条进行切换,但本身这个界面不能关闭或者隐藏挂起。具体使用方法可以参考官方的手册
时间设置主要是使用了Listwheel控制来做一个类似滑轮的效果来选择时间。
滑轮控制的写法是参考了野火的教程:野火列表轮教程
滑轮选择核心代码,原理参考野火的教程
case ID_LISTWHEEL_0: // Notifications sent by 'Listwheel'
switch(NCode) {
case WM_NOTIFICATION_CLICKED:
hItem = WM_GetDialogItem(pMsg->hWin, ID_LISTWHEEL_0);
LISTWHEEL_SetTextColor(hItem, LISTWHEEL_CI_SEL, 0x191919);
break;
case WM_NOTIFICATION_RELEASED:
break;
case WM_NOTIFICATION_SEL_CHANGED:
hItem = WM_GetDialogItem(pMsg->hWin, ID_LISTWHEEL_0);
LISTWHEEL_SetTextColor(hItem, LISTWHEEL_CI_SEL, 0x007dfe);
index1 = LISTWHEEL_GetPos(hItem);
LISTWHEEL_SetSel(hItem,index1);
LISTWHEEL_GetItemText(hItem,index1,Year,10);
break;
}
break;
这里的前端是使用了OneNet自带的大数据显示制作
然后点这个
点新建项目
进去之后就可以在这里找到我们常用的控件了。有一些控件需要会员才能解锁。
当我们获取到数据之后,就可以在前端直接拿后台的数据来进行显示
具体前端操作可以参考官方手册或者
View 2.0控件使用
View 2.0教程
这里列出我的数据源和过滤器
在新建数据源这里设备选择你上传数据的设备。在下面的数据流就可以选择你上传时候的文本了。
比如我上传数据时,报文是这样
sprintf(temp,"{\"temperature\":\"%d\",\"humidity\":\"%d\",\"Lux\":\"%d\",\"ppm\":\"%d\"}",
那么就会有4个数据流,分别是temperature、humidity、Lux、ppm。在这里进行选择就可以拿到单个数据了。
温度计过滤器
function filter(data, rootData, variables) {
data.forEach((item, index) => {
x = 2 * (item.value / 100)
y = item.value
});
return [{
graphic: [{
text: y +"°C"
},
],
wave: [x]
}]
}
仪表盘过滤器
function filter(data, rootData, variables) {
function last(arr) {
var len = arr ? arr.length : 0;
if (len) return arr[len - 1];
}
return [{
value: last(data).value,
}]
}
折线图过滤器
function filter(data, rootData, variables){
rootData.meiqi_lfWa.forEach((item, index) => {
item.x = item.update_at
item.y = item.value
item.y1 = 250
item.y2 = 200
});
return rootData.meiqi_lfWa
}
按钮和旋钮使用就比较简单了,只需要选择一个数据源,然后在样式里面就可以修改下发的命令了。贪方便可以全部控制控件都使用一个数据源来下发命令。这里的按钮1和按钮2就是按下和松开的状态,命令内容里面改成和下位机匹配的命令即可。
按钮
旋钮
注意旋扭要发送当前的数值,只能写成{V}这个格式
首先感谢大家看到这里,简单总结一下
这次课设的作品,总体来说做的完成度一般,没有特别深入,做到了基本的控制、数据显示、物联网,整体实现了一个简单的家居系统,系统可能还有许多BUG没有解决,大家参考的时候酌情参考。当然这个项目后续可以拓展的点很多
这是我第一次写博客,写的不好的地方请见谅,来都来了点个赞再走吧!