使用Python在行空板上实现卫星导航功能
本指南旨在向您展示在行空板上使用 Python 开发卫星导航应用程序是多么容易。在本教程结束时,您将能够进一步扩展代码并开发满足您需求的出色应用程序。
硬件清单
将天线与 GNSS 模块连接(天线接口:IPEX1)。现在将 GNSS 模块与 行空板连接并确保选择 I2C!最后一步很简单,只需将行空板与您的 PC 或笔记本电脑(通过 USB)连接即可。
步骤1 创建项目文件夹结构,包括文件
项目结构非常简单!例如,创建一个名为“GNSS”的项目目录,并在其中另外创建两个名为“img”和“lib”的目录。另外,直接在“GNSS”内创建一个Python文件“main.py”。该文件稍后将用于为应用程序创建 GUI。在目录“img”中存储名为“satellite.png”的图片,在目录“lib”中创建名为“DFRobot_GNSS_I2C.py”的Python库文件。该库将用于与 GNSS 模块进行通信。
这里是项目结构的概述:
步骤2 开发代码
让我们从名为“DFRobot_GNSS_I2C.py”的库文件开始。在那里您导入 2 个模块/包,定义一些常量并使用一些简单的方法创建一个类。 Python 文档字符串将详细解释每个方法的作用。
代码
from pinpong.board import I2C
from time import sleep
GNSS_DEVICE_ADDR = 0x20
MODE_GPS = 0x01
MODE_BeiDou = 0x02
MODE_GPS_BeiDou = 0x03
MODE_GLONASS = 0x04
MODE_GPS_GLONASS = 0x05
MODE_BEIDOU_GLONASS = 0x06
MODE_GPS_BEIDOU_GLONASS = 0x07
class DFRobot_GNSS_I2C:
ENABLE_POWER = 0x00
DISABLE_POWER = 0x01
RGB_ON = 0x05
RGB_OFF = 0x02
I2C_YEAR_H = 0x00
I2C_HOUR = 0x04
I2C_LAT_1 = 0x07
I2C_LON_1 = 0x0D
I2C_USE_STAR = 0x13
I2C_ALT_H = 0x14
I2C_SOG_H = 0x17
I2C_COG_H = 0x1A
I2C_GNSS_MODE = 0x22
I2C_SLEEP_MODE = 0x23
I2C_RGB_MODE = 0x24
def __init__(self, i2c_addr=GNSS_DEVICE_ADDR, bus=0):
"""
Initialize the DFRobot_GNSS communication
:param i2c_addr: I2C address
:param bus: I2C bus number
"""
self._addr = i2c_addr
try:
self._i2c = I2C(bus)
except Exception as err:
print(f'Could not initialize i2c! bus: {bus}, error: {err}')
def _write_reg(self, reg, data) -> None:
"""
Write data to the I2C register
:param reg: register address
:param data: data to write
:return: None
"""
if isinstance(data, int):
data = [data]
try:
self._i2c.writeto_mem(self._addr, reg, bytearray(data))
except Exception as err:
print(f'Write issue: {err}')
def _read_reg(self, reg, length) -> bytes:
"""
Reads data from the I2C register
:param reg: I2C register address
:param length: number of bytes to read
:return: bytes
"""
try:
result = self._i2c.readfrom_mem(self._addr, reg, length)
except Exception as err:
print(f'Read issue: {err}')
result = [0, 0]
return result
@staticmethod
def _calculate_latitude_longitude(value: bytes) -> float:
"""
Calculates the latitude and longitude from bytes to float
:param value: gnss bytes
:return: list
"""
val_dd = value[0]
val_mm = value[1]
val_mm_mm = value[2] * 65536 + value[3] * 256 + value[4]
degree = val_dd + val_mm / 60.0 + val_mm_mm / 100000.0 / 60.0
return degree
@staticmethod
def _optional_calculate_bytes_to_float(value: bytes) -> float:
"""
Calculates the bytes to float (for altitude, cog and sog)
:param value: gnss bytes
:return: float
"""
return value[0] * 256 + value[1] + value[2] / 100.0
def set_enable_power(self) -> None:
"""
Enable gnss power
:return: None
"""
self._write_reg(self.I2C_SLEEP_MODE, self.ENABLE_POWER)
sleep(.1)
def set_disable_power(self) -> None:
"""
Disable gnss power
:return: None
"""
self._write_reg(self.I2C_SLEEP_MODE, self.DISABLE_POWER)
sleep(.1)
def set_rgb_on(self) -> None:
"""
Turn LED on
:return: None
"""
self._write_reg(self.I2C_RGB_MODE, self.RGB_ON)
sleep(.1)
def set_rgb_off(self) -> None:
"""
Turn LED off
:return: None
"""
self._write_reg(self.I2C_RGB_MODE, self.RGB_OFF)
sleep(.1)
def set_gnss_mode(self, mode: int) -> None:
"""
Set gnss mode
- 1 for GPS
- 2 for BeiDou
- 3 for GPS + BeiDou
- 4 for GLONASS
- 5 for GPS + GLONASS
- 6 for BeiDou + GLONASS
- 7 for GPS + BeiDou + GLONASS
:param mode: number for mode
:return: None
"""
if 1 <= mode <= 7:
self._write_reg(self.I2C_GNSS_MODE, int(mode))
sleep(.1)
def get_gnss_mode(self) -> int:
"""
Get gnss mode (1 till 7)
:return: number for GNSS mode
"""
result = self._read_reg(self.I2C_GNSS_MODE, 1)
return int(result[0])
def get_num_sta_used(self) -> int:
"""
Get number of current satellite used
:return: number of current satellite used
"""
result = self._read_reg(self.I2C_USE_STAR, 1)
return int(result[0])
def get_date(self) -> str:
"""
Get date and return in format "YYYY-MM-DD"
:return: str
"""
year = 2000
month = 1
day = 1
result = self._read_reg(self.I2C_YEAR_H, 4)
if result != -1:
year = result[0] * 256 + result[1]
month = result[2]
day = result[3]
return f'{year}-{month:02d}-{day:02d}'
def get_time(self) -> str:
"""
Get utc time and return in format "HH:MM:SS"
:return: str
"""
hour = 0
minute = 0
second = 0
result = self._read_reg(self.I2C_HOUR, 3)
if result != -1:
hour = result[0]
minute = result[1]
second = result[2]
return f'{hour:02d}:{minute:02d}:{second:02d}'
def get_lat(self) -> list:
"""
Get latitude and return in format [degree, direction]
:return: list
"""
degree = 0.00
direction = 'S'
result = self._read_reg(self.I2C_LAT_1, 6)
if result != -1:
degree = DFRobot_GNSS_I2C._calculate_latitude_longitude(result)
direction = chr(result[5])
return [degree, direction]
def get_lon(self) -> list:
"""
Get longitude and return in format [degree, direction]
:return: list
"""
degree = 0.00
direction = 'W'
result = self._read_reg(self.I2C_LON_1, 6)
if result != -1:
degree = DFRobot_GNSS_I2C._calculate_latitude_longitude(result)
direction = chr(result[5])
return [degree, direction]
def get_alt(self) -> float:
"""
Get altitude over ground in meters
:return: float
"""
result = self._read_reg(self.I2C_ALT_H, 3)
if result != -1:
high = DFRobot_GNSS_I2C._optional_calculate_bytes_to_float(result)
else:
high = 0.0
return high
def get_cog(self) -> float:
"""
Get course over ground in degrees
:return: float
"""
result = self._read_reg(self.I2C_COG_H, 3)
if result != -1:
cog = DFRobot_GNSS_I2C._optional_calculate_bytes_to_float(result)
else:
cog = 0.0
return cog
def get_sog(self) -> float:
"""
Get speed over ground on knot
:return: float
"""
result = self._read_reg(self.I2C_SOG_H, 3)
if result != -1:
sog = DFRobot_GNSS_I2C._optional_calculate_bytes_to_float(result)
else:
sog = 0.0
return sog
现在您在“main.py”中开发代码。另外,这里没有什么大魔法!很少有模块/包被导入(包括库)。定义了一些常量并创建了2个函数。通过 Python 标准库“Tkinter”创建 GUI。
代码
from pinpong.board import Board
from tkinter import Tk, Label
from PIL import ImageTk, Image
from tkintermapview import TkinterMapView
from lib.DFRobot_GNSS_I2C import DFRobot_GNSS_I2C, MODE_GPS_BEIDOU_GLONASS
SCREEN_WIDTH: int = 240
SCREEN_HEIGHT: int = 320
MINIMUM_SATELLITES: int = 3
DELAY_MILLISECONDS: int = 2000
TILE_SERVER: str = 'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'
TILE_ZOOM: int = 15
def create_map(display, latitude, longitude) -> None:
"""
Create a map with marker
:param display: tkinter display object
:param latitude: float value of latitude
:param longitude: float value of longitude
:return: None
"""
gmap = TkinterMapView(display, width=SCREEN_WIDTH, height=SCREEN_HEIGHT)
gmap.pack(fill='both', expand=True)
gmap.set_tile_server(TILE_SERVER)
gmap.set_position(latitude[0], longitude[0], marker=True)
gmap.set_zoom(TILE_ZOOM)
def check_satellite() -> None:
"""
Check if minimum of satellites is reached
:return: None
"""
global satellite_found
global screen
global sensor
global image_label
num_satellites = sensor.get_num_sta_used()
current_time = sensor.get_time()
current_date = sensor.get_date()
print(f"Found {num_satellites} satellites at {current_date} {current_time}")
if num_satellites > MINIMUM_SATELLITES and not satellite_found:
satellite_found = True
lat = sensor.get_lat()
long = sensor.get_lon()
sensor.set_disable_power()
image_label.pack_forget()
create_map(display=screen, latitude=lat, longitude=long)
if not satellite_found:
screen.after(DELAY_MILLISECONDS, check_satellite)
if __name__ == '__main__':
Board().begin()
sensor = DFRobot_GNSS_I2C()
sensor.set_gnss_mode(MODE_GPS_BEIDOU_GLONASS)
sensor.set_enable_power()
sensor.set_rgb_on()
satellite_found = False
screen = Tk()
screen.geometry(f'{SCREEN_WIDTH}x{SCREEN_HEIGHT}+0+0')
screen.resizable(False, False)
image_label = Label(screen)
image_label.pack(fill='both')
image_src = Image.open("img/satellite.png")
image = ImageTk.PhotoImage(image_src)
image_label.config(image=image)
check_satellite()
screen.mainloop()
如果从未使用过 TkinterMapView 看看这里!您可以用它做更多的事情,还可以根据您的需要更改地图提供商。
步骤3 在行空板上安装 Python 包
即使行空板已经安装了很多 Python 包,你也必须自己快速安装 TkinterMapView。因此,通过 SSH(可能在 Windows 上使用 Putty)连接到 行空板并在行空板终端内运行以下命令:
$ pip3 安装 tkintermapview
查看几秒钟后,您应该准备好了。
步骤4 上传项目并运行
使用 SCP,您可以将项目从 PC/笔记本电脑上传到 UNIHIKER(可能在 Windows 上使用 WinSCP 或 SMB)。
$ scp -r GNSS root@10.1.2.3:/root/
用户root的密码是dfrobot这只是一个可能的示例,但我无法告诉您哪一种最适合您。如果你不明白我的意思,请阅读维基百科。
现在启动应用程序并等待卫星......
如果您无法立即找到卫星,请改变您的位置(最好在室外)。几秒钟后您应该会看到最终结果。
如果您想要更多类似的东西,请留下一个点赞。
作者:Lupin
发布时间:2024年2月16日