虚怀若谷 发表于 2024-4-8 14:38:42

使用Python在行空板上实现卫星导航功能

本帖最后由 虚怀若谷 于 2024-4-8 14:43 编辑

## 使用Python在行空板上实现卫星导航功能

本指南旨在向您展示在行空板上使用 Python 开发卫星导航应用程序是多么容易。在本教程结束时,您将能够进一步扩展代码并开发满足您需求的出色应用程序。

## 硬件清单
* 1x [行空板](https://www.dfrobot.com.cn/goods-3404.html)
* 1x [全球导航卫星系统(GNSS)模块](https://www.dfrobot.com.cn/goods-3652.html)

![](https://files.mdnice.com/user/56048/bfe6bdf4-9b2c-4593-a0e9-9879ca38fe4c.png)

将天线与 GNSS 模块连接(天线接口:IPEX1)。现在将 GNSS 模块与 行空板连接并确保选择 I2C!最后一步很简单,只需将行空板与您的 PC 或笔记本电脑(通过 USB)连接即可。

![](https://files.mdnice.com/user/56048/45044240-7568-40e2-bd2f-b8fae085ccf0.png)

## 步骤1 创建项目文件夹结构,包括文件
项目结构非常简单!例如,创建一个名为“GNSS”的项目目录,并在其中另外创建两个名为“img”和“lib”的目录。另外,直接在“GNSS”内创建一个Python文件“main.py”。该文件稍后将用于为应用程序创建 GUI。在目录“img”中存储名为“satellite.png”的图片,在目录“lib”中创建名为“DFRobot_GNSS_I2C.py”的Python库文件。该库将用于与 GNSS 模块进行通信。

这里是项目结构的概述:

![](https://files.mdnice.com/user/56048/584811e1-b23a-46ff-bfb1-fead653a953a.png)


## 步骤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 =

      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 =

      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
      val_mm = value
      val_mm_mm = value * 65536 + value * 256 + value
      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 * 256 + value + value / 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)

    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)

    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 * 256 + result
            month = result
            day = result

      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
            minute = result
            second = result

      return f'{hour:02d}:{minute:02d}:{second:02d}'

    def get_lat(self) -> list:
      """
      Get latitude and return in format
      :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)

      return

    def get_lon(self) -> list:
      """
      Get longitude and return in format
      :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)

      return

    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, longitude, 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 看看[这里](https://github.com/TomSchimansky/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这只是一个可能的示例,但我无法告诉您哪一种最适合您。如果你不明白我的意思,请阅读[维基百科](https://www.unihiker.com/wiki/)。



现在启动应用程序并等待卫星......



![](https://files.mdnice.com/user/56048/6c9c7818-9312-48c1-99f0-0e3dbf20986b.png)


如果您无法立即找到卫星,请改变您的位置(最好在室外)。几秒钟后您应该会看到最终结果。


![](https://files.mdnice.com/user/56048/28172aa6-b690-4f55-855a-d2f593cc88e8.png)



如果您想要更多类似的东西,请留下一个点赞。

#### 作者:Lupin

#### 发布时间:2024年2月16日

#### 原文链接:(https://community.dfrobot.com/makelog-314081.html)

曾剑波 发表于 2024-5-7 03:03:07

值得参考学习一下

easy猿 发表于 前天 22:32

国内的地图打不开
页: [1]
查看完整版本: 使用Python在行空板上实现卫星导航功能