绪论
硬件工具:STC89C52RC芯片、51单片机开发板、ESP8266通信模块、TTL转USB、各类传感器
软件工具:Keil开发环境,STC-ISP烧录工具,IDEA专业版,Postman,WebStorm,vx小程序开发工具,sscom串口助手,Socket-Tools,Tomcat8,JDK1.8,Navicat,Android Studio
开发环境:Java8、SpringBoot+MyBatis、MySQL、C51、阿里云、JavaScript、JQuery、Ajax、Windows操作系统、Chrome浏览器,Android操作系统
开发描述:基于ESP8266通信模块,通过51单片机采集环境传感数据并实现自动控制,通过AT指令将采集数据打包json格式发送目标服务器,云端部署jar包并建立数据库,将接收的数据解析并保存到设备数据表中,基于Ajax与Restful接口实现前后端分离,实现用户登录注册、设备管理、数据查询功能,并支持在Android和vx小程序上访问设备信息。
项目整体框架
一、硬件开发
? ? ? ? 本案例通过检测MQ-5可燃气体传感器、DHT数字温湿度传感器、火焰传感器这三个传感器感知的环境数据,进而通过逻辑判断通过I/O口输出到继电器、蜂鸣器和LED报警灯。通过ESP8266-01s模块通过WIFI接入云服务器,将采集的数据实时发送到指定端口,且51单片机根据传感器采集的各种火灾安全参数进行判断,如果出现异常情况则单片机启动接入的控制元器件进行自动控制,控制报警灯、蜂鸣器和风扇进行工作,硬件整体框架如下
ESP8266调试
? ? ? ? ?我们使用sscom调试助手进行调试。首先需要一个TTL转USB工具插上ESP8266模块连接PCcom口,打开调试助手检测到设备,然后可以向8266发送指令,指令集如下:
AT+RST :初始化
AT+CWMODE=3 :设置兼容模式,可以访问其他WiFi
AT+UART=<波特率>,8,1,0,0 :设置波特率
AT+CWJAP="<SSID>","<PWD>" :设置连接的WiFi名称和密码
AT+CIPSTART="TCP","<IP地址>",<端口号>
AT+CIPSEND=5 : 用于测试字符长度
<下面输入要发送的字符串>
C51单片机程序开发
? ? ? ? 使用Keil编写,打开工具,引入STC系列的MCU
//引入库与宏定义
#include <reg52.h>
#include <intrins.h>
#include <assert.h>
#include <stdlib.h>
#define uchar unsigned char
#define uint unsigned int
typedef unsigned char u8;
typedef unsigned int u16;
unsigned char strlen(unsigned char *str);
unsigned char flag;
sbit Data=P0^6;
sbit Gas_DOUT = P0^4;
sbit Fire_DOUT = P0^3;
sbit beep=P0^2;
sbit LED = P0^1;
sbit relay = P0^5;
bit uart_busy = 0;
uchar temp_dat[9];
uchar tempture[2];
uchar string_data1[80];
uchar string_data2[10];
char gas;
char fire;
//设置发送数据
uchar string_send[12];
uint i;
uint count;
void relay_fun();
uchar len_a;
uchar len_b;
uchar len_c;
uchar len_sum;
void LED_Alarm()
{
u8 i;
for(i=10;i>0;i--)
{
LED=0;
beep=0;
delay_u16(10);
LED=1;
beep=1;
delay_u16(10);
}
}
//设置频率
void delay_u16(u16 i)
{
while(i--);
}
//控制继电器风扇函数
void relay_fun(){
relay = ~relay;
LED_Alarm();
relay = ~relay;
}
void beep_led_alarm(){
beep = 0;
for(i=0;i<5000;i++){
beep=~beep;
delay_u16(100);
}
}
void DHT11_delay_us(uchar n)
{
while(--n);
}
void DHT11_delay_ms(uint z)
{
uint i,j;
for(i=z;i>0;i--)
for(j=110;j>0;j--);
}
void DHT11_start()
{
Data=1;
DHT11_delay_us(2);
Data=0;
DHT11_delay_ms(20);
Data=1;
DHT11_delay_us(30);
}
uchar DHT11_rec_byte()
{
uchar i,dat=0;
for(i=0;i<8;i++)
{
while(!Data);
DHT11_delay_us(8);
dat<<=1;
if(Data==1)
dat+=1;
while(Data);
}
return dat;
}
//DHT11AD转换函数
void DHT11_receive()
{
uchar R_H,R_L,T_H,T_L,RH,RL,TH,TL,revise;
DHT11_start();
if(Data==0)
{
while(Data==0);
DHT11_delay_us(40);
R_H=DHT11_rec_byte();
R_L=DHT11_rec_byte();
T_H=DHT11_rec_byte();
T_L=DHT11_rec_byte();
revise=DHT11_rec_byte();
DHT11_delay_us(25);
if((R_H+R_L+T_H+T_L)==revise)
{
RH=R_H;
RL=R_L;
TH=T_H;
TL=T_L;
}
if(TH > 24){
LED_Alarm();
LED = 0;
beep=0;
}
temp_dat[0]='0'+(TH/10);
temp_dat[1]='0'+(TH%10);
}
}
void delay5ms(){
unsigned char a,b;
for(b=201;b>0;b--)
for(a=247;a>0;a--);
}
void Init_uart(void) {
TMOD = TMOD | 0x20;
SCON = SCON | 0x50;
TH1 = 0xFd;
TL1 = TH1;
TR1 = 1;
EA =1;
ES =1;
}
void Uart_SendByteData(unsigned char msg){
while(uart_busy);
SBUF=msg;
uart_busy = 1;
}
void Uart_SendStrData(unsigned char *msg){
while(*msg){
Uart_SendByteData(*msg++);
}
}
//取字符串长度
unsigned char strlen(unsigned char *str)
{
unsigned char len=0;
while(1)
{
if(*str=='\0')break;//拷贝完成了.
len++;
str++;
}
return len;
}
void main(){
count = 0;
beep=1;
LED=1;
Init_uart();
len_a=0;
len_b=0;
len_sum=0;
while(1){
count++;
DHT11_receive();
delay5ms();delay5ms();
for(i=0;i<2;i++){
tempture[i]=temp_dat[i];
}
if(Gas_DOUT == 0)
{
gas='1';
LED_Alarm();
LED = 0;
beep=0;
}else{
gas='0';
}
if(Fire_DOUT == 0){
fire='1';
LED_Alarm();
LED = 0;
beep=0;
}else{
fire='0';
}
for(i=0;i<2;i++){
string_send[i]=tempture[i];
}
string_send[2]='t';
string_send[3]=fire;
string_send[4]='g';
string_send[5]=gas;
string_send[6]='f';
string_data1[]="{\"type\":\"02\",\"deviceCode\":\"Device\",\"deviceData\":\"";
string_data2[]="\"}";
len_a=strlen(string_data1);
leb_b=strlen(string_data2);
len_c=strlen(string_send);
len_sum=len_a+len_b+len_c;
Uart_SendStrData("AT+CIPSTART=\"TCP\",\"<ip>\",<port>\r\n");
delay5ms();
Uart_SendStrData("AT+CIPSEND=");
Uart_SendStrData(len_sum);
Uart_SendStrData("\r\n");
delay5ms();
Uart_SendStrData(string_data1);
Uart_SendStrData(string_send);
Uart_SendStrData(string_data2);
delay1_relay(2);
}
}
void UART_Interrupt(void) interrupt 4 {
unsigned char temp;
if(RI){
temp = SBUF;
RI = 0;
if(temp == 1){
flag = 1;
}
}
if(TI){
TI = 0;
uart_busy = 0;
}
}
调试通信
? ? ? ? ESP8266上的TX和RX一定要和单片机上的RX与TX交叉连接,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 8266-TX连接51-RX,8266-RX连接51-TX
以下为单片机串口调试图:
使用Socket-Tools,在云服务器上创建TCPServer指定端口号,开启单片机连接WiFi
下图为端口通信调试图:
二、服务器软件开发?
?TCP接收服务开发
创建socket通信端口以多线程方式等待监听端口
package com.bl.demo;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import static com.bl.utli.SocketParam.*;
public class TCPServer extends Thread {
ServerSocket serverSocket = null;
public static TCPServer tcpServer;
@PostConstruct
public void init(){
tcpServer = this;
tcpServer.start();
}
public String socketSendData(String deviceCode,String sendData){
//不为空时
subSocketClient temp = DeviceCode2SocketMap.get(deviceCode);
if(null!=temp){
return temp.sendSocketData(sendData,deviceCode);
}else{
return SendError;
}
}
@Override
public void run() {
try {
serverSocket = new ServerSocket(PortNum);
System.out.println("PortNum: "+PortNum);
} catch (IOException e) {
System.out.println("the port cannot open.");
}
while(true){
try {
System.out.println("wait. .. ...");
//使用accept()是阻塞方法
Socket socketTemp = serverSocket.accept();
new subSocketClient(this.serverSocket,socketTemp).start();
//为每个连接都创建一个线程客户端
} catch (IOException e) {
System.out.println("happening");
}
}
}
}
使用JDBC对MySQL进行操作, 在run()方法中解析数据并存储到数据库中
package com.bl.demo;
import com.alibaba.fastjson.JSONObject;
import com.bl.Data.Data;
import com.bl.Data.DataMapper;
import com.bl.Data.DataService;
import com.bl.utli.SocketParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.sql.*;
import static com.bl.utli.SocketParam.*;
//@Component
public class subSocketClient extends Thread {
private ServerSocket serverSocket;
private Socket socket;
@Autowired
@Resource
private DataMapper tempMapper_udp_save;
public static subSocketClient subSocketClient;
public subSocketClient(ServerSocket serverSocket, Socket socket) {
this.serverSocket = serverSocket;
this.socket = socket;
}
//初始化通信程序
@PostConstruct
public void init(){
subSocketClient = this;
subSocketClient.tempMapper_udp_save = this.tempMapper_udp_save;
}
private static final String DRIVER = "com.mysql.jdbc.Driver";
public void addTemp(String temp, String humi, String beam, String time) {
String url = "jdbc:mysql://localhost:3306/iot?serverTimezone=UTC&allowMultiQueries=true";
// 数据库用户名
String user = "root";
// 数据库密码
String password = "123456";
// 建立数据库连接,获得连接对象conn
try {
Connection conn = DriverManager.getConnection(url, user, password);
Class.forName("com.mysql.jdbc.Driver");
String sql = "insert into data_info_t (temp,humi,beam,time) values(?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, temp);
ps.setString(2, humi);
ps.setString(3, beam);
ps.setString(4, time);
// 执行sql语句
ps.executeUpdate();
System.out.println("SQL插入完毕!");
// 关闭数据库连接对象
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
String temp = "";
String humi = "";
String beam = "";
String time = "";
String str1 = "";
String str2 = "";
String str3 = "";
public String sendSocketData(String sendData, String deviceCode) {
try {
BufferedReader br = null;
PrintWriter pw = null;
br = new BufferedReader(new InputStreamReader(System.in));
pw = new PrintWriter(socket.getOutputStream(), true);
//查看socket是否存在
if (!socket.isClosed()) {
pw.println(sendData);
pw.flush();
return SendSuccess;
} else {
//不存在则移除该socket
DeviceCode2SocketMap.remove(deviceCode);
return SendError;
}
} catch (IOException e) {
e.printStackTrace();
}
return SendError;
}
@Override
public void run() {
try {
//获取输入流
InputStream inputStream = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
byte[] buf = new byte[1024];
//接收数据
int line = 0;
while ((line = inputStream.read(buf)) != -1) {
String param = new String(buf, 0, line);
//转成json格式
JSONObject jsonObject = JSONObject.parseObject(param);
//获取type类型,如果是02则自动注册设备
String type = jsonObject.getString("type");
if (type.equals(TypeUpdateData)) {
//获取设备编号
String deviceCode = jsonObject.getString("deviceCode");
System.out.println(deviceCode);
//获取传感数据
String deviceData = jsonObject.getString("deviceData");
System.out.println(deviceData);
//字符串解析
str1 = deviceData;
String[] strArr1 = StringUtils.split(str1, "t");
for (int i = 0; i < strArr1.length; i++) {
temp = strArr1[0];
str2 = strArr1[1];
}
String[] strArr2 = StringUtils.split(str2, "h");
for (int i = 0; i < strArr2.length; i++) {
humi = strArr2[0];
str3 = strArr2[1];
}
String[] strArr3 = StringUtils.split(str3, "g");
for (int i = 0; i < strArr3.length; i++) {
beam = strArr3[0];
}
Calendar calendar = Calendar.getInstance();
SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
time = sd.format(calendar.getTime());
System.out.println("温度: " + temp);
System.out.println("湿度: " + humi);
System.out.println("光照: " + beam);
System.out.println("时间: " + time);
addTemp(temp, humi, beam, time);
}
}
//线程停止10ms
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建restful端口,调用获取设备数据方法将设备传感数据返回,访问方法为POST
@RequestMapping(value = "/getData", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@CrossOrigin
public List<Data> getData() {
List<Data> temps = dataService.getTemps();
return temps;
}
创建数据服务接口,用于获取和添加传感器数据?
package com.bl.Data;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
@Mapper
public interface DataMapper {
int addTemp(Map<String,Object> param);
List<Data> getTemps();
}
package com.bl.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
@Service
public class DataService {
@Autowired
DataMapper dataMapper;
public int addTemp(Map<String, Object> param){
return dataMapper.addTemp(param);
}
public List<com.bl.Data.Data> getTemps()
{
return dataMapper.getTemps();
}
}
?编写xml文件,设备数据插入与查询,查询排序按时间递减
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bl.Data.DataMapper">
<insert id="addTemp" parameterType="map">
insert into data_info_t(temp,humi,beam,time) values (#{temp},#{humi},#{beam},#{time})
</insert>
<select id="getTemps" resultType="com.bl.Data.Data">
select * from data_info_t ORDER BY time DESC LIMIT 100
</select>
</mapper>
postman通信测试图
?
三、前端页面数据展示
?
?
?
?页面实现
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>后台管理系统</title>
<link rel="shortcut icon" href="images/favicon.ico"/>
<link rel="bookmark" href="images/favicon.ico"/>
<link rel="stylesheet" type="text/css" href="css/base.css">
<link rel="stylesheet" type="text/css" href="fonts/iconfont.css">
<script type="text/javascript" src="framework/jquery-1.11.3.min.js" ></script>
<script type="text/javascript" src="js/base.js"></script>
<link rel="stylesheet" type="text/css" href="layui/css/layui.css">
<script type="text/javascript" src="layui/layui.js"></script>
<script src="framework/cframe.js"></script>
<link rel="stylesheet" type="text/css" href="css/frameStyle.css">
<script type="text/javascript" src="framework/frame.js" ></script>
</head>
<body>
<div class="frameMenu">
<div class="logo">
<img src="images/logo.png"/>
<div class="logoText">
<h1>室内火灾监控平台</h1>
</div>
</div>
<div class="menu">
<div class="hoverBox"></div>
<ul>
<li>
<a class="menuFA" href="javascript:void(0)"><i class="iconfont icon-liuliangyunpingtaitubiao03 left" onmouseenter="getLeftTips(this, '用户管理')" onmouseleave="layer.closeAll('tips')"></i><font>用户管理</font><i class="iconfont icon-dajiantouyou right"></i></a>
<dl>
<dt><a href="javascript:void(0)" onclick="menuCAClick('tgls/user/user_add.html',this, 'page10022')">添加用户</a></dt>
<dt><a href="javascript:void(0)" onclick="menuCAClick('tgls/user/user_list.html',this, 'page10023')">用户列表</a></dt>
<dt><a href="javascript:void(0)" onclick="menuCAClick('tgls/user/modify.html',this, 'page10024')">修改信息</a></dt>
</dl>
</li>
<li>
<a class="menuFA" href="javascript:void(0)"><i class="iconfont icon-yunying left" onmouseenter="getLeftTips(this, '设备信息')" onmouseleave="layer.closeAll('tips')"></i><font>设备信息</font><i class="iconfont icon-dajiantouyou right"></i></a>
<dl>
<dt><a href="javascript:void(0)" onclick="menuCAClick('tgls/view/view_add.html',this, 'page10027')">添加设备</a></dt>
<dt><a href="javascript:void(0)" onclick="menuCAClick('tgls/view/view_list.html',this, 'page10028')">设备数据</a></dt>
</dl>
</li>
</ul>
</div>
</div>
<div class="main">
<div class="frameTop">
<div class="shrinkBut">
<div class="hoverBox"></div>
<ul>
<li><a href="javascript:void(0)" onclick="menuShrink(this)" onmouseenter="getTips(this, '收缩菜单')" onmouseleave="layer.closeAll('tips')"><i class="iconfont icon-caidan-shousuo"></i></a></li>
<li><a href="javascript:void(0)" onclick="frameRefresh()" onmouseenter="getTips(this, '刷新')" onmouseleave="layer.closeAll('tips')"><i class="iconfont icon-htmal5icon23"></i></a></li>
</ul>
</div>
<div class="topMenu">
<div class="hoverBox"></div>
<ul>
<li><a href="login1.html" onmouseenter="getTips(this, '退出系统')" onmouseleave="layer.closeAll('tips')"><i class="iconfont icon-084tuichu"></i></a></li>
</ul>
</div>
</div>
<div class="frameMain">
<div class="title" id="frameMainTitle">
<i class="iconfont icon-shuangzuojiantou- leftbut" onclick="pageShow('l')"></i>
<div class="mainPageBox">
<div class="mainPage">
<span class="active" onclick="pageSwitch(this)">
<div class="hoverBox"></div>
<i class="iconfont icon-shouye"></i>
</span>
</div>
</div>
<i class="iconfont icon-shuangyoujiantou- rightbut" onclick="pageShow('r')"></i>
</div>
<div class="con">
<div class="mainPageCon">
<iframe class="mainIframe" src="tgls/user/user_list.html" scrolling="yes"></iframe>
</div>
</div>
</div>
</div>
</body>
</html>
?
$("#search").click(function () {
var name=$("#input").val();
$.ajax
({
async:true,
type: "POST",
url: "http://47.102.42.105:8456/Device/getData",
dataType: "json",
data: JSON.stringify({
"name":name
}),
contentType: "application/json",
success: function (res) {
console.log(res);
for(var i=0; i<res.msg.length; i++){
console.log(res.msg[i]);
tr = '<td id="temp">'+res.resultList[i].temp+'</td>'
+'<td id="fire">' +res.resultList[i].fire+'</td>'
+'<td id="gas">'+res.resultList[i].gas+'</td>'
+'<td id="time">'+res.resultList[i].time+'</td>';
$("#table").append('<tr id="current" style="height: 30px; align-content: center">' + tr + '</tr>');
}
}
})
})
})
|