目录
0. 概述
0.0 实物图
继电器和温度传感器的GPIO引脚 7 和 12 在程序中互换了
0.1 web界面
0.2 接线图
MCP3008的引脚0 1 2分别接光敏传感器、雨滴传感器、火焰传感器的模拟输出引脚
可燃气体传感器的模拟输出引脚接在了PCF8591的AIN3引脚
MCP3008与树莓派的连接方式https://www.basemu.com/raspberry-pi-analog-sensing-interfacing.html写得很详细
PCF8591连接方式百度即可
0.3 功能
1. 整合 Socket 服务器的SpringBoot 后端
1.0 Socket 服务器程序 Server.java
package pioneer.pfix.SmartHome;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server {
static Map<String,Socket> stringSocketMap = new HashMap<>();
static Map<Socket,String> socketStringMap = new HashMap<>(); //两个Hash表用来存放socket和mac地址字符串双向的映射
static Map<String,JSONObject> stringJSONObjectMap = new HashMap<>(); //存放mac地址字符串和对应客户端状态的json对象
private final static Logger logger = LoggerFactory.getLogger(Server.class);
public static void start() throws IOException {
{
int port = 8086;
ServerSocket server = new ServerSocket(port);
logger.info("Server"+": Socket Built");
ExecutorService threadPool = Executors.newFixedThreadPool(100); //100个大小的线程池,存放多个socket线程
while (true) {
Socket socket = server.accept();
socket.setSoTimeout(5100);//毫秒,设置超时时间
logger.info("Connection from: "+socket.getRemoteSocketAddress());
Runnable runnable=()->{
try {
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
if(len!=-1)
{
String s = new String(bytes,0, len, "UTF-8");
logger.info("message from"+socket.getRemoteSocketAddress()+" "+s);
if(s.split("-").length==6) //简易判断第一次接收到的信息是不是mac地址,筛去不合规链接
{
stringSocketMap.put(s,socket);
socketStringMap.put(socket,s); //将mac地址字符串和socket存入Hash表
while (true) //循环接收心跳或状态信息,超时将触发异常中断
{
len = inputStream.read(bytes);
if(len==-1) {
break;
}
String s2 = new String(bytes,0, len, "UTF-8");
String[] strs = s2.split("-");//"-"前边是"HeartBeat",后边是json格式字符串
if(strs.length==2)
{
JSONObject jsonObjects = JSONObject.parseObject(strs[1]);//将strs转为json对象
stringJSONObjectMap.put(s,jsonObjects); //将json对象存入Hash表
// System.out.println(s+" "+jsonObjects.toString());
}
logger.info("message from"+socket.getRemoteSocketAddress()+" "+s2);
}
}
else //第一次发送的信息不是mac地址格式的直接断开socket连接
{
inputStream.close();
socket.close();
}
}
logger.info(socket.getInetAddress()+" disconnected");
String mac = socketStringMap.get(socket);
socketStringMap.remove(socket); //断开连接之后从hash表中删去映射
stringSocketMap.remove(mac);
stringJSONObjectMap.remove(mac); //从hash表中删除状态
inputStream.close();
socket.close();
} catch (SocketTimeoutException e)//超时异常处理
{
try {
socket.close();
String mac = socketStringMap.get(socket);
socketStringMap.remove(socket);
stringSocketMap.remove(mac);
stringJSONObjectMap.remove(mac);
logger.info("Timeout disconnection form:"+socket.getRemoteSocketAddress());
} catch (IOException ex) {ex.printStackTrace();}
}
catch (SocketException e) //其他异常处理
{
logger.info(socket.getRemoteSocketAddress()+" disconnected");
try {
socket.close();
String mac = socketStringMap.get(socket);
socketStringMap.remove(socket);
stringSocketMap.remove(mac);
stringJSONObjectMap.remove(mac);
} catch (IOException ex) {ex.printStackTrace();}
}
catch (IOException e) {
e.printStackTrace();
}
};
threadPool.submit(runnable); //递交线程池
}
}
}
public static boolean sendMessage(String mac,String message) throws IOException { //向指定mac地址的客户端发送信息
boolean online = stringSocketMap.containsKey(mac);
logger.info(mac+" is on line:"+ online);
Socket socket = stringSocketMap.get(mac);
if(socket!=null) {
logger.info("send message:"+message+" to "+mac);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(message.getBytes(),0,message.length());
return true;
}
return false;
}
public static JSONObject getStatus(String mac) throws IOException{ //返回状态json对象
return stringJSONObjectMap.get(mac);
}
public static void setSwitch(String mac,int value) throws IOException { //打开或关闭客户端开关
sendMessage(mac,"switchValue-"+value);
stringJSONObjectMap.get(mac).put("switchValue",value);
logger.info("value"+stringJSONObjectMap.get(mac).get("switchValue"));
}
}
1.1 Controller:SmartHomeFunction.java
package pioneer.pfix.SmartHome;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@CrossOrigin
@RestController
public class SmartHomeFunction {
@RequestMapping(value = "/api2/GetStatus")
public JSONObject GetStatus(String mac) throws IOException {
JSONObject jsonObject = Server.getStatus(mac);
// System.out.println(jsonObject.toString());
return jsonObject;
}
@RequestMapping(value = "/api2/SetSwitch")
public void SetSwitch(String mac,String value) throws IOException {
int value2 = Integer.parseInt(value);
Server.setSwitch(mac,value2);
}
}
1.2 启动类 Application.java
package pioneer.pfix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import pioneer.pfix.SmartHome.Server;
import java.io.IOException;
@SpringBootApplication
public class PFixApplication {
public static void main(String[] args) throws IOException {
SpringApplication.run(PFixApplication.class, args);
Server.start(); //智能设备的内容
}
}
(这种整合SpringBoot和Socket的方式为博主首创,并没有考虑太多效率因素,不喜勿喷)
2. Vue前端页面
<template>
<br>
<el-card class="box-card" v-loading="!online">
<template #header>
<div class="card-header">
<span>智能家居控制页面——<nobr v-if="online">在线</nobr><nobr v-if="!online">离线</nobr></span>
</div>
</template>
<table style="margin: 0 auto;border-collapse: collapse;" border="1">
<thead>
<tr>
<td style="height:50px;color: #fff;background-color: #3F3F3F;">设备</td>
<td style="height:50px;color: #fff;background-color: #3F3F3F;">状态</td>
</tr>
</thead>
<tbody>
<tr>
<td style="width:20%;height:30px"><label>远程开关</label></td>
<td style="width:20%;height:30px"><el-switch
style="display: block"
v-model="switchValue"
active-color="#13ce66"
inactive-color="#ff4949"
active-text="打开"
inactive-text="关闭"
></el-switch></td>
</tr>
<tr>
<td style="height:160px"><label>温度传感器-PCF8591</label></td>
<td style="height:160px"><el-progress type="dashboard" :percentage="temperature" :color="customColors" >{{temperature}}°C</el-progress></td>
</tr>
<tr>
<td style="height:160px"><label>温度传感器-DS18B20</label></td>
<td style="height:160px"><el-progress type="dashboard" :percentage="temperature2" :color="customColors" >{{temperature2}}°C</el-progress></td>
</tr>
<tr>
<td style="width:20%;height:160px"><label>光敏传感器1-数字信号</label></td>
<td style="width:20%;height:160px"><img src="../../assets/lighton.svg" v-if="lightValue===0"/>
<img src="../../assets/lightoff.svg" v-if="lightValue===1"/></td>
</tr>
<tr>
<td style="width:20%;height:30px"><label>光敏传感器1-模拟信号-MCP3008</label></td>
<td style="width:20%;height:30px">{{lightValue3}}</td>
</tr>
<tr>
<td style="width:20%;height:30px"><label>光敏传感器2-模拟信号-PCF8591</label></td>
<td style="width:20%;height:30px">{{lightValue2}}</td>
</tr>
<tr>
<td style="width:20%;height:160px"><label>雨滴传感器-数字信号</label></td>
<td style="width:20%;height:160px"><img src="../../assets/rainy.svg" v-if="rainValue===0"/>
<img src="../../assets/sunny.svg" v-if="rainValue===1"/></td>
</tr>
<tr>
<td style="width:20%;height:30px"><label>雨滴传感器-模拟信号-MCP3008</label></td>
<td style="width:20%;height:30px">{{rainValue2}}</td>
</tr>
<tr>
<td style="width:20%;height:160px"><label>可燃气体传感器-数字信号</label></td>
<td style="width:20%;height:160px"><img src="../../assets/dangerous.svg" v-if="gasValue===0"/>
<img src="../../assets/safe.svg" v-if="gasValue===1"/></td>
</tr>
<tr>
<td style="width:20%;height:30px"><label>可燃气体传感器-模拟信号-PCF8591</label></td>
<td style="width:20%;height:30px">{{gasValue2}}</td>
</tr>
<tr>
<td style="width:20%;height:30px"><label>火焰传感器-数字信号</label></td>
<td style="width:20%;height:30px"><img src="../../assets/dangerous.svg" v-if="fireValue2===1"/>
<img src="../../assets/safe.svg" v-if="fireValue2===0"/></td>
</tr>
<tr>
<td style="width:20%;height:30px"><label>火焰传感器-模拟信号-MCP3008</label></td>
<td style="width:20%;height:30px">{{fireValue}}</td>
</tr>
<tr>
<td style="width:20%;height:30px"><label>人体红外传感器-数字信号</label></td>
<td style="width:20%;height:30px"><div v-if="peopleValue===1">有人在活动</div>
<div v-if="peopleValue===0">没人在活动</div></td>
</tr>
<tr>
<td style="width:20%;height:30px"><label>电位计-模拟信号-PCF8591</label></td>
<td style="width:20%;height:30px">{{dianweiji}}</td>
</tr>
</tbody>
</table>
</el-card>
</template>
<script>
import axios from "axios";
export default {
name: "SmartHome",
data() {
return {
switchValue: "",
online:false,
temperature: 0,
temperature2: 0,
rainValue:1,
gasValue:1,
lightValue:1,
fireValue2:0, //数字信号
rainValue2:0,
gasValue2:0,
lightValue2:0,
lightValue3:0,
fireValue:-1,
dianweiji:-1,
peopleValue:0,
customColors: [
{color: '#f56c6c', percentage: 20},
{color: '#e6a23c', percentage: 40},
{color: '#5cb87a', percentage: 60},
{color: '#1989fa', percentage: 80},
{color: '#6f7ad3', percentage: 100}
]
}
},
watch:{
'switchValue':{
handler: function(val,oldVal) {
console.log(val,oldVal)
var i = val ? 1 : 0;
axios.get("/api2/SetSwitch?mac="+"11-11-11-11-11-11"+"&value="+i);
},
deep: true
}
},
mounted(){
setInterval(()=>{
axios.get("/api2/GetStatus?mac="+"11-11-11-11-11-11").then(r => {
// console.log("3 "+r.data)
if(r.data!=='')
{
if(r.data.switchValue===1)
{
this.switchValue = true
}
else
{
this.switchValue = false
}
this.rainValue=r.data.rainValue
this.gasValue = r.data.gasValue
this.lightValue = r.data.lightValue
this.online = true
this.temperature=r.data.temperature
this.temperature2=r.data.temperature2
this.rainValue2=r.data.rainValue2
this.gasValue2=r.data.gasValue2
this.lightValue2=r.data.lightValue2
this.lightValue3=r.data.lightValue3
this.fireValue=r.data.fireValue
this.dianweiji=r.data.dianweiji
this.fireValue2=r.data.fireValue2
this.peopleValue = r.data.peopleValue
// console.log("1 "+this.online)
}
else{this.online=false}
}).catch(error => {
console.log(error);
})
}, 3*1000)
}
}
</script>
<style scoped>
.box-card {
width: 90%;
margin: 0 auto;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
(然后加到路由里边)
3. 树莓派上的python程序
3.0 主程序:SmartHome.py
import socket
import threading
# import ProcessMessage
import sys
import time
import RPi.GPIO as GPIO
import PCF8591 as ADC
import MCP3008 as ADC2
import DS18B20 as temp
def doConnect(host, port):
sock = socket.socket()
try:
sock.connect((host, port))
except:
pass
return sock
class myThread1(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
while True:
try:
# mac = hex(uuid.getnode())[2:]
# mac = '-'.join(mac[i:i + 2] for i in range(0, len(mac), 2)) # 获取真实mac地址
mac = '11-11-11-11-11-11'
global s, status
s.send(mac.encode())
print("\r连接成功:"+time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()))
status = 1
time.sleep(1)
while True:
a = 'HeartBeat-'+str(object_status)
a = a.encode()
s.send(a)
print("\r发送心跳:"+time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()))
time.sleep(3)
except socket.error:
sys.stdout.write("\rsocket 错误,重连ing....")
sys.stdout.flush()
status = 0
s = doConnect(host, port)
time.sleep(1)
class myThread2(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global status
# process=ProcessMessage.ProcessMessage()
while True:
if status == 1:
print("持续接收:"+time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()))
try:
str = s.recv(1024).decode()
print("接收:"+str+":"+time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()))
strs = str.split("-")
if strs[0]=="switchValue":
object_status["switchValue"]=int(strs[1])
if int(strs[1]) == 1:
GPIO.output(16,GPIO.LOW)
elif int(strs[1]) == 0:
GPIO.output(16,GPIO.HIGH)
print(object_status)
except:
status = 0
class myThread3(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
while True:
object_status["rainValue"]=GPIO.input(12)
object_status["rainValue2"]=ADC2.interp(ADC2.analogInput(1), [0, 1023], [0, 100])
object_status["gasValue"]=GPIO.input(11)
object_status["gasValue2"]=ADC.read(3)
object_status["lightValue"]=GPIO.input(22)
object_status["lightValue2"]=ADC.read(1)
object_status["lightValue3"]=ADC2.interp(ADC2.analogInput(0), [0, 1023], [0, 255])
object_status["temperature"]=(143-ADC.read(2))
object_status["dianweiji"]=ADC.read(0)
object_status["fireValue"]=ADC2.interp(ADC2.analogInput(2), [0, 1023], [0, 100])
object_status["fireValue2"]=GPIO.input(40)
object_status["peopleValue"]=GPIO.input(37)
object_status["temperature2"]=temp.read_temp()
print(" rainValue:"+str(object_status["rainValue"])," rainValue2:"+str(object_status["rainValue2"]))
print(" temperature:"+str(object_status["temperature"]),"temperature2:"+str(object_status["temperature2"]))
print(" lightValue:"+str(object_status["lightValue"])," lightValue2:"+str(object_status["lightValue2"])," lightValue3:"+str(object_status["lightValue3"]))
print(" gasValue:"+str(object_status["gasValue"])," gasValue2:"+str(object_status["gasValue"]))
print(" fireValue:"+str(object_status["fireValue"])," fireValue2:"+str(object_status["fireValue2"]))
print(" peopleValue:"+str(object_status["peopleValue"])," dianweiji:"+str(object_status["dianweiji"]))
time.sleep(3)
# host = "172.20.10.9"
host = "172.18.3.183" # 设置服务器端SocketServer的ip
port = 8086
object_status = {
"switchValue":0,
"rainValue":1,
"rainValue2":-1,
"gasValue":1,
"gasValue2":-1,
"lightValue":1,
"lightValue2":-1,
"lightValue3":-1,
"temperature"-1,
"temperature2":-1,
"fireValue":0,
"dianweiji":-1,
"fireValue2":1,
"peopleValue":0,
}
ADC.setup(0x48)
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
GPIO.setup(16,GPIO.OUT) # 继电器
GPIO.setup(12,GPIO.IN) # 雨滴传感器
GPIO.setup(11,GPIO.IN) # 可燃气体传感器
GPIO.setup(22,GPIO.IN) # 光敏传感器
GPIO.setup(40,GPIO.IN) # 火焰传感器
GPIO.setup(37,GPIO.IN) # 人体红外传感器
if object_status["switchValue"] == 1:
GPIO.output(16,GPIO.LOW)
elif object_status["switchValue"] == 0:
GPIO.output(16,GPIO.HIGH)
s = doConnect(host, port)
status = 0
# 创建新线程
thread1 = myThread1()
thread2 = myThread2()
thread3 = myThread3()
# 开启新线程
thread1.start()
thread2.start()
thread3.start()
thread1.join()
thread2.join()
thread3.join()
print("退出主线程")
3.1 AD转换模块①:PCF8591.py
#!/usr/bin/env python
#------------------------------------------------------
#
# 您可以使用下面语句将此脚本导入另一个脚本:
# “import PCF8591 as ADC”
#
# ADC.Setup(Address) # 查询PCF8591的地址:“sudo i2cdetect -y 1”
# i2cdetect is a userspace program to scan an I2C bus for devices.
# It outputs a table with the list of detected devices on the specified bus.
# ADC.read(channal) # Channal范围从0到3
# ADC.write(Value) # Value范围从0到255
#
#------------------------------------------------------
#SMBus (System Management Bus,系统管理总线)
import smbus #在程序中导入“smbus”模块
import time
# for RPI version 1, use "bus = smbus.SMBus(1)"
# 0 代表 /dev/i2c-0, 1 代表 /dev/i2c-1 ,具体看使用的树莓派那个I2C来决定
bus = smbus.SMBus(1) #创建一个smbus实例
#在树莓派上查询PCF8591的地址:“sudo i2cdetect -y 1”
def setup(Addr):
global address
address = Addr
def read(chn): #channel
if chn == 0:
bus.write_byte(address,0x40) #发送一个控制字节到设备
if chn == 1:
bus.write_byte(address,0x41)
if chn == 2: #temperature
bus.write_byte(address,0x42)
if chn == 3:
bus.write_byte(address,0x43)
bus.read_byte(address) # 从设备读取单个字节,而不指定设备寄存器。
return bus.read_byte(address) #返回某通道输入的模拟值A/D转换后的数字值
def write(val):
temp = val # 将字符串值移动到temp
temp = int(temp) # 将字符串改为整数类型
# print temp to see on terminal else comment out
bus.write_byte_data(address, 0x40, temp)
#写入字节数据,将数字值转化成模拟值从AOUT输出
3.2 AD转换模块②:MCP3008.py
# Importing modules
import spidev # To communicate with SPI devices
from numpy import interp # To scale values
from time import sleep # To add delay
import RPi.GPIO as GPIO # To use GPIO pins
# Start SPI connection
spi = spidev.SpiDev() # Created an object
spi.open(0,0)
# Read MCP3008 data
def analogInput(channel):
spi.max_speed_hz = 1350000
adc = spi.xfer2([1,(8+channel)<<4,0])
data = ((adc[1]&3) << 8) + adc[2]
return data
3.3 温度传感器模块:DS18B20.py
# Importing modules
import spidev # To communicate with SPI devices
from numpy import interp # To scale values
from time import sleep # To add delay
import RPi.GPIO as GPIO # To use GPIO pins
# Start SPI connection
spi = spidev.SpiDev() # Created an object
spi.open(0,0)
# Read MCP3008 data
def analogInput(channel):
spi.max_speed_hz = 1350000
adc = spi.xfer2([1,(8+channel)<<4,0])
data = ((adc[1]&3) << 8) + adc[2]
return data
(MCP3008、PCF8591、DS18B20驱动代码来源于网络,侵权删除)