×

使用Python绘制LoRa节点的实时数据

消耗积分:0 | 格式:zip | 大小:0.34 MB | 2022-12-22

陈文博

分享资料个

描述

 

 

本文遵循有关 如何 使用Zerynth创建以Python编程并连接到The Things Network的 LoRa/LoraWAN 网络的教程

从上一个项目的相同设置开始,我们将了解如何使用 Zerynth、The Things Network 和 Matplotlib(一个非常流行的 Python 绘图库)获取和可视化 LoRa 节点的传感器数据。

您将看到如何:

  • 使用Zerynth在Python中 编写LoRa 节点以获取温度 和 湿度数据;
  • 通过 LoRa 网关向物联网发送数据;
  • 通过MQTT从 The Things Network 控制台获取数据
  • 使用Matplotlib绘制传感器数据

所需材料

本质上,我们需要与之前项目相同的配置,外加一个温度和湿度传感器:

  • 劳拉网关在本教程中,我们使用了 Link Labs BS-8,这是一种用于 LoRa 网络的工业级 IoT/M2M 网关。它能够支持具有 8 个同步接收通道的数千个端点。
  • LoRa 终端节点在本教程中,我们使用了Flip&Click作为 MCU 板,一个 LoRa Click 和一个Temp&Hum Click ,它带有 ST 的 HTS221 温度和相对湿度传感器。
  • Zerynth Studio:我们的专业 IDE,它提供了一个平台,用于开发 Python 或混合 C/Python 代码和管理您的开发板。它包括编译器、调试器和编辑器,以及提供轻松学习体验的教程和示例项目。

在 Python 中编程 LoRa 节点以获取传感器数据

在执行 LoRa 网关配置和 LoRa 节点配置步骤后(参见之前的项目),我们将能够对设备进行编程以从 Temp&Hum Click 获取数据并将这些值发送到 The Things Network 控制台。

特别是,我们必须在 Zerynth Studio 上创建一个新项目并粘贴此代码:

# LoraWAN sensor data Logging
 
import streams
from microchip.rn2483 import rn2483
from stm.hts221 import hts221
 
streams.serial()
 
try:
    rst = D16 # reset pin 
    # insert otaa credentials!
    appeui = "YOUAPPEUI"
    appkey =  "YOUAPPKEY"
    print("joining...")
    
    if not rn2483.init(SERIAL1, appeui, appkey, rst): # LoRa Click on slot A
        print("denied :(")
        raise Exception
 
    print("sending first message, res:")
    print(rn2483.tx_uncnf('TTN'))
 
    temp_hum = hts221.HTS221( I2C1,D31 ) # Temp Hum Click on slot C
    
    while True:
        temp, hum = temp_hum.get_temp_humidity()
        print('temp: ', temp, 'hum: ', hum)
        data = bytearray(4) 
        data[0:2] = bytearray([ int(temp) + 127, int((temp - int(temp)) * 100) ])
        data[2:4] = bytearray([ int(hum) + 127, int((hum - int(hum)) * 100) ])
        r = rn2483.tx_uncnf(data) # send data to TTN
        sleep(5000)
        
except Exception as e:
    print(e)

使用您可以在TTN 控制台的设备概述中找到的“ appeui ”和“ appkey ”值编辑行。

将代码上传到您的开发板,您就完成了!

现在单击 TTN 控制台的“数据”选项卡,您可以看到 LoRa 节点发送的传感器数据!

如您所见,此脚本与上一个项目中使用的“ping”示例非常相似。唯一的区别与Temp&Hum Click搭载的HTS221温湿度传感器的使用有关。感谢 Zerynth,您只需要几行Python ,就可以从传感器获取真实数据并将其发送到 LoRa 网络!

通过 MQTT 从 The Things Network 控制台获取数据

现在是从 TTN 控制台获取数据的时候了。为此,我们准备了一个非常简单的 Python 脚本,该脚本使用 Eclipse Paho MQTT Python 客户端库,该客户端库实现了MQTT协议的 3.1 和 3.1.1 版。

请注意,您必须在笔记本电脑上运行此脚本,因此您需要在 PC 上安装 Python (3.x)。

get_plot_data_TTN.py:

# Get and plot data from TTN Console using Python
 
import paho.mqtt.client as mqtt
import json
import base64
 
APPEUI = 'YOURAPPEUI'
APPID  = 'YOUAPPID'
PSW    = 'YOURPASSWORD'
 
import matplotlib.pyplot as plt
#import DataPlot and RealtimePlot from the file plot_data.py
from plot_data import DataPlot, RealtimePlot
 
fig, axes = plt.subplots()
plt.title('Data from TTN console')
 
data = DataPlot()
dataPlotting= RealtimePlot(axes)
 
count=0
 
def bytes_to_decimal(i,d):
    xx = i - 127
    dec = (-d if xx < 0 else d)/100
    return xx + dec
 
def on_connect(client, userdata, flags, rc):
    client.subscribe('+/devices/+/up'.format(APPEUI))
 
def on_message(client, userdata, msg):
    j_msg = json.loads(msg.payload.decode('utf-8'))
    dev_eui = j_msg['hardware_serial']
 
    tmp_hum = base64.b64decode(j_msg['payload_raw'])
    tmp = bytes_to_decimal(*tmp_hum[0:2])
    hum = bytes_to_decimal(*tmp_hum[2:4])
 
    # print data
    print('---')
    print('tmp:', tmp, ' hum:', hum)
    print('dev eui: ', dev_eui)
 
    # plot data
    global count
    count+=1
    data.add(count, tmp , hum)
    dataPlotting.plot(data)
    plt.pause(0.001)
 
# set paho.mqtt callback
ttn_client = mqtt.Client()
ttn_client.on_connect = on_connect
ttn_client.on_message = on_message
ttn_client.username_pw_set(APPID, PSW)
ttn_client.connect("eu.thethings.network", 1883, 60) #MQTT port over TLS
 
try:
    ttn_client.loop_forever()
except KeyboardInterrupt:
    print('disconnect')
    ttn_client.disconnect()

使用 Matplotlib 实时绘制传感器数据

一旦你的计算机中有了数据,你就可以用它做各种各样的事情。在这种情况下,我们想要读取这些温度和湿度值并将它们绘制为时间的函数。

因为我们喜欢 Python,所以我们准备了一个名为“ plot_data.py ”的脚本,它为此使用了Matplotlib库。

plot_data.py:

import time
import math
from collections import deque , defaultdict
 
import matplotlib.animation as animation
from matplotlib import pyplot as plt
 
import threading
 
from random import randint
 
from statistics import *
 
class DataPlot:
    def __init__(self, max_entries = 20):
        self.axis_x = deque(maxlen=max_entries)
        self.axis_y = deque(maxlen=max_entries)
        self.axis_y2 = deque(maxlen=max_entries)
 
        self.max_entries = max_entries
 
        self.buf1=deque(maxlen=5)
        self.buf2=deque(maxlen=5)
 
     
    def add(self, x, y,y2):
 
        self.axis_x.append(x)
        self.axis_y.append(y)
        self.axis_y2.append(y2)
 
class RealtimePlot:
    def __init__(self, axes):
     
        self.axes = axes
 
        self.lineplot, = axes.plot([], [], "ro-")
        self.lineplot2, = axes.plot([], [], "go-")
 
    def plot(self, dataPlot):
        self.lineplot.set_data(dataPlot.axis_x, dataPlot.axis_y)
        self.lineplot2.set_data(dataPlot.axis_x, dataPlot.axis_y2)
 
        self.axes.set_xlim(min(dataPlot.axis_x), max(dataPlot.axis_x))
        ymin = min([min(dataPlot.axis_y), min(dataPlot.axis_y2)])-10
        ymax = max([max(dataPlot.axis_y), max(dataPlot.axis_y2)])+10
        self.axes.set_ylim(ymin,ymax)
        self.axes.relim();
 
def main():
    fig, axes = plt.subplots()
    plt.title('Plotting Data')
 
    data = DataPlot();
    dataPlotting= RealtimePlot(axes)
 
    try:
        count=0
        while True:
            count+=1
            data.add(count, 30 + 1/randint(1,5) , 35 + randint(1,5))
            dataPlotting.plot(data)
 
            plt.pause(0.001)
    except KeyboardInterrupt:
        print('\n\nKeyboard exception received. Exiting.')
        plt.close()
        ser.close()
        exit()
 
if __name__ == "__main__": main()

请注意,您必须在之前的脚本中导入此文件才能正常工作。这就是情节的样子。随着数据的不断涌入,它会向右滚动。

使用 Zerynth Studio PRO 扩展您的 LoRa 网络

以下列表包括一些LoRa 关键特性:

  • 远距离:15 – 20 公里;
  • 电池寿命长:超过十年;
  • 百万节点!

Zerynth Studio 的免费版本允许您使用LoRa解决方案原型所需的所有功能,但您最多可以免费为每个受支持的电路板编程 5 个单元您可以使用Zerynth Studio PRO 版本(从 6 月 28 日开始提供,预购可享受高达 50% 的折扣)解锁此限制,该 版本还将包括工业级功能,例如:

  • 可选择实时操作系统;
  • 无线更新开发;
  • 硬件驱动的安全固件以工业规模烧录在设备上;
  • …以及更多
通过Zerynth 学院

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !

'+ '

'+ '

'+ ''+ '
'+ ''+ ''+ '
'+ ''+ '' ); $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code ==5){ $(pop_this).attr('href',"/login/index.html"); return false } if(data.code == 2){ //跳转到VIP升级页面 window.location.href="//m.lene-v.com/vip/index?aid=" + webid return false } //是会员 if (data.code > 0) { $('body').append(htmlSetNormalDownload); var getWidth=$("#poplayer").width(); $("#poplayer").css("margin-left","-"+getWidth/2+"px"); $('#tips').html(data.msg) $('.download_confirm').click(function(){ $('#dialog').remove(); }) } else { var down_url = $('#vipdownload').attr('data-url'); isBindAnalysisForm(pop_this, down_url, 1) } }); }); //是否开通VIP $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code == 2 || data.code ==5){ //跳转到VIP升级页面 $('#vipdownload>span').text("开通VIP 免费下载") return false }else{ // 待续费 if(data.code == 3) { vipExpiredInfo.ifVipExpired = true vipExpiredInfo.vipExpiredDate = data.data.endoftime } $('#vipdownload .icon-vip-tips').remove() $('#vipdownload>span').text("VIP免积分下载") } }); }).on("click",".download_cancel",function(){ $('#dialog').remove(); }) var setWeixinShare={};//定义默认的微信分享信息,页面如果要自定义分享,直接更改此变量即可 if(window.navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == 'micromessenger'){ var d={ title:'使用Python绘制LoRa节点的实时数据',//标题 desc:$('[name=description]').attr("content"), //描述 imgUrl:'https://'+location.host+'/static/images/ele-logo.png',// 分享图标,默认是logo link:'',//链接 type:'',// 分享类型,music、video或link,不填默认为link dataUrl:'',//如果type是music或video,则要提供数据链接,默认为空 success:'', // 用户确认分享后执行的回调函数 cancel:''// 用户取消分享后执行的回调函数 } setWeixinShare=$.extend(d,setWeixinShare); $.ajax({ url:"//www.lene-v.com/app/wechat/index.php?s=Home/ShareConfig/index", data:"share_url="+encodeURIComponent(location.href)+"&format=jsonp&domain=m", type:'get', dataType:'jsonp', success:function(res){ if(res.status!="successed"){ return false; } $.getScript('https://res.wx.qq.com/open/js/jweixin-1.0.0.js',function(result,status){ if(status!="success"){ return false; } var getWxCfg=res.data; wx.config({ //debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId:getWxCfg.appId, // 必填,公众号的唯一标识 timestamp:getWxCfg.timestamp, // 必填,生成签名的时间戳 nonceStr:getWxCfg.nonceStr, // 必填,生成签名的随机串 signature:getWxCfg.signature,// 必填,签名,见附录1 jsApiList:['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ','onMenuShareWeibo','onMenuShareQZone'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function(){ //获取“分享到朋友圈”按钮点击状态及自定义分享内容接口 wx.onMenuShareTimeline({ title: setWeixinShare.title, // 分享标题 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享给朋友”按钮点击状态及自定义分享内容接口 wx.onMenuShareAppMessage({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 type: setWeixinShare.type, // 分享类型,music、video或link,不填默认为link dataUrl: setWeixinShare.dataUrl, // 如果type是music或video,则要提供数据链接,默认为空 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ”按钮点击状态及自定义分享内容接口 wx.onMenuShareQQ({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口 wx.onMenuShareWeibo({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ空间”按钮点击状态及自定义分享内容接口 wx.onMenuShareQZone({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); }); }); } }); } function openX_ad(posterid, htmlid, width, height) { if ($(htmlid).length > 0) { var randomnumber = Math.random(); var now_url = encodeURIComponent(window.location.href); var ga = document.createElement('iframe'); ga.src = 'https://www1.elecfans.com/www/delivery/myafr.php?target=_blank&cb=' + randomnumber + '&zoneid=' + posterid+'&prefer='+now_url; ga.width = width; ga.height = height; ga.frameBorder = 0; ga.scrolling = 'no'; var s = $(htmlid).append(ga); } } openX_ad(828, '#berry-300', 300, 250);