
使用 pynmea2 在树莓派与虚拟 IoT 设备上解码 GPS NMEA 数据——IoT-For-Beginners 位置追踪实战指南【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners本篇技术指南聚焦于 IoT-For-Beginners 交通项目第 1 课位置追踪中的关键环节将 GPS 传感器通过 UART 串口输出的原始 NMEA 数据用开源库pynmea2解码为可直接使用的十进制经纬度坐标。读完本文你将掌握 NMEA 0183 消息格式的解析方法、(d)ddmm.mmmm度分格式到十进制度的换算原理以及如何在 Raspberry Pi 与 CounterFit 虚拟 IoT 设备上完成端到端的 GPS 数据解码并得到形如47.6423109,-122.1390293 - from 3 satellites的可读输出。前置知识为什么需要解码 NMEA 数据在上一环节参见 pi-gps-sensor.md 与 virtual-device-gps-sensor.md中从串口读出的是一行行形如下面的天书$GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67 $GPGSA,A,1,,,,,,,,,,,,,,,*1E $GPGSV,1,1,00*79这其实是标准的 NMEA 0183 文本消息。NMEANational Marine Electronics Association美国国家航海电子协会制定了一套航海电子设备间的通信标准GPS 传感器正是按照该标准输出定位数据。每条消息都以$开头随后 2 个字符表示卫星系统来源如GP为美国 GPS、GN为 GLONASS 等3 个字符表示消息类型其余部分为逗号分隔的字段以换行结尾。课程 README 中列出了常见的消息类型类型说明GGAGPS 定位数据GPS Fix Data包含传感器所在的纬度、经度、海拔以及参与定位计算的卫星数量ZDA当前日期与时间含本地时区信息GSV可见卫星详情即 GPS 传感器能检测到信号的卫星其中GGA 消息是我们最关心的——它携带当前位置的坐标。由于 GPS 数据自带时间戳IoT 设备甚至可以直接从 GPS 传感器取时间而无需依赖 NTP 服务器或内部实时时钟。NMEA 坐标格式(d)ddmm.mmmmGGA 消息中的经纬度采用(d)ddmm.mmmm度分格式并附带一个方向字符。d表示度m表示分秒以分的十进制小数呈现。例如 2°1743 在 NMEA 中写作217.716666667——即 2 度 17.716666667 分。方向字符对纬度是N北或S南对经度是E东或W西。例如纬度 2°1743 对应方向字符N而 -2°1743 则对应S。以$GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67为例纬度字段为4738.538654,N4738.538654即 47.6423109°47 度 38.538654 分方向为N北因此是正纬度47.6423109经度字段为12208.341758,W12208.341758即 122.1390293°122 度 08.341758 分方向为W西因此是负经度-122.1390293。手工换算繁琐且易错这正是引入开源 NMEA 解码库的价值所在。任务解码 GPS 数据下面以本课英文原版文档 single-board-computer-gps-decode.md 为主线逐步在gps-sensor应用中加入解码能力。第 1 步打开项目并安装 pynmea2确保gps-sensor应用项目已在 VS Code 中打开该应用来自上一环节的 code-gps 示例。然后在终端中安装pynmea2——这是一个专用于解析 NMEA 消息的 Python 开源包pip3 install pynmea2如果使用虚拟 IoT 设备请确认是在已激活的 Python 虚拟环境中执行安装与安装counterfit-shims-serial的方式一致。第 2 步导入 pynmea2 模块打开app.py在文件顶部的 import 区加入一行import pynmea2在树莓派示例中pynmea2与time、serial、json并列导入在虚拟设备示例中它与counterfit_shims_serial一起位于 CounterFit 连接初始化代码之后。完整的可运行代码可在仓库中直接查看树莓派版code-gps-decode/pi/gps-sensor/app.py虚拟设备版code-gps-decode/virtual-device/gps-sensor/app.py第 3 步替换解码逻辑将print_gps_data函数体替换为如下代码虚拟设备版中该函数名为send_gps_data逻辑一致msg pynmea2.parse(line) if msg.sentence_type GGA: lat pynmea2.dm_to_sd(msg.lat) lon pynmea2.dm_to_sd(msg.lon) if msg.lat_dir S: lat lat * -1 if msg.lon_dir W: lon lon * -1 print(f{lat},{lon} - from {msg.num_sats} satellites)这段代码完成了从原始 NMEA 行到可用坐标的完整转换解析pynmea2.parse(line)将 UART 串口读到的每一行解析为结构化消息对象类型筛选仅当msg.sentence_type GGA时才处理——GGA 是位置定位消息ZDA、GSV 等其他类型被忽略格式转换从消息中读取纬度msg.lat与经度msg.lon调用pynmea2.dm_to_sd函数把 NMEA 的(d)ddmm.mmmm度分格式转换为十进制度符号归一化检查方向字段——纬度若为S南则取负值经度若为W西则取负值从而与地理坐标系中的正负号约定一致北纬/东经为正南纬/西经为负输出将坐标与定位所用的卫星数msg.num_sats打印到控制台。第 4 步运行并验证运行代码。若使用树莓派piraspberrypi:~/gps-sensor $ python3 app.py 47.6423109,-122.1390293 - from 3 satellites若使用虚拟 IoT 设备务必先启动 CounterFit 应用并确保 GPS 数据正在发送详见下文。源码深读串口读取与异常处理结合仓库中的完整实现可以看清解码环节在整个数据链路中的位置。树莓派版本 code-gps-decode/pi/gps-sensor/app.py 在解码之前先完成了串口初始化import time import serial import pynmea2 import json serial serial.Serial(/dev/ttyAMA0, 9600, timeout1) serial.reset_input_buffer() serial.flush()这里通过pyserial连接 Grove Base Hat 的 UART 端口/dev/ttyAMA0波特率 9600并清空输入缓冲。主循环持续读取串口行逐一交给解码函数每次循环间隔 1 秒。值得注意的细节是串口读行可能抛出UnicodeDecodeError停止并重启程序时读到的第一个字节可能恰好处在某个 UTF-8 字符的中间导致解码失败。树莓派示例用try-except处理了这种随机性while True: try: line serial.readline().decode(utf-8) while len(line) 0: print_gps_data(line) line serial.readline().decode(utf-8) # Theres a random chance the first byte being read is part way through a character. # Read another full line and continue. except UnicodeDecodeError: line serial.readline().decode(utf-8) time.sleep(1)遇到UnicodeDecodeError时重新读取一整行再继续避免程序崩溃。这与前一环节 pi-gps-sensor.md 中介绍的错误处理方式一脉相承。虚拟设备版本 code-gps-decode/virtual-device/gps-sensor/app.py 则通过counterfit_shims_serial.Serial(/dev/ttyAMA0)连接由 CounterFit 模拟的 UART GPS 传感器数据读取与解码逻辑与树莓派版完全一致体现了两种硬件载体在应用层的统一。虚拟设备实战三种方式向 CounterFit 注入 GPS 数据使用虚拟 IoT 设备时GPS 数据并非来自真实卫星而是由 CounterFit 应用模拟注入。运行解码程序前需在 CounterFit 的 GPS 传感器上配置数据源并点击Set提交可选择以下三种方式详见 virtual-device-gps-sensor.mdLat/Lon 模式直接设定纬度和经度以及定位使用的卫星数。该值只发送一次勾选Repeat可让数据每秒重复发送便于解码程序持续接收到 GGA 消息NMEA 模式在文本框中粘贴原始 NMEA 句子每条新的 GGA定位句子按 1 秒间隔依次发送。可借助在线工具在地图上绘制后生成这些句子勾选Repeat使全部数据发送完毕后 1 秒再循环GPX 文件模式上传包含轨迹位置的 GPX 文件模拟器将按 1 秒间隔依次返回每个轨迹点。选择NMEA模式并填入$GNGGA,...这样的原始句子恰好可以端到端验证解码链路CounterFit 模拟串口输出原始 NMEA →readline()读取 →pynmea2.parse解析 → 输出十进制坐标。注意虚拟设备程序与 CounterFit 应用应在不同的终端中运行以保证 CounterFit 持续提供服务。延伸挑战不依赖第三方库手写解码器课程 README 的 Challenge 环节提出了一个进阶练习不借助pynmea2等第三方库自己实现一个 NMEA 解码器来提取经纬度。结合上文对 GGA 格式的分析你至少需要处理用split(,)拆分句子字段识别GGA类型的第 2、3、4、5 字段纬度值、纬度方向、经度值、经度方向将(d)ddmm.mmmm转换为十进制度度 数值整除 100分 数值对 100 取余十进制度 度 分 / 60依据方向字符对南纬、西经取负。完成这一步你就真正理解了 NMEA 协议与坐标换算的全部细节也为后续课程中将坐标数据上传云端如store-location-data一课打下了坚实基础。至此你的 GPS 传感器程序已成功具备数据解码能力从串口读入原始 NMEA 消息提取 GGA 定位消息经dm_to_sd换算与方向归一化后输出可直接用于地图展示、轨迹记录与位置服务的十进制经纬度坐标。【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考