湖南省物联网应用创新竞赛之技能赛——物联网技术在校园直饮水管理系统中的创新应用
物联网赛题
1.应用场景描述
直饮水在校园和公共场所到处可见,如校园教室楼、图书馆、体育馆、食堂和宿舍,以及公共场所如火车站、机场、购物中心、公园等。直饮水给我们学习、工作和生活带来很多便利。然而在直饮水广泛使用的同时,我们也面临如下问题:
到了一个陌生地方,如何知道哪里有水喝?
所供饮水设备是否工作正常?水质是否合格?
作为直饮水管理部门,如何快速、准确、方便了解所管控设备的状况?
基于物联网的校园直饮水管理系统让饮水、饮水人、饮水机、饮水管理和关注者等所有与“饮水”关联的人和物之间实现饮水信息相连相息。
2. 竞赛题目
不限平台,搭建基于物联网的校园直饮水管理系统。假设系统由直饮水机、后端服务器、前端应用终端、以及直饮水机专用巡检装置组成,各部分功能作用如下:
A. 直饮水机
设直饮水机组成和控制如图所示:
1) 每台饮水机有一个唯一的ID号, 如:ID1=2001 ID2=2002等;。
2) 饮水机可以读取学生校园卡 ( RFID卡,数量2个及以上)卡号CardNumber,只有当合法学生校园卡放置在读卡区(比赛场景仅要求读面到校园卡号,无需核实其合法性),饮水机才提供饮水服务;
3) 每台饮水机有“复位”、“暂停”“正常”三种状态,其工作能被远皮远程后台控制: a)饮水机在 “复位”、“暂停”状态时不提供饮水服务,仅“正常状态提供饮水服务;
b) 饮水机每次上电后进入“复位"并向后台发送复位消息。后台收到复位消息后,自动执行:
向饮水机发送系统时钟(时分秒),以帮助饮水机同步时间(饮水机后续计时可由本地处理);
根据系统设定情况向饮水机发出“正常”或“暂停”指令。
4) 常温水箱内部设有高水位(Wh)、 低水位(W1)检测传感器(竞赛场景可用接近开关、红外开关、微动开关、或按键等代替):
a. “正常”状态时,当水位低于Wl时自动启动加压水泵M (竞赛中用电机代替),经M加压的高压水通过净化装置净化后流入常温水箱;当水位高于Wh时加压泵M停止工作。
b. 如果水位传感器状态指示:低于WI、同时高于Wh,判断为设备不正常,系统应提示设备故障(现场显示故障,蜂鸣器循环发“响0.4秒、停0.6秒”报警声,并向后台发出设备故障代码“Error1”);
5) TDS ( 水中固体物质含量指标,单位ppm)是衡量水质的一一个重要指标。TDS测定与测量探头设计、测量电路、被测水温、标定方法等因素相关,具体到水质测量一般可简化为水温和电压的测量。例如:
TDS=110*(Vcc-V)/V)*(1+0.02*(T-25))
是一款水质传感器的TDS计算公式。假设在水源(净化前)和常温水箱(净化后)两处测定TDS以监测饮水机工作状况和饮水水质情况,并假设被测水温T=25°C、水质测量电路供 电电压Vcc=5 (伏):
a. 水质计算公 式为:
水源水质: TDS1=110*((Vcc-V1)/V1)(V1不等于0时)
饮水水质: TDS2=110*(Vcc-V2)/V2) (V2 不等于0时)
式中: V1、V2为水源和常温水箱两处水质探测得到的电压(竞赛场景用电压代替)。b)当水质TDS2值大于100ppm表示饮水水质不合格(现场显示警告,蜂鸣器循环发“响0.1秒、停0.9秒”警告声,并向后台发出警告代码“Warning1");
6) 饮水量由流量计F (竞赛场景可用编码器、按键等代替,每一个转动量、或脉冲计数代表一定流量饮水)测定;
7) 热饮水加热器P (可用指示灯代替)受热饮水温度T2自动控制:
a) 供热饮水时,当T2低于设定值TH时加热、高于TH时停止加热。
b)TH可本地或经移动终端尚设定和改变。
8) 电磁水阀s1或s2 (竞赛中电磁水阀可用指示灯代替,设常温饮水和热饮水共用一个出水口,控制常温饮水或热饮水开闭,S1和S2不能同时“开”(即不能同时出常温水和热)。
9) 按键K1 (常温水)和K2 (热水)操控饮水机出水
a)停止出水(S1闭、S2闭)情况下:
按下K1出常温水,松开K1维持出常温水状态不变:
或按下K2出热水,松开K2维持出热水状态不变:
b) 出常温水(S1开、S2闭)时:
按下K1停止出水;
或按下K2转换成出热水;
c) 出热水(S1闭、S2开)时:
按下K1转换成出常温水;
或按下K2停止出水;
d) 一次出水量(流量计F计数值)超过一定流量值时,也自动停止出水。
10) 饮水机上能够显示(同时、分时、切换均可):
a) 本地时间(时分秒)
b) 水温(T1、T2)
c) 水质(TDS1、 TDS2)
11) 饮水机具备与后台服务器通信功能,将下列信息传到后台服务器供管理和应用:
a) 饮水机ID;
b) 饮水学生校园卡卡号(CardNumber);
c)饮水时间(时分秒)
d)饮水量
e)故障时设备故障代码“Error1”
f)警告时警告代码“Warning1”。
12) 挑战性功能1:当饮水机临时断电,本地或经移动终端设置的热水控制参数TH,在饮水机复电后仍然自动有效;
13) 挑战性功能2:当饮水机与后台服务器“断网”,联网后能够将“断网”期间发生的相关饮水事件数据不丢失地传送至后端服务器。竞赛申假设“断网”期间发生的饮水事件不小于2次。
B. 后端服务器
构建校园直饮水信息物联网后台服务器,支撑校园直饮水信息收集、储存、管理、应用。竞赛题目要求后台服务器:
1) 与各饮水机之间建立通信联系;
2) 收到某饮水机“复位”消息后,向饮水机回送系统时钟(时分秒) ,系统设定情况向饮水机发出正常”或“暂停”指令(参见A3.b)
3) 后台服务器数据或数据库包含:
a)饮水机位置信息, 如:
1D=2001位置:图书馆、或其它
1D-2002位置:教学楼、或其它
b)饮水机工作属性:
复位:(上电未与服务器连通时状态、不提供饮水服务)
暂停: (该饮水机因故暂停工作、不提供饮水服务)
正常: (该饮水机正常工作)
4) 接收、记录饮水机发出的各种饮水信息(参见A.11);
5) 给应用前端(移动端用户、计算机用户等)提供查询、统计和控制操作等。
C. 前端应用终端
校园直饮水信息系统前端应用如通过网络终端(如计算机)或移动终端(如智能手机App)提供的应用(竞赛作品简化,不区分饮水用户和管理用户),要求:
1)选择实现:移动终端(智能手机App)或网络终端(网页浏览器),二选一即可,移动终端优先;
2)应用终端上可修改后台服务器上饮水机工作属性(“正常”或“暂停”)。修改后,后台服务器应根据工作属性对饮水机进行同步控制;
3)应用终端上可查询全部饮水机(不少于2台)位置、工作状态及水质信息;4)应用终端上可查询某饮水机某段时间内输出的饮水总量;
5)应用终端上可查询某学生某段时间内在所有饮水机(不少于2台)上的饮水总量;6)饮水机“故障”、或“报警”时,应用终端上同步显示。
D. 巡检装置
专用于饮水机维护维修的移动装置。当巡检人员携带该巡检装置到达饮水机附近时,可与饮水机进行信息交互,方便维护维修人员快速了解饮水机状况,要求:
1)与饮水机之间不经过后台服务器、公共通信网络,仅巡检装置与饮水机之间现场点对点、无线方式通信;
2)巡检设备可显示连接的饮水机: ID 号,水质检测电压V1、V2,报警信息。
一、思维导图
1. 处理模块
以Stm32开发板作为中心节点,还要两块Stm32开发板作为子节点,外接多路传感器,分别采集实时数据,打印在oled显示屏上。
2. 服务器
可以选择Linux平台作为服务器,通过MQTT 将Stm32上采集到的数据发送至服务器上然后解析出数据,插入本地数据库。
3. Android或Web
①用Java spring boot+mysql开发Android,通过查看服务器中的数据库更新,发现异样可通过服务器进而控制Stm32端的操作。
②用uniapp+echarts开发Android,通过查看服务器中的数据库更新,发现异样可通过服务器进而控制Stm32端的操作。
③用vue +element +echarts来编写Web开发,通过查看服务器中的数据库更新,发现异样可通过服务器进而控制Stm32端的操作。
4. 巡检装置
外加一块Stm32板子,通过zigbee通信 查看各处理模块的状态
二、硬件选择
饮水机控制模块: 3块Stm32开发板,3块zigbee模块
冷、热水显示: 三色LED灯(红灯表示热水,绿灯表示冷水)
温度、湿度: DHT11
光照强度: BH1750
校园卡号: RFID
电泵:直流电机
通信模块: ESP8266
报警: 蜂鸣器
热冷水切换: 矩阵按键
电压、电流: ADC
显示屏:0.96寸OLED、串口助手、
三、STM32端
本篇采用的模块有:
RFID(串口通信)
ESP8266(串口通信)
zigbee(串口通信)
bh1750(IIC通信)
OLED(IIC通信)
Timer(调制电机速度)
Flash (断网重传)
Beep (报警)
LED (状态显示)
ADC (测电压)
Usart (数据传输)
Key (控制逻辑)
1. RFID
/*********接受到来自串口2的消息***************/
if(usart_recev2.end_flag == 1)
{
usart_recev2.end_flag = 0;
uint8_t i;
if(Send_ID_flag == 0)
{
status1 = RxCheckSum(usart_recev2.buf, usart_recev2.len); //对接收到的数据校验
status2 = usart_recev2.buf[4];
if ((usart_recev2.buf[0] == 0x01) && (usart_recev2.buf[2] == 0xa1)&&(status1 == STATUS_OK)&&(status2 == STATUS_OK)) //判断是否为读卡号返回的数据包
{
for(i=7;i<11;i++)
{
IDcard = IDcard<<8|usart_recev2.buf[i];
}
Send_ID_flag = 1;//读数据
printf_usart1("U2rec %08x, U3Send %s\r\n",IDcard,Send_Message);
}
else IDcard = 0;
}
else if(Send_ID_flag == 1)
{
status1 = RxCheckSum(usart_recev2.buf, usart_recev2.len); //对接收到的数据校验
status2 = usart_recev2.buf[4]; //获取返回包状态
if ((usart_recev2.buf[0] == 0x01) && (usart_recev2.buf[2] == 0xa3)&&(status1 == STATUS_OK)&&(status2 == STATUS_OK)) //判断是否为读块数据返回的数据包
{
IDdate[0] = usart_recev2.buf[5];
IDdate[1] = usart_recev2.buf[6];
printf_usart1("ID数据为:%d,%d\r\n",IDdate[0],IDdate[1]);
Send_ID_flag = 2;
}
if(IDdate[1] == 0x11)
{
if(IDdate[0] == 0x01) ID_state = 2;
else if(IDdate[0] == 0x00) ID_state = 1;
}
else ID_state = 3;
}
uart2_rec_buf_clean();
}
2.zigbee–stm32代码
static uint8_t last_Sec,cont;
if(last_Sec!=GetTime.Seconds)
{
char message1[526];
if(++cont>=3)//3s发送一次
{
cont=0;
Message_flag ^= 1;
sprintf(Send_Message,Send_Message_Zigbee,\
Mode,\
LEDpwmA.LED_nt,\
LEDpwmA.LED_NF,\
ST,\
GetTime.Hours,\
GetTime.Minutes,\
GetTime.Seconds);
printf_usart3("%s",Send_Message);//发给协调器
printf_usart1("Uart3Send %s",Send_Message);//打印到串口查看是否错误
}
}
3.bh1750
/**
* @brief 向BH1750发送一条指令
* @param cmd —— BH1750工作模式指令(在BH1750_MODE中枚举定义)
* @retval 成功返回HAL_OK
*/
static uint8_t BH1750_Send_Cmd(BH1750_MODE cmd)
{
return HAL_I2C_Master_Transmit(&bh1750_i2c, BH1750_ADDR_WRITE, (uint8_t*)&cmd, 1, 0xFFFF);
}
/**
* @brief 从BH1750接收一次光强数据
* @param dat —— 存储光照强度的地址(两个字节数组)
* @retval 成功 —— 返回HAL_OK
*/
static uint8_t BH1750_Read_Dat(uint8_t* dat)
{
return HAL_I2C_Master_Receive(&bh1750_i2c, BH1750_ADDR_READ, dat, 2, 0xFFFF);
}
/**
* @brief 将BH1750的两个字节数据转换为光照强度值(0-65535)
* @param dat —— 存储光照强度的地址(两个字节数组)
* @retval 成功 —— 返回光照强度值
*/
static uint16_t BH1750_Dat_To_Lux(uint8_t* dat)
{
uint16_t lux = 0;
lux = dat[0];
lux <<= 8;
lux |= dat[1];
lux = (int)(lux / 1.2);
return lux;
}
/**
* @brief 返回光照强度值
* @param 无
* @retval 成功 —— 返回光照强度值
*/
uint16_t Get_BH1750_Value(void)
{
uint8_t dat[2] = {0}; //dat[0]是高字节,dat[1]是低字节
uint16_t lux;
if(HAL_OK != BH1750_Send_Cmd(ONCE_H_MODE))
{
return 0;
}
HAL_Delay(120);
if(HAL_OK != BH1750_Read_Dat(dat))
{
return 0;
}
lux = BH1750_Dat_To_Lux(dat);
return lux;
}
4.OLED
void OLED_Init(void)
{
OLED_RES_Clr();
HAL_Delay(200);
OLED_RES_Set();
OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
OLED_WR_Byte(0x40,OLED_CMD);//--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
OLED_WR_Byte(0xCF,OLED_CMD);// Set SEG Output Current Brightness
OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset Shift Mapping RAM Counter (0x00~0x3F)
OLED_WR_Byte(0x00,OLED_CMD);//-not offset
OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
OLED_WR_Byte(0x12,OLED_CMD);
OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
OLED_WR_Byte(0x30,OLED_CMD);//Set VCOM Deselect Level
OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
OLED_WR_Byte(0x02,OLED_CMD);//
OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
OLED_Clear();
OLED_WR_Byte(0xAF,OLED_CMD);
}
5.ADC
/***********水质读取*************/
void Read_TDS(void)
{
if(ReadTDS_flag == 1)
{
ReadTDS_flag = 0;
HAL_ADC_Start(&hadc1); //启动ADC转换
HAL_ADC_PollForConversion(&hadc1, 50); //等待转换完成,50为最大等待时间,单位为ms
if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC))
{
V1 = HAL_ADC_GetValue(&hadc1); //获取AD值
V1=V1*3.3f/4096;
}
TDS1 = 100*((5-V1)/V1);
HAL_Delay(1000);
HAL_ADC_Start(&hadc2); //启动ADC转换
HAL_ADC_PollForConversion(&hadc2, 50); //等待转换完成,50为最大等待时间,单位为ms
if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc2), HAL_ADC_STATE_REG_EOC))
{
V2 = HAL_ADC_GetValue(&hadc2); //获取AD值
V2=V2*3.3f/4096;
V2 = (V2-0.6)*(3.3/2.7);
if(V2 < 0) V2 = 0;
}
if(V2 != 0)
TDS2 = 100*((5-V2)/V2);
if(TDS2 > 100)
{
Machine1.worn = 1;
}
else Machine1.worn = 0;
}
}
6.zigbee数据传输
MT_UartInit(); //串口初始化
MT_UartRegisterTaskID(task_id); //注册串口任务
// P0SEL &= 0x7f; //P0_7配置成通用io
mytask_id = task_id;
RegisterForKeys(mytask_id);
MT_UartRegisterTaskID(mytask_id);
line = osal_msg_allocate(LEN);
len_rcv = 0;
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )//接收
{
uint16 flashTime;
switch ( pkt->clusterId )
{
case SAMPLEAPP_P2P_CLUSTERID:
if ((SampleApp_NwkState == DEV_ZB_COORD))
{
HalUARTWrite(0, (pkt->cmd).Data, (pkt->cmd).DataLength); //输出接收到的数据
HalUARTWrite(0, "\r\n", 2); // 回车换行
}
break;
case SAMPLEAPP_PERIODIC_CLUSTERID:
HalUARTWrite(0, (pkt->cmd).Data, (pkt->cmd).DataLength);
break;
case SAMPLEAPP_FLASH_CLUSTERID:
HalUARTWrite(0, (pkt->cmd).Data, (pkt->cmd).DataLength);
break;
}
}
作者:Redemption