【智能家居入门4】(FreeRTOS、MQTT服务器、MQTT协议、微信小程序)
前面已经发了智能家居入门的1、2、3了,在实际开发中一般都会使用到实时操作系统,这里就以FreeRTOS为例子,使用标准库。记录由裸机转到实时操作系统所遇到的问题以及总体流程。相较于裸机,系统实时性强了很多,小程序下发的指令基本立马执行,没有啥延迟,调整任务的挂起时间,可以进一步提高系统效率
- 前言
- 问题汇总
- 1、内存不足
- 2、延时函数
- 一、项目总体介绍
- 二、代码
- 1、下位机
- 2、微信小程序
- 三、工程源码获取
前言
主控仍旧是STM32F103C8T6,实时操作系统选择的是FreeRTOS。
主要功能:
①环境信息采集并上传至微信小程序
②微信小程序下发指令控制家电
③由雨滴传感器和步进电机能够实现下雨自动收起衣服,停雨自动晒出衣服(由于驱动板和步进电机不在身边,这里代码中就用舵机来模拟,之前的项目中有用stm32来驱动步进电机,代码在另外的工程里,有需要的话可以关注我私聊,无偿)
前几篇博客:
环境信息采集(HTTP+ONENET)
智能家居(MQTT+ONENET)
智能家居(MQTT+MQTT服务器)
问题汇总
1、内存不足
将FreeRTOS移植到自己的工程项目里后(移植可以看上一篇博客),经过测试是可以用的,但是在我将之前的net文件夹移植过来之后编译就报错了,net文件夹里是与服务器通信的一些源文件(onenet.c、esp8266.c、MqttKit.c、cJSON.c),对net文件不了解的可以看这篇博客,有介绍,或者到文章最后下载源代码自己分析代码。下面贴出错误:
经过网上查找,这是由于RAM内存不够导致的,解决办法两种:
①更换开发板,换成STM32F103ZET6或者其他RAM大的开发板,亲测可以。
②修改FreeRTOSConfig.h文件:
2、延时函数
当我将dht11的驱动代码移植过来之后,编译没有报错,抱着侥幸心理以为没问题,运行之后发现串口没有调试信息输出,然后进入调试,发现任务函数2执行到DHT11_Read_Data()时就不往后走了,每次停止的时候都会卡在不同的函数里,还真是棘手,无从下手。经过长时间问题找寻,并没有在网上找到答案,我就一步一步调试,发现每次就是延时函数出问题:
然后追溯回延时函数实现,我怀疑是因为这个延时函数使用到了系统时钟中断,然后导致和freertos起冲突,不过我只是怀疑,并不知道具体原因,然后我就将延时函数的源文件和头文件移除,使用定时器来实现延时,成功解决。
用定时器实现的延时函数如下,用这两个函数替换dht11.c和esp8266.c中的延时函数即可:
void TIM3_Init(void)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); ///使能TIM3时钟TIM_TimeBaseInitStructure.TIM_Period = 50000-1; //自动重装载值TIM_TimeBaseInitStructure.TIM_Prescaler = 60-1; //定时器分频TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//初始化TIM3
}
//微秒级延时
void TIM3_Delayus(u16 xus)
{TIM_Cmd(TIM3,ENABLE); //启动定时器while(TIM3->CNT < xus);TIM3->CNT = 0;TIM_Cmd(TIM3,DISABLE); //关闭定时器
}
//毫秒级延时
void TIM3_Delayms(u16 xms)
{int i;for(i=0;i<xms;i++){TIM3_Delayus(1000);}
}
一、项目总体介绍
用到的设备:sg90舵机、MG946舵机、MQ-2、MQ-4、MQ-9传感器、雨滴传感器、火焰传感器、继电器、风扇、水泵、DHT11、USB转TTL、J-;ink、STM32F103C8T6、Esp8266-01s、led。
①标准库移植FreeRTOS参考之前的一篇博客:https://blog.csdn.net/m0_71523511/article/details/135956912
②两个舵机由定时器二的通道0、1输出PWM进行控制,这里不会的可以参考:https://blog.csdn.net/m0_71523511/article/details/135851583
③MQ系列传感器由adc三个通道进行采集,adc知识可以看这个视频,我的代码直接搬过来改的:https://www.bilibili.com/video/BV1th411z7sn/?p=22&spm_id_from=pageDriver&vd_source=2a10d30b8351190ea06d85c5d0bfcb2a
④继电器的使用和接线参考这篇博客:https://blog.csdn.net/m0_71523511/article/details/135926339
⑤雨滴传感器和火焰传感器都直接接收DO输出口,直接读取数字量,当然也可以接AO口,用adc读取模拟量,可以自己尝试。
⑥DHT11原理可以百度也可以参考这篇博客里面的:https://blog.csdn.net/m0_71523511/article/details/135892908
⑦新买的esp8266-01s如果没法用的话可以参考:https://blog.csdn.net/m0_71523511/article/details/135887108
二、代码
1、下位机
1、main.c:
创建了两个任务,高优先级的任务1(负责接收服务器下发指令),低优先级任务2(负责读取传感器数据并打包发送给服务器)。
#include "stm32f10x.h" // Device header
#include "Delay.h"#include "FreeRTOS.h"//操作系统头文件
#include "task.h"#include "dht11.h"//外设头文件
#include "timer.h"
#include "usart.h"
#include "led.h"
#include "adc.h"
#include "Servo.h"//舵机
#include "PWM.h"
#include "jdq.h"#include "esp8266.h"//网络相关头文件
#include "onenet.h"#include <math.h>//数学公式头文件TaskHandle_t mytask1;
TaskHandle_t mytask2;u8 humidityH; //湿度整数部分
u8 humidityL; //湿度小数部分
u8 temperatureH; //温度整数部分
u8 temperatureL; //温度小数部分u32 gas;//天然气adc读回的值
u32 ranqi;//可燃气体adc读回的值
u32 yanwu;//烟雾adc读回的值float w2;//天然气浓度电压值
float w3;//可燃气体浓度电压值
float w4;//烟雾浓度电压值u32 ppm2;//天然气浓度值
u32 ppm3;//可燃气体浓度值
u32 ppm4;//烟雾浓度值unsigned char hy,yd;//火焰char PUB_BUF[256];//上传数据的buf
const char *devSubTopic[] = {"/mysmarthome/sub"};
const char devPubTopic[] = "/smarthome/pub";
u8 ESP8266_INIT_OK = 0;//esp8266初始化完成标志unsigned char *dataPtr = NULL;//任务1用来接收云平台下发数据
void myTask1( void *arg )
{while(1){dataPtr = ESP8266_GetIPD(10);DEBUG_LOG("收到小程序下发指令");if(dataPtr != NULL)OneNet_RevPro(dataPtr);vTaskDelay(200);}
}//任务2用于采集传感器数据并上云
void myTask2( void *arg )
{while(1){//读取火焰传感器数字io输出:PB7hy = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7);//读取雨滴传感器数字io输出:PB8yd = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_8);//读取MQ传感器原始数据:PA6、PA7、PB0分别对应MQ-4、MQ-9、MQ-2taskENTER_CRITICAL();gas = AD_GetValue(ADC_Channel_6);taskEXIT_CRITICAL();w3 = gas*5.0/4095;ppm2 = pow((7.2495*353.134*w3)/(23.5-4.7*w3),0.6077);taskENTER_CRITICAL();ranqi = AD_GetValue(ADC_Channel_7);taskEXIT_CRITICAL();w2 = ranqi*5.0/4095;ppm3 = pow((24.5911*430.323*w2)/(50-10*w2),0.6934);taskENTER_CRITICAL();yanwu = AD_GetValue(ADC_Channel_8);taskEXIT_CRITICAL();w3 = yanwu*5.0/4095;ppm4 = pow((24.5911*430.323*w3)/(50-10*w3),0.6934);//读取DHT11数据:PA8taskENTER_CRITICAL();//关闭中断,防止执行读取温湿度时被任务1打断DHT11_Read_Data(&humidityH,&humidityL,&temperatureH,&temperatureL);taskEXIT_CRITICAL();//发布数据taskENTER_CRITICAL();DEBUG_LOG("==================================================================================");DEBUG_LOG("发布数据");sprintf(PUB_BUF,"{\"Humi\":%d.%d,\"Temp\":%d.%d,\"huoyan\":%d,\"xiayu\":%d,\"gas\":%d,\"ranqi\":%d,\"yanwu\":%d}",humidityH,humidityL,temperatureH,temperatureL,!hy,yd,ppm2,ppm3,ppm4);OneNet_Publish(devPubTopic, PUB_BUF);DEBUG_LOG("==================================================================================");taskEXIT_CRITICAL();vTaskDelay(5000);}
}int main(void)
{TIM3_Init();Usart1_Init(115200);DEBUG_LOG("串口1初始化完成\r\n");//串口打印调试信息:TX PA9 ; RX PA10LED_Init();DEBUG_LOG("led初始化完成\r\n");Usart2_Init(115200);//stm32-esp8266通讯串口;TX PA2 ; RX PA3DEBUG_LOG("串口2初始化完成\r\n");DHT11_Init();DEBUG_LOG("DHT11初始化完成\r\n");AD_Init();DEBUG_LOG("MQ-4、MQ-9初始化完成\r\n");Servo_Init();DEBUG_LOG("舵机初始化完成\r\n");//PA1Jdq_GPIO_Init();DEBUG_LOG("继电器初始化完成\r\n");//风扇、水泵:PB5、6HY_GPIO_Init();DEBUG_LOG("火焰传感器初始化完成\r\n");//火焰传感器:PB7YD_GPIO_Init();DEBUG_LOG("雨滴传感器初始化完成\r\n");//雨滴传感器:PB8DEBUG_LOG("初始化ESP8266 WIFI模块...");if(!ESP8266_INIT_OK){DEBUG_LOG("WIFI连接中...");}ESP8266_Init(); //初始化ESP8266DEBUG_LOG("服务器连接中...");while(OneNet_DevLink()){//接入OneNETTIM3_Delayms(500);} OneNet_Subscribe(devSubTopic, 1);//订阅上位机下发数据TopicxTaskCreate(myTask1,"matask1",512,NULL,3,&mytask1);xTaskCreate(myTask2,"matask2",512,NULL,2,&mytask2);DEBUG_LOG("任务创建完成,开始任务调度\r\n");vTaskStartScheduler();//开始任务调度,这之后的代码不会执行while (1){}
}
2、onenet.c中的OneNet_RevPro()函数:
这个函数负责解析微信小程序发送给服务器,又由服务器转发给下位机的指令:
void OneNet_RevPro(unsigned char *cmd)
{MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0}; //协议包char *req_payload = NULL;char *cmdid_topic = NULL;unsigned short topic_len = 0;unsigned short req_len = 0;unsigned char type = 0;unsigned char qos = 0;static unsigned short pkt_id = 0;short result = 0;char *dataPtr = NULL;char numBuf[10];int num = 0;cJSON *json , *json_value;type = MQTT_UnPacketRecv(cmd);switch(type){case MQTT_PKT_CMD: //命令下发result = MQTT_UnPacketCmd(cmd, &cmdid_topic, &req_payload, &req_len); //解出topic和消息体if(result == 0){DEBUG_LOG("cmdid: %s, req: %s, req_len: %d\r\n", cmdid_topic, req_payload, req_len);MQTT_DeleteBuffer(&mqttPacket); //删包}break;case MQTT_PKT_PUBLISH: //接收MQTT服务器转发的小程序下发数据result = MQTT_UnPacketPublish(cmd, &cmdid_topic, &topic_len, &req_payload, &req_len, &qos, &pkt_id);//解析MQTT数据包if(result == 0){DEBUG_LOG("topic: %s, topic_len: %d, payload: %s, payload_len: %d\r\n",cmdid_topic, topic_len, req_payload, req_len);// 对数据包内的req_payload(消息体)进行JSON格式解析json = cJSON_Parse(req_payload);if (!json)UsartPrintf(USART_DEBUG,"Error before: [%s]\n",cJSON_GetErrorPtr());else{json_value = cJSON_GetObjectItem(json , "target"); //在JSON对象json中查找“target”这个键的值,并返回对应的cjson指针 //{'target':'leds'},这就是键值对//串口打印键值对 UsartPrintf(USART_DEBUG,"json_string: [%s]\n",json_value->string);//string-键UsartPrintf(USART_DEBUG,"json_value: [%s]\n",json_value->valuestring);//valuestring-值//strstr是标准字符串判断函数,判断前面查找到的“target”这个键的值是否等于ledsif(strstr(json_value->valuestring,"leds") != NULL){json_value = cJSON_GetObjectItem(json , "value");//进行第二次get,这样获取的就是value:1if(json_value->valueint)GPIO_SetBits(GPIOC,GPIO_Pin_13);else GPIO_ResetBits(GPIOC,GPIO_Pin_13);}else if(strstr(json_value->valuestring,"men") != NULL){json_value = cJSON_GetObjectItem(json , "value");//进行第二次get,这样获取的就是value:1if(json_value->valueint){Servo_SetAngle(180);TIM3_Delayms(20);}else{Servo_SetAngle(0);TIM3_Delayms(20);}}else if(strstr(json_value->valuestring,"beep") != NULL)//窗帘一档{json_value = cJSON_GetObjectItem(json,"value");if(json_value->valueint){Servo_SetAngle2(60);TIM3_Delayms(20);}else{Servo_SetAngle2(60);TIM3_Delayms(20);}}else if(strstr(json_value->valuestring,"sandang") != NULL)//窗帘三档{json_value = cJSON_GetObjectItem(json,"value");if(json_value->valueint){Servo_SetAngle2(180);TIM3_Delayms(20);}else{Servo_SetAngle2(180);TIM3_Delayms(20);}}else if(strstr(json_value->valuestring,"erdang") != NULL)//窗帘二档{json_value = cJSON_GetObjectItem(json,"value");if(json_value->valueint){Servo_SetAngle2(120);TIM3_Delayms(20);}else{Servo_SetAngle2(120);TIM3_Delayms(20);}}else if(strstr(json_value->valuestring,"guanchuang") != NULL)//关闭窗帘{json_value = cJSON_GetObjectItem(json,"value");if(json_value->valueint){Servo_SetAngle2(0);}else{Servo_SetAngle2(0);}}else if(strstr(json_value->valuestring,"fen") != NULL)//小程序下发开风扇指令{json_value = cJSON_GetObjectItem(json,"value");if(json_value->valueint){GPIO_SetBits(GPIOB,GPIO_Pin_5); }else{GPIO_ResetBits(GPIOB,GPIO_Pin_5); }}else if(strstr(json_value->valuestring,"water") != NULL)//小程序下发开风扇指令{json_value = cJSON_GetObjectItem(json,"value");//小程序下发开水阀指令if(json_value->valueint){GPIO_SetBits(GPIOB,GPIO_Pin_6); }else{GPIO_ResetBits(GPIOB,GPIO_Pin_6); }}else{}}cJSON_Delete(json);}break;case MQTT_PKT_PUBACK: //发送Publish消息,平台回复的Ack,这里只有消息服务质量等级升高时才会用到(qos)if(MQTT_UnPacketPublishAck(cmd) == 0)DEBUG_LOG("Tips: MQTT Publish Send OK\r\n");break;case MQTT_PKT_PUBREC: //发送Publish消息,平台回复的Rec,设备需回复Rel消息if(MQTT_UnPacketPublishRec(cmd) == 0){DEBUG_LOG("Tips: Rev PublishRec\r\n");if(MQTT_PacketPublishRel(MQTT_PUBLISH_ID, &mqttPacket) == 0){DEBUG_LOG("Tips: Send PublishRel\r\n");ESP8266_SendData(mqttPacket._data, mqttPacket._len);MQTT_DeleteBuffer(&mqttPacket);}}break;case MQTT_PKT_PUBREL: //收到Publish消息,设备回复Rec后,平台回复的Rel,设备需再回复Compif(MQTT_UnPacketPublishRel(cmd, pkt_id) == 0){DEBUG_LOG("Tips: Rev PublishRel\r\n");if(MQTT_PacketPublishComp(MQTT_PUBLISH_ID, &mqttPacket) == 0){DEBUG_LOG("Tips: Send PublishComp\r\n");ESP8266_SendData(mqttPacket._data, mqttPacket._len);MQTT_DeleteBuffer(&mqttPacket);}}break;case MQTT_PKT_PUBCOMP: //发送Publish消息,平台返回Rec,设备回复Rel,平台再返回的Compif(MQTT_UnPacketPublishComp(cmd) == 0){DEBUG_LOG("Tips: Rev PublishComp\r\n");}break;case MQTT_PKT_SUBACK: //发送Subscribe消息的Ackif(MQTT_UnPacketSubscribe(cmd) == 0)DEBUG_LOG("Tips: MQTT Subscribe OK\r\n");elseDEBUG_LOG("Tips: MQTT Subscribe Err\r\n");break;case MQTT_PKT_UNSUBACK: //发送UnSubscribe消息的Ackif(MQTT_UnPacketUnSubscribe(cmd) == 0)DEBUG_LOG("Tips: MQTT UnSubscribe OK\r\n");elseDEBUG_LOG("Tips: MQTT UnSubscribe Err\r\n");break;default:result = -1;break;}ESP8266_Clear(); //清空缓存if(result == -1)return;dataPtr = strchr(req_payload, '}'); //搜索'}'if(dataPtr != NULL && result != -1) //如果找到了{dataPtr++;while(*dataPtr >= '0' && *dataPtr <= '9') //判断是否是下发的命令控制数据{numBuf[num++] = *dataPtr++;}num = atoi((const char *)numBuf); //转为数值形式}if(type == MQTT_PKT_CMD || type == MQTT_PKT_PUBLISH){MQTT_FreeBuffer(cmdid_topic);MQTT_FreeBuffer(req_payload);}}
2、微信小程序
1、index.js
// index.js
// 获取应用实例
const app = getApp()const{ connect } = require('../../utils/mqtt')const mqttHost = 'broker.emqx.io'
const mqttPort = 8084const deviceSub = '/mysmarthome/sub' //小程序发布指令的Topic
const devicePub = '/smarthome/pub' //小程序接收数据的Topicconst mpSubTopic = devicePub //mp指小程序(这样更直观)
const mpPubTopic = deviceSub //小程序发布指令的TopicPage({data: {client: null,Humi:0,Temp:0,huoyan:1,gas:0,leds:false,ranqi:0,fen:false,water:false,men:false},//下发指令:ledonledsChange(event){const that = thisconsole.log(event.detail.value);const sw = event.detail.valuethat.setData({leds:sw})if(sw){that.data.client.publish(mpPubTopic,JSON.stringify({target:"leds",value:1}),function (err) {if(!err){console.log('成功下发指令打开led')console.log('{target:"leds",value:1}')}})}else{that.data.client.publish(mpPubTopic,JSON.stringify({target:"leds",value:0}),function (err) {if(!err){console.log('成功下发指令关闭led')console.log('{target:"leds",value:0}')}})}},//下发指令:风扇onfenChange(event){const that = thisconsole.log(event.detail.value);const sw = event.detail.valuethat.setData({fen:sw})if(sw){that.data.client.publish(mpPubTopic,JSON.stringify({target:"fen",value:1}),function (err) {if(!err){console.log('成功下发指令打开排气扇')}})}else{that.data.client.publish(mpPubTopic,JSON.stringify({target:"fen",value:0}),function (err) {if(!err){console.log('成功下发指令关闭排气扇')}})}},//下发指令:水泵onwaterChange(event){const that = thisconsole.log(event.detail.value);const sw = event.detail.valuethat.setData({water:sw})if(sw){that.data.client.publish(mpPubTopic,JSON.stringify({target:"water",value:1}),function (err) {if(!err){console.log('成功下发指令打开水泵')}})}else{that.data.client.publish(mpPubTopic,JSON.stringify({target:"water",value:0}),function (err) {if(!err){console.log('成功下发指令关闭水泵')}})}},//下发指令:门onmenChange(event){const that = thisconsole.log(event.detail.value);const sw = event.detail.valuethat.setData({men:sw})if(sw){that.data.client.publish(mpPubTopic,JSON.stringify({target:"men",value:1}),function (err) {if(!err){console.log('成功下发指令打开门')}})}else{that.data.client.publish(mpPubTopic,JSON.stringify({target:"men",value:0}),function (err) {if(!err){console.log('成功下发指令关闭门')}})}},//舵机逻辑//下发指令:窗帘一档goToPage1(event){const that = thisthat.setData({cnt1:this.data.cnt1 + 1})if(this.data.cnt1 > 0){that.data.client.publish(mpPubTopic,JSON.stringify({target:"beep",value:1}),function (err) {if(!err){console.log('成功下发指令打开窗帘一档')}})}else{that.data.client.publish(mpPubTopic,JSON.stringify({target:"beep",value:1}),function (err) {if(!err){console.log('成功下发指令关闭窗帘一档')}})}},//下发指令:二档goToPage2(event){const that = thisthat.setData({cnt2:this.data.cnt2 + 1})if(this.data.cnt2 >= 0){that.data.client.publish(mpPubTopic,JSON.stringify({target:"erdang",value:1}),function (err) {if(!err){console.log('成功下发指令打开窗帘二档')}})}else{that.data.client.publish(mpPubTopic,JSON.stringify({target:"erdang",value:1}),function (err) {if(!err){console.log('成功下发指令关闭窗帘二档')}})}},//下发指令:三档goToPage3(event){const that = thisthat.setData({cnt3:this.data.cnt3 + 1})if(this.data.cnt3 >= 0){that.data.client.publish(mpPubTopic,JSON.stringify({target:"sandang",value:1}),function (err) {if(!err){console.log('成功下发指令打开窗帘三档')}})}else{that.data.client.publish(mpPubTopic,JSON.stringify({target:"sandang",value:1}),function (err) {if(!err){console.log('成功下发指令关闭窗帘s档')}})}},goToPage4(event){const that = thisthat.setData({cnt4:this.data.cnt4 + 1})if(this.data.cnt4 >= 0){that.data.client.publish(mpPubTopic,JSON.stringify({target:"guanchuang",value:1}),function (err) {if(!err){console.log('成功下发指令关闭窗帘')}})}else{that.data.client.publish(mpPubTopic,JSON.stringify({target:"guanchuang",value:1}),function (err) {if(!err){console.log('成功下发指令关闭窗帘')}})}},//显示接受到的数据onShow(){const that = thisthat.setData({client:connect(`wxs://${mqttHost}:${mqttPort}/mqtt`)})that.data.client.on('connect',function (params) {console.log('成功连接到mqtt服务器')wx.showToast({title: '连接成功',icon:'success',mask:true})that.data.client.subscribe(mpSubTopic,function (err) {if(!err){console.log('成功订阅设备上行数据Topic')}})})that.data.client.on('message',function (topic,message) {console.log(topic);let dataFromDev = {}try {dataFromDev = JSON.parse(message)console.log(dataFromDev);that.setData({Temp:dataFromDev.Temp,Humi:dataFromDev.Humi,huoyan:dataFromDev.huoyan,gas:dataFromDev.gas,ranqi:dataFromDev.ranqi,xiayu:dataFromDev.xiayu,yanwu:dataFromDev.yanwu,})} catch (error) {console.log('JSON解析失败',error)}})},
})
2、index.wxml
<!--index.wxml-->
<view class="page-container">
<!--头部部分--><view class="header-container"><view class="header-one"><view>战损版</view></view><view class="header-two"><view>智能家居</view></view></view>
<!--数据部分-->
<view class="data-container"><!--温度--><view class="data-card"><image class="data-card__icon" src="/static/wendu.png" /><view class="data-card__text"><view class="data-card__title">温度</view><view class="data-card__value">{{ Temp }} ℃</view></view></view><!--湿度--><view class="data-card"><image class="data-card__icon" src="/static/shidu.png" /><view class="data-card__text"><view class="data-card__title">湿度</view><view class="data-card__value">{{ Humi }}%rh</view></view></view><!--可燃气体浓度--><view class="data-card"><image class="data-card__icon" src="/static/co.png" /><view class="data-card__text"><view class="data-card__title">可燃气浓度</view><view class="data-card__value">{{ ranqi }} ppm</view></view></view><!--天然气浓度--><view class="data-card"><image class="data-card__icon" src="/static/gas.png" /><view class="data-card__text"><view class="data-card__title">天然气浓度</view><view class="data-card__value">{{ gas }} ppm</view></view></view><!--烟雾浓度--><view class="data-card"><image class="data-card__icon" src="/static/yanwu.png" /><view class="data-card__text"><view class="data-card__title">烟雾浓度</view><view class="data-card__value">{{ yanwu }} ppm</view></view></view><view class="data-card"><image class="data-card__icon" src="/static/huoyan.png" /><view class="data-card__text"><view class="data-card__title">火焰</view><view class="data-card__value"><view wx:if="{{huoyan == 0}}">无</view><view wx:elif="{{huoyan == 1}}">有</view></view></view></view><!--雨水--><view class="data-card"><image class="data-card__icon" src="/static/yudi.png" /><view class="data-card__text"><view class="data-card__title">雨</view><view class="data-card__value"><view wx:if="{{xiayu==0}}">下雨</view><view wx:elif="{{xiayu==1}}">未下雨</view></view></view></view><!--开启or关闭排气扇--><view class="data-card"><image class="data-card__icon" src="/static/fen.png"/><view class="data-card__text"><view class="data-card__title">排气扇</view><view class="data-card__value"><switch checked="{{fen}}" bindchange="onfenChange" color="#3d7ef9"/></view></view></view><!--开启or关闭水泵--><view class="data-card"><image class="data-card__icon" src="/static/water.png"/><view class="data-card__text"><view class="data-card__title">水泵</view><view class="data-card__value"><switch checked="{{water}}" bindchange="onwaterChange" color="#3d7ef9"/></view></view></view><!--开启or关闭卧室灯--><view class="data-card"><image class="data-card__icon" src="/static/leds.png" /><view class="data-card__text"><view class="data-card__title">卧室灯</view><view class="data-card__value"><switch checked="{{ leds }}" bindchange="onledsChange" color="#3d7ef9"/></view></view></view><!--开启or关闭卧室灯--><view class="data-card"><image class="data-card__icon" src="/static/door-open.png" /><view class="data-card__text"><view class="data-card__title">门</view><view class="data-card__value"><switch checked="{{ men }}" bindchange="onmenChange" color="#3d7ef9"/></view></view></view><!-- 开启or关闭门<view class="data-card"><image class="data-card__icon" src="/static/door-open.png"/><view class="data-card__text"><view class="data-card__title">门</view></view></view> --><view class="choice"><view>窗帘</view>
</view><view class="s_view"><button class='btn1' hover-class="btn-class" style="width: 45vw;height: 8vh;"bindtap="goToPage1">一档</button><button style="width: 45vw;height: 8vh;"class='btn2' hover-class="btn-class2" bindtap="goToPage2">二档</button><button style="width: 45vw;height: 8vh;"class='btn3' hover-class="btn-class3" bindtap="goToPage3">三档</button><button style="width: 45vw;height: 8vh;"class='btn4' hover-class="btn-class4" bindtap="goToPage4">关闭</button>
</view>
</view>
</view>
3、index.wxss
/**小程序整个页面的设置**/
.page-container{padding: 36rpx;
}
/**设置顶部大筐筐**/
.header-container{background-color: #fff;color: black;box-shadow: #d6d6d6 0 0 20rpx;border-radius: 36rpx;padding: 3rpx 3rpx;
}
/**设置顶部大筐筐内的第一行字**/
.header-container .header-one{display: flex;justify-content: center;padding: 10rpx;font-size: 50rpx;font: bold;
}
/**设置顶部大筐筐内的第二行字**/
.header-container .header-two{ display: flex;justify-content: center;padding: 10rpx;font-size: 40rpx;font: bold;
}.choice {display: flex;flex-direction: row;font-size: 20px;justify-content: center;color: rgb(38, 38, 39);margin-top: 30rpx;margin-bottom: 20rpx;top: 3rpx;box-shadow:0px 0px 10px #bfbfc0 inset;
}.btn1 {/* float:left;background-color: rgb(112, 107, 107);color: rgb(66, 63, 63); */float:left;top: 3rpx;box-shadow:0px 0px 8px #999 inset;
}.btn-class{float:left;top: 3rpx;box-shadow:0px 0px 10px #b8b9bd inset;
}.btn2 {float:right; top: 3rpx;box-shadow:0px 0px 8px #999 inset;
}
.btn-class2{float:right;top: 3rpx;box-shadow:0px 0px 10px #b8b9bd inset;
}.btn3 {float:left;top: 3rpx;box-shadow:0px 0px 8px #999 inset;
}
.btn-class3{float:left;top: 3rpx;box-shadow:0px 0px 10px #b8b9bd inset;
}.btn4 {float:right;top: 3rpx;box-shadow:0px 0px 8px #999 inset;
}
.btn-class4{float:right;top: 3rpx;box-shadow:0px 0px 10px #b8b9bd inset;
}.s_view{bottom: 0rpx; width: 100%;
}/**设置数据部分总体布局**/
.data-container{margin-top: 30rpx;display: grid;justify-content: center;grid-template-columns: repeat(auto-fill,300rpx);grid-gap: 30rpx;
}
/**设置数据部分小卡片内的格式**/
.data-container .data-card{position: relative;background-color: #fff;height: 140rpx;box-shadow: #d6d6d6 0 0 8rpx;border-radius: 36rpx;display: flex;justify-content: space-between;padding: 16rpx;
}
/**以下是小卡片内的文本、图片、标题、数值设置**/
.data-container .data-card .data-card__text{position: absolute;top: 20rpx;right: 24rpx;text-align: right;white-space: nowrap;
}.data-container .data-card .data-card__icon{position: absolute;height: 72rpx;width: 72rpx;left: 32rpx;top: 16rpx;
}.data-container .data-card .data-card__value{font-size: 40rpx;margin-top: 20rpx;
}.data-container .data-card .data-card__title{font-size: 34rpx;
}
三、工程源码获取
关注私聊。
相关文章:

【智能家居入门4】(FreeRTOS、MQTT服务器、MQTT协议、微信小程序)
前面已经发了智能家居入门的1、2、3了,在实际开发中一般都会使用到实时操作系统,这里就以FreeRTOS为例子,使用标准库。记录由裸机转到实时操作系统所遇到的问题以及总体流程。相较于裸机,系统实时性强了很多,小程序下发…...

爬取豆瓣(线程、Session)优化版本
爬取豆瓣(线程、Session)优化版本 该文章只是为了精进基础,对Session、threading、网站请求解析的理解。 此版本没有爬取详情页。还在学习阶段的读者可以尝试一下。 适用于基础刚开始学习爬虫的! 1.改进点: 将普通的r…...

拷贝控制总结
1.拷贝、值与销毁: 拷贝构造函数:如果一个构造函数的第一个参数是自身类类型的引用,且其他(如果有的话)参数都有默认实参,则此构造函数叫做拷贝构造函数;如果我们没有为类定义一个拷贝构造函数…...

无重复字符串的最长子串
题目描述:给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串的长度。 第一次提交记录 class Solution:def lengthOfLongestSubstring(self, s: str) -> int:if not s:return 0lookup set()left res 0for right in range(len(s)):while s…...

javaScript Object.hasOwn()的用法
Object.hasOwn() 如果指定的对象自身有指定的属性,则静态方法 Object.hasOwn() 返回 true。如果属性是继承的或者不存在,该方法返回 false。 备注: Object.hasOwn() 旨在取代 Object.prototype.hasOwnProperty()。 **语法:**Objec…...

MINI2440 开发板 给他干出来了
环境是ubuntu14.04。不要问我为什么是这个版本,因为之前的ubuntu12.04 环境干不出来,你去试试就知道了!各种资源包下载不下来。 输入启动参数: 进入MINI2440:别说心里一万个开心,启动完成,输入p…...

上海人工智能实验室的书生·浦语大模型学习笔记(第二期第三课——上篇)
书生浦语是上海人工智能实验室和商汤科技联合研发的一款大模型,这次有机会参与试用,特记录每次学习情况。 一、课程笔记 本次学习的是RAG(Retrieval Augmented Generation)技术,它是通过检索与用户输入相关的信息片段…...

前端小白的学习之路(Vue2 三)
提示:学习vue2的第三天,笔记记录:生命周期,组件(注册,传值) 目录 一、生命周期 二、组件 1.注册组件 1)全局注册 2)局部注册 2.组件传值 1)父传子 2)子传父 3)兄弟传值 一…...

ChatGPT 之优势与缺陷
原文: 译者:飞龙 协议:CC BY-NC-SA 4.0 介绍 欢迎来到《ChatGPT:好的、坏的和丑陋的》。在本书中,我们踏上了探索 ChatGPT 多面世界的旅程,这是由 OpenAI 开发的先进自然语言处理模型。随着 ChatGPT 和类似…...

python爬虫———post请求方式(第十四天)
🎈🎈作者主页: 喔的嘛呀🎈🎈 🎈🎈所属专栏:python爬虫学习🎈🎈 ✨✨谢谢大家捧场,祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天…...

51蓝桥杯之DS18B20
DS18B20 基础知识 代码流程实现 将官方提供例程文件添加到工程中 添加onewire.c文件到keil4里面 一些代码补充知识 代码 #include "reg52.h" #include "onewire.h" #include "absacc.h" unsigned char num[10]{0xc0,0xf9,0xa4,0xb0,0x99,…...

TiDB 组件 GC 原理及常见问题
本文详细介绍了 TiDB 的 Garbage Collection(GC)机制及其在 TiDB 组件中的实现原理和常见问题排查方法。 TiDB 底层使用单机存储引擎 RocksDB,并通过 MVCC 机制,基于 RocksDB 实现了分布式存储引擎 TiKV,以支持高可用分…...

【c++】STl-list使用list模拟实现
主页:醋溜马桶圈-CSDN博客 专栏:c_醋溜马桶圈的博客-CSDN博客 gitee:mnxcc (mnxcc) - Gitee.com 目录 1. list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 …...

号卡极团分销管理系统 index.php SQL注入漏洞复现
0x01 产品简介 号卡极团分销管理系统,同步对接多平台,同步订单信息,支持敢探号一键上架,首页多套UI+商品下单页多套模板,订单查询支持实时物流信息、支持代理商自定义域名、泛域名绑定,内置敢探号、172平台、号氪云平台第三方接口以及号卡网同系统对接! 0x02 漏洞概述…...

内核驱动更新
1.声明我们是开源的 .c 文件末尾加上 2.在Kconfig里面修改设备,bool(双态)-----》tristate(三态) 3.进入menuconfig修改为M 4.编译内核 make modules 也许你会看到一个 .ko 文件 5.复制到根目录文件下 在板子…...

故障诊断 | 一文解决,PLS偏最小二乘法的故障诊断(Matlab)
效果一览 文章概述 故障诊断 | 一文解决,PLS偏最小二乘法的故障诊断(Matlab) 模型描述 偏最小二乘法(Partial Least Squares, PLS)是一种统计建模方法,用于建立变量之间的线性关系模型。它是对多元线性回归方法的扩展,特别适用于处理高维数据和具有多重共线性的数据集。…...

我为什么选择成为程序员?
前言: 我选择成为程序员不是兴趣所在,也不是为了职业发展,全是生活所迫! 第一章:那年,我双手插兜,对外面的世界一无所知 时间回到2009年,时间过得真快啊,一下就是15年前…...

Open CASCADE学习|统计形状拓扑数量
边界表示法(Boundary Representation,简称B-Rep)是几何造型中最成熟、无二义的表示法。它主要用于描述物体的几何信息和拓扑信息。在边界表示法中,一个实体(Solid)由一组封闭的面(Faceÿ…...

LeetCode 热题 100 题解(二):双指针部分(2)| 滑动窗口部分(1)
题目四:接雨水(No. 43) 题目链接:https://leetcode.cn/problems/trapping-rain-water/description/?envTypestudy-plan-v2&envIdtop-100-liked 难度:困难 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&am…...

常用的深度学习自动标注软件
0. 简介 自动标注软件是一个非常节省人力资源的操作,而随着深度学习的发展,这些自动化标定软件也越来越多。本文章将会着重介绍其中比较经典的自动标注软件 1. AutoLabelImg AutoLabelImg 除了labelimg的初始功能外,额外包含十多种辅助标注…...

选择程序员是为什么?
本章节是关于为什么会选择一名程序员的经验分享 首先,我为什么会选择这个方向,可能是因为钱多,学东西不就是为了赚钱嘛?这是一点,不过最让我接收这个行业的是好奇世界的新大陆,可以简单的说就是,…...

线程池参数如何设置
线程池参数设置 hello丫,各位小伙伴们,好久不见了! 下面,我们先来复习一下线程池的参数 1、线程池参数有哪些? corePoolSize(核心线程数):线程池中的常驻核心线程数。即使这些线程…...

qt环境搭建-镜像源安装Qt Creator(5.15.2)以及配置环境变量
前言: 版本:5.15.2 镜像源:ustc与清华 纯小白,找了半天的镜像源安装qtcreator,搞了半天结果安装的是最新的,太新的对小白很不友好,bug比较多,支持的系统也不全,口碑不…...

SQL Server详细安装使用教程
1.安装环境 现阶段基本不用SQL Server数据库了,看到有这样的分析话题,就把多年前的存货发一下,大家也可以讨论看看,思路上希望还有价值。 SQL Server 2008 R2有32位版本和64位版本,32位版本可以安装在Windows XP及以上…...

深度解读C++17中的std::string_view:解锁字符串处理的新境界
深入研究C17中的std::string_view:解锁字符串处理的新境界 一、简介二、std::string_view的基础知识2.1、构造函数2.2、成员函数 三、std::string_view为什么性能高?四、std::string_view的使用陷阱五、std::string_view源码解析六、总结 一、简介 C中有…...

汇编基础-----常见命令基本使用
汇编基础-----常见命令基本使用 MOV:将数据从一个位置复制到另一个位置。 MOV destination, source例如: MOV RAX, RBX ; 将RBX寄存器中的值复制到RAX寄存器中ADD/SUB:将两个操作数相加或相减。 ADD destination, source SUB destinatio…...

科研学习|可视化——相关性结果的可视化
一、相关性分析介绍 相关性分析是指研究两种或者两种以上的变量之间相关关系的统计分析方法,一般分析步骤为: 1)判断变量间是否存在关联;2)分析关联关系(线性/非线性)、关联方向(正相…...

MapReduce过程解析
一、Map过程解析 Read阶段:MapTask通过用户编写的RecordReader,从输入的InputSplit中解析出一个个key/value。Map阶段:将解析出的key/value交给用户编写的Map()函数处理,并产生一系列的key/value。Collect阶段:在用户编…...

速看!这8道嵌入式面试题你都会吗?
大家好,我是知微! 正逢求职季,分享一些嵌入式面试当中经常会遇到的题目,希望这些干货对小伙伴们面试有用哦! 1、介绍一下static关键字的作用 在C语言中,static 关键字有几种不同的作用,根据其…...

基于SSM的电影网站(有报告)。Javaee项目。ssm项目。
演示视频: 基于SSM的电影网站(有报告)。Javaee项目。ssm项目。 项目介绍: 采用M(model)V(view)C(controller)三层体系结构,通过Spring SpringMv…...