在上节课,我们设计了一个智慧农业物联网系统,通过一块行空板来检测植物生长时的土壤湿度情况,将湿度数据上传到SIoT物联网平台进行远程查看并在湿度较低时从平台远程控制浇水。
然而,在真实的农业场景中,常常需要检测不同场所内的多样数据,并汇总到一个平台总站以便远程访问。
那么,在这节课上,让我们借助DFRobot的3块行空板,一起来设计一个多节点的智慧农业系统,模拟一下真实的农业场景吧。
?
任务目标
准备多块行空板(以3块为例),将所有行空板与电脑连接到同一个局域网段内。之后单独在第三块行空板上开启SIoT应用作为服务器,将通过第一块行空板检测到的土壤湿度数据和第二块行空板检测到的光线值数据,各自显示在屏幕上的同时,也发送到板3的SIoT物联网平台上;最后,使第三块行空板订阅物联网平台接收到的消息,将板1的土壤湿度情况和板2的光线强度情况一起显示在屏幕上,并且,当检测到的两个环境数据不佳时,自动给我们的邮箱发送一封警报邮件,以此来提醒我们及时浇水和补光。
?
?
?
?
?
?
知识点
- 学习使用3块行空板搭建多节点物联网系统的方法
- 掌握给行空板供电的多种不同的方式
- 掌握远程连接行空板并运行程序的不同方法
- 学习使用smtplib库发送邮件的流程
- 学习使用smtplib库发送邮件的具体方法
材料清单
硬件清单:
? 行空板x3 | ? Type-C&Micro二合一USB线x3 | ? 土壤湿度传感器x1 | ? 12V/1000mA 电源适配器x1 | 简易继电器模块 x1 | ? 带水管潜水泵 x1 ? | ? 两头PH2.0-3P白色硅胶绞线x2 | ? 数字红色LED发光模块 x1 | |
软件使用:Mind+编程软件x1
其他:??1、带植物的花盆?x1
- 盛有水的烧杯?x1
- 十字/一字两用螺丝刀 x1
- TypeC或USB-A口的电源适配器或充电宝 ?x3
知识储备
- smtplib库与email库
使用Python编程来发送邮件需要两个关键步骤,一步是构造邮件信息内容,另一步是发送邮件。前者,我们可以借助email库来实现,后者,我们可以使用smtplib库来进行。
?
2、email.mime.text包MIMEText模块创建邮件文本
email.mime.text包的MIMEText模块可在构造邮件时创建文本内容,使用时,需要先导入该模块,之后以实例化MIMEText()类的形式来创建文本对象。
from email.mime.text import MIMEText # 导入email.mime.text包中的MIMEText模块
msg=MIMEText('Hello World','plain','utf-8') ?# 创建邮件文本对象,‘Hello World’对应文本内容,'plain'指设置的文本格式,'utf-8'指设置的编码
其中,“Hello World”指要发送的文本内容,“plain”指文本的格式,“utf-8”指的是编码。
3、email.utils包formataddr模块格式化内容
email.utils包的?formataddr模块可在构造邮件时将输入的内容进行格式化操作,以便邮件服务器能够识别。使用时,需要先导入该模块。
from email.utils import formataddr # 导入formataddr模块,负责将输入的内容格式化
'''三个头部信息:发件人,收件人,主题'''
?????msg['From']=formataddr([my_name,my_sender]) # 定义发件人信息:括号里的对应发件人邮箱昵称、发件人邮箱账号
?????msg['To']=formataddr([my_user_name,my_user]) # 定义收件人信息:括号里的对应收件人邮箱昵称、收件人邮箱账号
?????msg['Subject']= '邮件测试'?# 定义邮件的主题,也可以说是标题
其中,“msg['From']”表示邮件信息中发件人信息,包括邮箱、昵称;“msg['To']”表示邮件信息中收件人的信息,包括邮箱、昵称;“msg['Subject']”表示邮件的主题,文本内容“邮件测试”是它的具体内容。
这里,我们通过“formataddr()”函数将邮件的发件人信息和收件人信息文本进行了格式化操作。
4、smtplib库各功能函数
smtplib库可实现发送邮件功能,使用时,首先需要先导入该库,其次,通过其中的“SMTP_SSL()”函数来创建一个SMTP服务对象以连接邮箱,接着通过“login()”函数登录邮箱,之后使用“sendmail()”函数即可发送邮件,最后通过“quit()”函数退出与邮箱服务器的连接。
(1)smtplib库SMTP_SSL()函数创建SMTP服务对象
smtplib库中的SMTP_SSL()函数可创建SMTP服务对象以连接邮箱。使用时,首先需要先导入该库。 import smtplib?# 导入smtplib库
server=smtplib.SMTP_SSL("smtp.qq.com", 465) # 创建SMTP服务,连接qq邮箱服务器,发件人邮箱中的SMTP服务器,SMTP协议加密端口是465
其中,“smtp.qq.com”指的是QQ邮箱服务器,“465”表示 SMTP协议加密端口。
(2)smtplib库login()函数登录邮箱
smtplib库中的login()函数可实现对发件人邮箱的登录。使用时,需设定好所要登录邮箱的账号和第三方登录授权码。
my_sender='10******69@qq.com'?# 设置发件人邮箱账号,输入自己的邮箱
my_pass = 'sq******ga' # 设置发件人邮箱授权码
server.login(my_sender, my_pass) ?# 登录邮箱,括号中对应的是发件人邮箱账号、邮箱密码
其中,“my_sender”指的是发件人邮箱账号,“my_pass”指的是发件人邮箱授权码。
(3)smtplib库sendmail()函数发送邮件
smtplib库中的sendmail()函数可实现邮件的发送。使用时,需设定好发件人邮箱账号、收件人邮箱账号和所要发送邮件信息内容。
msg=MIMEText(content,'plain','utf-8') ?# 创建邮件文本对象,Title对应文本内容,'plain'指设置的文本格式,'utf-8'指设置的编码
my_sender='10******69@qq.com'?# 设置发件人邮箱账号,输入自己的邮箱
my_user='10******69@qq.com' # 设置收件人邮箱账号,我这边发送给自己
server.sendmail(my_sender,my_user,msg.as_string()) ?# 发送邮件,括号中对应的是发件人邮箱账号、收件人邮箱账号、邮件信息的字符串格式
其中,“my_sender”指的是发件人邮箱账号,“my_user”指的是收件人邮箱账号,“msg.as_string()”指的是将邮件定义为字符串格式。
(4)smtplib库quit()函数关闭连接
smtplib库中的quit()函数可关闭与邮箱服务器之间的连接。
server.quit() ?# 关闭连接
动手实践
任务描述1:前期准备
1、分析设计
在这个项目中,我们将完成多节点智慧农业系统的设计。首先,我们需要将3块行空板与电脑都连接在同一个局域网内(借助路由器/手机热点);之后,我们要将每一块板子轮流使用USB线连接电脑进行网络配置;最后,我们再单独开启其中一块板子的SIoT应用使其作为服务器,而其他板子作为客户端,通过编程将数据发送到SIoT物联网平台上。
?
2、配置网络
STEP1:将电脑连入路由器的Wi-Fi中
?
Tips :没有路由器也可通过手机开热点的方式实现联网。
STEP2:取一块板子,将其通过USB连接线连接到电脑。
STEP3:打开浏览器,登录“10.1.2.3”行空板网页菜单,配置网络,使其连在同一Wi-Fi中。
?
STEP4:完成后,拔下USB线,用同样的方式给其他两块行空板配置好网络。
STEP5:给3块板子上电,使其都保持开机状态
这里,给板子上电有以下三种方式,任选其一即可:
- 使用USB或type-c口输出的电源适配器
- 使用USB或type-c口输出的充电宝
- 通过电脑的USB端口
STEP6:检查各板子无线连接的IP地址。
依次查看并记录下3块板子的无线连接的IP地址,如下图,前三组数字应该是一样的,这说明我们成功将3块板子都连接在了同一局域网段内。
Tips:这里,三块板子的无线连接IP地址分别为“192.168.43.199”、“192.168.43.200”、“192.168.43.201”。
?
3、开启SIoT
STEP1:单独启动一块板子的SIoT服务
找到其中一块板子,按下HOME键进入菜单,单击“应用开关”,找到SIoT应用后点击启用,如下图所示。同时,确认其余两块板子SIoT应用关闭。
Tip1:这里,我们将SIoT服务启动在了IP为“192.168.43.201”的板子上。
Tip2:关闭其他板子的SIoT是为了避免如果输入地址错误导致数据不在同一个地方。
?
STEP2:检查电脑联网
打开浏览器,输入IP地址“192.168.43.201”并回车,进入行空板的网页菜单,找到应用开关下的SIoT服务,点击“打开页面”。
?
Tips:这里,需将“192.168.43.201”改为个人实际的IP地址。
如出现下列界面(物联网平台网页端),说明电脑和板子都连在了同一局域网内,且SIoT应用启动成功。
?
任务描述2:行空板1检测土壤湿度
这里,为了便于区分,我们将开启SIoT应用的板子记作行空板3,其余两块板子分别为行空板1和行空板2 。
这个任务中,我们将在行空板1的屏幕上显示一张智慧农业的背景图,使外接土壤湿度传感器检测到的湿度数据一边显示在屏幕上,一边发送到板3的SIoT平台上,这里的整体功能同上节课。
同时,对板1设定设置自动和手动两个模式,在自动模式下,当检测到的土壤湿度值过低时,通过外接继电器和水泵来自动浇水;在手动模式下,可通过点击屏幕上按钮的方式进行浇水;模式之间也可通过按钮手动切换。
此外,也可在SIoT物联网平台网页端发送“relay on”、“relay off”、“auto”和“manual”四个指令,远程控制浇水、关水、切换自动模式以及切换手动模式。
1、硬件搭建
STEP1:将土壤湿度传感器接入行空板1的P21引脚,继电器接入P23引脚
?
STEP2:利用螺丝刀将水泵正负线与转接头连接起来
?
STEP3:利用继电器将12V电源开关与水泵的转接头连接起来
?
STEP4:将继电器开关拨至NC端
?
STEP5:将水管和土壤湿度传感器插入花盆中,水泵固定在满水的烧杯中
?
STEP6:将12V电源开关插上220V电源插座
Tips :上述具体步骤皆可参考第8课。
2、程序编写
STEP1:创建与保存项目文件
启动Mind+,另存项目并命名为“010、多节点智慧农业系统01”。
STEP2:远程连接行空板
对不同的板子编程,我们可以同时打开多个Mind+,在各个Mind+中手动输入不同的IP地址,这样就可以连接访问不同板子了。
(1)选择“手动输入”
?
- 输入行空板1 的无线连接IP地址“192.168.43.199”
Tips:需改为个人实际的行空板1的IP地址,同时这里的板子须为正常开机状态且板子与电脑在同一个Wi-Fi下方可连接成功。
?
STEP3:在行空板1中创建一个新的文件夹,并命名为“多节点智慧农业系统”
Tips:这里,我们将程序直接建在板子中,这样可以便于直接通过行空板的Home菜单来运行程序而不需要启动MInd+运行,操作方法后续会介绍。
?
STEP4:创建与保存Python文件
在“多节点智慧农业系统”文件夹中创建一个Python程序文件“main1.py”,双击打开。
?
STEP5:导入素材文件夹
在“多节点智慧农业系统”文件夹中导入素材文件夹。(下载链接见附录1)
?
?
STEP6:程序编写
- 导入所需功能库
from unihiker import GUI ?# 导入unihiker库GUI模块 from pinpong.board import Board, Pin ?# 导入pinpong库下的Board, Pin模块 import time ?# 导入time库 import siot ?# 导入siot库 |
- 初始化板子和引脚
'''初始化板子和硬件''' Board().begin() ?# 初始化行空板 adc0 = Pin(Pin.P21, Pin.ANALOG)??# 初始化21引脚为模拟输入模式 relay = Pin(Pin.P23, Pin.OUT) ?# 初始化23引脚为数字输出模式 |
- 连接SIoT物联网平台并设置发送和订阅消息数据功能
Tips:这里的IP地址写入的是之前记录的行空板3的无线热点IP。
'''设置物联网平台连接参数''' SERVER = "192.168.43.201" ?# MQTT服务器IP,输入个人实际IP CLIENT_ID = "" ?# 在SIoT上,CLIENT_ID可以留空 IOT_UserName = 'siot' ?# 用户名 IOT_PassWord = 'dfrobot' ?# 密码 IOT_pubTopic1 = '多节点智慧农业系统/Soil_moisture_value' ?# 湿度topic,“项目名称/设备名称” # 定义回调函数 def sub_relay(client, userdata, msg): ????topic = msg.topic ????payload = msg.payload.decode() ????'''定义接收到指令时的操作''' ????print("\nTopic:" + topic + " Message:" + payload) ?# 打印接收到的信息 ????if payload == 'relay on': ?# 如果接收到“relay on” ????????img.config(w=240, h=320, image='img/浇水1.png') ????????relay.write_digital(1) ?# 继电器输出高电平 ????elif payload == 'relay off': ?# 如果接收到“relay off” ????????img.config(w=240, h=320, image='img/关水1.png') ????????relay.write_digital(0) ?# 继电器输出低电平 ????elif payload == 'auto': ?# 如果接收到“auto” ????????print("auto") ????????click_C() ?# 切换模式--自动模式 ????elif payload == 'manual': ?# 如果接收到“manual” ????????print("manual") ????????click_C() ?# 切换模式--手动模式 siot.init(CLIENT_ID, SERVER, user=IOT_UserName,password=IOT_PassWord) ?# 初始化,确定输入的用户名和密码正确 siot.connect() ?# 连接siot物联网平台 siot.subscribe(IOT_pubTopic1, sub_relay) # 订阅物联网平台消息 siot.loop() ?# 循环 |
- 显示屏幕界面
'''显示屏幕界面''' gui = GUI() ?# 实例化gui对象 img = gui.draw_image(w=240, h=320, image='img/关水1.png')?# 显示背景图片 # 定义三个按钮的回调函数 def click_A(): ????'''定义点击按钮A时的操作--切换显示浇水图片''' ????img.config(w=240, h=320, image='img/浇水1.png') ????relay.write_digital(1) ?# 继电器输出高电平 def click_B(): ????'''定义点击按钮B时的操作--切换显示关水图片''' ????img.config(w=240, h=320, image='img/关水1.png') ????relay.write_digital(0) ?# 继电器输出低电平 is_auto_mode = 1 ?# 定义一个标志位--自动模式,1为开,0为关 def click_C(): ?# 定义点击按钮C时的操作--切换模式 ????global is_auto_mode ????if is_auto_mode == 0:?# 如果自动模式标志位为0, 即原来为手动模式,那么点击后 ????????text_mode.config(text="自动") ????????button_A.config(state='disabled') # 设置按钮状态为disabled,表示无法点击 ????????button_B.config(state='disabled') ????????is_auto_mode = 1 # 设置自动模式标志位为1 ,即打开自动模式 ????elif is_auto_mode == 1: # 如果自动模式标准位为1, 即原来为自动模式,那么点击后 ????????text_mode.config(text="手动") ????????button_A.config(state='normal') # 设置按钮状态为normal,表示可以被点击 ????????button_B.config(state='normal') ????????is_auto_mode = 0 # 设置自动模式标志位为0 ,即打开手动模式 # 绘制填充矩形 gui.fill_rect(x=65, y=35, w=70, h=30, color="white") # 绘制矩形以显示“湿度值” gui.fill_rect(x=148, y=35, w=55, h=30, color="white") # 绘制矩形以显示湿度值数据 gui.fill_rect(x=100, y=160, w=70, h=30, color="white")?# 绘制矩形以显示“当前模式” gui.fill_rect(x=180, y=160, w=50, h=30, color="white") ?# 绘制矩形以显示模式类型 # 在矩形框内显示文字 gui.draw_text(x=68, y=36, color="red", text='湿度值:') ?# 显示文字“湿度值:” text_value = gui.draw_text(x=155, y=36, color="red", text="") # 显示湿度值数据 gui.draw_text(x=105, y=163, color="red", text='当前模式:', font_size=11) # 显示文字“当前模式:” text_mode = gui.draw_text(x=190, y=163, color="red", text="自动", font_size=11) # 显示模式类型 # 显示按钮 '''显示按钮并设置按钮点击后触发的功能,以及按钮初始时处于不可被点击状态''' button_A = gui.add_button(x=50, y=260, w=70, h=40,text="浇水", οnclick=click_A, state='disabled') button_B = gui.add_button(x=140, y=260, w=70, h=40,text="关水", οnclick=click_B, state='disabled') button_C = gui.add_button(x=10, y=160, w=70, h=30,text="切换模式", οnclick=click_C) |
- 定义自动补水功能
# 定义湿度值较低时自动补水3秒 def auto_watering(): ????'''定义湿度值较低时自动补水3秒''' ????if Soil_moisture_value < 2120: ????????click_A() ????????time.sleep(3) ????????click_B() ????else: ????????click_B() |
- 循环检测湿度值并发送数据至物联网平台
while True: ?# 循环 ????Soil_moisture_value = adc0.read_analog()??# 读取模拟值 ????print(Soil_moisture_value) ?# 打印显示湿度值 ????siot.publish(IOT_pubTopic1, Soil_moisture_value) ?# 发布信息至物联网平台 ????if is_auto_mode == 1: ????????auto_watering() ?# 自动补水 ????text_value.config(text=Soil_moisture_value) ?# 更新湿度值 ????time.sleep(1) ?# delay1秒 |
Tips:完整示例程序如下:
# 板1检测土壤湿度数据,发送到板3开启的SIoT物联网平台上。 from unihiker import GUI ?# 导入unihiker库GUI模块 from pinpong.board import Board, Pin ?# 导入pinpong库下的Board, Pin模块 import time ?# 导入time库 import siot ?# 导入siot库 '''初始化板子和硬件''' Board().begin() ?# 初始化行空板 adc0 = Pin(Pin.P21, Pin.ANALOG)??# 初始化21引脚为模拟输入模式 relay = Pin(Pin.P23, Pin.OUT) ?# 初始化23引脚为数字输出模式 '''设置物联网平台连接参数''' SERVER = "192.168.43.201" ?# MQTT服务器IP,输入个人实际IP? CLIENT_ID = "" ?# 在SIoT上,CLIENT_ID可以留空 IOT_UserName = 'siot' ?# 用户名 IOT_PassWord = 'dfrobot' ?# 密码 IOT_pubTopic1 = '多节点智慧农业系统/Soil_moisture_value' ?# 湿度topic,“项目名称/设备名称” # 定义回调函数 def sub_relay(client, userdata, msg): ????topic = msg.topic ????payload = msg.payload.decode() ????'''定义接收到指令时的操作''' ????print("\nTopic:" + topic + " Message:" + payload) ?# 打印接收到的信息 ????if payload == 'relay on': ?# 如果接收到“relay on” ????????img.config(w=240, h=320, image='img/浇水1.png') ????????relay.write_digital(1) ?# 继电器输出高电平 ????elif payload == 'relay off': ?# 如果接收到“relay off” ????????img.config(w=240, h=320, image='img/关水1.png') ????????relay.write_digital(0) ?# 继电器输出低电平 ????elif payload == 'auto': ?# 如果接收到“auto” ????????print("auto") ????????click_C() ?# 切换模式--自动模式 ????elif payload == 'manual': ?# 如果接收到“manual” ????????print("manual") ????????click_C() ?# 切换模式--手动模式 siot.init(CLIENT_ID, SERVER, user=IOT_UserName,password=IOT_PassWord) ?# 初始化,确定输入的用户名和密码正确 siot.connect() ?# 连接siot物联网平台 siot.subscribe(IOT_pubTopic1, sub_relay) # 订阅物联网平台消息 siot.loop() ?# 循环 '''显示屏幕界面''' gui = GUI() ?# 实例化gui对象 img = gui.draw_image(w=240, h=320, image='img/关水1.png')?# 显示背景图片 # 定义三个按钮的回调函数 def click_A(): ????'''定义点击按钮A时的操作--切换显示浇水图片''' ????img.config(w=240, h=320, image='img/浇水1.png') ????relay.write_digital(1) ?# 继电器输出高电平 def click_B(): ????'''定义点击按钮B时的操作--切换显示关水图片''' ????img.config(w=240, h=320, image='img/关水1.png') ????relay.write_digital(0) ?# 继电器输出低电平 is_auto_mode = 1 ?# 定义一个标志位--自动模式,1为开,0为关 def click_C(): ?# 定义点击按钮C时的操作--切换模式 ????global is_auto_mode ????if is_auto_mode == 0:?# 如果自动模式标志位为0, 即原来为手动模式,那么点击后 ????????text_mode.config(text="自动") ????????button_A.config(state='disabled') # 设置按钮状态为disabled,表示无法点击 ????????button_B.config(state='disabled') ????????is_auto_mode = 1 # 设置自动模式标志位为1 ,即打开自动模式 ????elif is_auto_mode == 1: # 如果自动模式标准位为1, 即原来为自动模式,那么点击后 ????????text_mode.config(text="手动") ????????button_A.config(state='normal') # 设置按钮状态为normal,表示可以被点击 ????????button_B.config(state='normal') ????????is_auto_mode = 0 # 设置自动模式标志位为0 ,即打开手动模式 # 绘制填充矩形 gui.fill_rect(x=65, y=35, w=70, h=30, color="white") # 绘制矩形以显示“湿度值” gui.fill_rect(x=148, y=35, w=55, h=30, color="white") # 绘制矩形以显示湿度值数据 gui.fill_rect(x=100, y=160, w=70, h=30, color="white")?# 绘制矩形以显示“当前模式” gui.fill_rect(x=180, y=160, w=50, h=30, color="white") ?# 绘制矩形以显示模式类型 # 在矩形框内显示文字 gui.draw_text(x=68, y=36, color="red", text='湿度值:') ?# 显示文字“湿度值:” text_value = gui.draw_text(x=155, y=36, color="red", text="") # 显示湿度值数据 gui.draw_text(x=105, y=163, color="red", text='当前模式:', font_size=11) # 显示文字“当前模式:” text_mode = gui.draw_text(x=190, y=163, color="red", text="自动", font_size=11) # 显示模式类型 # 显示按钮 '''显示按钮并设置按钮点击后触发的功能,以及按钮初始时处于不可被点击状态''' button_A = gui.add_button(x=50, y=260, w=70, h=40,text="浇水", οnclick=click_A, state='disabled') button_B = gui.add_button(x=140, y=260, w=70, h=40,text="关水", οnclick=click_B, state='disabled') button_C = gui.add_button(x=10, y=160, w=70, h=30,text="切换模式", οnclick=click_C) # 定义湿度值较低时自动补水3秒 def auto_watering(): ????'''定义湿度值较低时自动补水3秒''' ????if Soil_moisture_value < 2120: ????????click_A() ????????time.sleep(3) ????????click_B() ????else: ????????click_B() while True: ?# 循环 ????Soil_moisture_value = ?adc0.read_analog()??# 读取模拟值 ????print(Soil_moisture_value) ?# 打印显示湿度值 ????siot.publish(IOT_pubTopic1, Soil_moisture_value) ?# 发布信息至物联网平台 ????if is_auto_mode == 1: ????????auto_watering() ?# 自动补水 ????text_value.config(text=Soil_moisture_value) ?# 更新湿度值 ????time.sleep(1) ?# delay1秒 |
3、程序运行
STEP1:运行观察效果
点击Mind+软件上的运行按钮,观察行空板1,可以看到板1界面如下,能在手动和自动两个模式下控制浇水,也可以在浏览器中输入SIoT服务器所在的行空板的IP地址(此处为“192.168.43.201”),打开网页菜单应用开关中的SIoT,从物联网平台远程查看湿度数据并且进行浇水等操作。
?
?
Tips:只有手动模式下发送消息才有效
任务描述3:行空板2检测光线
这里,我们将在行空板2的屏幕上显示一张小灯背景图,将板载光线传感器检测到的光线值数据一边显示在屏幕上,一边发送到板3的SIoT平台上。
同时,对板2设定设置自动和手动两个模式,在自动模式下,当检测到的光线值过低时,通过外接LED来自动补光;在手动模式下,可通过点击屏幕上按钮的方式来补光;模式之间也可通过按钮手动切换。
此外,也可在SIoT物联网平台网页端发送“led on”、“led off”、“auto”和“manual”四个指令,远程控制LED灯打开、LED灯熄灭、切换自动模式以及切换手动模式。
1、硬件搭建
将LED接入行空板2的P23引脚
?
2、程序编写
STEP1:创建与保存项目文件
再启动一个Mind+软件,另存项目并命名为“010、多节点智慧农业系统02”。
STEP2:远程连接行空板
(1)选择“手动输入”
(2)输入行空板2 的无线连接IP地址“192.168.43.200”
?
STEP3:和板1一样,直接在行空板2中创建一个新的文件夹,并命名为“多节点智慧农业系统”
STEP4:创建与保存Python文件
在“多节点智慧农业系统”文件夹中创建一个Python程序文件“main2.py”,双击打开。
STEP5:在“多节点智慧农业系统”文件夹中导入素材文件夹。
?
STEP6:程序编写
整体上,这里的编程逻辑同上述板1,因此我们不做过多赘述,注意写入实际行空板3的IP地址。
Tips:完整示例程序如下:
# 板2检测光线数据,发送到板3开启的SIoT物联网平台上。 from unihiker import GUI ?# 导入unihiker库 from pinpong.board import Board, Pin ?# 导入pinpong库下的Board, Pin模块 from pinpong.extension.unihiker import * # 导入pinpong.extension.unihiker包中所有模块 import time ?# 导入time库 import siot ?# 导入siot库 '''初始化板子和硬件''' Board().begin() ?# 初始化行空板 led = Pin(Pin.P23, Pin.OUT) ?# 初始化23引脚为数字输出模式 '''设置物联网平台连接参数''' SERVER = "192.168.43.201" ?# MQTT服务器IP,输入个人实际IP? CLIENT_ID = "" ?# 在SIoT上,CLIENT_ID可以留空 IOT_UserName = 'siot' ?# 用户名 IOT_PassWord = 'dfrobot' ?# 密码 IOT_pubTopic2 = '多节点智慧农业系统/light' ?# 湿度topic,“项目名称/设备名称” # 定义回调函数 def sub_led(client, userdata, msg): ????topic = msg.topic ????payload = msg.payload.decode() ????'''定义接收到指令时的操作''' ????print("\nTopic:" + topic + " Message:" + payload) ?# 打印接收到的信息 ????if payload == 'led on': ?# 如果接收到“led on” ????????img.config(w=240, h=320, image='img/开灯1.png') ????????led.write_digital(1) ?# led输出高电平 ????elif?payload == 'led off': ?# 如果接收到“led off” ????????img.config(w=240, h=320, image='img/关灯1.png') ????????led.write_digital(0) ?# led输出低电平 ????elif payload == 'auto': ?# 如果接收到“auto” ????????print("auto") ????????click_C() ?# 切换模式--自动模式 ????elif payload == 'manual': ?# 如果接收到“manual” ????????print("manual") ????????click_C() ?# 切换模式--手动模式 siot.init(CLIENT_ID, SERVER, user=IOT_UserName,password=IOT_PassWord) ?# 初始化,确定输入的用户名和密码正确 siot.connect() ?# 连接siot物联网平台 siot.subscribe(IOT_pubTopic2, sub_led)?# 订阅物联网平台消息 siot.loop()??# 循环 '''显示屏幕界面''' gui = GUI() ?# 实例化gui对象 img = gui.draw_image(w=240, h=320, image='img/关灯1.png') # 显示背景图片 # 定义回调函数 def click_A(): ????'''定义点击按钮A时的操作--切换显示开灯图片''' ????img.config(w=240, h=320, image='img/开灯1.png') ????led.write_digital(1)?# 继电器输出高电平 def click_B(): ????'''定义点击按钮B时的操作--切换显示关灯图片''' ????img.config(w=240, h=320, image='img/关灯1.png') ????led.write_digital(0) ?# 继电器输出低电平 is_auto_mode = 1 ?# 定义一个标志位--自动模式,1为开,0为关 def click_C(): ?# 定义点击按钮C时的操作--切换模式 ????global is_auto_mode ????if is_auto_mode == 0: # 如果自动模式标志位为0, 即原来为手动模式,那么点击后 ????????text_mode.config(text="自动") ????????button_A.config(state='disabled') # 设置按钮状态为disabled,表示无法点击 ????????button_B.config(state='disabled') ????????is_auto_mode = 1 # 设置自动模式标志位为1 ,即打开自动模式 ????elif is_auto_mode == 1: # 如果自动模式标准位为1, 即原来为自动模式,那么点击后 ????????text_mode.config(text="手动") ????????button_A.config(state='normal')?# 设置按钮状态为normal,表示可以被点击 ????????button_B.config(state='normal') ????????is_auto_mode = 0 # 设置自动模式标志位为0 ,即打开手动模式 # 绘制填充矩形 gui.fill_rect(x=65, y=35, w=70, h=30, color="white") ?# 绘制矩形以显示“光线值” gui.fill_rect(x=148, y=35, w=55, h=30, color="white") ?# 绘制矩形以显示光线值数据 gui.fill_rect(x=100, y=160, w=70, h=30, color="white") ?# 绘制矩形显示“当前模式” gui.fill_rect(x=180, y=160, w=50, h=30, color="white") ?# 绘制矩形显示模式类型 # 在矩形框内显示文字 gui.draw_text(x=68, y=36, color="red", text='光线值') # 显示文字“光线值” text_value = gui.draw_text(x=155, y=36, color="red", text="") # 显示光线值数据 gui.draw_text(x=105, y=163, color="red", text='当前模式:', font_size=11) # 显示文字“当前模式:” text_mode = gui.draw_text(x=190, y=163, color="red", text="自动", font_size=11) # 显示模式类型 # 显示按钮 '''显示按钮并设置按钮点击后触发的功能,以及按钮初始时处于不可被点击状态''' button_A = gui.add_button(x=50, y=260, w=70, h=40,text="开灯", οnclick=click_A, state='disabled') button_B = gui.add_button(x=140, y=260, w=70, h=40,text="关灯", οnclick=click_B, state='disabled') button_C = gui.add_button(x=10, y=160, w=70, h=30,text="切换模式", οnclick=click_C) ?????????????????????????? # 定义光线值较低时自动开灯 def auto_light(): ????'''定义光线值较低时自动开灯''' ????if Light< 666: ????????click_A() ????else: ????????click_B() while True: ?# 循环 ????Light = light.read() # 读取光线值 ????print(Light) ?# 打印显示光线值 ????siot.publish(IOT_pubTopic2, Light) ?# 发布信息至物联网平台 ????if is_auto_mode == 1: ????????auto_light() ?# 自动补水 ????text_value.config(text=Light) ?# 更新光线值 ????time.sleep(1) ?# delay1秒 |
3、程序运行
STEP1:运行观察效果
点击Mind+软件上的运行按钮,观察行空板2,可以看到板2界面如下,能在手动和自动两个模式下控制补光(打开LED),也可以在浏览器中输入SIoT服务器所在的行空板的IP地址(此处为“192.168.43.201”),打开网页应用中的SIoT,从物联网平台远程查看光线值数据并进行补光等操作。
注意板2的Topic与板1的Topic不同,需要返回设备列表操作名称为“light”的项目。
?
?
Tips:只有手动模式下发送消息才有效
任务描述4:行空板3远程监控
这里,我们将行空板3作为总站,接收行空板1和行空板2发送到SIoT物联网平台上的土壤湿度和光线值数据,然后汇总显示在板3的屏幕上,并通过板3上的按钮来对板1 和板2进行控制,实现各自的浇水、关水、开灯、关灯功能。
同时,在板3上设定自动和手动两个模式开关按钮,分别用来控制板1和板2两个模式的切换,初始时默认为自动模式。
此外,最重要的,板3的总站也可以对接收到的数据进行实时判别,当某一环境数据不佳时,远程发送一封邮件至邮箱作为警报提示,以便我们能及时获取信息并进行浇水补光。
1、邮箱设置
STEP1:记录qq邮箱授权码
- 打开qq邮箱,点击页面上方的“设置”
?
?2 开启SMTP服务,生成授权码并记录
Tips:需要通过手机发送验证短信,生成授权码
?
?
?3微信开启QQ邮箱提醒
这里,为了方便查看邮件,我们也可在微信上开启QQ邮箱提醒功能。
?
Tips:具体操作方法可参考一下链接:
微信如何开启或关闭QQ邮箱提醒
2、程序编写
STEP1:创建与保存项目文件
再启动一个Mind+软件,另存项目并命名为“010、多节点智慧农业系统03”。
STEP2:远程连接行空板
(1)选择“手动输入”
- 输入行空板3 的无线连接IP地址“192.168.43.201”
STEP3:直接在行空板3中创建一个新的文件夹,并命名为“多节点智慧农业系统”
STEP4:创建与保存Python文件
在“多节点智慧农业系统”文件夹中创建一个Python程序文件“main3.py”,双击打开。
??
Step5:程序编写
(1)导入功能库
这里,为了能成功发送邮件,我们首先需要导入smtplib库,同时,在构造邮件时,我们需要处理文本,以及将输入的内容格式化,那么我们还需导入MIMEText模块和formataddr模块。
from unihiker import GUI ??# 导入unihiker库GUI模块 import time ?# 导入time库 import siot ?# 导入siot库 import smtplib # 导入smtplib库 from email.mime.text import MIMEText # 导入email.mime.text库MIMEText模块,负责处理文本 from email.utils import formataddr # 导入email.utils库formataddr模块,负责将输入的内容格式化 |
(2)设置物联网平台连接参数
接着,由于我们将接收板1和板2发送到物联网平台的数据,因此我们需要先设置好物联网平台的连接参数,以便后续订阅。
'''设置物联网平台连接参数''' SERVER = "192.168.43.201" ?# ?MQTT服务器IP地址,输入个人实际IP CLIENT_ID = "" ?# 在SIoT上,CLIENT_ID可以留空 IOT_UserName = 'siot' ?# 用户名 IOT_PassWord = 'dfrobot' ?# 密码 IOT_pubTopic1 = '多节点智慧农业系统/Soil_moisture_value' ?# 湿度topic,“项目名称/设备名称” IOT_pubTopic2 = '多节点智慧农业系统/light' ?# 光强topic,“项目名称/设备名称” |
(3)显示屏幕页面
之后,我们在行空板3的屏幕上显示四部分内容,首先是板1和板2 的湿度值和光线值数据;其次是对数据进行判别后的情况分析,比如在屏幕上显示“当前土壤湿度适当”、“当前光线强度适当”;接着是模式切换按钮和当前模式的显示;最后是手动模式下的四个按钮控件。而在按钮中,我们通过定义回调函数的方式,来设定其功能。
'''显示屏幕页面''' # 定义五个按钮的回调函数 def click_A(): ?# 定义点击按钮A时的操作--切换图片 ????siot.publish(IOT_pubTopic1, 'relay on') ?# 发布信息'relay on'至物联网平台 def click_B(): ?# 定义点击按钮B时的操作--切换图片 ????siot.publish(IOT_pubTopic1, 'relay off') ?# 发布信息'relay off'至物联网平台 def click_C(): ?# 定义点击按钮C时的操作--切换图片 ????siot.publish(IOT_pubTopic2, 'led on') ?# 发布信息'led on'至物联网平台 def click_D(): ?# 定义点击按钮D时的操作--切换图片 ????siot.publish(IOT_pubTopic2, 'led off') ?# 发布信息'led off'至物联网平台 is_auto_mode = 1 ?# 定义一个标志位--自动模式,1为开,0为关 def click_E(): ?# 定义点击按钮E时的操作--切换模式 ????global is_auto_mode ????if is_auto_mode == 0: ????????text_mode.config(text="自动") ????????button_A.config(state='disabled') ????????button_B.config(state='disabled') ????????button_C.config(state='disabled') ????????button_D.config(state='disabled') ????????siot.publish(IOT_pubTopic1, 'auto') ?# 发布信息'auto'至物联网平台 ????????siot.publish(IOT_pubTopic2, 'auto') ?# 发布信息'auto'至物联网平台 ????????is_auto_mode = 1 ????elif is_auto_mode == 1: ????????text_mode.config(text="手动") ????????button_A.config(state='normal') ????????button_B.config(state='normal') ????????button_C.config(state='normal') ????????button_D.config(state='normal') ????????siot.publish(IOT_pubTopic1, 'manual') ?# 发布信息'manual'至物联网平台 ????????siot.publish(IOT_pubTopic2, 'manual') ?# 发布信息'manual'至物联网平台 ????????is_auto_mode = 0 gui=GUI() # 实例化gui对象 # 显示填充矩形 gui.fill_rect(x=0, y=0, w=240, h=320, color="#99CCFF")?# 绘制填充矩形显示为背景 # 显示标题 title = gui.draw_text(x=30, y=5, text='多节点智慧农业系统', font_size=14, color='blue') # 显示part1数据(圆角矩形、文字) gui.draw_round_rect(x=20, y=33, w=200, h=42, r=8, width=1) # 显示圆角矩形,(起点坐标(20,33),宽200,高42,圆角半径8,线宽1) gui.draw_text(x=35, y=32, text='湿度值:', font_size=11) ?# 显示文字“湿度值:” text_value = gui.draw_text(x=110, y=32, color="red", text="", font_size=12) ?# 显示光线值数据 gui.draw_text(x=35, y=52, text='光线值:', font_size=11) # 显示文字“光线值” text_value_2 = gui.draw_text(x=110, y=52, color="red", text="", font_size=12) # 显示光线值数据 # 显示part2数据情况(圆角矩形、文字) gui.draw_round_rect(x=20, y=80, w=200, h=80, r=8,width=1) text1 = gui.draw_text(x=30, y=80, text='土壤湿度情况:', font_size=11, color='black') # 在(30,60)坐标位显示“土壤湿度情况:”,字体大小为11,颜色为黑 text2 = gui.draw_text(x=30, y=98, text='当前土壤湿度适当', font_size=12, color='red') text3 = gui.draw_text(x=30, y=120, text='光线强度情况:', font_size=11, color='black') text4 = gui.draw_text(x=30, y=138, text='当前光线强度适当', font_size=12, color='red') # 显示part3切换模式功能(圆角矩形、按钮、文字) gui.draw_round_rect(x=20, y=165, w=200, h=40, r=8,width=1) button_E = gui.add_button(x=30, y=170, w=70, h=30, text="切换模式", οnclick=click_E) gui.draw_text(x=110, y=173, text='当前模式:', font_size=11) # 显示文字“当前模式:” text_mode = gui.draw_text(x=178, y=173, color="red", text="自动", font_size=12) # 显示模式类型 # 显示part4手动控制模式(圆角矩形、按钮、文字) gui.draw_round_rect(x=20, y=210, w=200, h=105, r=8,width=1) gui.draw_text(x=33, y=213, text='手动控制:',font_size=11) # 显示文字“手动控制:” button_A = gui.add_button(x=30, y=240, w=70, h=30, text="浇水", οnclick=click_A,state='disabled') button_B = gui.add_button(x=140, y=240, w=70, h=30, text="关水", οnclick=click_B,state='disabled') button_C = gui.add_button(x=30, y=280, w=70, h=30, text="开灯", οnclick=click_C,state='disabled') button_D = gui.add_button(x=140, y=280, w=70, h=30, text="关灯", οnclick=click_D,state='disabled') |
(4)邮件设置
接下来,我们将定义邮件发送功能,首先设置好发送邮件所需的参数,包括发件人邮箱账号,发件人邮箱授权码,发件人邮箱昵称,收件人邮箱账号,收件人邮箱昵称总计五个参数。之后,定义一个发送邮件的函数,在其中将邮件内容和标题作为参数传入,以便能多次调用以发送邮件。
Tips:须填入实际的发件人账号、授权码信息,昵称可随意编写,但不可为空。
'''邮件设置''' my_sender='10******69@qq.com'?# 设置发件人邮箱账号,输入自己的邮箱 my_pass = '******' # 设置发件人邮箱授权码,不可为空 my_name = 'IvanD.Mido'?# 设置发件人邮箱昵称 my_user='10******69@qq.com' # 设置收件人邮箱账号,我这边发送给自己 my_user_name = 'IvanD.Mido' # 设置收件人邮箱昵称 # 定义警报发邮件函数 def mail(content,title): ????ret=True # 定义一个标记ret,记录发送邮件事件,初始值为True ????try: ????????msg=MIMEText(content,'plain','utf-8') ?# 创建邮件文本对象,Title对应文本内容,'plain'指设置的文本格式,'utf-8'指设置的编码 ????????'''三个头部信息:发件人,收件人,主题''' ????????msg['From']=formataddr([my_name,my_sender]) # 定义发件人信息:括号里的对应发件人邮箱昵称、发件人邮箱账号 ????????msg['To']=formataddr([my_user_name,my_user]) # 定义收件人信息:括号里的对应收件人邮箱昵称、收件人邮箱账号 ????????msg['Subject']=title # 定义邮件的主题,也可以说是标题 ????????server=smtplib.SMTP_SSL("smtp.qq.com", 465) # 创建SMTP服务,连接qq邮箱服务器,发件人邮箱中的SMTP服务器,SMTP协议加密端口是465 ????????server.login(my_sender, my_pass) ?# 登录邮箱,括号中对应的是发件人邮箱账号、邮箱密码 ????????server.sendmail(my_sender,my_user,msg.as_string()) ?# 发送邮件,括号中对应的是发件人邮箱账号、收件人邮箱账号、邮件信息的字符串格式 ????????server.quit() ?# 关闭连接 ????except Exception: ?# 如果 try 中的语句没有执行,则会执行下面的 ret=False ????????ret=False # 将ret标记记为False ????return ret # 返回ret标记 |
(6)定义回调函数
由于发送邮件是在检测到板1和板2 的数据不佳的情况下进行,因此,我们需要先设定接收到SIoT物联网平台的数据时的功能操作。
当接收到Topic1下的土壤湿度的数据后,我们做三步处理,首先是将数据呈现在屏幕上,其次是对数据进行判别,于屏幕更新显示判别分析后的情况,最后是依据判别结果,当结果数据不佳时设定发送一封警报邮件,并且每封主题邮件之间的间隔不少于10s。
而当接收到Topic2下的光线值数据后,我们做同样的处理。
Tips:这里的邮件发送频率可自行修改。
soil_mail_enable?=?True??# 定义发送土壤警报邮件使能的标志 light_mail_enable?=?True??# 定义发送光线警报邮件使能的标志 soil_mail_time?=?time.time()??# 定时邮件发送时间记录标志 light_mail_time?=?time.time()??# 定时邮件发送时间记录标志 #?定义回调函数 def?sub_cb(client,?userdata,?msg):? ????global?soil_mail_enable,light_mail_enable,soil_mail_time,light_mail_time ????topic?=?msg.topic?#?topic数据存入变量 ????payload?=?msg.payload.decode()?#?payload消息数据转换为字符串后存入变量 ????print("\nTopic:"?+?topic?+?"?Message:"?+?payload)?#?终端打印数据 ????if?topic?==?IOT_pubTopic1:??#?定义接收到Soil_moisture_value时的操作 ????????if?payload.isdigit():?#?接收的消息是数字类型的则说明是湿度值而不是控制命令 ????????????Soil_moisture_value?=?int(payload)??#?转换为数字类型方便后面做判断 ????????????text_value.config(text?=?str(Soil_moisture_value))??#?更新屏幕显示数据 ????????????if?Soil_moisture_value?<?2120:?#?阈值设置为2120,可根据实际情况调整 ????????????????text2.config(text="当前土壤湿度过低")??#?更新文字警告 ????????????????if?soil_mail_enable?==?True?and?(time.time()-soil_mail_time)?>?10:??#?如果允许且距离上次发送邮件时间间隔10秒以上才进行发送的操作 ????????????????????ret1=mail("土壤湿度报警","当前土壤湿度值为?"?+?str(Soil_moisture_value)?+?",请及时补水!") ????????????????????if?ret1:??#??如果有发送邮件事件 ????????????????????????print("邮件发送成功1") ????????????????????????soil_mail_enable?=?False??#?已经发送过邮件,改变标记避免重复发送 ????????????????????????soil_mail_time?=?time.time()???#?记录本次邮件发送的时间 ????????????????????else:?#?否则 ????????????????????????print("邮件发送失败1") ????????????else: ????????????????text2.config(text="当前土壤湿度适当")??#?更新文字 ????????????????soil_mail_enable?=?True???#?如果警报解除了就允许后续发邮件 ????elif??topic?==?IOT_pubTopic2:??#?定义接收到Light时的操作 ????????if?payload.isdigit():?#?接收的消息是数字类型的则说明是光线值而不是控制命令 ????????????light_value?=?int(payload)??#??转换为数字类型方便后面做判断 ????????????text_value_2.config(text?=?str(light_value))??#??屏幕更新光线值 ????????????if?light_value?<?666:???#??阈值设置为666,可根据实际情况调整 ????????????????text4.config(text="当前光线强度过低")??#??更新文字警告 ????????????????if?light_mail_enable?==?True?and??(time.time()-light_mail_time)?>?10:??#?如果允许发送邮件才进行发送的操作 ????????????????????ret2=mail("光线强度报警","当前光线值为?"?+?str(light_value)?+?",请及时补光!") ????????????????????if?ret2:??#??如果有发送邮件事件 ????????????????????????print("邮件发送成功2") ????????????????????????light_mail_enable?=?False??#?已经发送过邮件,改变标记避免重复发送 ????????????????????????light_mail_time?=?time.time()??#?记录本次邮件发送的时间 ????????????????????else:??#??否则 ????????????????????????print("邮件发送失败2") ????????????else: ????????????????text4.config(text="当前光线强度适当")?#?更新文字警告 ????????????????light_mail_enable?=?True??#?如果警报解除了就允许后续发邮件 |
(7)订阅物联网平台消息
在定义好回调函数后,我们设定板3订阅物联网平台的消息,并在开始时发送“auto”至两个Topic下,以便设定起始默认模式为自动。
siot.init(CLIENT_ID, SERVER, user=IOT_UserName,password=IOT_PassWord) ?# 初始化,确定输入的用户名和密码正确 siot.connect() ?# 连接siot物联网平台 siot.subscribe(IOT_pubTopic1, sub_cb) ?# 订阅Topic1 土壤湿度数据 siot.subscribe(IOT_pubTopic2, sub_cb) ?# 订阅Topic2 光线强度数据 siot.publish(IOT_pubTopic1,'auto')??#发布信息'auto'至物联网平台 siot.publish(IOT_pubTopic2,'auto')??#发布信息'auto'至物联网平台 siot.loop() ?# 循环 |
(8)循环保持程序运行
while True: ?# 循环 ????time.sleep(0.5) ?# delay0.5秒 |
Tips:完整示例程序如下:
'''板3订阅板1&板2发送到物联网平台的消息,显示在板3上,数值不佳时警报发邮件''' from unihiker import GUI ??# 导入unihiker库GUI模块 import time ?# 导入time库 import siot ?# 导入siot库 import smtplib # 导入smtplib库 from email.mime.text import MIMEText # 导入email.mime.text包中的MIMEText模块,负责处理文本 from email.utils import formataddr # 导入email.utils包中的formataddr模块,负责将输入的内容格式化 '''设置物联网平台连接参数''' SERVER = "192.168.43.201" ?# MQTT服务器IP地址,输入个人实际IP CLIENT_ID = "" ?# 在SIoT上,CLIENT_ID可以留空 IOT_UserName = 'siot' ?# 用户名 IOT_PassWord = 'dfrobot' ?# 密码 IOT_pubTopic1 = '多节点智慧农业系统/Soil_moisture_value' ?# 湿度topic,“项目名称/设备名称” IOT_pubTopic2 = '多节点智慧农业系统/light' ?# 光强topic,“项目名称/设备名称” # 定义五个按钮的回调函数 def click_A(): ?# 定义点击按钮A时的操作--切换图片 ????siot.publish(IOT_pubTopic1, 'relay on') ?# 发布信息'relay on'至物联网平台 def click_B(): ?# 定义点击按钮B时的操作--切换图片 ????siot.publish(IOT_pubTopic1, 'relay off') ?# 发布信息'relay off'至物联网平台 def click_C(): ?# 定义点击按钮C时的操作--切换图片 ????siot.publish(IOT_pubTopic2, 'led on') ?# 发布信息'led on'至物联网平台 def click_D(): ?# 定义点击按钮D时的操作--切换图片 ????siot.publish(IOT_pubTopic2, 'led off') ?# 发布信息'led off'至物联网平台 is_auto_mode = 1 ?# 定义一个标志位--自动模式,1为开,0为关 def click_E(): ?# 定义点击按钮E时的操作--切换模式 ????global is_auto_mode ????if is_auto_mode == 0: ????????text_mode.config(text="自动") ????????button_A.config(state='disabled') ????????button_B.config(state='disabled') ????????button_C.config(state='disabled') ????????button_D.config(state='disabled') ????????siot.publish(IOT_pubTopic1, 'auto') ?# 发布信息'auto'至物联网平台 ????????siot.publish(IOT_pubTopic2, 'auto') ?# 发布信息'auto'至物联网平台 ????????is_auto_mode = 1 ????elif is_auto_mode == 1: ????????text_mode.config(text="手动") ????????button_A.config(state='normal') ????????button_B.config(state='normal') ????????button_C.config(state='normal') ????????button_D.config(state='normal') ????????siot.publish(IOT_pubTopic1, 'manual') ?# 发布信息'manual'至物联网平台 ????????siot.publish(IOT_pubTopic2, 'manual') ?# 发布信息'manual'至物联网平台 ????????is_auto_mode = 0 '''显示屏幕页面''' gui=GUI() # 实例化gui对象 # 显示填充矩形 gui.fill_rect(x=0, y=0, w=240, h=320, color="#99CCFF")?# 绘制填充矩形显示为背景 # 显示标题 title = gui.draw_text(x=30, y=5, text='多节点智慧农业系统', font_size=14, color='blue') # 显示part1数据(圆角矩形、文字) gui.draw_round_rect(x=20, y=33, w=200, h=42, r=8, width=1) # 显示圆角矩形,(起点坐标(20,33),宽200,高42,圆角半径8,线宽1) gui.draw_text(x=35, y=32, text='湿度值:', font_size=11) ?# 显示文字“湿度值:” text_value = gui.draw_text(x=110, y=32, color="red", text="", font_size=12) ?# 显示光线值数据 gui.draw_text(x=35, y=52, text='光线值:', font_size=11) # 显示文字“光线值” text_value_2 = gui.draw_text(x=110, y=52, color="red", text="", font_size=12) # 显示光线值数据 # 显示part2数据情况(圆角矩形、文字) gui.draw_round_rect(x=20, y=80, w=200, h=80, r=8,width=1) text1 = gui.draw_text(x=30, y=80, text='土壤湿度情况:', font_size=11, color='black') # 在(30,60)坐标位显示“土壤湿度情况:”,字体大小为11,颜色为黑 text2 = gui.draw_text(x=30, y=98, text='当前土壤湿度适当', font_size=12, color='red') text3 = gui.draw_text(x=30, y=120, text='光线强度情况:', font_size=11, color='black') text4 = gui.draw_text(x=30, y=138, text='当前光线强度适当', font_size=12, color='red') # 显示part3切换模式功能(圆角矩形、按钮、文字) gui.draw_round_rect(x=20, y=165, w=200, h=40, r=8,width=1) button_E = gui.add_button(x=30, y=170, w=70, h=30, text="切换模式", οnclick=click_E) gui.draw_text(x=110, y=173, text='当前模式:', font_size=11) # 显示文字“当前模式:” text_mode = gui.draw_text(x=178, y=173, color="red", text="自动", font_size=12) # 显示模式类型 # 显示part4手动控制模式(圆角矩形、按钮、文字) gui.draw_round_rect(x=20, y=210, w=200, h=105, r=8,width=1) gui.draw_text(x=33, y=213, text='手动控制:',font_size=11) # 显示文字“手动控制:” button_A = gui.add_button(x=30, y=240, w=70, h=30, text="浇水", οnclick=click_A,state='disabled') button_B = gui.add_button(x=140, y=240, w=70, h=30, text="关水", οnclick=click_B,state='disabled') button_C = gui.add_button(x=30, y=280, w=70, h=30, text="开灯", οnclick=click_C,state='disabled') button_D = gui.add_button(x=140, y=280, w=70, h=30, text="关灯", οnclick=click_D,state='disabled') '''邮件设置''' my_sender='10******69@qq.com' # 设置发件人邮箱账号,输入自己的邮箱 my_pass = '' # 设置发件人邮箱授权码,不可为空 my_name = 'IvanD.Mido' # 设置发件人邮箱昵称 my_user='10******69@qq.com' # 设置收件人邮箱账号,我这边发送给自己 my_user_name = 'IvanD.Mido' # 设置收件人邮箱昵称 # 定义警报发邮件函数 def mail(content,title): ????ret=True # 定义一个标记ret,记录发送邮件事件,初始值为True ????try: ????????msg=MIMEText(content,'plain','utf-8') ?# 创建邮件文本对象,Title对应文本内容,'plain'指设置的文本格式,'utf-8'指设置的编码 ????????'''三个头部信息:发件人,收件人,主题''' ????????msg['From']=formataddr([my_name,my_sender]) # 定义发件人信息:括号里的对应发件人邮箱昵称、发件人邮箱账号 ????????msg['To']=formataddr([my_user_name,my_user]) # 定义收件人信息:括号里的对应收件人邮箱昵称、收件人邮箱账号 ????????msg['Subject']=title?# 定义邮件的主题,也可以说是标题 ????????server=smtplib.SMTP_SSL("smtp.qq.com", 465)?# 创建SMTP服务,连接qq邮箱服务器,发件人邮箱中的SMTP服务器,SMTP协议加密端口是465 ????????server.login(my_sender, my_pass) ?# 登录邮箱,括号中对应的是发件人邮箱账号、邮箱密码 ????????server.sendmail(my_sender,my_user,msg.as_string()) ?# 发送邮件,括号中对应的是发件人邮箱账号、收件人邮箱账号、邮件信息的字符串格式 ????????server.quit() ?# 关闭连接 ????except Exception: ?# 如果 try 中的语句没有执行,则会执行下面的 ret=False ????????ret=False # 将ret标记记为False ????return ret # 返回ret标记 soil_mail_enable?=?True??# 定义发送土壤警报邮件使能的标志 light_mail_enable?=?True??# 定义发送光线警报邮件使能的标志 soil_mail_time?=?time.time()??# 定时邮件发送时间记录标志 light_mail_time?=?time.time()??# 定时邮件发送时间记录标志 #?定义回调函数 def?sub_cb(client,?userdata,?msg):? ????global?soil_mail_enable,light_mail_enable,soil_mail_time,light_mail_time ????topic?=?msg.topic?#?topic数据存入变量 ????payload?=?msg.payload.decode()?#?payload消息数据转换为字符串后存入变量 ????print("\nTopic:"?+?topic?+?"?Message:"?+?payload)?#?终端打印数据 ????if?topic?==?IOT_pubTopic1:??#?定义接收到Soil_moisture_value时的操作 ????????if?payload.isdigit():?#?接收的消息是数字类型的则说明是湿度值而不是控制命令 ????????????Soil_moisture_value?=?int(payload)??#?转换为数字类型方便后面做判断 ????????????text_value.config(text?=?str(Soil_moisture_value))??#?更新屏幕显示数据 ????????????if?Soil_moisture_value?<?2120:?#?阈值设置为2120,可根据实际情况调整 ????????????????text2.config(text="当前土壤湿度过低")??#?更新文字警告 ????????????????if?soil_mail_enable?==?True?and?(time.time()-soil_mail_time)?>?10:??#?如果允许且距离上次发送邮件时间间隔10秒以上才进行发送的操作 ????????????????????ret1=mail("土壤湿度报警","当前土壤湿度值为?"?+?str(Soil_moisture_value)?+?",请及时补水!") ????????????????????if?ret1:??#??如果有发送邮件事件 ????????????????????????print("邮件发送成功1") ????????????????????????soil_mail_enable?=?False??#?已经发送过邮件,改变标记避免重复发送 ????????????????????????soil_mail_time?=?time.time()???#?记录本次邮件发送的时间 ????????????????????else:?#?否则 ????????????????????????print("邮件发送失败1") ????????????else: ????????????????text2.config(text="当前土壤湿度适当")??#?更新文字 ????????????????soil_mail_enable?=?True???#?如果警报解除了就允许后续发邮件 ????elif??topic?==?IOT_pubTopic2:??#?定义接收到Light时的操作 ????????if?payload.isdigit():?#?接收的消息是数字类型的则说明是光线值而不是控制命令 ????????????light_value?=?int(payload)??#??转换为数字类型方便后面做判断 ????????????text_value_2.config(text?=?str(light_value))??#??屏幕更新光线值 ????????????if?light_value?<?666:???#??阈值设置为666,可根据实际情况调整 ????????????????text4.config(text="当前光线强度过低")??#??更新文字警告 ????????????????if?light_mail_enable?==?True?and??(time.time()-light_mail_time)?>?10:??#?如果允许发送邮件才进行发送的操作 ????????????????????ret2=mail("光线强度报警","当前光线值为?"?+?str(light_value)?+?",请及时补光!") ????????????????????if?ret2:??#??如果有发送邮件事件 ????????????????????????print("邮件发送成功2") ????????????????????????light_mail_enable?=?False??#?已经发送过邮件,改变标记避免重复发送 ????????????????????????light_mail_time?=?time.time()??#?记录本次邮件发送的时间 ????????????????????else:??#??否则 ????????????????????????print("邮件发送失败2") ????????????else: ????????????????text4.config(text="当前光线强度适当")?#?更新文字警告 ????????????????light_mail_enable?=?True??#?如果警报解除了就允许后续发邮件 siot.init(CLIENT_ID, SERVER, user=IOT_UserName,password=IOT_PassWord) ?# 初始化,确定输入的用户名和密码正确 siot.connect() ?# 连接siot物联网平台 siot.subscribe(IOT_pubTopic1, sub_cb) ?# 订阅Topic1 土壤湿度数据 siot.subscribe(IOT_pubTopic2, sub_cb) ?# 订阅Topic2 光线强度数据 siot.publish(IOT_pubTopic1,'auto')??#发布信息'auto'至物联网平台 siot.publish(IOT_pubTopic2,'auto')??#发布信息'auto'至物联网平台 siot.loop() ?# 循环 while True: ?# 循环 ????time.sleep(0.5) ?# delay0.5秒 |
Tips:须填入实际的发件人账号、授权码等信息,不可为空。
3、程序运行
STEP1:运行程序观察效果
点击Mind+软件上的运行按钮,观察行空板3,可以看到初始时模式为自动,此时浇水、关水、开灯、关灯四个按钮为灰色,不可被点击,点击切换模式后,按钮恢复正常状态,同时检测到的土壤湿度值和光线值数据也显示在了屏幕上。此时,在板3上点击“浇水”和“开灯”后,板1上的水泵开始工作,板2上的LED亮起。当点击“关水”和“关灯”后,水泵和LED则停止了工作。
??????
观察物联网平台,可以看到在两个不同的Topic下,皆有数据传入。
????
??
同时,观察QQ邮箱,当土壤湿度或光线值数据不佳时,我们会接收到一封邮件。
??
??
Tip1:这里,由于我们直接将Python程序文件建在了行空板的内存中,因此,我们也可以直接在板子上运行程序,以板3为例,操作方法如下:
??
Tip2:这里的路由器或手机热点须能正常上网,方可发送邮件。
挑战自我
想一想,在本节课介绍的三种给行空板供电的方式中(电源适配器、充电宝、电脑USB口),对于需要长期运行程序的场景,该选择哪种方式更适合呢,而对于需要在户外使用的场景,又适合哪种方式呢?
除了发邮件警报作为一种手段之外,还可以有哪些可行的远程提醒的方法呢?
在既没有路由器,也没有手机热点的场景下,我们还能搭建多节点的物联网系统吗?
Tips:答案见附录2。
附录
在既没有路由器和手机也无法开热点的情况下,我们也可以通过开启其中一块行空板自身的热点功能创建一个Wi-Fi无线热点,其他行空板都连接这个Wi-Fi热点,来使各块板子连在同一局域网内。开启方法如下。
??
|