工程与源代码下载地址 Gitee:源码点这里 Github: 源码点这里
一、功能分析与效果展示
1.功能需求与分析
- 采集四种居家常用数据(温度、湿度、光照强度、空气中的可燃气体含量)
- 根据光照强度来控制舵机拉动床帘(模拟卧室根据日出情况来控制窗帘,智能卧室功能)
- 根据温度、湿度来判断室内情况,控制电机和舵机(模拟高温自动开窗散热和开风扇散热)
- 根据空气中的可燃气体含量判断室内情况,控制蜂鸣器、电机舵机(模拟煤气泄漏报警并自动开窗和开排气扇)
- 制作主控的Ul界面,显示上述的常用数据和控制家里常用电器(风扇、灯光、门窗)
- 将上述的常用数据上传至云端数据库,并做出前端UI对数据进行显示。
- 通过手机连接云端,通过云端远程监控家庭情况并做出控制
简单分析一下
- 主控使用STM32,采集数据使用常规的传感器就可以采集到这些数据。在程序中对采集回来的值做一个判断,然后控制舵机和电机。
- UI界面这块,使用STEMWIN这个UI库来做,这个库整体使用起来比较简单好上手,做出来的UI也OK。
- 连网这方面使用ESP8266模块,只要配置好串口就可以轻松驾驭。平台基本上百度、阿里、OneNet都可以,只要支持MQTT协议接入的平台就行。
- 为了方便开发和项目管理,使用UCOS-III系统,将每个情况分成小任务来完成,这样做快捷方便拓展性强
2.硬件选型
序号 | 名称 | 数量 |
---|
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 |
3.效果展示
硬件实物图
UI界面
UI界面使用图标均使用阿里图标库中的图案,具体请看UI界面设计。
前端界面
展示视频
b站
二、下位机-STM32程序
整体框架图 硬件定时器分配:
定时器 | 任务 |
---|
TIM1 | 舵机1 | TIM2 | L298N电机调速 | TIM3 | 心跳包定时发送 | TIM4 | 保存云端下发数据 | TIM8 | 舵机2 |
UCOSIII软件定时器:蜂鸣器控制
1.系统任务设计
STM32中,使用UCOS-III系统,将功能分成了8个小任务分别实现。具体每个模块如何使用网上教程很多,这里就不一一贴出,有需要可以自行搜索。详细代码可以参考开源连接
1.1 数据采集任务
将每个模块读取数据单独封装一个函数,将函数放入任务中,再将数据读取至数组中,完毕后通过消息队列发送到需要数据的任务。对每个模块数据的大小要有了解,不然有可能会出现数组越界卡死的情况。考虑到居家情况,可以对数据做一个小范围的限幅。同时在这里通过全局变量更新数据显示界面的数据可以让数据显示更加流畅 这里注意模块读取数据有严格时序,使用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;
OSQPost((OS_Q *)&SensorDataMsg,
(void *)SensorData,
(OS_MSG_SIZE )30,
(OS_OPT )OS_OPT_POST_FIFO | OS_OPT_POST_ALL,
(OS_ERR *)&err);
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_PERIODIC,&err);
}
}
1.2 场景处理任务
这里主要是接收采集任务发来的数据,然后根据数据的不同来控制不同的外设。简单判断一下即可,这里每个判断加一个标志位与手动控制区分开,这样就可以通过按钮来切换自动或者手动控制外设。
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);
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;
}
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);
}else if(TEXT_Buffer[2] <= 120 && TEXT_Buffer[2] >= 30 && control_flag){
TIM_SetCompare1(TIM1,185);
}else if (TEXT_Buffer[2] <= 400 && TEXT_Buffer[2] >= 120 && control_flag){
TIM_SetCompare1(TIM1,175);
}
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);
}
}
}
1.3 OneNet连接任务
接下来就是要将数据发送到云端和接受云端数据了,这个可以选择的平台和教程也很多,这里贴几个给大家参考,我是选择使用电信部的OneNet平台。 连接OneNet平台 STM32连接OneNet 大致步骤就是上网站注册,拿到IP、端口号、产品ID、设备号、密码。通过TCP连接到服务器之后使用MQTT协议发送报文就可以建立连接了。具体参考开源代码 注意OneNet是选择多协议接入,不是选MQTT套件
这里OneNet的IP和端口号是固定的,直接复制即可
产品ID: 设备号和密码 在MQTT.h中填入
#define PRODUCTKEY "183.230.40.39"
#define PRODUCTKEY_LEN strlen(PRODUCTKEY)
#define DEVICENAME "927136809"
#define DEVICENAME_LEN strlen(DEVICENAME)
#define P_TOPIC_NAME "$dp"
#define PRODUCTID "503697"
#define AUTHENTICATION "123456"
在任务中,首先初始化WIFI模块、MQTT堆栈、OneNet连接信息等,然后与OneNet发起TCP连接,连接成功后。向OneNet发送订阅报文,订阅成功后,启动定时器3,定时30S,每30S向OneNet发送一次心跳,保证我们时刻在线,同时启动上传数据任务和下发命令处理来上传我们采集的数据和处理云端对下位机的控制。连接任务具体见下代码,定时器中断、WIFI连接代码和TCP代码参考开源代码,这里就不一一贴出
void connectOneNet_task(void *p_arg)
{
OS_ERR err;
WiFi_ResetIO_Init();
MQTT_Buff_Init();
OneNetIoT_Parameter_Init();
while(1)
{
if(Connect_flag==1)
{
if(MQTT_TxDataOutPtr != MQTT_TxDataInPtr)
{
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];
}
}
if(MQTT_RxDataOutPtr != MQTT_RxDataInPtr){
printf("接收到数据:");
if(MQTT_RxDataOutPtr[2]==0x20){
switch(MQTT_RxDataOutPtr[5]){
case 0x00 : printf("CONNECT报文成功\r\n");
ConnectPack_flag = 1;
break;
case 0x01 : printf("连接已拒绝,不支持的协议版本,准备重启\r\n");
Connect_flag = 0;
break;
case 0x02 : printf("连接已拒绝,不合格的客户端标识符,准备重启\r\n");
Connect_flag = 0;
break;
case 0x03 : printf("连接已拒绝,服务端不可用,准备重启\r\n");
Connect_flag = 0;
break;
case 0x04 : printf("连接已拒绝,无效的用户名或密码,准备重启\r\n");
Connect_flag = 0;
break;
case 0x05 : printf("连接已拒绝,未授权,准备重启\r\n");
Connect_flag = 0;
break;
default : printf("连接已拒绝,未知状态,准备重启\r\n");
Connect_flag = 0;
break;
}
}
else if(MQTT_RxDataOutPtr[2]==0x90){
switch(MQTT_RxDataOutPtr[6]){
case 0x00 :
case 0x01 : printf("订阅成功\r\n");
SubcribePack_flag = 1;
Ping_flag = 0;
LED1 = 0;
TIM3_ENABLE_30S();
OS_TaskResume(&TASK9_TCB,&err);
OS_TaskResume(&TASK10_TCB,&err);
break;
default : printf("订阅失败,准备重启\r\n");
Connect_flag = 0;
break;
}
}
else if(MQTT_RxDataOutPtr[2]==0xD0)
{
printf("PING报文回复\r\n");
if(Ping_flag==1){
Ping_flag = 0;
}else if(Ping_flag>1){
Ping_flag = 0;
TIM3_ENABLE_30S();
}
}
else if((MQTT_RxDataOutPtr[2]==0x30))
{
printf("服务器等级0推送\r\n");
MQTT_DealPushdata_Qs0(MQTT_RxDataOutPtr);
}
MQTT_RxDataOutPtr += BUFF_UNIT;
if(MQTT_RxDataOutPtr==MQTT_RxDataEndPtr)
MQTT_RxDataOutPtr = MQTT_RxDataBuf[0];
}
}
else
{
printf("需要连接服务器\r\n");
TIM_Cmd(TIM4,DISABLE);
TIM_Cmd(TIM3,DISABLE);
WiFi_RxCounter=0;
memset(WiFi_RX_BUF,0,WiFi_RXBUFF_SIZE);
if(WiFi_Connect_IoTServer()==0)
{
printf("建立TCP连接成功\r\n");
Connect_flag = 1;
WiFi_RxCounter=0;
memset(WiFi_RX_BUF,0,WiFi_RXBUFF_SIZE);
MQTT_Buff_ReInit();
}
}
delay_ms(100);
}
}
1.3 OneNet上传数据任务
成功连接上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];
char tempAll[150];
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);
memset(temp, 0, 50);
memset(tempAll, 0, 100);
memset(head1, 0, 3);
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;
MQTT_PublishQs0(P_TOPIC_NAME,tempAll, dataLen);
OSTimeDlyHMSM(0,0,5,0,OS_OPT_TIME_PERIODIC,&err);
}
1.4 OneNet下发命令处理任务
这里检测接收的数据是否与我们预先设定的命令相符,如果相符,则执行我们设定的动作。具体见下代码
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)
{
printf("命令:%s\r\n",&MQTT_CMDOutPtr[2]);
control_flag = 0;
if(!memcmp(&MQTT_CMDOutPtr[2],LED1_ON,strlen(LED1_ON)))
{
Light1 = 1;
printf("已打开LED1!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED1_OFF,strlen(LED1_OFF)))
{
Light1 = 0;
printf("已关闭LED1!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED2_ON,strlen(LED2_ON)))
{
Light2 = 1;
printf("已打开LED2!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED2_OFF,strlen(LED2_OFF)))
{
Light2 = 0;
printf("已关闭LED2!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED3_ON,strlen(LED3_ON)))
{
Light3 = 1;
printf("已打开LED3!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED3_OFF,strlen(LED3_OFF)))
{
Light3 = 0;
printf("已关闭LED3!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED4_ON,strlen(LED4_ON)))
{
Light4 = 1;
printf("已打开LED4!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],LED4_OFF,strlen(LED4_OFF)))
{
Light4 = 0;
printf("已关闭LED4!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],Paiqishan_ON,strlen(Paiqishan_ON)))
{
MotorPWM = 1;
printf("已打开排气扇!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],Paiqishan_OFF,strlen(Paiqishan_OFF)))
{
MotorPWM = 0;
printf("已关闭排气扇!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],Windos_ON,strlen(Windos_ON)))
{
TIM_SetCompare1(TIM8,175);
printf("已打开窗户!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],Windos_OFF,strlen(Windos_OFF)))
{
TIM_SetCompare1(TIM8,195);
printf("已关闭窗户!\r\n");
}
if(!memcmp(&MQTT_CMDOutPtr[2],dianji,strlen(dianji)))
{
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);
}
}
1.5 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);
}
}
1.6 系统指示灯与堆栈检测任务
用指示灯闪烁来证明系统正在工作,堆栈检测是前期做UI时,监控UI任务是否有出现爆栈的情况
void SystemLED_task(void *p_arg)
{
OS_ERR err;
CPU_STK_SIZE free,used;
while(1)
{
LED0 = !LED0;
OSTaskStkChk (&EmwindemoTaskTCB,&free,&used,&err);
OSTimeDlyHMSM(0,0,0,500,OS_OPT_TIME_PERIODIC,&err);
}
}
2.UI界面设计
UI界面这边是使用STEMWIN来制作,因为F1的RAM比较小,在显示大量控件和图片时会出现卡顿的情况,所以UI整体只是将基本控件放置在屏幕中,没有做换肤和美化。
STEMWIN这个库,使用起来非常方便,在界面构建时,可以使用自带的GUIBuilder来构建,在完成基本布局之后可以自动生成C代码,在具体功能调试时,可以使用仿真文件进行仿真,功能完成之后,再移植到板子上。
UI界面代码中,样式占了大多数,具体的样式代码就不一一贴出,这里就只贴出关键功能代码,具体可以参考开源代码
2.1主界面
主界面的布局如下图所示,使用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:
switch(NCode) {
case WM_NOTIFICATION_CLICKED:
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;
2.1灯光界面
使用Dialog为主窗体,控制使用Button控制来控制GPIO的高低电平,通过IO电平来控制继电器的吸合从而控制12V的点灯。 4个Button的写法都是一样,这里只贴出一个,其余只需要改IO口和控件号就可以
case ID_BUTTON_0:
switch(NCode) {
case WM_NOTIFICATION_CLICKED:
break;
case WM_NOTIFICATION_RELEASED:
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;
2.2 数据显示界面
使用Dialog为主窗体,显示4个图标来表示数据。 这里的数据更新并不是在UI里面完成,而是在外面的数据采集任务中完成,进入此界面置位标志位,让任务对这里的数据进行更新。具体可以看数据采集任务
2.3 电器控制界面
使用Dialog为主窗体,2Slider滑条分别控制风扇的PWM和舵机的旋转角度,两个Button分别控制另一个风扇和舵机旋转角度。一个Checkbox来切换手动和自动模式的标志位,上电默认是自动模式。
这样做主要是考虑到在实际中,厨房排气扇一般开就满速,不需要做调节,同理窗户也是只有全开和全关。 Slider滑条控制舵机转动
case ID_SLIDER_1:
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);
}
if(WindosValue >= 25){
sprintf(dis_data2,"窗帘状态:1/4开");
TIM_SetCompare1(TIM1,190);
}
if(WindosValue >= 50){
sprintf(dis_data2,"窗帘状态:半开");
TIM_SetCompare1(TIM1,185);
}
if(WindosValue >= 75){
sprintf(dis_data2,"窗帘状态:3/4开");
TIM_SetCompare1(TIM1,180);
}
if(WindosValue == 100){
sprintf(dis_data2,"窗帘状态:全开");
TIM_SetCompare1(TIM1,175);
}
TEXT_SetText(WM_GetDialogItem(pMsg->hWin, ID_TEXT_1), dis_data2);
break;
}
break;
Slider控制风扇转速
case ID_SLIDER_0:
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;
2.4系统设置界面
使用Dialog为主窗体,中间使用Multiedit来做多个子页面切换的效果。这里只做了系统时间设置,可以在后续的拓展中,做WIFI信号选择等。
Multiedit效果就是在本身这个界面中,再添加一个其他子界面到这个页面中,然后通过上面的横条进行切换,但本身这个界面不能关闭或者隐藏挂起。具体使用方法可以参考官方的手册
时间设置主要是使用了Listwheel控制来做一个类似滑轮的效果来选择时间。 滑轮控制的写法是参考了野火的教程:野火列表轮教程
滑轮选择核心代码,原理参考野火的教程
case ID_LISTWHEEL_0:
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;
2.5 关于界面
显示信息,没什么好说的
三、上位机-OneNet云端前端界面
这里的前端是使用了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没有解决,大家参考的时候酌情参考。当然这个项目后续可以拓展的点很多
- 增加OpenMv模块来实现人脸检测,这部分做到门锁中
- 增加语音识别模块来提供语音控制
- 增加外红遥控模块来控制空调、或者其他红外设备
- 增加火焰传感器来检测火情
- UI界面可以进行二次美化
这是我第一次写博客,写的不好的地方请见谅,来都来了点个赞再走吧!
|