news 2026/9/16 9:36:13

IoT-For-Beginners 运输篇课后挑战:深入挖掘 NMEA 中的日期时间、海拔与速度等 GPS 扩展数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IoT-For-Beginners 运输篇课后挑战:深入挖掘 NMEA 中的日期时间、海拔与速度等 GPS 扩展数据

IoT-For-Beginners 运输篇课后挑战:深入挖掘 NMEA 中的日期时间、海拔与速度等 GPS 扩展数据

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

本指南对应 IoT-For-Beginners 项目运输(Transport)篇第 1 课"位置跟踪(Location tracking)"的课后作业,主题是:在已经能解析经纬度的基础上,进一步挖掘 GPS 传感器 NMEA 数据流中除位置之外的扩展信息——UTC 日期时间、海拔高度、对地速度等,并让它们在你的 IoT 设备上真正可用(上报为遥测数据,或用来校准设备时钟)。读完本文后,你将掌握 NMEA 0183 句子的字段级解读方法、基于pynmea2(Python)与TinyGPSPlus(Arduino)两种解码路径的扩展数据提取写法,以及虚拟设备场景下用 nmeagen.org 生成测试数据的方法,并理解本作业的评分标准。

作业任务全景:从"定位"走向"全量 GPS 数据"

本课的常规练习只把 GPS 传感器输出中的经纬度提取出来打印。而课后作业 assignment.md 要求更进一步:

  • 研究 NMEA 句子中除位置以外的其他数据,并把它们用到自己的 IoT 设备上;
  • 例如:能否拿到当前日期和时间?如果用的是微控制器,能否像上一个项目中用NTP 信号校时那样,用 GPS 数据设置时钟?
  • 能否拿到海拔高度(相对于海平面的高度)或当前速度
  • 如果使用虚拟 IoT 设备,可以通过发送用 nmeagen.org 之类工具生成的 NMEA 句子来获取部分这类数据。

作业的出发点是一个客观事实:GPS 传感器输出的是标准化的 NMEA 数据流,其中不止包含经纬度——时间戳、海拔、速度、卫星数量、定位质量等信息都以明文文本形式存在于同一串句子中。能否"看到"并"用起来",取决于你对 NMEA 句子结构(lesson README)和解码库 API 的掌握程度。

背景:NMEA 0183 句子中到底有哪些数据

GPS 传感器输出的原始数据遵循NMEA 0183标准(由美国国家海洋电子协会 National Marine Electronics Association 制定)。每一条数据是一段以$开头、以换行符结尾的文本"句子":$后跟 2 个字符表示信号来源(如GP表示美国 GPS 系统、GN表示兼容 GNSS 多星座),再跟 3 个字符表示句子类型,其余为逗号分隔的字段。

课程 README 中列出的常见句子类型如下:

类型说明
GGAGPS 定位数据(GPS Fix Data),包含经纬度、海拔,以及参与计算定位的可见卫星数
ZDA当前日期与时间,包括本地时区信息
GSV可见卫星详情(GPS 传感器能够检测到信号的卫星)

课程中特别指出:GPS 数据本身就带时间戳,所以 IoT 设备完全可以从 GPS 传感器获取时间,而不必依赖 NTP 服务器或板载实时时钟(RTC)。这正是作业中"用 GPS 校时"一说的依据来源。

逐字段拆解一条真实 GGA 句子

课程 README 给出了完整示例句:

$GNGGA,020604.001,4738.538654,N,12208.341758,W,1,3,,164.7,M,-17.1,M,,*67

按 GGA 标准字段顺序解读:

字段含义
句子头$GNGGAGN 多星座来源 + GGA 类型
时间020604.001UTC 时间 02:06:04.001
纬度4738.538654,N47°38.538654′N,即十进制度 47.6423109
经度12208.341758,W122°08.341758′W,即十进制度 -122.1390293
定位质量11 = 已获得 GPS 定位(fix)
卫星数33 颗卫星参与定位计算
水平精度因子(空)HDOP 未提供
海拔164.7,M海拔 164.7 米(高于海平面)
大地水准面高度-17.1,M大地水准面与椭球面的差值 -17.1 米
差分信息两个空字段距最近 DGPS 更新的时间与台站号
校验*67异或校验和

可以看到,海拔(164.7 米)和时间(02:06:04.001 UTC)本来就在 GGA 句子里,不需要额外硬件,只需要在解码时把它们读出来。日期(年/月/日)通常由 ZDA 或 RMC 句子携带;对地速度通常由 RMC(以节 knot 为单位)或 VTG 句子携带。

三条数据来源路线

作业面向三类硬件平台,数据来源各不相同,但最终得到的都是同一种 NMEA 句子流:

  1. 物理传感器(Wio Terminal / Raspberry Pi):使用 Grove GPS Air530 传感器(UART 接口,可连接多个 GPS 星座以快速获得精确定位)。接线与读取方法分别见 wio-terminal-gps-sensor.md 与 pi-gps-sensor.md。传感器通过 UART 持续输出 NMEA 句子,波特率 9600,例如:

    $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
  2. 虚拟 IoT 设备(CounterFit):在 CounterFit 应用中创建UART GPS传感器(端口保持/dev/ttyAMA0),随后可在Source中选择三种数据注入方式(详见 virtual-device-gps-sensor.md):

    • Lat/Lon:手动指定经纬度与卫星数;
    • NMEA:直接粘贴 NMEA 句子文本(建议勾选Repeat让数据每秒重复发送)——作业中提到的 nmeagen.org 就是用于在网页地图上绘制轨迹、自动生成整套 NMEA 句子的工具,这是虚拟设备路线获取日期、海拔、速度等扩展数据的最直接手段;
    • GPX 文件:上传包含多条轨迹点的 GPX 文件,传感器按 1 秒间隔依次返回每个位置。

    数据流在 Python 端通过counterfit-shims-serial包的串口模拟层读取(见 code-gps/virtual-device 下的完整代码)。

  3. 与上一项目 NTP 校时对照:作业提示"像用 NTP 信号设置时钟那样用 GPS 数据校时"。在农场项目第 4 课 wio-terminal-connect-hub.md 及其 ntp.h 实现中,设备通过 NTP 服务器获取网络时间并校准本地时钟;而 GPS 路线的优势在于无需联网——只要天线能看到天空,NMEA 句子自带 UTC 时间戳,即可同样完成校时。这也解释了为什么 GGA 句子把 UTC 时间放在第一个字段。

实战一:Python 路线(Raspberry Pi / 虚拟设备)提取扩展数据

课程的解码任务使用pynmea2库解析串口行数据。仓库中的完整实现见 code-gps-decode/pi/gps-sensor/app.py 与 code-gps-decode/virtual-device/gps-sensor/app.py,核心逻辑如下:

import time import serial # Raspberry Pi 使用 pyserial;虚拟设备使用 counterfit_shims_serial import pynmea2 serial = serial.Serial('/dev/ttyAMA0', 9600, timeout=1) serial.reset_input_buffer() serial.flush() def print_gps_data(line): 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')

安装依赖:pip3 install pynmea2(虚拟设备额外需pip install counterfit-shims-serial,参考 virtual-device-gps-sensor.md)。运行成功的输出形如:

47.6423109,-122.1390293 - from 3 satellites

向作业目标扩展:读出时间、海拔与速度

pynmea2.parse会按句子类型把字段映射为对象属性,作业涉及的扩展数据正好落在这些属性上:

  • GGA 句子msg.timestamp(UTC 时间)、msg.altitude(海拔,单位米)、msg.altitude_unitsmsg.num_sats(卫星数);
  • ZDA 句子msg.timestamp连同msg.daymsg.monthmsg.year(完整日期),以及msg.local_zone_hours/msg.local_zone_minutes(本地时区);
  • RMC 句子msg.spd_over_grnd(对地速度,单位为节 knot),msg.true_course(真航向),msg.datestamp(日期)。

因此,在既有解码循环里只需按句子类型分别处理,即可把扩展数据一并产出,例如:

msg = pynmea2.parse(line) if msg.sentence_type == 'GGA': # 既有经纬度逻辑…… if msg.altitude is not None: print(f'altitude: {msg.altitude}{msg.altitude_units}') # 例如 164.7M print(f'UTC time: {msg.timestamp}') elif msg.sentence_type == 'ZDA': print(f'date: {msg.year}-{msg.month}-{msg.day}, zone: {msg.local_zone_hours}h') elif msg.sentence_type == 'RMC': speed_kmh = msg.spd_over_grnd * 1.852 # 节 → 公里/小时 print(f'speed: {speed_kmh:.1f} km/h, course: {msg.true_course}')

说明:RMC 的速度字段以节为单位,1 节 = 1.852 km/h;若设备需要网络无关的校时,把 GGA/ZDA 解析出的 UTC 时间换算为本地时间并写入时钟即可,原理与 ntp.h 中的 NTP 校时流程一致,只是时间来源从网络变成了 GPS 卫星。

实战二:Arduino 路线(Wio Terminal)提取扩展数据

Wio Terminal 的解码任务使用开源库TinyGPSPlus(在 platformio.ini 中以mikalhart/TinyGPSPlus @ 1.0.2声明依赖),完整代码见 code-gps-decode/wio-terminal/gps-sensor/src/main.cpp。其核心是逐字节喂给解码器:

#include <TinyGPS++.h> TinyGPSPlus gps; void printGPSData() { if (gps.encode(Serial3.read())) { if (gps.location.isValid()) { Serial.print(gps.location.lat(), 6); Serial.print(F(",")); Serial.print(gps.location.lng(), 6); Serial.print(" - from "); Serial.print(gps.satellites.value()); Serial.println(" satellites"); } } }

TinyGPSPlus是一个持续累积状态的对象:每读入一个字符调用一次gps.encode(),当累积出有效句子时返回true。与pynmea2的"整句解析"不同,TinyGPSPlus 会把所有句子的数据汇总到gps对象上,因此扩展数据可直接通过成员访问:

  • gps.time.hour()/gps.time.minute()/gps.time.second()—— UTC 时间;
  • gps.date.day()/gps.date.month()/gps.date.year()—— 日期;
  • gps.altitude.meters()—— 海拔(米);
  • gps.speed.kmph()(或knots()mph())—— 对地速度;
  • gps.satellites.value()—— 定位所用卫星数(代码中已用到)。

例如在printGPSData中追加:

if (gps.time.isValid() && gps.date.isValid()) { Serial.print("UTC: "); Serial.print(gps.date.year()); Serial.print("-"); Serial.print(gps.date.month()); Serial.print("-"); Serial.print(gps.date.day()); Serial.print(" "); Serial.print(gps.time.hour()); Serial.print(":"); Serial.print(gps.time.minute()); Serial.print(":"); Serial.println(gps.time.second()); } if (gps.altitude.isValid()) { Serial.print("altitude: "); Serial.print(gps.altitude.meters(), 1); Serial.println(" m"); } if (gps.speed.isValid()) { Serial.print("speed: "); Serial.print(gps.speed.kmph(), 1); Serial.println(" km/h"); }

由于 UART 串口初始化、SERCOM3中断重定向等接线代码与课程既有工程一致(见 code-gps-decode/wio-terminal/gps-sensor/src/main.cpp),作业只需在上述解码函数内扩展输出即可,无需改动硬件接线。

用评分标准自检:你的作业处于哪个等级

作业的 Rubric(评分标准)只有一条维度,评价关键在"能否获取"与"能否使用"的区分:

标准优秀达标待改进
获取更多 GPS 数据能获取并实际使用更多 GPS 数据——作为遥测上报,或用来配置/校准 IoT 设备能获取更多 GPS 数据,但无法把它们用起来无法获取更多 GPS 数据

据此,评判作业完成度的关键不在"打印出了多少字段",而在数据是否真正进入了设备的业务逻辑。从本课代码结构看(single-board-computer-gps-decode.md),三种"使用"方向值得参考:

  1. 作为遥测数据上报:把 UTC 时间、海拔、速度与经纬度一起打包发送到 IoT Hub 等云端,扩展车队遥测维度;
  2. 用于设备校时:无网络环境下,用 GPS 时间戳替代 NTP/RTC 校准时钟(对照农场项目 ntp.h 的实现思路);
  3. 触发设备行为:例如以海拔或速度阈值判断运输途中状态(静止/行驶/爬坡),为后续课程的车队管理与地理围栏功能打基础。

小结

NMEA 0183 数据流是"位置跟踪"课程最富信息量的副产品:仅凭一条 GGA 句子就能同时获得 UTC 时间、经纬度、卫星数和海拔,配合 ZDA/RMC 句子还能补齐完整日期与对地速度。本作业的实质是从"能用经纬度"升级到"能用整条 NMEA 数据流":Python 端利用pynmea2按句子类型取字段,Arduino 端利用TinyGPSPlus的状态累积对象直接访问时间、日期、海拔与速度成员;虚拟设备场景则可用 nmeagen.org 生成包含全部扩展字段的 NMEA 句子注入 CounterFit 验证。想继续深挖,可回读本课主文档 README.md 中关于 GGA/ZDA/GSV 三类句子的定义,以及解码部分的 single-board-computer-gps-decode.md 与 wio-terminal-gps-decode.md。

【免费下载链接】IoT-For-Beginners12 Weeks, 24 Lessons, IoT for All!项目地址: https://gitcode.com/GitHub_Trending/io/IoT-For-Beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 9:35:40

Polars vs Pandas:现代DataFrame高性能数据操作实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 9:35:11

Hermes Agent的Profile机制及其应用场景

一个 AI 助理服务两个部门&#xff1a;Hermes Agent 多档案怎么配才不串线 Hermes Agent 多档案教程 | 基于 Hermes Agent v0.21.0 实测&#xff08;Profile 机制自 v0.20.0 起实测稳定&#xff09; &#x1f4d6; 摘要&#xff1a;同一个 AI 助理先给市场部做了知识问答机器人…

作者头像 李华
网站建设 2026/9/16 9:34:05

MATLAB实现CNN手写数字识别:从LeNet-5到99%准确率实战

简介&#xff1a;基于MATLAB实现卷积神经网络&#xff08;CNN&#xff09;手写数字识别的完整源码&#xff0c;面向机器学习入门者、计算机视觉初学者以及需要在MATLAB环境中快速搭建图像分类模型的开发者。以MNIST数据集为对象&#xff0c;通过一个可直接运行的m脚本串联数据导…

作者头像 李华
网站建设 2026/9/16 9:33:00

Flutter 性能优化实战指南:Profile 调优、构建精简与卡顿治理

Flutter 性能优化实战指南&#xff1a;Profile 调优、构建精简与卡顿治理 【免费下载链接】claude-skills 67 Specialized Skills for Full-Stack Developers. Transform Claude Code into your expert pair programmer. 项目地址: https://gitcode.com/GitHub_Trending/clau…

作者头像 李华
网站建设 2026/9/16 9:32:17

从 runner = unittest.TextTestRunner() 讲透测试执行器

第一次在测试脚本里看到runner unittest.TextTestRunner()这行赋值时&#xff0c;我甚至把变量名看成了unner——不是看错&#xff0c;而是很多教程代码里随手写的变量名确实容易晃眼。它其实是runner&#xff0c;一个真正决定 unittest 结果“怎么被记录、怎么被打印”的对象…

作者头像 李华
网站建设 2026/9/16 9:32:15

多模态RAG实战:从图文解析到检索问答,给LLM装上视觉之眼

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华