像越来越多的人一样,我在 COVID-19 封锁期间迷上了我的植物。不幸的是,额外的爱和感情似乎阻碍了我最喜欢的植物之一在今年春天长出任何新叶子。现在一个普通人可能会给植物浇水,把它放在阳光下,然后等待——但在制造商社区,我们喜欢数据和过度设计的解决方案。所以,我从旧硬件盒中取出一些传感器,开始制作植物监视器!
最初,这将是一个闭环、离线解决方案,当土壤湿度低于某个待确定值时给植物浇水——最终更多地成为一种数据收集工具,用于查看本地网络上的传感器数据。我以前从未使用过任何与网络相关的东西,所以这是一个将简单的想法从传感器带到前端的好机会。
该项目包含三个主要部分——Arduino、中间件和前端。确切的代码和文件结构在链接的 GitHub 存储库中,但我将尝试概述下面的代码库。
Arduino 部分是我最熟悉的部分,因此启动并运行它大约需要 15 分钟。连接示意图如下所示。
您的温度传感器可能有四个引脚而不是三个引脚,仍然只有三个活动连接,但请查看特定传感器的文档。
DHT11 需要 Arduino 提供的 GND 和 VCC,以及 Arduino 读取的数据信号。土壤湿度传感器有点棘手,它需要与 DHT11(GND、VCC、信号)相同的连接,但由于传感器有效地设置了电解,它的使用寿命很短。如果你让它不断地关闭直流电压,其中一条腿(阳极)会迅速氧化,传感器将开始给你提供虚假读数。
因此,建议在读数时只为湿度传感器供电。这很容易通过将 VCC 信号连接到 Arduino 的数字输出引脚之一,并在您期望读取之前发送一个“HIGH”脉冲来实现。在我的情况下,它是从引脚 8 供电的。
获取传感器读数的软件如下。确保你有我安装的 DHT11 传感器库(注意我没有使用 Adafruit 电路游乐场库,使用的库更轻量级。)
#include
const int DHT_pin = A0;
const int soil_moisture_pin = A1;
const int soil_moisture_trigger = 8;
int soil_moisture = 0;
unsigned long ms_per_s = 1000L;
unsigned long minutes = 60;
unsigned long sampling_time = minutes * ms_per_s * 60;
int dht_sig = 0;
int soil_moisture_sig = 0;
dht DHT;
void setup() {
pinMode(soil_moisture_pin,INPUT);
pinMode(soil_moisture_trigger,OUTPUT);
Serial.begin(9600);
}
void loop() {
dht_sig = DHT.read11(DHT_pin);
digitalWrite(soil_moisture_trigger,HIGH);
delay(10);
soil_moisture_sig = analogRead(soil_moisture_pin);
digitalWrite(soil_moisture_trigger,LOW);
soil_moisture = map(soil_moisture_sig,168,0,0,100);
Serial.print(DHT.temperature);
Serial.print(DHT.humidity);
Serial.print(soil_moisture);
delay(sampling_time);
}
实际上,您可以通过在控制回路旁边添加一个水泵来自动为您的植物浇水,从而将项目留在这里,但让我们继续。
现在 Arduino 部分运行良好,我们需要在 Raspberry Pi 上设置代码以获取此信息。Pis 唯一真正的工作是从 Arduino 获取数据并将其推送到将在下一部分中制作的服务器上。如果你想降低项目的成本,你可以简单地使用一个兼容 wifi 的微控制器——老实说,使用整个 Raspberry Pi 3b+ 来完成这个任务是多余的。
无论如何,我们将使用 PySerial 从 Arduino 获取数据。请注意,Arduino 端的串行打印是通过串行通信发送数据。因此,我们在 Arduino 代码中打印到串行控制台的任何内容都可以被 PySerial 读取。确保您的 Arduino 已插入 Pi 的 USB 端口之一。
首先要做的是创建一个main.py 文件并通过执行获取 PySerial
sudo pip install pyserial
在 Pi 的终端中。让我们开始编码。
打开main.py 并导入 pyserial - 注意模块名称只是“serial”。
import serial
我们现在可以使用以下行设置串行对象:
ser = serial.Serial("/dev/ttyACM0",9600)
第一个参数是 COM 端口,这对您来说可能有所不同。9600 是波特率,也是在 Arduino 端设置的。
PySerial 是 Python 中任何与串行相关的功能强大的包 - 但我们只会使用 read 函数。我们将在从 Arduino 到 Raspberry pi 的每个数据包中发送三个数据值——温度是前 5 个字节,湿度是接下来的 5 个字节,土壤湿度是最后两个字节。这可以使用
ser.read()
功能。我们还需要解码传入的字节,因此您的代码如下所示:
import serial
ser = serial.Serial("/dev/ttyACM0",9600)
temperature_r = ser.read(5).decode('utf-8')
humidity_r = ser.read(5).decode('utf-8')
soil_m_r = ser.read(2).decode('utf-8')
关于 ser.read() 有几点需要注意。首先,它是阻塞的,这意味着在读取某些内容之前不会执行其他代码。在等待来自 Arudino 的数据时,我们将利用这一优势,但是,如果软件没有按预期运行,这是一件好事。
现在我们已经从传感器获取到 Raspberry Pi 的数据,是时候将其放到网络上了。
Flask 是一个用于 Python 的 microweb 框架,可用于制作服务器。不建议将 Flask 用作生产服务器 - 它也在他们的官方文档中说明了这一点,所以如果你打算将基于它的项目商业化,请三思。
要设置服务器,我们需要制作一个烧瓶应用程序。首先,我们需要进行一些导入并初始化一个 Flask 对象:
from flask import Flask, jsonify,render_template,request
app = Flask(__name__)
if __name__ == '__main__':
app.run()
现在该应用程序已准备好让我们向其中添加一些路线。让我们从加载页面开始。
from flask import Flask, jsonify,render_template,request
app = Flask(__name__)
@app.route("/")
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()
在这种情况下,index.html是要加载的 HTML 页面。此功能将由 Javascript 驱动。
您可以提供app.run()
一些参数,在我的例子中,我指定了要在 Raspberry Pi 的 IP 上运行的主机和端口。
app.run(host = '0.0.0.0', port = 5000)
我们运行系统的三个部分:Arduino,它是数据收集单元,Pyserial,它抓取 Arduino 数据,以及将数据推送到本地网络的烧瓶服务器。
在开始集成之前,让我先回顾一下代码流程。Arduino 将全天收集数据,当有人登录本地网络时,所有这些数据都会被绘制出来。为此,我们需要长时间存储数据,直到请求处于活动状态。我们可以使用线程来做到这一点。在这个线程中,我们要做的就是在一个while循环中收集数据。由于读取功能被阻塞,循环将被卡住,直到 Arduino 首先发送任何数据。线程将在单独的函数中定义,并通过队列将数据发送到烧瓶应用程序。代码现在看起来像这样:
from flask import Flask, jsonify,render_template,request
import threading, queue
import serial
from datetime import datetime
ser = serial.Serial("/dev/ttyACM0",9600)
app = Flask(__name__)
q = queue.Queue()
temperature = []
humidity = []
soil_moisture = []
time_ax = []
def data_collection():
while(1):
#set variables
i = 0
#read is blocking so waits till next packet of data is sent
temperature_r = ser.read(5).decode('utf-8')
humidity_r = ser.read(5).decode('utf-8')
soil_m_r = ser.read(2).decode('utf-8')
time_of_reading = datetime.now() #this will be the x axis
#put data in queue
q.put(temperature_r)
q.put(humidity_r)
q.put(soil_m_r)
q.put(time_of_reading.strftime("%H:%M"))
@app.route("/")
def index():
return render_template('index.html')
if __name__ == '__main__':
x = threading.Thread(target=data_collection)
x.start()
app.run(host = '0.0.0.0', port = 5000)
现在我们终于可以定义第二条路由,它将从数据收集线程中获取数据,并将其发送到前端。Queue 模块中的 get 和 put 方法也是阻塞的——所以如果事情不正常,请务必查看文档。
from flask import Flask, jsonify,render_template,request
import threading, queue
import serial
from datetime import datetime
ser = serial.Serial("/dev/ttyACM0",9600)
app = Flask(__name__)
q = queue.Queue()
temperature = []
humidity = []
soil_moisture = []
time_ax = []
def data_collection():
while(1):
#set variables
i = 0
#read is blocking so waits till next packet of data is sent
temperature_r = ser.read(5).decode('utf-8')
humidity_r = ser.read(5).decode('utf-8')
soil_m_r = ser.read(2).decode('utf-8')
time_of_reading = datetime.now() #this will be the x axis
#put data in queue
q.put(temperature_r)
q.put(humidity_r)
q.put(soil_m_r)
q.put(time_of_reading.strftime("%H:%M"))
@app.route("/update", methods = ['GET'])
def update_chart():
while not q.empty():
temperature.append(q.get())
humidity.append(q.get())
soil_moisture.append(q.get())
time_ax.append(q.get())
return jsonify(results = [temperature,humidity,soil_moisture,time_ax])
@app.route("/")
def index():
return render_template('index.html')
if __name__ == '__main__':
x = threading.Thread(target=data_collection)
x.start()
app.run(host = '0.0.0.0', port = 5000)
这就是 Python 方面的内容。
所有后端都准备好了 - 我们可以使用 chartjs 使用 Javascript 绘制数据。首先设置chartjs环境。我使用了以下代码行:
var ctx = $("#temperature_chart");
console.log("test");
var temperature_chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets : [
{
label: 'temperature',
data: [temperature],
borderColor: [
'#060666',
],
borderWidth: 3,
fill: false,
yAxisID: "temperature"
},
{
label: 'Humidity',
data: [humidity],
borderColor: [
'#d6c73e'
],
fill: false,
yAxisID: "humidity"
},
{
label: 'Soil Moisture',
data: [soil_moisture],
borderColor: [
'#7fced4'
],
fill: false,
yAxisID: "humidity"
}
]
},
options: {
responsive: false,
scales:{
xAxes: [ {
//type: 'time',
display: true,
scaleLabel : {
display: true,
labelString: 'Time (s)'
},
ticks: {
autoSkip: true,
maxTicksLimit: 12
}
}],
yAxes: [ {
id: "temperature",
display: true,
position: 'left',
ticks: {
suggestedMin: 15,
suggestedMax: 30
},
scaleLabel : {
display: true,
labelString: 'Temperature (C)'
}
},
{
id: "humidity",
display: true,
position: 'right',
ticks: {
suggestedMin: 0,
suggestedMax: 100
},
scaleLabel : {
display: true,
labelString: 'Percentage Humidity'
}
}]
}
}
});
最后,为了从 Flask 后端获取数据,我们使用以下代码行:
var x = 0;
var temperature = 0;
var humidity = 0;
var soil_moisture = 0;
var updated_data = $.get('/update');
updated_data.done(function(results){
temperature = results.results[0];
humidity = results.results[1];
soil_moisture = results.results[2];
x = results.results[3];
console.log(temperature);
console.log(humidity);
console.log(soil_moisture);
temperature_chart.data.datasets[0].data = temperature;
temperature_chart.data.datasets[1].data = humidity;
temperature_chart.data.datasets[2].data = soil_moisture;
temperature_chart.data.labels = x;
temperature_chart.update();
});
当有人进入网页时,这会调用 Python 脚本中指定的更新路由。
结果
如您所见,最终结果是带有两个 y 轴的时间戳图。该项目总体运行良好,在更多的日子里,我能够看到温度和湿度的明显周期性,以及土壤湿度的线性下降趋势,正如预期的那样!这些数据是完全原始的,有几种方法可以使这个项目变得更好
此项目中的传感器可以更换为与 Arduino 或 Raspberry Pi 兼容的任何其他传感器。最终,您可能想要进行一些实际处理而不是绘制原始数据 - 因此,将所有数据不断发送到 csv 可能更容易,而不是将其放入队列中以进行检索。这意味着在您的 wifi 中断或系统停止响应的情况下,您可以使用数据。
您还可以连接水泵或其他一些可以从本地网络控制的执行器 - 如果在前端按下按钮,只需添加另一条路线。
Chartjs 并不是真正用于实时绘图,但您可以通过另一条轮询数据的路由轻松地使其每隔几秒更新一次。然后,您可以在 javascript 中检索此数据并在 setInterval() 函数中更新图表。
如果您有任何问题,请随时与我们联系。我将尽我所能回答!
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
全部0条评论
快来发表一下你的评论吧 !