2022-8-12 15:48:32 [显示全部楼层]
1909浏览
查看: 1909|回复: 0

[M10项目] 家庭安全相册(一) - 行空板部分

[复制链接]
本帖最后由 szjuliet 于 2022-9-8 23:03 编辑

家庭安全相册(一) - 行空板部分

完整项目





背景引入

家中老人走失、孩子迷路或私自外出玩耍,家人都分外担心。如果有一个能随时了解被监护人的位置是否安全的设备就可以让亲人放心了。使用行空板、手机、Tinkernode,通过物联网发送家庭成员的实时位置,在行空板上显示当前成员的相片、经纬度、位置,播报当前状态,为家人安全保驾护航。

学习目标


  • 初步理解万物互联给人类信息社会带来的影响、机遇和挑战;
  • 通过物联网技术解决生活中的实际问题;
  • 初步了解多种硬件的使用方法并通过物联网产生联结,进一步理解物联网的含义;
  • 灵活运用Python的库,高效开发程序;
  • 运用计算思维的分解、建模、算法设计解决问题。

教学重难点教学重点


  • 理解物联网的含义及特点
  • 物联网平台订阅和发布信息
  • 学会运用计算思维解决问题

教学难点


  • 有效处理数据,数据的编码和解码
  • 理解JSON文本的数据结构

教学准备


  • 行空板、Tinkernode等相关硬件的知识准备(行空板维库Tinkernode维库
  • 下载安装项目中行空板用到的库
  • Tinkernode测试:NB信号测试,北斗模块测试
  • 获取APP INVENTOR开发code,模拟器下载安装
  • 高德地图申请apiKey
  • 家庭成员相片准备及处理(可以使用卡通图片替代)

材料清单


项目方案介绍

行空板

  • 实时接收被监护人经纬度信息并转换为位置信息
  • 显示当前成员的相片、经纬度及地址
  • 判断当前位置是否在电子围栏内

Tinkernode

发送当前位置经纬度

APP(被监护人)


发送当前位置经纬度(当位置发生改变时)

APP(监护人)


  • 实时接收被监护人经纬度信息并转换为位置信息
  • 显示家庭成员当前位置的静态地图
  • 判断当前位置是否在电子围栏内

开发过程


编写行空板程序

行空板这部分的程序涉及到的内容比较多,一定要将问题分解成小的问题,一个个的逐步解决,将小问题解决后,通过模块化定义为函数,最后形成完整的程序。
行空板涉及的子功能有:


1. MTQQ参数设置、消息订阅


2. 显示家庭成员的照片


3. 显示家庭成员经纬度,刷新后能够覆盖上一次的文字

4. 判断某个坐标是否在多边形范围内,判断经纬度是否在某个特定范围内

5. 使用坐标拾取器将所有家庭成员的电子围栏保存为CSV文件,在python中读取CVS文件并转换为列表

6. 对经纬度进行解码

7. 判断家庭成员是否在电子围栏内

8. 高德地图进行逆地理编码查询,返回家庭成员当前地址

9. 离线朗读文字消息

编写Tinkernode程序

1. MTQQ参数设置、消息发布

2. WiFi参数设置

3. 经纬度编码

4. 以在行空板中定义好的数据格式将数据发送

编写App Inventor程序(被监护人)

1. MQTT参数设置、发布消息

2. 定义各家庭成员的电子围栏

3. 获取当前实时位置

4. 定义用户点击屏幕行为(单击地图、双击地图、长按地图等)

5. 对经纬度进行编码并按行空板指定的格式发送

编写App Inventor程序(监护人)

1. MQTT参数设置、接收消息

2. 接收家庭成员位置信息并解码

3. 显示当前位置的静态地图

4. 如果家庭成员位置有可疑,发起报警(短信或电话)
步骤1
申请高德地图apiKey

进入网址https://lbs.amap.com/,注册账号后登录,点击“Web服务API”。


点击“获取Key”。根据页面指引创建应用并获取key。


应用创建成功查看添加的key,将key复制保存备用。


步骤2


处理图片

本项目中使用的图片来自人教版小学英语第三册中的卡通人物,图片来源:
无忧文档

原始图片如下:


处理好的图片如下:


步骤3


创建家庭成员电子围栏CSV文件

本项目任务中有6个家庭成员,分别是爸爸、妈妈、爷爷、奶奶、大宝、小宝。

打开
高德地图,点击下方的“开放平台”。


点击“开发支持”下的“坐标拾取器”



点击“更换城市”,切换到自己需要的城市。


根据家庭成员的活动范围在高德地图上选定4个点作为电子围栏的4组坐标。每个家庭成员一个电子围栏,爷爷和奶奶可以使用相同的电子围栏。


爸爸的活动范围。


妈妈的活动范围。


爷爷奶奶的活动范围是整个社区和对面的荔枝公园等地方。


大宝的活动范围是小区和小区对面自己的学校。


小宝年纪小,由家中****送到学校门口,独自的活动范围是在小学内部。


得到6组家庭成员的电子围栏数据,将文件保存为逗号分隔的CSV文件。



步骤4

硬件连接

通过USB连接线将行空板连接到计算机,将USB小喇叭接到行空板的USB接口。
步骤5

将行空板联网

1.  浏览器输入行空板默认地址10.1.2.3


2.  点击“网络设置”


3.  选择网络名称

4.  输入密码

5.  联网成功后会显示网络名称和IP地址


步骤6

代码编写

导入库

  1. from unihiker import GUI  # 导入unihiker库
  2. import paho.mqtt.client as mqtt # 导入mqtt库
  3. import time  # 导入time库
  4. from matplotlib.path import Path # Path库用于定义电子围栏
  5. import random
  6. import csv # 读取csv文件
  7. import requests # 对高德地图API进行地理编码和逆地理编码查询
  8. import pyttsx3 #离线语音合成
  9. from pinpong.board import Board # 从pinpong.board包中导入Board模块
复制代码

初始化板子和变量

  1. Board().begin() # 初始化,选择板型和端口号,不输入则进行自动识别
  2. gui = GUI()  # 实例化GUI类,创建gui对象
  3. img = gui.draw_image(x=0,y=0,w=240, h=320, image='family.png')  # 开机显示初始背景图为family
  4. pic_list = ['father.png', 'mother.png', 'grandfather.png', 'grandmother.png', 'daughter.png', 'son.png']
  5. engine = pyttsx3.init()
复制代码

设置MQTT参数

  1. #设置MQTT参数
  2. server =  "182.254.130.180"
  3. port = 1883
  4. iot_id = "" #填写easyiot的用户id
  5. iot_pwd = "" #填写easyiot的密码
  6. topic = "" #填写easyiot的订阅主题
  7. # 以当前时间作为client_id
  8. client_id = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))
  9. # ClientId不能重复,所以使用当前时间
  10. client = mqtt.Client(client_id)
  11. #设置连接的服务器、端口及keepalive
  12. client.connect(server,port , 30)
  13. # 设置连接用户和密码,必须设置,否则会返回Connected with result code 4
  14. client.username_pw_set(iot_id, iot_pwd)
复制代码

定义家庭成员电子围栏

读取已经编辑好的家庭成员电子围栏CSV文件,并转换为列表。

  1. #定义家庭成员电子围栏
  2. def family_fences():
  3.     fences = [] # 存放所有家庭成员的电子围栏坐标
  4.     with open('fence1.csv') as f:
  5.         for row in csv.reader(f): # 每一行的数据是每个家庭成员的电子围栏经纬度,包括4组坐标
  6.             tmp = [] #存放某个家庭成员的围栏坐标
  7.             for data in row:
  8.                 # 每一行一共有4组数据,既围栏的4个点。每个点是一组(经度,纬度)值,以","分隔
  9.                 temp = data.split(',') # 将经纬度分割
  10.                 temp = [float(temp[0]),float(temp[1])] # 将经纬度由字符串转换为浮点数
  11.                 tmp.append(temp) # 将每个坐标点追加到该成员围栏列表
  12.             fences.append(tmp) # 将完整的电子围栏坐标追加到成员列表中
  13.     return fences # 返回所有家庭成员电子围栏
复制代码

显示成员相片及经纬度

当接收到成员位置更新后,显示该成员的相片和经纬度。


  1. #显示接收到的某个家庭成员对应的照片及当前位置,ID是家庭成员的序号,longi是经度,lati是纬度
  2. def show_pic(ID, longi, lati):
  3.     img.config(image = 'photo/'+pic_list[int(ID)-1]) # 根据ID显示家庭成员图片
  4.     gui.fill_rect(x=0, y=0, w=240, h=70, color=(247,246,249)) # 将前一次经纬度及地址信息覆盖
  5.     gui.draw_text(x=5, y=0, text='经度:', font_size=10) # 显示“经度”
  6.     gui.draw_text(x=50, y=0, text=longi, font_size=10) # 显示经度值
  7.     gui.draw_text(x=5, y=15, text='纬度:', font_size=10) # 显示“纬度”
  8.     gui.draw_text(x=50, y=15, text=lati, font_size=10) # 显示纬度值
复制代码

判断安全状态

根据返回的经纬度和ID判断该家庭成员是否在电子围栏范围内并使用语音播报结果。

  1. #显示当前家庭成员的位置是否在电子围栏范围内
  2. def show_status(ID, longi, lati):
  3.     fence = Path(family_fences()[int(ID)-1]) # 获取当前家庭成员的电子围栏4个点的坐标
  4.     status = fence.contains_point((float(longi), float(lati)))# 当前坐标是否在围栏范围内
  5.     if status:
  6.         #gui.draw_text(x=5, y=40, text='在电子围栏内', font_size=12, color='green') # 显示安全状态
  7.         engine.say('在电子围栏内')
  8.     else:
  9.         #gui.draw_text(x=5, y=40, text='不在电子围栏内', font_size=12, color='red') # 显示可疑状态
  10.         engine.say('不在电子围栏内')
  11.     engine.runAndWait()
复制代码

逆地理编码查询

根据经纬度查询对应的地址和街道名。

  1. # 执行一次高德地图地理逆编码的查询
  2. def geocode(loca):
  3.     url = 'https://restapi.amap.com/v3/geocode/regeo?key=a1e0c550b1068165e2274410cd7b8b9b&location=' #url前段
  4.     url = url + loca
  5.     response = requests.get(url=url) # 查询逆地理编码
  6.     answer = response.json() # 返回获取的json形式的文本
  7.     address = answer['regeocode']['formatted_address'] # 提取返回的格式化地址
  8.     street = answer['regeocode']['addressComponent']['streetNumber']['street'] # 提取返回的街道信息
  9.     print('规范地址:',address) # 地址
  10.     print('街道:', street) # 街道名
  11.     gui.draw_text(x=5, y=30, text=address[0:16], font_size=10) # 显示当前位置
  12.     gui.draw_text(x=5, y=45, text=address[16:], font_size=10) # 返回的当前位置太长,分两行显示
复制代码

数据解析:

下图中的所有元素是变量answer返回的某个成员在某个位置时的一个值。

1. answer['regeocode']['formatted_address']的值是?

answer['regeocode']指整个返回值中键'regeocode'的值,是下图从{'addressComponent':开始到'formatted_address': '广东省深圳市南山区沙河街道香云路侨城一号广场'}结束,两个大括号之间的所有内容。


2. answer['regeocode']['addressComponent']['streetNumber']['street']的值是?


answer['regeocode']['formatted_address']的值就是'广东省深圳市南山区沙河街道香云路侨城一号广场'


answer['regeocode']['addressComponent']的值是{'city': '深圳市',开始到'citycode': '0755'}结束,两个大括号之间的所有内容(青绿色开始,青绿色结束)。

answer['regeocode']['addressComponent']['streetNumber']的值是黄色的{'number': '340号',  开始到黄色的'street': '侨香路'}结束,两个大括号之间的所有内容。

answer['regeocode']['addressComponent']['streetNumber']['street']的值是'侨香路'



如果要得到id的值'440305',变量要如何表示?

answer['regeocode']['addressComponent']['businessAreas'][0]['id']


下图是另一个地址的返回结果示例。可以看到返回的id解析的是正确的。


订阅发布MQTT消息


  1. # 连接mqtt并订阅消息
  2. def on_connect(client, userdata, flags, rc):
  3.     client.subscribe(topic)  # 填写订阅的主题
  4. #发布消息
  5. def on_publish(topic, payload, qos):
  6.     client.publish(topic, payload, qos)
  7. #连接mqtt并接收消息
  8. def subscribe_msg():
  9.     client.on_connect = on_connect
  10.     client.on_message = on_message
  11.     client.loop_forever()
复制代码

接收到消息后

调用函数显示家庭成员相片、经纬度、所在地址及安全状态。

  1. #当接收到订阅的消息后
  2. def on_message(client, userdata, msg):
  3.     print(msg.topic+" " + ":" + str(msg.payload))  # 打印接收的消息
  4.     message = str(msg.payload)[2:21] # 返回消息的格式为 b'1135159981101938488',从第3~21位是我们的数据,索引为2~20
  5.     # print(message, message[0], message[1:10], message[10:19])
  6.     familyID = message[0] # 第1位是家庭成员ID
  7.     longitude = format(float(message[1:10]) / (10**6) - 100, '6f') #第2~10位是经度,转换为浮点数后除以10的6次方再减去100,保留小数点后7位,还原为经度的原始值
  8.     latitude = format(float(message[10:19]) / (10**6) - 100, '.6f') #第11~19位是纬度,转换为浮点数后除以10的6次方再减去100,保留小数点后6位,还原为纬度的原始值
  9.     # print(longitude, latitude)
  10.     show_pic(familyID, longitude, latitude) # 调用函数在行空板显示图片及经纬度
  11.     show_status(familyID, longitude, latitude) # 调用函数显示家庭成员状态
  12.     location = str(longitude) +','+ str(latitude)
  13.     geocode(location) # 调用函数查询逆地理编码(经纬度转换为规范地址)
复制代码

完整代码

  1. from unihiker import GUI  # 导入unihiker库
  2. import paho.mqtt.client as mqtt # 导入mqtt库
  3. import time  # 导入time库
  4. from matplotlib.path import Path # Path库用于定义电子围栏
  5. import random
  6. import csv # 读取csv文件
  7. import requests # 对高德地图API进行地理编码和逆地理编码查询
  8. import pyttsx3 #离线语音合成
  9. from pinpong.board import Board # 从pinpong.board包中导入Board模块
  10. Board().begin() # 初始化,选择板型和端口号,不输入则进行自动识别
  11. gui = GUI()  # 实例化GUI类,创建gui对象
  12. img = gui.draw_image(x=0,y=0,w=240, h=320, image='family.png')  # 开机显示初始背景图为family
  13. pic_list = ['father.png', 'mother.png', 'grandfather.png', 'grandmother.png', 'daughter.png', 'son.png']
  14. engine = pyttsx3.init()
  15. #设置MQTT参数
  16. server =  "182.254.130.180"
  17. port = 1883
  18. iot_id = "" # 填写easyiot的用户id
  19. iot_pwd = "" # 填写easyiot的密码
  20. topic = "" # 填写easyiot的订阅主题
  21. # 以当前时间作为client_id
  22. client_id = time.strftime('%Y%m%d%H%M%S',time.localtime(time.time()))
  23. # ClientId不能重复,所以使用当前时间
  24. client = mqtt.Client(client_id)
  25. #设置连接的服务器、端口及keepalive
  26. client.connect(server,port , 30)
  27. # 设置连接用户和密码,必须设置,否则会返回Connected with result code 4
  28. client.username_pw_set(iot_id, iot_pwd)
  29. #定义家庭成员电子围栏
  30. def family_fences():
  31.     fences = [] # 存放所有家庭成员的电子围栏坐标
  32.     with open('fence1.csv') as f:
  33.         for row in csv.reader(f): # 每一行的数据是每个家庭成员的电子围栏经纬度,包括4组坐标
  34.             tmp = [] #存放某个家庭成员的围栏坐标
  35.             for data in row:
  36.                 # 每一行一共有4组数据,既围栏的4个点。每个点是一组(经度,纬度)值,以","分隔
  37.                 temp = data.split(',') # 将经纬度分割
  38.                 temp = [float(temp[0]),float(temp[1])] # 将经纬度由字符串转换为浮点数
  39.                 tmp.append(temp) # 将每个坐标点追加到该成员围栏列表
  40.             fences.append(tmp) # 将完整的电子围栏坐标追加到成员列表中
  41.     return fences # 返回所有家庭成员电子围栏
  42. #显示接收到的某个家庭成员对应的照片及当前位置,ID是家庭成员的序号,longi是经度,lati是纬度
  43. def show_pic(ID, longi, lati):
  44.     img.config(image = 'photo/'+pic_list[int(ID)-1]) # 根据ID显示家庭成员图片
  45.     gui.fill_rect(x=0, y=0, w=240, h=70, color=(247,246,249)) # 将前一次经纬度及地址信息覆盖
  46.     gui.draw_text(x=5, y=0, text='经度:', font_size=10) # 显示“经度”
  47.     gui.draw_text(x=50, y=0, text=longi, font_size=10) # 显示经度值
  48.     gui.draw_text(x=5, y=15, text='纬度:', font_size=10) # 显示“纬度”
  49.     gui.draw_text(x=50, y=15, text=lati, font_size=10) # 显示纬度值
  50. #显示当前家庭成员的位置是否在电子围栏范围内
  51. def show_status(ID, longi, lati):
  52.     fence = Path(family_fences()[int(ID)-1]) # 获取当前家庭成员的电子围栏4个点的坐标
  53.     status = fence.contains_point((float(longi), float(lati)))
  54.     if status:
  55.         #gui.draw_text(x=5, y=40, text='在电子围栏内', font_size=12, color='green') # 显示安全状态
  56.         engine.say('在电子围栏内')
  57.     else:
  58.         #gui.draw_text(x=5, y=40, text='不在电子围栏内', font_size=12, color='red') # 显示可疑状态
  59.         engine.say('不在电子围栏内')
  60.     engine.runAndWait()
  61. # 执行一次高德地图地理逆编码的查询
  62. def geocode(loca):
  63.     url = 'https://restapi.amap.com/v3/geocode/regeo?key=a1e0c550b1068165e2274410cd7b8b9b&location=' #url前段
  64.     url = url + loca
  65.     response = requests.get(url=url) # 查询逆地理编码
  66.     answer = response.json() # 返回获取的json形式的文本
  67.     address = answer['regeocode']['formatted_address'] # 提取返回的格式化地址
  68.     street = answer['regeocode']['addressComponent']['streetNumber']['street'] # 提取返回的街道信息
  69.     print('规范地址:',address) # 地址
  70.     print('街道:', street) # 街道名
  71.     gui.draw_text(x=5, y=30, w=240, text=address, font_size=10) # 显示当前位置,文字超出长度自动换行
  72. # 连接mqtt并订阅消息
  73. def on_connect(client, userdata, flags, rc):
  74.     client.subscribe(topic)  # 填写订阅的主题
  75. #当接收到订阅的消息后
  76. def on_message(client, userdata, msg):
  77.     print(msg.topic+" " + ":" + str(msg.payload))  # 打印接收的消息
  78.     message = str(msg.payload)[2:21] # 返回消息的格式为 b'1135159981101938488',从第3~21位是我们的数据,索引为2~20
  79.     # print(message, message[0], message[1:10], message[10:19])
  80.     familyID = message[0] # 第1位是家庭成员ID
  81.     longitude = format(float(message[1:10]) / (10**6) - 100, '6f') #第2~10位是经度,转换为浮点数后除以10的6次方再减去100,保留小数点后7位,还原为经度的原始值
  82.     latitude = format(float(message[10:19]) / (10**6) - 100, '.6f') #第11~19位是纬度,转换为浮点数后除以10的6次方再减去100,保留小数点后6位,还原为纬度的原始值
  83.     # print(longitude, latitude)
  84.     show_pic(familyID, longitude, latitude) # 调用函数在行空板显示图片及经纬度
  85.     show_status(familyID, longitude, latitude) # 调用函数显示家庭成员状态
  86.     location = str(longitude) +','+ str(latitude)
  87.     geocode(location) # 调用函数查询逆地理编码(经纬度转换为规范地址)
  88. #发布消息
  89. def on_publish(topic, payload, qos):
  90.     client.publish(topic, payload, qos)
  91. #连接mqtt并接收消息
  92. def subscribe_msg():
  93.     client.on_connect = on_connect
  94.     client.on_message = on_message
  95.     client.loop_forever()
  96. if __name__ == '__main__':
  97.     subscribe_msg()
复制代码

演示视频

行空板通过远程桌面来观察其演示情况。

以下为视频链接

https://www.bilibili.com/video/BV1m94y1S7fV

https://www.bilibili.com/video/BV1n34y1n7Pj


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail