1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 基于STM32的物联网健康监测系统设计(附源码)

基于STM32的物联网健康监测系统设计(附源码)

时间:2020-12-14 05:43:16

相关推荐

基于STM32的物联网健康监测系统设计(附源码)

目录

1、项目简介

2、整体项目架构设计

2、硬件架构设计

(1)硬件型号

(2)传感器驱动程序设计

3、基于MQTT协议的数据传输

4、微信小程序上位机设计

1.主界面

2. 健康监测系统

3. 环境监测系统

4. 显示实时动态曲线

1、项目简介

该项目是利用STM32开发板进行开发的基于MQTT协议的物联网健康监测系统,并开发了微信小程序作为该项目的软件上位机。该产品可以用来实时监测人体的心率、血氧等生理参数,也可以监测家庭环境中的温湿度、烟雾浓度等环境参数,从而为您营造出一个健康的居住环境。此项目是本人在寒假利用业余时间开发的,从理论知识的学习到做出最终成品耗时将近两个月,可用于嵌入式软件方向的毕业设计。

该项目源码已全部开源,在本文中仅介绍核心代码原理,源码已发布在我的GitHub主页,如对源码有疑惑,或者对该项目有改进性意见,可以评论或私信本人,欢迎大家一起交流学习!

我的github主页:/SichengLong26

源代码地址:/SichengLong26/health-iot

2、整体项目架构设计

该项目主要分为硬件架构设计和软件架构设计,硬件架构设计包括电路设计、PCB焊接、驱动程序编写、数据传输(数据上云)的程序编写;软件架构设计则包括前端UI界面的设计和后端的数据处理,此次软件上位机是基于微信小程序来开发的。

2、硬件架构设计

(1)硬件型号

主控芯片:STM32F103RCT6

传感器:MAX30102心率传感器、DHT11温湿度传感器、MQ2烟雾传感器

通信模块:ESP8266 WIFI模块

(2)传感器驱动程序设计

MAX30102心率血氧传感器(通过IIC驱动)

MAX3010VCC引脚连接STM32F103mini单片机的5伏引脚,GND连接5伏对应的GND,SCL连PC12,SDA连PC11,INT连PA5。MAX30102的其他引脚没有用到。

本代码能够正常接收MAX30102心率血氧传感器返回的red与ir的数值,能够比较正常计算出心率血氧数值。当心率或血氧值的计算结果有误时对应的变量值为-999。

main.c

#include "delay.h"#include "sys.h"#include "usart.h"#include "myiic.h"#include "max30102.h"#include "algorithm.h"#define MAX_BRIGHTNESS 255#define START 100#define DATA_LENGTH 500uint32_t aun_ir_buffer[DATA_LENGTH]; //IR LED sensor dataint32_t n_ir_buffer_length; //data lengthuint32_t aun_red_buffer[DATA_LENGTH]; //Red LED sensor dataint32_t n_sp02; //SPO2 valueint8_t ch_spo2_valid; //indicator to show if the SP02 calculation is validint32_t n_heart_rate; //heart rate valueint8_t ch_hr_valid; //indicator to show if the heart rate calculation is validuint8_t uch_dummy;int main(void){ uint32_t un_min, un_max, un_prev_data; //variables to calculate the on-board LED brightness that reflects the heartbeatsint i;int32_t n_brightness;float f_temp;NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2delay_init();//延时函数初始化 uart_init(9600); //串口初始化为115200IIC_Init(); maxim_max30102_reset(); //resets the MAX30102// initialize serial communication at 115200 bits per second://read and clear status registermaxim_max30102_read_reg(0,&uch_dummy);maxim_max30102_init(); //initializes the MAX30102n_brightness=0;un_min=0x3FFFF;un_max=0;n_ir_buffer_length=DATA_LENGTH; //buffer length of 100 stores 5 seconds of samples running at 100sps//read the first 500 samples, and determine the signal rangefor(i=0;i<n_ir_buffer_length;i++){while(PAin(5)==1); //wait until the interrupt pin assertsmaxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i)); //read from MAX30102 FIFOif(un_min>aun_red_buffer[i])un_min=aun_red_buffer[i]; //update signal minif(un_max<aun_red_buffer[i])un_max=aun_red_buffer[i]; //update signal maxprintf("心率:%i次/min,", aun_red_buffer[i]/1000);printf("血氧=%i % \r\n", aun_ir_buffer[i]/1000+16);}un_prev_data=aun_red_buffer[i];//calculate heart rate and SpO2 after first 500 samples (first 5 seconds of samples)maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); while(1){i=0;un_min=0x3FFFF;un_max=0;//dumping the first 100 sets of samples in the memory and shift the last 400 sets of samples to the topfor(i=START;i<DATA_LENGTH;i++){aun_red_buffer[i-START]=aun_red_buffer[i];aun_ir_buffer[i-START]=aun_ir_buffer[i];//update the signal min and maxif(un_min>aun_red_buffer[i])un_min=aun_red_buffer[i];if(un_max<aun_red_buffer[i])un_max=aun_red_buffer[i];}//take 100 sets of samples before calculating the heart rate.for(i=400;i<DATA_LENGTH;i++){un_prev_data=aun_red_buffer[i-1];while(PAin(5)==1);maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i));if(aun_red_buffer[i]>un_prev_data)//just to determine the brightness of LED according to the deviation of adjacent two AD data{f_temp=aun_red_buffer[i]-un_prev_data;f_temp/=(un_max-un_min);f_temp*=MAX_BRIGHTNESS;n_brightness-=(int)f_temp;if(n_brightness<0)n_brightness=0;}else{f_temp=un_prev_data-aun_red_buffer[i];f_temp/=(un_max-un_min);f_temp*=MAX_BRIGHTNESS;n_brightness+=(int)f_temp;if(n_brightness>MAX_BRIGHTNESS)n_brightness=MAX_BRIGHTNESS;}//re_oxen=(float)aun_red_buffer[i]/(float)aun_ir_buffer[i];//oxen=45.06*re_oxen*re_oxen+30.354*re_oxen+94.845;//send samples and calculation result to terminal program through UART//printf("red=%i,", aun_red_buffer[i]);//printf(" ir=%i,", aun_ir_buffer[i]);}maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, n_ir_buffer_length, aun_red_buffer, &n_sp02, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid); printf(" HR=%i,", n_heart_rate); printf(" HRvalid=%i,", ch_hr_valid);printf(" SpO2=%i,", n_sp02);printf(" SPO2Valid=%i\r\n", ch_spo2_valid);}}

max30102.c(驱动程序)

#include "max30102.h"#include "myiic.h"#define max30102_WR_address 0xAEbool maxim_max30102_write_reg(uint8_t uch_addr, uint8_t uch_data)/*** \brief Write a value to a MAX30102 register* \parDetails*This function writes a value to a MAX30102 register** \param[in] uch_addr - register address* \param[in] uch_data - register data** \retval true on success*/{/* 第1步:发起I2C总线启动信号 */IIC_Start();/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_WR);/* 此处是写指令 *//* 第3步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;/* EEPROM器件无应答 */}/* 第4步:发送字节地址 */IIC_Send_Byte(uch_addr);if (IIC_Wait_Ack() != 0){goto cmd_fail;/* EEPROM器件无应答 */}/* 第5步:开始写入数据 */IIC_Send_Byte(uch_data);/* 第6步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;/* EEPROM器件无应答 */}/* 发送I2C总线停止信号 */IIC_Stop();return true;/* 执行成功 */cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 *//* 发送I2C总线停止信号 */IIC_Stop();return false;}bool maxim_max30102_read_reg(uint8_t uch_addr, uint8_t *puch_data)/*** \brief Read a MAX30102 register* \parDetails*This function reads a MAX30102 register** \param[in] uch_addr - register address* \param[out] puch_data - pointer that stores the register data** \retval true on success*/{/* 第1步:发起I2C总线启动信号 */IIC_Start();/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_WR);/* 此处是写指令 *//* 第3步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;/* EEPROM器件无应答 */}/* 第4步:发送字节地址, */IIC_Send_Byte((uint8_t)uch_addr);if (IIC_Wait_Ack() != 0){goto cmd_fail;/* EEPROM器件无应答 */}/* 第6步:重新启动I2C总线。下面开始读取数据 */IIC_Start();/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_RD);/* 此处是读指令 *//* 第8步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;/* EEPROM器件无应答 */}/* 第9步:读取数据 */{*puch_data = IIC_Read_Byte();/* 读1个字节 */IIC_NAck();/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */}/* 发送I2C总线停止信号 */IIC_Stop();return true;/* 执行成功 返回data值 */cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 *//* 发送I2C总线停止信号 */IIC_Stop();return false;}bool maxim_max30102_init(void)/*** \brief Initialize the MAX30102* \parDetails*This function initializes the MAX30102** \param None** \retval true on success*/{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);//使能PORTA,PORTC时钟GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//关闭jtag,使能SWD,可以用SWD模式调试GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PA5GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //PA5设置成浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA5if(!maxim_max30102_write_reg(REG_INTR_ENABLE_1, 0xc0)) // INTR settingreturn false;if(!maxim_max30102_write_reg(REG_INTR_ENABLE_2, 0x00))return false;if(!maxim_max30102_write_reg(REG_FIFO_WR_PTR, 0x00)) //FIFO_WR_PTR[4:0]return false;if(!maxim_max30102_write_reg(REG_OVF_COUNTER, 0x00)) //OVF_COUNTER[4:0]return false;if(!maxim_max30102_write_reg(REG_FIFO_RD_PTR, 0x00)) //FIFO_RD_PTR[4:0]return false;if(!maxim_max30102_write_reg(REG_FIFO_CONFIG, 0x6f)) //sample avg = 8, fifo rollover=false, fifo almost full = 17return false;if(!maxim_max30102_write_reg(REG_MODE_CONFIG, 0x03)) //0x02 for Red only, 0x03 for SpO2 mode 0x07 multimode LEDreturn false;if(!maxim_max30102_write_reg(REG_SPO2_CONFIG, 0x2F)) // SPO2_ADC range = 4096nA, SPO2 sample rate (400 Hz), LED pulseWidth (411uS)return false;if(!maxim_max30102_write_reg(REG_LED1_PA, 0x17)) //Choose value for ~ 4.5mA for LED1return false;if(!maxim_max30102_write_reg(REG_LED2_PA, 0x17)) // Choose value for ~ 4.5mA for LED2return false;if(!maxim_max30102_write_reg(REG_PILOT_PA, 0x7f)) // Choose value for ~ 25mA for Pilot LEDreturn false;return true;}bool maxim_max30102_read_fifo(uint32_t *pun_red_led, uint32_t *pun_ir_led)/*** \brief Read a set of samples from the MAX30102 FIFO register* \parDetails*This function reads a set of samples from the MAX30102 FIFO register** \param[out] *pun_red_led - pointer that stores the red LED reading data* \param[out] *pun_ir_led - pointer that stores the IR LED reading data** \retval true on success*/{uint32_t un_temp;uint8_t uch_temp;*pun_ir_led = 0;*pun_red_led = 0;maxim_max30102_read_reg(REG_INTR_STATUS_1, &uch_temp);maxim_max30102_read_reg(REG_INTR_STATUS_2, &uch_temp);/* 第1步:发起I2C总线启动信号 */IIC_Start();/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_WR);/* 此处是写指令 *//* 第3步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;/* EEPROM器件无应答 */}/* 第4步:发送字节地址, */IIC_Send_Byte((uint8_t)REG_FIFO_DATA);if (IIC_Wait_Ack() != 0){goto cmd_fail;/* EEPROM器件无应答 */}/* 第6步:重新启动I2C总线。下面开始读取数据 */IIC_Start();/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */IIC_Send_Byte(max30102_WR_address | I2C_RD);/* 此处是读指令 *//* 第8步:发送ACK */if (IIC_Wait_Ack() != 0){goto cmd_fail;/* EEPROM器件无应答 */}un_temp = IIC_Read_Byte();IIC_Ack();un_temp <<= 16;*pun_red_led += un_temp;un_temp = IIC_Read_Byte();IIC_Ack();un_temp <<= 8;*pun_red_led += un_temp;un_temp = IIC_Read_Byte();IIC_Ack();*pun_red_led += un_temp;un_temp = IIC_Read_Byte();IIC_Ack();un_temp <<= 16;*pun_ir_led += un_temp;un_temp = IIC_Read_Byte();IIC_Ack();un_temp <<= 8;*pun_ir_led += un_temp;un_temp = IIC_Read_Byte();IIC_Ack();*pun_ir_led += un_temp;*pun_red_led &= 0x03FFFF; //Mask MSB [23:18]*pun_ir_led &= 0x03FFFF; //Mask MSB [23:18]/* 发送I2C总线停止信号 */IIC_Stop();return true;cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 *//* 发送I2C总线停止信号 */IIC_Stop();return false;}bool maxim_max30102_reset()/*** \brief Reset the MAX30102* \parDetails*This function resets the MAX30102** \param None** \retval true on success*/{if(!maxim_max30102_write_reg(REG_MODE_CONFIG, 0x40))return false;elsereturn true;}

alogrihm.c心率血氧算法:

/** \file algorithm.cpp ******************************************************** Project: MAXREFDES117#* Filename: algorithm.cpp* Description: This module calculates the heart rate/SpO2 level*** --------------------------------------------------------------------** This code follows the following naming conventions:** char ch_pmod_value* char (array)s_pmod_s_string[16]* float f_pmod_value* int32_t n_pmod_value* int32_t (array) an_pmod_value[16]* int16_t w_pmod_value* int16_t (array) aw_pmod_value[16]* uint16_tuw_pmod_value* uint16_t (array) auw_pmod_value[16]* uint8_t uch_pmod_value* uint8_t (array) auch_pmod_buffer[16]* uint32_tun_pmod_value* int32_t * pn_pmod_value** ------------------------------------------------------------------------- *//******************************************************************************** Copyright (C) Maxim Integrated Products, Inc., All Rights Reserved.** Permission is hereby granted, free of charge, to any person obtaining a* copy of this software and associated documentation files (the "Software"),* to deal in the Software without restriction, including without limitation* the rights to use, copy, modify, merge, publish, distribute, sublicense,* and/or sell copies of the Software, and to permit persons to whom the* Software is furnished to do so, subject to the following conditions:** The above copyright notice and this permission notice shall be included* in all copies or substantial portions of the Software.** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR* OTHER DEALINGS IN THE SOFTWARE.** Except as contained in this notice, the name of Maxim Integrated* Products, Inc. shall not be used except as stated in the Maxim Integrated* Products, Inc. Branding Policy.** The mere transfer of this software does not imply any licenses* of trade secrets, proprietary technology, copyrights, patents,* trademarks, maskwork rights, or any other form of intellectual* property whatsoever. Maxim Integrated Products, Inc. retains all* ownership rights.********************************************************************************/#include "algorithm.h"//uch_spo2_table is approximated as -45.060*ratioAverage* ratioAverage + 30.354 *ratioAverage + 94.845 ;const uint8_t uch_spo2_table[184] = { 95, 95, 95, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 99, 99, 99, 99,99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 97, 97,97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91,90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81,80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67,66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50,49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29,28, 27, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 5,3, 2, 1} ;void maxim_heart_rate_and_oxygen_saturation(uint32_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint32_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid,int32_t *pn_heart_rate, int8_t *pch_hr_valid)/*** \brief Calculate the heart rate and SpO2 level* \parDetails*By detecting peaks of PPG cycle and corresponding AC/DC of red/infra-red signal, the an_ratio for the SPO2 is computed.*Since this algorithm is aiming for Arm M0/M3. formaula for SPO2 did not achieve the accuracy due to register overflow.*Thus, accurate SPO2 is precalculated and save longo uch_spo2_table[] per each an_ratio.** \param[in] *pun_ir_buffer - IR sensor data buffer* \param[in] n_ir_buffer_length- IR sensor data buffer length* \param[in] *pun_red_buffer- Red sensor data buffer* \param[out] *pn_spo2- Calculated SpO2 value* \param[out] *pch_spo2_valid - 1 if the calculated SpO2 value is valid* \param[out] *pn_heart_rate- Calculated heart rate value* \param[out] *pch_hr_valid - 1 if the calculated heart rate value is valid** \retval None*/{uint32_t un_ir_mean;int32_t k, n_i_ratio_count;int32_t i, n_exact_ir_valley_locs_count, n_middle_idx;int32_t n_th1, n_npks;int32_t an_ir_valley_locs[15] ;int32_t n_peak_interval_sum;int32_t n_y_ac, n_x_ac;int32_t n_spo2_calc;int32_t n_y_dc_max, n_x_dc_max;int32_t n_y_dc_max_idx, n_x_dc_max_idx;int32_t an_ratio[5], n_ratio_average;int32_t n_nume, n_denom ;// calculates DC mean and subtract DC from irun_ir_mean = 0;for (k = 0 ; k < n_ir_buffer_length ; k++ ) un_ir_mean += pun_ir_buffer[k] ;un_ir_mean = un_ir_mean / n_ir_buffer_length ;// remove DC and invert signal so that we can use peak detector as valley detectorfor (k = 0 ; k < n_ir_buffer_length ; k++ )an_x[k] = -1 * (pun_ir_buffer[k] - un_ir_mean) ;// 4 pt Moving Averagefor(k = 0; k < BUFFER_SIZE - MA4_SIZE; k++){an_x[k] = ( an_x[k] + an_x[k + 1] + an_x[k + 2] + an_x[k + 3]) / (int)4;}// calculate thresholdn_th1 = 0;for ( k = 0 ; k < BUFFER_SIZE ; k++){n_th1 += an_x[k];}n_th1 = n_th1 / ( BUFFER_SIZE);if( n_th1 < 30) n_th1 = 30; // min allowedif( n_th1 > 60) n_th1 = 60; // max allowedfor ( k = 0 ; k < 15; k++) an_ir_valley_locs[k] = 0;// since we flipped signal, we use peak detector as vSalley detectormaxim_find_peaks( an_ir_valley_locs, &n_npks, an_x, BUFFER_SIZE, n_th1, 4, 15 );//peak_height, peak_distance, max_num_peaksn_peak_interval_sum = 0;if (n_npks >= 2){for (k = 1; k < n_npks; k++) n_peak_interval_sum += (an_ir_valley_locs[k] - an_ir_valley_locs[k - 1] ) ;n_peak_interval_sum = n_peak_interval_sum / (n_npks - 1);*pn_heart_rate = (int32_t)( (FS * 60) / n_peak_interval_sum );*pch_hr_valid = 1;}else{*pn_heart_rate = -999; // unable to calculate because # of peaks are too small*pch_hr_valid = 0;}// load raw value again for SPO2 calculation : RED(=y) and IR(=X)for (k = 0 ; k < n_ir_buffer_length ; k++ ){an_x[k] = pun_ir_buffer[k] ;an_y[k] = pun_red_buffer[k] ;}// find precise min near an_ir_valley_locsn_exact_ir_valley_locs_count = n_npks;//using exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration an_ratio//finding AC/DC maximum of rawn_ratio_average = 0;n_i_ratio_count = 0;for(k = 0; k < 5; k++) an_ratio[k] = 0;for (k = 0; k < n_exact_ir_valley_locs_count; k++){if (an_ir_valley_locs[k] > BUFFER_SIZE ){*pn_spo2 = -999 ; // do not use SPO2 since valley loc is out of range*pch_spo2_valid = 0;return;}}// find max between two valley locations// and use an_ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2for (k = 0; k < n_exact_ir_valley_locs_count - 1; k++){n_y_dc_max = -16777216 ;n_x_dc_max = -16777216;if (an_ir_valley_locs[k + 1] - an_ir_valley_locs[k] > 3){for (i = an_ir_valley_locs[k]; i < an_ir_valley_locs[k + 1]; i++){if (an_x[i] > n_x_dc_max){n_x_dc_max = an_x[i];n_x_dc_max_idx = i;}if (an_y[i] > n_y_dc_max){n_y_dc_max = an_y[i];n_y_dc_max_idx = i;}}n_y_ac = (an_y[an_ir_valley_locs[k + 1]] - an_y[an_ir_valley_locs[k] ] ) * (n_y_dc_max_idx - an_ir_valley_locs[k]); //redn_y_ac = an_y[an_ir_valley_locs[k]] + n_y_ac / (an_ir_valley_locs[k + 1] - an_ir_valley_locs[k]) ;n_y_ac = an_y[n_y_dc_max_idx] - n_y_ac; // subracting linear DC compoenents from rawn_x_ac = (an_x[an_ir_valley_locs[k + 1]] - an_x[an_ir_valley_locs[k] ] ) * (n_x_dc_max_idx - an_ir_valley_locs[k]); // irn_x_ac = an_x[an_ir_valley_locs[k]] + n_x_ac / (an_ir_valley_locs[k + 1] - an_ir_valley_locs[k]);n_x_ac = an_x[n_y_dc_max_idx] - n_x_ac;// subracting linear DC compoenents from rawn_nume = ( n_y_ac * n_x_dc_max) >> 7 ; //prepare X100 to preserve floating valuen_denom = ( n_x_ac * n_y_dc_max) >> 7;if (n_denom > 0 && n_i_ratio_count < 5 && n_nume != 0){an_ratio[n_i_ratio_count] = (n_nume * 100) / n_denom ; //formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ;n_i_ratio_count++;}}}// choose median value since PPG signal may varies from beat to beatmaxim_sort_ascend(an_ratio, n_i_ratio_count);n_middle_idx = n_i_ratio_count / 2;if (n_middle_idx > 1)n_ratio_average = ( an_ratio[n_middle_idx - 1] + an_ratio[n_middle_idx]) / 2; // use medianelsen_ratio_average = an_ratio[n_middle_idx ];if( n_ratio_average > 2 && n_ratio_average < 184){n_spo2_calc = uch_spo2_table[n_ratio_average] ;*pn_spo2 = n_spo2_calc ;*pch_spo2_valid = 1;// float_SPO2 = -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ; // for comparison with table}else{*pn_spo2 = -999 ; // do not use SPO2 since signal an_ratio is out of range*pch_spo2_valid = 0;}}void maxim_find_peaks( int32_t *pn_locs, int32_t *n_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num )/*** \brief Find peaks* \parDetails*Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE** \retval None*/{maxim_peaks_above_min_height( pn_locs, n_npks, pn_x, n_size, n_min_height );maxim_remove_close_peaks( pn_locs, n_npks, pn_x, n_min_distance );*n_npks = min( *n_npks, n_max_num );}void maxim_peaks_above_min_height( int32_t *pn_locs, int32_t *n_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height )/*** \brief Find peaks above n_min_height* \parDetails*Find all peaks above MIN_HEIGHT** \retval None*/{int32_t i = 1, riseFound = 0, holdOff1 = 0, holdOff2 = 0, holdOffThresh = 4;*n_npks = 0;while (i < n_size - 1){if (holdOff2 == 0){if (pn_x[i] > n_min_height && pn_x[i] > pn_x[i - 1])// find left edge of potential peaks{riseFound = 1;}if (riseFound == 1){if ((pn_x[i] < n_min_height) && (holdOff1 < holdOffThresh))// if false edge{riseFound = 0;holdOff1 = 0;}else{if (holdOff1 == holdOffThresh){if ((pn_x[i] < n_min_height) && (pn_x[i - 1] >= n_min_height)){if ((*n_npks) < 15 ){pn_locs[(*n_npks)++] = i; // peak is right edge}holdOff1 = 0;riseFound = 0;holdOff2 = 8;}}else{holdOff1 = holdOff1 + 1;}}}}else{holdOff2 = holdOff2 - 1;}i++;}}void maxim_remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance)/*** \brief Remove peaks* \parDetails*Remove peaks separated by less than MIN_DISTANCE** \retval None*/{int32_t i, j, n_old_npks, n_dist;/* Order peaks from large to small */maxim_sort_indices_descend( pn_x, pn_locs, *pn_npks );for ( i = -1; i < *pn_npks; i++ ){n_old_npks = *pn_npks;*pn_npks = i + 1;for ( j = i + 1; j < n_old_npks; j++ ){n_dist = pn_locs[j] - ( i == -1 ? -1 : pn_locs[i] ); // lag-zero peak of autocorr is at index -1if ( n_dist > n_min_distance || n_dist < -n_min_distance )pn_locs[(*pn_npks)++] = pn_locs[j];}}// Resort indices int32_to ascending ordermaxim_sort_ascend( pn_locs, *pn_npks );}void maxim_sort_ascend(int32_t *pn_x, int32_t n_size)/*** \brief Sort array* \parDetails*Sort array in ascending order (insertion sort algorithm)** \retval None*/{int32_t i, j, n_temp;for (i = 1; i < n_size; i++){n_temp = pn_x[i];for (j = i; j > 0 && n_temp < pn_x[j - 1]; j--)pn_x[j] = pn_x[j - 1];pn_x[j] = n_temp;}}void maxim_sort_indices_descend( int32_t *pn_x, int32_t *pn_indx, int32_t n_size)/*** \brief Sort indices* \parDetails*Sort indices according to descending order (insertion sort algorithm)** \retval None*/{int32_t i, j, n_temp;for (i = 1; i < n_size; i++){n_temp = pn_indx[i];for (j = i; j > 0 && pn_x[n_temp] > pn_x[pn_indx[j - 1]]; j--)pn_indx[j] = pn_indx[j - 1];pn_indx[j] = n_temp;}}

DHT11温湿度传感器驱动程序:

dht11.c

#include "dht11.h"#include "delay.h"//由于DHT11为单总线通信,即发送、接收都为同一根数据线,STM32的GPIO无法像51的IO同时配置为输入输出模式,//因此需要将与DHT11数据线相连的GPIO写两套初始化函数,向DHT11发送数据时先调用DHT11_IO_OUT()函数,再//发送数据,接收DHT11的数据时先调用DHT11_IO_IN()函数,再接收数据void DHT11_IO_OUT(void){GPIO_InitTypeDef GPIO_InitStructure;//RCC_APB2PeriphClockCmd(DHT11_GPIO_CLK|RCC_APB2Periph_AFIO, ENABLE); //使能PG端口时钟//GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //由于PA15为JTAG调试接口,需要先禁用JTAG功能才能作为普通的GPIO口//使用,若使用的是普通的GPIO,可将 RCC_APB2Periph_AFIO 与 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE) 去掉 //禁用JTAGGPIO_InitStructure.GPIO_Pin = DHT11_GPIO_PIN; //PG11端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStructure); //初始化IO口}void DHT11_IO_IN(void){GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(DHT11_GPIO_CLK|RCC_APB2Periph_AFIO, ENABLE); //使能PG端口时钟GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//禁用JTAGGPIO_InitStructure.GPIO_Pin = DHT11_GPIO_PIN; //PG11端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //推挽输出GPIO_Init(DHT11_GPIO_PORT, &GPIO_InitStructure); //初始化IO口}//复位DHT11void DHT11_Rst(void) { DHT11_IO_OUT(); //SET OUTPUTDHT11_DQ_OUT=0; //拉低DQdelay_ms(20); //拉低至少18msDHT11_DQ_OUT=1; //DQ=1 delay_us(30);//主机拉高20~40us}//等待DHT11的回应//返回1:未检测到DHT11的存在//返回0:存在u8 DHT11_Check(void) { u8 retry=0;DHT11_IO_IN();//SET INPUT while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us{retry++;delay_us(1);}; if(retry>=100)return 1;else retry=0;while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us{retry++;delay_us(1);};if(retry>=100)return 1; return 0;}//从DHT11读取一个位//返回值:1/0u8 DHT11_Read_Bit(void) {u8 retry=0;while(DHT11_DQ_IN&&retry<100)//等待变为低电平{retry++;delay_us(1);}retry=0;while(!DHT11_DQ_IN&&retry<100)//等待变高电平{retry++;delay_us(1);}delay_us(40);//等待40usif(DHT11_DQ_IN)return 1;else return 0; }//从DHT11读取一个字节//返回值:读到的数据u8 DHT11_Read_Byte(void) { u8 i,dat;dat=0;for (i=0;i<8;i++) {dat<<=1; dat|=DHT11_Read_Bit();} return dat;}//从DHT11读取一次数据//temp:温度值(范围:0~50°)//humi:湿度值(范围:20%~90%)//返回值:0,正常;1,读取失败u8 DHT11_Read_Data(u8 *temp,u8 *humi) { u8 buf[5];u8 i;DHT11_Rst();if(DHT11_Check()==0){for(i=0;i<5;i++)//读取40位数据{buf[i]=DHT11_Read_Byte();}if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4]){*humi=buf[0];*temp=buf[2];}}else return 1;return 0; }//初始化DHT11的IO口 DQ 同时检测DHT11的存在//返回1:不存在//返回0:存在u8 DHT11_Init(void){ DHT11_Rst(); //复位DHT11return DHT11_Check();//等待DHT11的回应}

MQ2烟雾传感器驱动程序:

bsp_adc.c

#include "bsp_adc.h"#define ADC1_DR_Address ((u32)0x40012400+0x4c) //定义ADC的内存地址#include <math.h>static int floag1=0;#define CAL_PPM 20 // 校准环境中PPM值#define RL5// RL阻值static float R0=1; // 元件在洁净空气中的阻值float ppm;__IO uint16_t ADC_ConvertedValue;static void ADC1_GPIO_Config(void) //ADC端口配置{GPIO_InitTypeDef GPIO_InitStructure;//GPIO初始化结构体;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能DMA的时钟;RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOC, ENABLE);//打开ADC1和GPIOC的时钟;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//配置PC0引脚;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//设置工作模式为模拟输入;GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIOC;}static void ADC1_Mode_Config(void) //配置ADC1的模式{/********以下是有关DMA的相关配置*************/DMA_InitTypeDef DMA_InitStructure;//DMA初始化结构体定义DMA初始化变量ADC_InitTypeDef ADC_InitStructure;//ADC初始化结构体定义ADC初始化变量DMA_DeInit(DMA1_Channel1);//设置DMA1通道1DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//设定ADC的地址;DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue;//内存地址,采集的数据存在这里;DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//设为源,表示数据是从这里出发的;DMA_InitStructure.DMA_BufferSize = 1;//因为一次只发送一个数据所以设为1;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;//因为只涉及一路数据的采集发送因此内存地址不变DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//因为只涉及一路数据的采集发送因此外设地址不变DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//外设至少要半字即16位才可以满足要求DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//内存至少要半字即16位才可以满足要求DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//DMA模式为循环传输,因为要采集多次;DMA_InitStructure.DMA_Priority = DMA_Priority_High;//设置为高、中、低优先级都可以因为只有一路在采集DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//关闭内存到内存的传输,因为我们需要的是外设传到内存的传输DMA_Init(DMA1_Channel1,&DMA_InitStructure);//DMA1通道1最后初始化DMA_Cmd(DMA1_Channel1, ENABLE);//使能DMA1的通道1;/********以下是有关ADC的相关配置*************/ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//设置为独立ADC模式,因为其采集只有一个通道;ADC_InitStructure.ADC_ScanConvMode = DISABLE;//禁止扫描模式,扫描模式适用于多通道采集ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//开启连续转换,以不停地进行ADC转换ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//不使用外部触发转换,而使用内部软件触发ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//采集数据右对齐ADC_InitStructure.ADC_NbrOfChannel = 1;//ADC number of channel,即要转换的通道数目;ADC_Init(ADC1, &ADC_InitStructure);//调用ADC初始化库函数RCC_ADCCLKConfig(RCC_PCLK2_Div8);//配置ADC时钟为8分频,9MHZADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5);//配置ADC1的通道为55.5个采样周期,ADC_DMACmd(ADC1, ENABLE);//使能ADC1的DMA传输ADC_Cmd(ADC1, ENABLE);//使能ADC1;while(ADC_GetResetCalibrationStatus(ADC1));//等待校准寄存器复位完成;ADC_StartCalibration(ADC1);//调用校准函数开始ADC校准;while(ADC_GetResetCalibrationStatus(ADC1));//等待校准寄存器复位完成;ADC_SoftwareStartConvCmd(ADC1, ENABLE);//前面不采用外部触发,而是采用内部软件触发,此处使能软件触发}void ADC1_Init(void){ADC1_GPIO_Config();ADC1_Mode_Config();}// 传感器校准函数void MQ2_PPM_Calibration(float RS){R0 = RS / pow(CAL_PPM / 613.9f, 1 / -2.074f);}// MQ2传感器数据处理float MQ2_GetPPM(void){float Vrl = (float) ADC_ConvertedValue/4096*3.3;float RS = (3.3f - Vrl) / Vrl * RL; if(Vrl>1&&floag1==0) // 获取系R0{MQ2_PPM_Calibration(RS);floag1=1;}ppm = 613.9f * pow(RS/R0, -2.074f);return ppm;}

3、基于MQTT协议的数据传输

STM32开发板通过ESP8266模块连接上局域网(也就是我们家庭中的路由器、WIFI),同时ESP8266通过互联网连接到远程MQTT服务器(MQTT Server),这样便达到了将传感器所采集到的数据储存在云端,可供多个客户端进行访问。

在数据传输中,用到了一种很核心的物联网数据传输协议:MQTT协议。MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。它的基本原理是:一个客户端(ESP8266)向服务器发布(Publish)一个带有传感采集到的数据的主题,则另外一个客户端(软件上位机)则通过向服务器订阅(Subscribe)服务器上ESP8266客户端发布的主题,即可以接收到各传感器采集到的数据,从而达到实时显示、实时检测的目的。

同时数据在传输的过程中都是以JSON格式进行传输的。

核心代码介绍:

由于该部分核心代码过多,就不在本文展示,请到我的github源码仓库进行访问,在这里简要对一些程序文件进行说明:

esp8266.c: ESP8266模块的驱动程序

onenet.c: 数据上传云域网程序

MqttKit.c: 客户端对服务器进行一系列数据传输程序(例如主题的发布、订阅)

4、微信小程序上位机设计

小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。同时因为它不像安卓APP一样需要下载,可以免安装使用,有一定的便利性,所以本项目采用微信小程序来开发上位机。下面该软件上位机的各个UI界面及其所对应的功能

1.主界面

这是该软件的首页,通过和风天气平台的支持,可以获取到当前你所在地的天气状况,同时正中间的三个控件可以进入三个不同的系统。

代码:

index.wxml:

<!--pages/home/home.wxml--><view class="wrapper"><view class="header-wrapper" bindtap="toDetail"><view class="header-title"> <text>空气质量-{{weather_quality}}</text><text>{{city}}-{{area}}</text></view><view class="header-text"><text>{{weather_temp}}℃</text><text>{{weather_text}}</text></view><view class="weather-advice"><text>{{advice}}</text></view></view><view class="botton-wrapper"><button id="btn1" bindtap="toEnv">环境监测系统</button><button id="btn2" bindtap="toHealth">健康监测系统</button><button id="btn2" bindtap="toCanvas">查看实时动态曲线</button></view></view>

index.wxss

/* pages/home/home.wxss */.wrapper{padding: 30rpx 20rpx;/* background-color: beige; */}.header-wrapper{background-color: #3d7ef6;border-radius: 40rpx;padding: 30rpx 50rpx;box-shadow: #d6d6d6 1px 1px 1px;color: floralwhite;}.header-title{display: flex;font-size: 30rpx;justify-content: space-between;}.header-text{display: flex;font-size: 48rpx;font-weight: 400;padding: 7rpx 0rpx;justify-content: space-between;}.weather-advice{font-size: 26rpx;margin-top: 50rpx;}.botton-wrapper{padding: 240rpx 30rpx;}#btn1{height: 80rpx;width: 60% ;color: white;font-size: 30rpx ;background-color: #3d7ef6 ;margin-top: 5rpx;justify-content: center;box-shadow: #d6d6d6 2px 2px 2px;border-radius: 40rpx;}#btn2{height: 80rpx;width: 60% ;color: white;font-size: 30rpx ;background-color: #3d7ef6 ;margin-top: 50rpx;justify-content: center;box-shadow: #d6d6d6 2px 2px 2px;border-radius: 40rpx;}

index.js

Page({/*** 页面的初始数据*/data: {weather_quality:"请求中",weather_text:"请求中",weather_temp:"请求中",city:"请求中",area:"请求中",advice:"请求中"},toEnv(){wx.navigateTo({url: '../env/env'})},toHealth(){wx.navigateTo({url: '../healthy/healthy'})},toCanvas(){wx.navigateTo({url: '../curves/curves'})},toDetail(){wx.navigateTo({url: '../weather/weather'})},toBluetooth(){wx.navigateTo({url: '../bluetooth/bluetooth'})},/*** 生命周期函数--监听页面加载*/onLoad: function (options) {},onShow: function () {var that=thiswx.getLocation({success(res){const {latitude} = resconst {longitude} = resconst key = '1c6a3dc86f2544a3b18828ca409858c9'// 请求温度与天气状况wx.request({url: `/v7/weather/now?location=${longitude},${latitude}&key=${key}`,success(res){// console.log(res)that.setData({weather_temp:res.data.now.temp,weather_text:res.data.now.text})}})// 地理位置wx.request({url: `/v2/city/lookup?location=${longitude},${latitude}&key=${key}`,success(res){// console.log(res)that.setData({city:res.data.location[0].adm2,area:res.data.location[0].name})}})//指数wx.request({url: `/v7/indices/1d?location=${longitude},${latitude}&key=${key}&type=${0}`,success(res){// console.log(res)that.setData({advice:res.data.daily[2].text})}})//空气质量wx.request({url: `/v7/air/now?location=${longitude},${latitude}&key=${key}`,success(res){console.log(res)that.setData({weather_quality:res.data.now.category})}})}})},/*** 生命周期函数--监听页面隐藏*/onHide: function () {},/*** 生命周期函数--监听页面卸载*/onUnload: function () {},/*** 页面相关事件处理函数--监听用户下拉动作*/onPullDownRefresh: function () {},/*** 页面上拉触底事件的处理函数*/onReachBottom: function () {},/*** 用户点击右上角分享*/onShareAppMessage: function () {}})

2. 健康监测系统

用于显示MAX30102心率血氧传感器所采集到的人体的心率、血氧值,可以对其进行实时监测并显示

healthy.wxml :

<view class="body-wrapper"><view class="sensor-others"><image class="sensor-logo-water" src="../../icon/heart.png"/><view class="sensor-text"><view class="sensor-title">实时心率</view><view class="sensor-value">{{Heart}}</view></view></view></view><view class="body-wrapper"><view class="sensor-others"><image class="sensor-logo-oxygen" src="../../icon/oxygen.png"/><view class="sensor-text"><view class="sensor-title">血氧浓度</view><view class="sensor-value">{{Spo2}}%</view></view></view></view>

healty.wxss:

/* pages/healthy/healthy.wxss *//* pages/env/env.wxss */.body-wrapper{padding: 30rpx 20rpx;}.sensor{width: 100%;height: 190rpx;border-radius: 40rpx;box-shadow: #d6d6d6 1px 1px 5px;margin-top: 50rpx;display: flex;justify-content: space-between;}.sensor-logo{padding: 20rpx 30rpx;height: 130rpx;width: 190rpx;margin-top: 10rpx;}.sensor-logo-water{padding: 20rpx 50rpx;height: 130rpx;width: 180rpx;margin-top: 10rpx;}.sensor-logo-oxygen{padding: 20rpx 90rpx;height: 118rpx;width: 120rpx;margin-top: 10rpx;}.sensor-text{padding: 0rpx 120rpx;margin-top: 26rpx;color: #2e2e2e;}.sensor-title{font-size: 35rpx;}.sensor-value{font-size: 66rpx;}.sensor-others{width: 100%;height: 190rpx;border-radius: 40rpx;box-shadow: #d6d6d6 1px 1px 5px;margin-top: 0rpx;display: flex;justify-content: space-between;}

healty.js

var mqtt=require('../../utils/mqtt.min.js')let client=nullPage({data: {Heart:0,Spo2:0},/*** 生命周期函数--监听页面加载*/onLoad: function (options) {this.connectmqtt()},connectmqtt:function(){var that=thisconst options={connectTimeout:4000,clientId:"d1f22er224",port:8084,username:'f585c3d5f499ffea9b710f13709a855d',password:'123456'}client=mqtt.connect('wxs://t.yoyolife.fun/mqtt',options)client.on('connect',(e)=>{console.log("mqtt服务器连接成功")client.subscribe('/iot/943/pub',{qos:0},function(err){if(!err){console.log("订阅成功!")}})})client.on('message',function(topic,message){let dataFrameDev=[]dataFrameDev=JSON.parse(message)console.log(dataFrameDev)})}})

3. 环境监测系统

用于显示DHT11温湿度传感器、MQ2传感器所采集到的环境参数数据,可以对其进行实时监测并显示。

env.wxml:

<!--pages/env/env.wxml--><view class="body-wrapper"><view class="sensor"><image class="sensor-logo" src="../../icon/temperature.png"/><view class="sensor-text"><view class="sensor-title">实时温度</view><view class="sensor-value">{{Temp}}℃</view></view></view></view><view class="body-wrapper"><view class="sensor-others"><image class="sensor-logo-water" src="../../icon/water.png"/><view class="sensor-text"><view class="sensor-title">实时湿度</view><view class="sensor-value">{{Hum}}%</view></view></view></view><view class="body-wrapper"><view class="sensor-others"><image class="sensor-logo-smoke" src="../../icon/smoke.png"/><view class="sensor-text"><view class="sensor-title">烟雾浓度</view><view class="sensor-value">{{Smoke}}</view></view></view></view><view class="body-wrapper"><view class="sensor-others"><image class="sensor-logo" src="../../icon/lx.png"/><view class="sensor-text"><view class="sensor-title">光照度</view><view class="sensor-value">29lx</view></view></view></view><view class="control-wrapper"><view class="body-wrapper"><view class="control"><image class="control-logo" src="../../icon/beep.png"/><view class="control-text"><view class="control-title">报警器</view><view class="control-value"><switch bindchange="handleLED" checked="{{true}}"></switch></view></view></view></view><view class="body-wrapper"><view class="control"><image class="control-logo" src="../../icon/led.png"/><view class="control-text"><view class="control-title">房间灯</view><view class="control-value"><switch bindchange="handleLED" checked="{{true}}"></switch></view></view></view><view>{{event}}</view></view></view>

env.wxss

/* pages/env/env.wxss */.body-wrapper{padding: 30rpx 20rpx;}.sensor{width: 100%;height: 190rpx;border-radius: 40rpx;box-shadow: #d6d6d6 1px 1px 5px;margin-top: 50rpx;display: flex;justify-content: space-between;}.sensor-logo{padding: 20rpx 30rpx;height: 130rpx;width: 190rpx;margin-top: 10rpx;}.sensor-logo-water{padding: 20rpx 50rpx;height: 130rpx;width: 180rpx;margin-top: 10rpx;}.sensor-logo-smoke{padding: 20rpx 50rpx;height: 130rpx;width: 140rpx;margin-top: 10rpx;}.sensor-text{padding: 0rpx 120rpx;margin-top: 26rpx;color: #2e2e2e;}.sensor-title{font-size: 35rpx;}.sensor-value{font-size: 66rpx;}.sensor-others{width: 100%;height: 190rpx;border-radius: 40rpx;box-shadow: #d6d6d6 1px 1px 5px;margin-top: 0rpx;display: flex;justify-content: space-between;}.control{width: 100%;height: 190rpx;border-radius: 40rpx;box-shadow: #d6d6d6 1px 1px 5px;margin-top: 0rpx;display: flex;justify-content: space-between;}.control-logo{padding: 40rpx 30rpx;height: 100rpx;width: 100rpx;margin-top: 10rpx;}.control-text{padding: 0rpx 30rpx;margin-top: 26rpx;color: #2e2e2e;}.control-title{font-size: 33rpx;}.control-value{font-size: 60rpx;}.control-wrapper{display: flex;padding: 0rpx 19.5rpx;}

env.js

var mqtt=require('../../utils/mqtt.min.js')var client=nullPage({/*** 页面的初始数据*/data: {Temp:0,Hum:0,Smoke:0,Led:false,Beep:false,event:""},/*** 生命周期函数--监听页面加载*/onLoad(){this.connectmqtt()},connectmqtt:function(){var that=thisconst options={connectTimeout:4000,clientId:"df2er24",port:8084,username:'f585c3d5f499ffea9b710f13709a855d',password:'123456'}client=mqtt.connect('wxs://t.yoyolife.fun/mqtt',options)client.on('connect',(e)=>{console.log("服务器连接成功")client.subscribe('/iot/943/pub',{qos:0},function(err){if(!err){console.log("订阅成功")}})})// 信息监听事件client.on('message', function(topic,message){// console.log(topic)let dataFrameDev ={}dataFrameDev = JSON.parse(message)console.log(dataFrameDev)that.setData({Temp:dataFrameDev.Temp,Hum:dataFrameDev.Hum,Smoke:dataFrameDev.Smoke})console.log(that.data.Temp)})client.on('reconnect', (error)=>{console.log('正在重新连接'+error)})client.on('error', (error)=>{console.log('连接失败')})},handleLED(e){var that=this// console.log(e)let {value}=e.detail// console.log(value)that.setData({Led:value,})if(value===true){that.setData({event:"您已开灯!",})client.publish('/iot/943/sub','{"target":"LED","value":1}',function(err){if(!err){console.log("成功发布开灯命令")}})}else{that.setData({event:""})client.publish('/iot/943/sub','{"target":"LED","value":0}',function(err){if(!err){console.log("成功发布关灯命令")}})}}})

4. 显示实时动态曲线

为了能显示出一段时间内各个参数的动态变化过程,这里开发了一个实时动态曲线功能,每当接收到服务端传来的消息,则刷新一次曲线从而达到实时更新的目的。

curves.wxml

<Tabs list="{{list}}" binditemChange="handleItemChange"><block wx:if="{{list[0].isActive}}" class="tabs"> <view class="body"><view class="body-content"><view class="body-title">温湿度、烟雾浓度实时曲线图</view><view class="meandata-body"><view class="meandata"><view><view>平均浓度</view><view>{{smoke_average}}bpm</view></view><view><view>平均湿度</view><view>{{hum_average}}%</view></view><view> <view>平均温度</view><view>{{temp_average}}℃</view></view></view></view><canvas canvas-id="lineCanvas" disable-scroll="true" class="canvas" bindtouchstart="touchHandler"></canvas><view class="timestyle">{{time}}</view></view></view></block><block wx:elif="{{list[1].isActive}}"> <view class="detail-title"><view class="title-time">时间</view><view class="title-temp">温度</view><view class="title-hum">湿度</view><view class="title-smoke">烟雾浓度</view></view><view class="data"><view class="data-time"><view wx:for="{{time_array}}" wx:key="*this">{{item}}</view></view><view class="data-temp"><view wx:for="{{Temp_array}}" wx:key="*this" class="data-temp">{{item}}℃</view></view><view class="data-hum"><view wx:for="{{Hum_array}}" wx:key="*this" class="data-hum">{{item}}%</view></view><view class="data-smoke"><view wx:for="{{Smoke_array}}" wx:key="*this" class="data-smoke">{{item}}bpm</view></view></view></block></Tabs>

curves.wxss

page {background-color: rgba(239, 239, 240);}.body{padding: 0rpx 20rpx;margin-top: 100rpx;}.body-content{background-color:#ffffff;border-radius: 40rpx;}.body-title{display: flex;justify-content: center;font-size: 30rpx;padding: 50rpx 0rpx 20rpx;}.meandata-body{padding: 20rpx 39rpx;}.meandata{display: flex;justify-content: space-around;border-bottom: 1rpx solid rgba(216, 216, 216, 1);border-top: 1rpx solid rgba(216, 216, 216, 1);padding: 24rpx;}.canvas {width: 100%;height: 550rpx;}.timestyle{padding: 50rpx 165rpx;}.detail-title{display: flex;}.data-temp{padding: 19rpx 30rpx;}.data-hum{padding: 19rpx 30rpx;}.data-smoke{padding: 19rpx 30rpx;}.data{display: flex;}.title-time{padding: 0rpx 0rpx 0rpx 60rpx;}.title-temp{padding: 0rpx 0rpx 0rpx 111rpx;}.title-hum{padding: 0rpx 0rpx 0rpx 117rpx;}.title-smoke{padding: 0rpx 0rpx 0rpx 140rpx;}

curves.js

// pages/index/lookrecord/lookrecord.jsvar wxCharts = require('../../utils/wxcharts.js'); //引入wxChart文件var mqtt=require('../../utils/mqtt.min.js') // 引入mqtt文件var util = require("../../utils/util.js");var client=nullvar app = getApp();var lineChart = null;Page({/*** 页面的初始数据*/data: {list:[{id:0,name:"趋势图",isActive:true},{id:1,name:"数据记录",isActive:false},],time:"",xtime:"",Temp:0,temp_average:0,temp_sum:0,Hum:0,hum_average:0,hum_sum:0,Smoke:0,smoke_average:0,smoke_sum:0,Temp_array:[],Hum_array:[],Smoke_array:[],time_array:[],waterwaterdata:[50, 100, 80, 115, 120, 90, 125],smokesmokedata:[60, 70, 90, 105, 120, 130, 95],tempdata: [60,90, 60, 110,120,105,70], //数据点categories: ['-6-13', '-6-14', '-6-15', '-6-16', '-6-17', '-6-18', '-6-19'], //模拟的x轴横坐标参数},touchHandler: function (e) {lineChart.showToolTip(e, {// background: '#7cb5ec',format: function (item, category) {return category + ' ' + item.name + ':' + item.data}});},/*** 生命周期函数--监听页面加载*/onLoad(){this.curve()this.connectmqtt()// this.gettime()},onShow:function(){this.notification() // 调用方法},connectmqtt:function(){var that=thisconst options={connectTimeout:4000,clientId:"df2er24",port:8084,username:'f585c3d5f499ffea9b710f13709a855d',password:'123456'}client=mqtt.connect('wxs://t.yoyolife.fun/mqtt',options)client.on('connect',(e)=>{console.log("服务器连接成功")client.subscribe('/iot/943/pub',{qos:0},function(err){if(!err){console.log("订阅成功")}})})// 信息监听事件client.on('message', function(topic,message){// console.log(topic)let dataFrameDev ={}dataFrameDev = JSON.parse(message)console.log(dataFrameDev)that.setData({Temp:dataFrameDev.Temp,Hum:dataFrameDev.Hum,Smoke:dataFrameDev.Smoke,})// 设置温度、湿度、烟雾浓度的数组that.setData({Temp_array:that.data.Temp_array.concat(that.data.Temp),Hum_array:that.data.Hum_array.concat(that.data.Hum),Smoke_array:that.data.Smoke_array.concat(that.data.Smoke)})console.log(that.data.Temp)console.log(that.data.Temp_array)console.log(that.data.Hum_array)console.log(that.data.Smoke_array)// 获取到sensor data 后开始获取本地时间var xtime=that.data.xtimethat.setData({xtime:util.formatTime(new Date())})console.log(that.data.xtime)that.setData({time_array:that.data.time_array.concat(that.data.xtime)})// 求烟雾浓度平均值var smoke_average=that.data.smoke_averagevar Smoke_array=that.data.Smoke_arrayvar smoke_sum=that.data.smoke_sumthat.setData({smoke_sum:smoke_sum+that.data.Smoke,smoke_average:parseInt(that.data.smoke_sum/(Smoke_array.length))})console.log(that.data.time_array)console.log("平均浓度"+that.data.smoke_average)// 求温度平均值var temp_average=that.datatempe_averagevar Temp_array=that.data.Temp_arrayvar temp_sum=that.data.temp_sumthat.setData({temp_sum:temp_sum+that.data.Temp,temp_average:parseInt(that.data.temp_sum/(Temp_array.length))})console.log(that.data.time_array)console.log("平均温度"+that.data.temp_average)// 求平均湿度var hum_average=that.data.hum_averagevar Hum_array=that.data.Hum_arrayvar hum_sum=that.data.hum_sumthat.setData({hum_sum:hum_sum+that.data.Hum,hum_average:parseInt(that.data.hum_sum/(Hum_array.length))})console.log(that.data.time_array)console.log("平均湿度"+that.data.hum_average)})client.on('reconnect', (error)=>{console.log('正在重新连接'+error)})client.on('error', (error)=>{console.log('连接失败')})},curve (e) {var that=thisvar windowWidth = '', windowHeight=''; //定义宽高that.data.setInter = setInterval(function(){var waterwaterdata=that.data.Hum_arrayvar smokesmokedata=that.data.Smoke_arrayvar tempdata=that.data.Temp_arrayvar categories=that.data.time_arraytry {var res = wx.getSystemInfoSync(); //试图获取屏幕宽高数据windowWidth = res.windowWidth / 750 * 690; //以设计图750为主进行比例算换windowHeight = res.windowWidth / 750 * 550 //以设计图750为主进行比例算换} catch (e) {console.error('getSystemInfoSync failed!'); //如果获取失败}lineChart = new wxCharts({//定义一个wxCharts图表实例canvasId: 'lineCanvas',//输入wxml中canvas的idtype: 'line', //图标展示的类型有:'line','pie','column','area','ring','radar'categories: categories,animation: true, //是否开启动画series: [{ //具体坐标数据name: '温度', //名字data: tempdata, //数据点format: function (val, name) { //点击显示的数据注释return val + '℃';}}, {name: '烟雾浓度',data: smokesmokedata,format: function (val, name) {return val + 'bpm';}}, {name: '湿度',data: waterwaterdata,format: function (val, name) {return val + '%';}}],xAxis: { //是否隐藏x轴分割线disableGrid: true,},yAxis: {//y轴数据title: '数值', //标题format: function (val) { //返回数值return val.toFixed(2);},min: 30, //最小值max:180, //最大值gridColor: '#D8D8D8',},width: windowWidth, //图表展示内容宽度height: windowHeight, //图表展示内容高度dataLabel: false, //是否在图表上直接显示数据dataPointShape: true, //是否在图标上显示数据点标志extra: {lineStyle: 'curve' //曲线},});},9000)},notification: function () {var _this = this;var time = _this.data.time;_this.data.setInter = setInterval(function () {_this.setData({time: util.formatTime(new Date())}); //console.log("时间为"+_this.data.time); }, 1000); },// gettime(){// var that=this// var xtime=that.data.xtime// that.setData({//xtime:util.formatTime(new Date())// })// }handleItemChange(e){// console.log(e)const {index}=e.detail;let {list}=this.data;list.forEach((v,i)=>i===index?v.isActive=true:v.isActive=false);this.setData({list}) }})

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。