【得捷电子Follow me第1期】+ 提交贴
## 项目描述使用 Raspberry Pico W 控制板板载的无线模块连接 WIFI 进行时间同步,通过串口连接 Grove - GPS (Air530) 模块获取定位信息,并转化为具体的城市位置,然后再通过和风天气提供的天气接口 https://dev.qweather.com/docs/api/weather/weather-now/,获取城市当前的天气信息,并通过 I2C 接口连接的 OLED 进行显示。设备在运行过程中每一分钟进行一次位置更新,每一个小时进行一次时间同步和天气更新。
## 任务讲解
### 熟悉micropython的基本语法
1. 烧录固件
点击 https://micropython.org/download/rp2-pico-w/rp2-pico-w-latest.uf2 链接下载UF2文件; 也可前往树莓派官网下载。
按住板卡上的BOOTSEL按键,将树莓派Pico W通过Micro USB线接到电脑,然后松开按键。
接入之后,电脑会自动识别到一个可移动盘(RPI-RP2)。
将前面下载的固件文件,复制拖拽到RPi-RP2移动盘上。
复制完成之后,Pico会自动重启, 自动重启之后,pico会被识别为一个虚拟串口
2. 安装Mu Editor
Mu Editor的下载地址:https://codewith.mu/en/
直接双击安装即可。模式选项中配置为 RP2040 芯片进行连接
3. 熟悉micropython语法
MicroPython 是编程语言 Python3 的精简高效实现,语法和 Python3 保持一致,但只实现了 Python 标准库的一小部分,并且经过优化,可以在 MCU,WIFI SOC 上等资源受限的环境中使用。
简单控制一下板卡上的小灯,实验一下。
```python
from machine import Pin, Timer
led = Pin("LED", Pin.OUT)
timer = Timer()
def blink(timer):
led.toggle()
timer.init(freq=2.5, mode=Timer.PERIODIC, callback=blink)
```
### 驱动外设
1. 驱动OLED显示屏
将 Grove-OLED-Display-0.96-SSD1315 模块连接至 IIC0 接口,对应的 PICO 引脚 SCL PIN9,SDA PIN8
导入 ssd1306 的显示驱动库
```python
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
# https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py
from micropython import const
import framebuf
# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xA4)
SET_NORM_INV = const(0xA6)
SET_DISP = const(0xAE)
SET_MEM_ADDR = const(0x20)
SET_COL_ADDR = const(0x21)
SET_PAGE_ADDR = const(0x22)
SET_DISP_START_LINE = const(0x40)
SET_SEG_REMAP = const(0xA0)
SET_MUX_RATIO = const(0xA8)
SET_IREF_SELECT = const(0xAD)
SET_COM_OUT_DIR = const(0xC0)
SET_DISP_OFFSET = const(0xD3)
SET_COM_PIN_CFG = const(0xDA)
SET_DISP_CLK_DIV = const(0xD5)
SET_PRECHARGE = const(0xD9)
SET_VCOM_DESEL = const(0xDB)
SET_CHARGE_PUMP = const(0x8D)
# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
def __init__(self, width, height, external_vcc):
self.width = width
self.height = height
self.external_vcc = external_vcc
self.pages = self.height // 8
self.buffer = bytearray(self.pages * self.width)
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
self.init_display()
def init_display(self):
for cmd in (
SET_DISP,# display off
# address setting
SET_MEM_ADDR,
0x00,# horizontal
# resolution and layout
SET_DISP_START_LINE,# start at line 0
SET_SEG_REMAP | 0x01,# column addr 127 mapped to SEG0
SET_MUX_RATIO,
self.height - 1,
SET_COM_OUT_DIR | 0x08,# scan from COM to COM0
SET_DISP_OFFSET,
0x00,
SET_COM_PIN_CFG,
0x02 if self.width > 2 * self.height else 0x12,
# timing and driving scheme
SET_DISP_CLK_DIV,
0x80,
SET_PRECHARGE,
0x22 if self.external_vcc else 0xF1,
SET_VCOM_DESEL,
0x30,# 0.83*Vcc
# display
SET_CONTRAST,
0xFF,# maximum
SET_ENTIRE_ON,# output follows RAM contents
SET_NORM_INV,# not inverted
SET_IREF_SELECT,
0x30,# enable internal IREF during display on
# charge pump
SET_CHARGE_PUMP,
0x10 if self.external_vcc else 0x14,
SET_DISP | 0x01,# display on
):# on
self.write_cmd(cmd)
self.fill(0)
self.show()
def poweroff(self):
self.write_cmd(SET_DISP)
def poweron(self):
self.write_cmd(SET_DISP | 0x01)
def contrast(self, contrast):
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)
def invert(self, invert):
self.write_cmd(SET_NORM_INV | (invert & 1))
def rotate(self, rotate):
self.write_cmd(SET_COM_OUT_DIR | ((rotate & 1) << 3))
self.write_cmd(SET_SEG_REMAP | (rotate & 1))
def show(self):
x0 = 0
x1 = self.width - 1
if self.width != 128:
# narrow displays use centred columns
col_offset = (128 - self.width) // 2
x0 += col_offset
x1 += col_offset
self.write_cmd(SET_COL_ADDR)
self.write_cmd(x0)
self.write_cmd(x1)
self.write_cmd(SET_PAGE_ADDR)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
self.write_data(self.buffer)
def clear(self):
self.fill(0)
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
self.i2c = i2c
self.addr = addr
self.temp = bytearray(2)
self.write_list = # Co=0, D/C#=1
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.temp = 0x80# Co=1, D/C#=0
self.temp = cmd
self.i2c.writeto(self.addr, self.temp)
def write_data(self, buf):
self.write_list = buf
self.i2c.writevto(self.addr, self.write_list)
class SSD1306_SPI(SSD1306):
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
self.rate = 10 * 1024 * 1024
dc.init(dc.OUT, value=0)
res.init(res.OUT, value=0)
cs.init(cs.OUT, value=1)
self.spi = spi
self.dc = dc
self.res = res
self.cs = cs
import time
self.res(1)
time.sleep_ms(1)
self.res(0)
time.sleep_ms(10)
self.res(1)
super().__init__(width, height, external_vcc)
def write_cmd(self, cmd):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(0)
self.cs(0)
self.spi.write(bytearray())
self.cs(1)
def write_data(self, buf):
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
self.cs(1)
self.dc(1)
self.cs(0)
self.spi.write(buf)
self.cs(1)
```
添加应用代码, 这边我为了能显示中文,又导入了 **ufont** 库及其字库文件 **unifont-14-12917-16.v3.bmf**
```python
from machine import Pin, I2C, RTC, Timer, UART
import time
from ssd1306 import SSD1306_I2C
import ufont
import framebuf
led = Pin('LED', Pin.OUT)
timer = Timer()
i2c_oled = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
display = SSD1306_I2C(128, 64, i2c_oled)
display.clear()
font = ufont.BMFont("unifont-14-12917-16.v3.bmf")
buffer = bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00|?\x00\x01\x86@\x80\x01\x01\x80\x80\x01\x11\x88\x80\x01\x05\xa0\x80\x00\x83\xc1\x00\x00C\xe3\x00\x00~\xfc\x00\x00L'\x00\x00\x9c\x11\x00\x00\xbf\xfd\x00\x00\xe1\x87\x00\x01\xc1\x83\x80\x02A\x82@\x02A\x82@\x02\xc1\xc2@\x02\xf6>\xc0\x01\xfc=\x80\x01\x18\x18\x80\x01\x88\x10\x80\x00\x8c!\x00\x00\x87\xf1\x00\x00\x7f\xf6\x00\x008\x1c\x00\x00\x0c \x00\x00\x03\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
fb = framebuf.FrameBuffer(buffer, 32, 32, framebuf.MONO_HLSB)
display.blit(fb, 96, 0)
font.text(display, "Thanks", 0, 0, show=False)
#display.text("eeworld!", 8, 16)
#display.text("Digi-Key!", 16, 32)
font.text(display, "eeworld!", 16, 16, show=False)
font.text(display, "Digi-Key!", 16, 32, show=False)
font.text(display, "Follow me 活动", 0, 48, show=False)
display.show()
```
显示效果
2. 驱动蜂鸣器
使用的 Grove - Buzzer 模块为无源蜂鸣器,可以选择拉高电平或者输出 pwm 信号进行控制,这里使用了PICO W的 26 脚。
```python
from machine import I2C, Pin, PWM
import utime
tones = {"1": 262, "2": 294, "3": 330, "4": 349, "5": 392, "6": 440, "7": 494, "-": 0}
melody = "1155665-4433221-5544332-5544332-1155665-4433221"
beeper = PWM(Pin(16))
beeper.duty_u16(1000)
for tone in melody:
freq = tones
if freq:
beeper.freq(freq)
beeper.duty_u16(1000)
#beeper = PWM(Pin(16), freq=freq, duty_u16=1000)
else:
beeper.duty_u16(0)
utime.sleep_ms(400)
beeper.duty_u16(0)
utime.sleep_ms(100)
beeper.deinit()
```
运行之后可以听到有声音的变化
### 同步网络时间
通过板载的 WIFI 模块和自带的 NTP 库进行网络同步系统时间
```python
from machine import Pin, Timer, RTC
import time
import network
import ntptime
WIFI_SSID = "your-ssid"
WIFI_PASSWORD = "your-password"
UTC_OFFSET = 8 * 60 * 60
led = Pin("LED", Pin.OUT)
rtc = RTC()
# Connect to WiFi
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
access_points = wlan.scan()
for AP in access_points:
print(AP)
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
while not wlan.isconnected() and wlan.status() >= 0:
print("Waiting to connect:")
led.toggle()
time.sleep(0.25)
print("Connected to WiFi!")
print(wlan.ifconfig())
# Get the current network time and set the system clock
print("Local time before synchronization:%s" % str(time.localtime()))
ntptime.timeout = 3
ntptime.settime()
utctime = ntptime.time() + UTC_OFFSET
print("Local time after synchronization:%s" % str(time.localtime(utctime)))
```
运行之后的 log 信息如下:
```python
Connected to WiFi!
('192.168.0.3', '255.255.255.0', '192.168.0.1', '211.136.150.66')
Local time before synchronization:(2023, 6, 7, 14, 19, 21, 2, 158)
Local time after synchronization:(2023, 6, 7, 22, 19, 21, 2, 158)
```
### 实现定位功能
使用 UART0 接口连接 Grove - GPS (Air530)模块,通讯采用的是标准的 NMEA-0183 协议,这边我导入了开源的 micropyGPS 进行解析
```python
"""
# MicropyGPS - a GPS NMEA sentence parser for Micropython/Python 3.X
# Copyright (c) 2017 Michael Calvin McCoy (calvin.mccoy@protonmail.com)
# The MIT License (MIT) - see LICENSE file
"""
# TODO:
# Time Since First Fix
# Distance/Time to Target
# More Helper Functions
# Dynamically limit sentences types to parse
from math import floor, modf
# Import utime or time for fix time handling
try:
# Assume running on MicroPython
import utime
except ImportError:
# Otherwise default to time module for non-embedded implementations
# Should still support millisecond resolution.
import time
class MicropyGPS(object):
"""GPS NMEA Sentence Parser. Creates object that stores all relevant GPS data and statistics.
Parses sentences one character at a time using update(). """
# Max Number of Characters a valid sentence can be (based on GGA sentence)
SENTENCE_LIMIT = 90
__HEMISPHERES = ('N', 'S', 'E', 'W')
__NO_FIX = 1
__FIX_2D = 2
__FIX_3D = 3
__DIRECTIONS = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W',
'WNW', 'NW', 'NNW')
__MONTHS = ('January', 'February', 'March', 'April', 'May',
'June', 'July', 'August', 'September', 'October',
'November', 'December')
def __init__(self, local_offset=0, location_formatting='ddm'):
"""
Setup GPS Object Status Flags, Internal Data Registers, etc
local_offset (int): Timzone Difference to UTC
location_formatting (str): Style For Presenting Longitude/Latitude:
Decimal Degree Minute (ddm) - 40° 26.767′ N
Degrees Minutes Seconds (dms) - 40° 26′ 46″ N
Decimal Degrees (dd) - 40.446° N
"""
#####################
# Object Status Flags
self.sentence_active = False
self.active_segment = 0
self.process_crc = False
self.gps_segments = []
self.crc_xor = 0
self.char_count = 0
self.fix_time = 0
#####################
# Sentence Statistics
self.crc_fails = 0
self.clean_sentences = 0
self.parsed_sentences = 0
#####################
# Logging Related
self.log_handle = None
self.log_en = False
#####################
# Data From Sentences
# Time
self.timestamp =
self.date =
self.local_offset = local_offset
# Position/Motion
self._latitude =
self._longitude =
self.coord_format = location_formatting
self.speed =
self.course = 0.0
self.altitude = 0.0
self.geoid_height = 0.0
# GPS Info
self.satellites_in_view = 0
self.satellites_in_use = 0
self.satellites_used = []
self.last_sv_sentence = 0
self.total_sv_sentences = 0
self.satellite_data = dict()
self.hdop = 0.0
self.pdop = 0.0
self.vdop = 0.0
self.valid = False
self.fix_stat = 0
self.fix_type = 1
########################################
# Coordinates Translation Functions
########################################
@property
def latitude(self):
"""Format Latitude Data Correctly"""
if self.coord_format == 'dd':
decimal_degrees = self._latitude + (self._latitude / 60)
return ]
elif self.coord_format == 'dms':
minute_parts = modf(self._latitude)
seconds = round(minute_parts * 60)
return , int(minute_parts), seconds, self._latitude]
else:
return self._latitude
@property
def longitude(self):
"""Format Longitude Data Correctly"""
if self.coord_format == 'dd':
decimal_degrees = self._longitude + (self._longitude / 60)
return ]
elif self.coord_format == 'dms':
minute_parts = modf(self._longitude)
seconds = round(minute_parts * 60)
return , int(minute_parts), seconds, self._longitude]
else:
return self._longitude
########################################
# Logging Related Functions
########################################
def start_logging(self, target_file, mode="append"):
"""
Create GPS data log object
"""
# Set Write Mode Overwrite or Append
mode_code = 'w' if mode == 'new' else 'a'
try:
self.log_handle = open(target_file, mode_code)
except AttributeError:
print("Invalid FileName")
return False
self.log_en = True
return True
def stop_logging(self):
"""
Closes the log file handler and disables further logging
"""
try:
self.log_handle.close()
except AttributeError:
print("Invalid Handle")
return False
self.log_en = False
return True
def write_log(self, log_string):
"""Attempts to write the last valid NMEA sentence character to the active file handler
"""
try:
self.log_handle.write(log_string)
except TypeError:
return False
return True
########################################
# Sentence Parsers
########################################
def gprmc(self):
"""Parse Recommended Minimum Specific GPS/Transit data (RMC)Sentence.
Updates UTC timestamp, latitude, longitude, Course, Speed, Date, and fix status
"""
# UTC Timestamp
try:
utc_string = self.gps_segments
if utc_string:# Possible timestamp found
hours = (int(utc_string) + self.local_offset) % 24
minutes = int(utc_string)
seconds = float(utc_string)
self.timestamp =
else:# No Time stamp yet
self.timestamp =
except ValueError:# Bad Timestamp value present
return False
# Date stamp
try:
date_string = self.gps_segments
# Date string printer function assumes to be year >=2000,
# date_string() must be supplied with the correct century argument to display correctly
if date_string:# Possible date stamp found
day = int(date_string)
month = int(date_string)
year = int(date_string)
self.date = (day, month, year)
else:# No Date stamp yet
self.date = (0, 0, 0)
except ValueError:# Bad Date stamp value present
return False
# Check Receiver Data Valid Flag
if self.gps_segments == 'A':# Data from Receiver is Valid/Has Fix
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments
lat_degs = int(l_string)
lat_mins = float(l_string)
lat_hemi = self.gps_segments
# Longitude
l_string = self.gps_segments
lon_degs = int(l_string)
lon_mins = float(l_string)
lon_hemi = self.gps_segments
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Speed
try:
spd_knt = float(self.gps_segments)
except ValueError:
return False
# Course
try:
if self.gps_segments:
course = float(self.gps_segments)
else:
course = 0.0
except ValueError:
return False
# TODO - Add Magnetic Variation
# Update Object Data
self._latitude =
self._longitude =
# Include mph and hm/h
self.speed =
self.course = course
self.valid = True
# Update Last Fix Time
self.new_fix_time()
else:# Clear Position Data if Sentence is 'Invalid'
self._latitude =
self._longitude =
self.speed =
self.course = 0.0
self.valid = False
return True
def gpgll(self):
"""Parse Geographic Latitude and Longitude (GLL)Sentence. Updates UTC timestamp, latitude,
longitude, and fix status"""
# UTC Timestamp
try:
utc_string = self.gps_segments
if utc_string:# Possible timestamp found
hours = (int(utc_string) + self.local_offset) % 24
minutes = int(utc_string)
seconds = float(utc_string)
self.timestamp =
else:# No Time stamp yet
self.timestamp =
except ValueError:# Bad Timestamp value present
return False
# Check Receiver Data Valid Flag
if self.gps_segments == 'A':# Data from Receiver is Valid/Has Fix
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments
lat_degs = int(l_string)
lat_mins = float(l_string)
lat_hemi = self.gps_segments
# Longitude
l_string = self.gps_segments
lon_degs = int(l_string)
lon_mins = float(l_string)
lon_hemi = self.gps_segments
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Update Object Data
self._latitude =
self._longitude =
self.valid = True
# Update Last Fix Time
self.new_fix_time()
else:# Clear Position Data if Sentence is 'Invalid'
self._latitude =
self._longitude =
self.valid = False
return True
def gpvtg(self):
"""Parse Track Made Good and Ground Speed (VTG) Sentence. Updates speed and course"""
try:
course = float(self.gps_segments) if self.gps_segments else 0.0
spd_knt = float(self.gps_segments) if self.gps_segments else 0.0
except ValueError:
return False
# Include mph and km/h
self.speed = (spd_knt, spd_knt * 1.151, spd_knt * 1.852)
self.course = course
return True
def gpgga(self):
"""Parse Global Positioning System Fix Data (GGA) Sentence. Updates UTC timestamp, latitude, longitude,
fix status, satellites in use, Horizontal Dilution of Precision (HDOP), altitude, geoid height and fix status"""
try:
# UTC Timestamp
utc_string = self.gps_segments
# Skip timestamp if receiver doesn't have on yet
if utc_string:
hours = (int(utc_string) + self.local_offset) % 24
minutes = int(utc_string)
seconds = float(utc_string)
else:
hours = 0
minutes = 0
seconds = 0.0
# Number of Satellites in Use
satellites_in_use = int(self.gps_segments)
# Get Fix Status
fix_stat = int(self.gps_segments)
except (ValueError, IndexError):
return False
try:
# Horizontal Dilution of Precision
hdop = float(self.gps_segments)
except (ValueError, IndexError):
hdop = 0.0
# Process Location and Speed Data if Fix is GOOD
if fix_stat:
# Longitude / Latitude
try:
# Latitude
l_string = self.gps_segments
lat_degs = int(l_string)
lat_mins = float(l_string)
lat_hemi = self.gps_segments
# Longitude
l_string = self.gps_segments
lon_degs = int(l_string)
lon_mins = float(l_string)
lon_hemi = self.gps_segments
except ValueError:
return False
if lat_hemi not in self.__HEMISPHERES:
return False
if lon_hemi not in self.__HEMISPHERES:
return False
# Altitude / Height Above Geoid
try:
altitude = float(self.gps_segments)
geoid_height = float(self.gps_segments)
except ValueError:
altitude = 0
geoid_height = 0
# Update Object Data
self._latitude =
self._longitude =
self.altitude = altitude
self.geoid_height = geoid_height
# Update Object Data
self.timestamp =
self.satellites_in_use = satellites_in_use
self.hdop = hdop
self.fix_stat = fix_stat
# If Fix is GOOD, update fix timestamp
if fix_stat:
self.new_fix_time()
return True
def gpgsa(self):
"""Parse GNSS DOP and Active Satellites (GSA) sentence. Updates GPS fix type, list of satellites used in
fix calculation, Position Dilution of Precision (PDOP), Horizontal Dilution of Precision (HDOP), Vertical
Dilution of Precision, and fix status"""
# Fix Type (None,2D or 3D)
try:
fix_type = int(self.gps_segments)
except ValueError:
return False
# Read All (up to 12) Available PRN Satellite Numbers
sats_used = []
for sats in range(12):
sat_number_str = self.gps_segments
if sat_number_str:
try:
sat_number = int(sat_number_str)
sats_used.append(sat_number)
except ValueError:
return False
else:
break
# PDOP,HDOP,VDOP
try:
pdop = float(self.gps_segments)
hdop = float(self.gps_segments)
vdop = float(self.gps_segments)
except ValueError:
return False
# Update Object Data
self.fix_type = fix_type
# If Fix is GOOD, update fix timestamp
if fix_type > self.__NO_FIX:
self.new_fix_time()
self.satellites_used = sats_used
self.hdop = hdop
self.vdop = vdop
self.pdop = pdop
return True
def gpgsv(self):
"""Parse Satellites in View (GSV) sentence. Updates number of SV Sentences,the number of the last SV sentence
parsed, and data on each satellite present in the sentence"""
try:
num_sv_sentences = int(self.gps_segments)
current_sv_sentence = int(self.gps_segments)
sats_in_view = int(self.gps_segments)
except ValueError:
return False
# Create a blank dict to store all the satellite data from this sentence in:
# satellite PRN is key, tuple containing telemetry is value
satellite_dict = dict()
# CalculateNumber of Satelites to pull data for and thus how many segment positions to read
if num_sv_sentences == current_sv_sentence:
# Last sentence may have 1-4 satellites; 5 - 20 positions
sat_segment_limit = (sats_in_view - ((num_sv_sentences - 1) * 4)) * 5
else:
sat_segment_limit = 20# Non-last sentences have 4 satellites and thus read up to position 20
# Try to recover data for up to 4 satellites in sentence
for sats in range(4, sat_segment_limit, 4):
# If a PRN is present, grab satellite data
if self.gps_segments:
try:
sat_id = int(self.gps_segments)
except (ValueError,IndexError):
return False
try:# elevation can be null (no value) when not tracking
elevation = int(self.gps_segments)
except (ValueError,IndexError):
elevation = None
try:# azimuth can be null (no value) when not tracking
azimuth = int(self.gps_segments)
except (ValueError,IndexError):
azimuth = None
try:# SNR can be null (no value) when not tracking
snr = int(self.gps_segments)
except (ValueError,IndexError):
snr = None
# If no PRN is found, then the sentence has no more satellites to read
else:
break
# Add Satellite Data to Sentence Dict
satellite_dict = (elevation, azimuth, snr)
# Update Object Data
self.total_sv_sentences = num_sv_sentences
self.last_sv_sentence = current_sv_sentence
self.satellites_in_view = sats_in_view
# For a new set of sentences, we either clear out the existing sat data or
# update it as additional SV sentences are parsed
if current_sv_sentence == 1:
self.satellite_data = satellite_dict
else:
self.satellite_data.update(satellite_dict)
return True
##########################################
# Data Stream Handler Functions
##########################################
def new_sentence(self):
"""Adjust Object Flags in Preparation for a New Sentence"""
self.gps_segments = ['']
self.active_segment = 0
self.crc_xor = 0
self.sentence_active = True
self.process_crc = True
self.char_count = 0
def update(self, new_char):
"""Process a new input char and updates GPS object if necessary based on special characters ('$', ',', '*')
Function builds a list of received string that are validate by CRC prior to parsing by theappropriate
sentence function. Returns sentence type on successful parse, None otherwise"""
valid_sentence = False
# Validate new_char is a printable char
ascii_char = ord(new_char)
if 10 <= ascii_char <= 126:
self.char_count += 1
# Write Character to log file if enabled
if self.log_en:
self.write_log(new_char)
# Check if a new string is starting ($)
if new_char == '$':
self.new_sentence()
return None
elif self.sentence_active:
# Check if sentence is ending (*)
if new_char == '*':
self.process_crc = False
self.active_segment += 1
self.gps_segments.append('')
return None
# Check if a section is ended (,), Create a new substring to feed
# characters to
elif new_char == ',':
self.active_segment += 1
self.gps_segments.append('')
# Store All Other printable character and check CRC when ready
else:
self.gps_segments += new_char
# When CRC input is disabled, sentence is nearly complete
if not self.process_crc:
if len(self.gps_segments) == 2:
try:
final_crc = int(self.gps_segments, 16)
if self.crc_xor == final_crc:
valid_sentence = True
else:
self.crc_fails += 1
except ValueError:
pass# CRC Value was deformed and could not have been correct
# Update CRC
if self.process_crc:
self.crc_xor ^= ascii_char
# If a Valid Sentence Was received and it's a supported sentence, then parse it!!
if valid_sentence:
self.clean_sentences += 1# Increment clean sentences received
self.sentence_active = False# Clear Active Processing Flag
if self.gps_segments in self.supported_sentences:
# parse the Sentence Based on the message type, return True if parse is clean
if self.supported_sentences](self):
# Let host know that the GPS object was updated by returning parsed sentence type
self.parsed_sentences += 1
return self.gps_segments
# Check that the sentence buffer isn't filling up with Garage waiting for the sentence to complete
if self.char_count > self.SENTENCE_LIMIT:
self.sentence_active = False
# Tell Host no new sentence was parsed
return None
def new_fix_time(self):
"""Updates a high resolution counter with current time when fix is updated. Currently only triggered from
GGA, GSA and RMC sentences"""
try:
self.fix_time = utime.ticks_ms()
except NameError:
self.fix_time = time.time()
#########################################
# User Helper Functions
# These functions make working with the GPS object data easier
#########################################
def satellite_data_updated(self):
"""
Checks if the all the GSV sentences in a group have been read, making satellite data complete
:return: boolean
"""
if self.total_sv_sentences > 0 and self.total_sv_sentences == self.last_sv_sentence:
return True
else:
return False
def unset_satellite_data_updated(self):
"""
Mark GSV sentences as read indicating the data has been used and future updates are fresh
"""
self.last_sv_sentence = 0
def satellites_visible(self):
"""
Returns a list of of the satellite PRNs currently visible to the receiver
:return: list
"""
return list(self.satellite_data.keys())
def time_since_fix(self):
"""Returns number of millisecond since the last sentence with a valid fix was parsed. Returns 0 if
no fix has been found"""
# Test if a Fix has been found
if self.fix_time == 0:
return -1
# Try calculating fix time using utime; if not running MicroPython
# time.time() returns a floating point value in secs
try:
current = utime.ticks_diff(utime.ticks_ms(), self.fix_time)
except NameError:
current = (time.time() - self.fix_time) * 1000# ms
return current
def compass_direction(self):
"""
Determine a cardinal or inter-cardinal direction based on current course.
:return: string
"""
# Calculate the offset for a rotated compass
if self.course >= 348.75:
offset_course = 360 - self.course
else:
offset_course = self.course + 11.25
# Each compass point is separated by 22.5 degrees, divide to find lookup value
dir_index = floor(offset_course / 22.5)
final_dir = self.__DIRECTIONS
return final_dir
def latitude_string(self):
"""
Create a readable string of the current latitude data
:return: string
"""
if self.coord_format == 'dd':
formatted_latitude = self.latitude
lat_string = str(formatted_latitude) + '° ' + str(self._latitude)
elif self.coord_format == 'dms':
formatted_latitude = self.latitude
lat_string = str(formatted_latitude) + '° ' + str(formatted_latitude) + "' " + str(formatted_latitude) + '" ' + str(formatted_latitude)
else:
lat_string = str(self._latitude) + '° ' + str(self._latitude) + "' " + str(self._latitude)
return lat_string
def longitude_string(self):
"""
Create a readable string of the current longitude data
:return: string
"""
if self.coord_format == 'dd':
formatted_longitude = self.longitude
lon_string = str(formatted_longitude) + '° ' + str(self._longitude)
elif self.coord_format == 'dms':
formatted_longitude = self.longitude
lon_string = str(formatted_longitude) + '° ' + str(formatted_longitude) + "' " + str(formatted_longitude) + '" ' + str(formatted_longitude)
else:
lon_string = str(self._longitude) + '° ' + str(self._longitude) + "' " + str(self._longitude)
return lon_string
def speed_string(self, unit='kph'):
"""
Creates a readable string of the current speed data in one of three units
:param unit: string of 'kph','mph, or 'knot'
:return:
"""
if unit == 'mph':
speed_string = str(self.speed) + ' mph'
elif unit == 'knot':
if self.speed == 1:
unit_str = ' knot'
else:
unit_str = ' knots'
speed_string = str(self.speed) + unit_str
else:
speed_string = str(self.speed) + ' km/h'
return speed_string
def date_string(self, formatting='s_mdy', century='20'):
"""
Creates a readable string of the current date.
Can select between long format: Januray 1st, 2014
or two short formats:
11/01/2014 (MM/DD/YYYY)
01/11/2014 (DD/MM/YYYY)
:param formatting: string 's_mdy', 's_dmy', or 'long'
:param century: int delineating the century the GPS data is from (19 for 19XX, 20 for 20XX)
:return: date_stringstring with long or short format date
"""
# Long Format Januray 1st, 2014
if formatting == 'long':
# Retrieve Month string from private set
month = self.__MONTHS - 1]
# Determine Date Suffix
if self.date in (1, 21, 31):
suffix = 'st'
elif self.date in (2, 22):
suffix = 'nd'
elif self.date == (3, 23):
suffix = 'rd'
else:
suffix = 'th'
day = str(self.date) + suffix# Create Day String
year = century + str(self.date)# Create Year String
date_string = month + ' ' + day + ', ' + year# Put it all together
else:
# Add leading zeros to day string if necessary
if self.date < 10:
day = '0' + str(self.date)
else:
day = str(self.date)
# Add leading zeros to month string if necessary
if self.date < 10:
month = '0' + str(self.date)
else:
month = str(self.date)
# Add leading zeros to year string if necessary
if self.date < 10:
year = '0' + str(self.date)
else:
year = str(self.date)
# Build final string based on desired formatting
if formatting == 's_dmy':
date_string = day + '/' + month + '/' + year
else:# Default date format
date_string = month + '/' + day + '/' + year
return date_string
# All the currently supported NMEA sentences
supported_sentences = {'GPRMC': gprmc, 'GLRMC': gprmc,
'GPGGA': gpgga, 'GLGGA': gpgga,
'GPVTG': gpvtg, 'GLVTG': gpvtg,
'GPGSA': gpgsa, 'GLGSA': gpgsa,
'GPGSV': gpgsv, 'GLGSV': gpgsv,
'GPGLL': gpgll, 'GLGLL': gpgll,
'GNGGA': gpgga, 'GNRMC': gprmc,
'GNVTG': gpvtg, 'GNGLL': gpgll,
'GNGSA': gpgsa,
}
if __name__ == "__main__":
pass
```
编写测试代码如下
```python
from machine import UART, Pin
import time
from micropyGPS import MicropyGPS
uart0 = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1))
my_gps = MicropyGPS()
while True:
if uart0.any():
if my_gps.update(uart0.read(1).decode("ascii")):
print("Latitude:", my_gps.latitude_string())
print("Longitude:", my_gps.longitude_string())
print("Speed:", my_gps.speed_string("kph"), "or", my_gps.speed_string("mph"), "or", my_gps.speed_string("knot"))
print("Date (Long Format):", my_gps.date_string("long"))
print("Date (Short D/M/Y Format):", my_gps.date_string("s_dmy"))
print("Date (Short M/D/Y Format):", my_gps.date_string("s_mdy"))
time.sleep(5)
```
等待运行一段时间后日志如下显示
```python
Latitude: 0° 0.0’ N
Longitude: 0° 0.0’ W
Speed: 0.0 km/h or 0.0 mph or 0.0 knots
Date (Long Format): June 8th, 2023
Date (Short D/M/Y Format): 08/06/23
Date (Short M/D/Y Format): 06/08/23
Latitude: 31° 10.23957’ N
Longitude: 121° 31.71391’ E
Speed: 0.0 km/h or 0.0 mph or 0.0 knots
Date (Long Format): June 8th, 2023
Date (Short D/M/Y Format): 08/06/23
Date (Short M/D/Y Format): 06/08/23
```
### 综合示例
设备运行之前,需要先将 **WIFI_SSID**、**WIFI_PASSWORD** 设置为你的无线账户,并注册和风天气创建自己的项目获取 KEY填充 **QWEATHER_KEY**
设备运行时会先连接网络,同步时间,然后进行 gps 定位,通过百度接口转换为城市信息,并通过和风天气接口获取实时天气
```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from machine import Pin, I2C, RTC, Timer, UART
import time
import utime
import network
import ntptime
import urequests
import ujson
import uzlib
from micropyGPS import MicropyGPS
from ssd1306 import SSD1306_I2C
import ufont
import weather_ico
import framebuf
# gps status
GPS_STATUS_NONE = 0
GPS_STATUS_INIT = 1
GPS_STATUS_IDLE = 2
GPS_STATUS_SYNC = 3
GPS_STATUS_UPDATE = 4
WIFI_SSID = "your-ssid"
WIFI_PASSWORD = "your-password"
QWEATHER_KEY = ""
UTC_OFFSET = 8 * 60 * 60
WEEK = ['一', '二', '三', '四', '五', '六', '日']
g_tick_counter = 1
g_gps_status = GPS_STATUS_NONE
g_longi = "0.0"
g_lati = "0.0"
led = Pin("LED", Pin.OUT)
beep = Pin(Pin(16), Pin.OUT)
timer = Timer()
rtc = RTC()
i2c_oled = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
display = SSD1306_I2C(128, 64, i2c_oled)
display.clear()
font = ufont.BMFont("unifont-14-12917-16.v3.bmf")
uart_gps = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1))
gps = MicropyGPS(location_formatting='dd')
wlan = network.WLAN(network.STA_IF)
def gps_process():
if uart_gps.any():
if gps.update(uart_gps.read(1).decode("ascii")):
# print("Latitude:", gps.latitude_string())
# print("Longitude:", gps.longitude_string())
# print("Speed:", gps.speed_string("kph"), "or", gps.speed_string("mph"), "or", gps.speed_string("knot"))
# # print("Date (Long Format):", gps.date_string("long"))
# # print("Date (Short D/M/Y Format):", gps.date_string("s_dmy"))
# print("Date (Short M/D/Y Format):", gps.date_string("s_mdy"))
global g_gps_status, g_longi, g_lati
if gps.longitude != 0.0:
if g_gps_status == GPS_STATUS_NONE:
g_gps_status = GPS_STATUS_INIT
g_longi = str(round(gps.longitude, 2))
g_lati = str(round(gps.latitude, 2))
#print("Latitude:", g_longi)
#print("Longitude:", g_lati)
elif g_gps_status == GPS_STATUS_SYNC:
g_gps_status = GPS_STATUS_UPDATE
g_longi = str(round(gps.longitude, 2))
g_lati = str(round(gps.latitude, 2))
#print("Latitude:", g_longi)
#print("Longitude:", g_lati)
def connect_to_network():
# Connect to WiFi
wlan.active(True)
# wlan.config(pm = 0xa11140) # Disable power-save mode
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
max_wait = 10
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print("waiting for connection..." + str(wlan.status()))
time.sleep(1)
if wlan.status() != 3:
raise RuntimeError("network connection failed")
else:
print("connected")
status = wlan.ifconfig()
print("ip = " + status)
def ntp_sync_time():
# Get the current network time and set the system clock
# print("Local time before synchronization:%s" % str(time.localtime()))
# ntptime.settime()
# utctime = ntptime.time() + UTC_OFFSET
# print("Local time after synchronization:%s" % str(time.localtime(utctime)))
print("Local time before synchronization:%s" % str(time.localtime()))
ntptime.host = 'ntp1.aliyun.com'
ntptime.timeout = 5
t = ntptime.time() + UTC_OFFSET
tm = utime.gmtime(t)
rtc.datetime((tm, tm, tm, tm + 1, tm, tm, tm, 0))
print("Local time after synchronization:%s" % str(time.localtime()))
def baidu_get_location():
url = "http://api.map.baidu.com/geocoder?location=" + g_lati + "," + g_longi + "&output=json"
location = {}
# print('old url = ' + url)
# new_url = url.replace("***", g_lati_longi)
print("request url = " + url)
res = urequests.get(url=url).json()
print(res)
if res["status"] == "OK":
location['city'] = res["result"]["addressComponent"]["city"]
location['district'] = res["result"]["addressComponent"]["district"]
print("location at " + location['city'] + " " + location['district'])
return location
def qweather_decompress(data):
FTEXT = 1
FHCRC = 2
FEXTRA = 4
FNAME = 8
FCOMMENT = 16
assert data == 0x1f and data == 0x8b
assert data == 8
flg = data
assert flg & 0xe0 == 0
i = 10
if flg & FEXTRA:
i += data << 8 + data + 2
if flg & FNAME:
while data:
i += 1
i += 1
if flg & FCOMMENT:
while data:
i += 1
i += 1
if flg & FHCRC:
i += 2
return uzlib.decompress(memoryview(data), -15)
def qweather_city_search():
url = "https://geoapi.qweather.com/v2/city/lookup?key=" + QWEATHER_KEY + "&location=" + g_longi + "," + g_lati
print("request url = " + url)
res = urequests.get(url=url)
data = qweather_decompress(res.content).decode()
dic_res = ujson.loads(data)
if dic_res['code'] == '200':
name = dic_res["location"]["name"]
name_id = dic_res["location"]["id"]
city = dic_res["location"]["adm2"]
province = dic_res["location"]["adm1"]
print('locationa at ' + name_id + ' ' + name + ' ' + city + ' ' + province)
def qweather_weather_now():
url = "https://devapi.qweather.com/v7/weather/now?key=" + QWEATHER_KEY + "&location=" + g_longi + "," + g_lati
print("request url = " + url)
res = urequests.get(url=url)
data = qweather_decompress(res.content).decode()
dic_res = ujson.loads(data)
weather = {}
if dic_res['code'] == '200':
weather['temp'] = dic_res["now"]["temp"]
weather['humi'] = dic_res["now"]["humidity"]
weather['text'] = dic_res["now"]["text"]
weather['icon'] = dic_res["now"]["icon"]
print('weather: temp=' + weather['temp'] + ' hump=' + weather['humi'] + ' text=' + weather['text'])
return weather
def systick_handle(timer):
global g_tick_counter
g_tick_counter += 1
# print("g_tick_counter = " + str(g_tick_counter))
if wlan.status() != 3 and wlan.status() != 1:
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
if not wlan.isconnected():
led.toggle()
else:
if g_tick_counter % 5 == 0:
led.toggle()
display.rect(0, 48, 128, 16, 0, True)
date = time.localtime()
nyr = '{}/{}/{}'.format(date, date, date)
display.text(nyr, 0, 48)
sfm = '{}/{}/{}'.format(date, date, date)
display.text(sfm, 0, 56)
font.text(display, '星期' + WEEK], 112-32, 48, show=True)
font.text(display, "WIFI 接入中。。。", 0, 0, show=True)
connect_to_network()
time.sleep_ms(250)
timer.init(freq=5, mode=Timer.PERIODIC, callback=systick_handle)
if wlan.isconnected():
display.rect(0, 0, 128, 16, 0, True)
font.text(display, "WIFI 接入成功", 0, 0, show=True)
ntp_sync_time()
display.rect(0, 48, 128, 16, 0, True)
date = time.localtime()
nyr = '{}/{}/{}'.format(date, date, date)
display.text(nyr, 0, 48)
sfm = '{}/{}/{}'.format(date, date, date)
display.text(sfm, 0, 56)
font.text(display, '星期' + WEEK], 112-32, 48, show=True)
'''weather = qweather_weather_now()
if weather:
display.rect(0, 16, 112, 32, 0, True)
font.text(display, '温度 ' + weather['temp'], 0, 16, show=False)
font.text(display, '湿度 ' + weather['humi'], 0, 32, show=False)
# font.text(display, weather['text'], 0, 48, show=True)
fb = framebuf.FrameBuffer(weather_ico.ico], 32, 32, framebuf.MONO_HLSB)
display.blit(fb, 80, 16)
display.show()'''
time.sleep_ms(250)
uart_gps.read()
display.rect(0, 0, 128, 16, 0, True)
font.text(display, "定位中。。。", 0, 0, show=True)
time.sleep_ms(250)
sync_start = gps_start = time.ticks_ms()
while True:
gps_process()
if wlan.isconnected():
if time.ticks_diff(time.ticks_ms(), sync_start) > (1000 * 3600):
sync_start = time.ticks_ms()
ntp_sync_time()
weather = qweather_weather_now()
if weather:
display.rect(0, 16, 112, 32, 0, True)
font.text(display, '温度 ' + weather['temp'], 0, 16, show=False)
font.text(display, '湿度 ' + weather['humi'], 0, 32, show=False)
# font.text(display, weather['text'], 0, 48, show=True)
fb = framebuf.FrameBuffer(weather_ico.ico], 32, 32, framebuf.MONO_HLSB)
display.blit(fb, 80, 16)
display.show()
if time.ticks_diff(time.ticks_ms(), gps_start) > (1000 * 60):
g_gps_status = GPS_STATUS_SYNC
if g_gps_status == GPS_STATUS_INIT:
g_gps_status = GPS_STATUS_IDLE
display.rect(0, 0, 128, 16, 0, True)
location = baidu_get_location()
if location:
display.rect(0, 0, 128, 16, 0, True)
display.text('E' + g_longi, 0, 0)
display.text('N' + g_lati, 0, 8)
#display.text(gps.longitude + str(round(gps.longitude, 2)), 0, 0)
#display.text(gps.latitude + str(round(gps.latitude, 2)), 0, 8)
font.text(display, location['district'], 63, 0, show=True)
weather = qweather_weather_now()
if weather:
display.rect(0, 16, 112, 32, 0, True)
font.text(display, '温度 ' + weather['temp'], 0, 16, show=False)
font.text(display, '湿度 ' + weather['humi'], 0, 32, show=False)
# font.text(display, weather['text'], 0, 48, show=True)
fb = framebuf.FrameBuffer(weather_ico.ico], 32, 32, framebuf.MONO_HLSB)
display.blit(fb, 80, 16)
display.show()
elif g_gps_status == GPS_STATUS_UPDATE:
g_gps_status = GPS_STATUS_IDLE
location = baidu_get_location()
if location:
display.rect(63, 0, 64, 16, 0, True)
font.text(display, loca['district'], 63, 0, show=True)
```
显示效果如下
##心得体会
首先非常开心参加这次活动,在此期间让我对 micropython 进一步认识和熟悉。当然也因为个人时间的问题,没能更深入的学习比较遗憾。
最后,感谢社区,感谢得捷!
##相应的代码,演示视频
[演示视频](https://training.eeworld.com.cn/video/36917 "演示视频")
这个项目感觉非常有个性呀,汉字的大小可以调节的吗? lugl4313820 发表于 2023-7-1 09:11
这个项目感觉非常有个性呀,汉字的大小可以调节的吗?
<p>可以调节,显示的时候有参数可选,但是这个字库支持的汉字有点少的,你可以github 上搜一下这个库 ufont</p>
<p>不知道这个室内效果如何。</p>
damiaa 发表于 2024-11-6 08:46
不知道这个室内效果如何。
<p>不太行,至少得放窗边</p>
ID.LODA 发表于 2024-11-7 09:31
不太行,至少得放窗边
<p><img height="48" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/wanwan41.gif" width="48" />知道了。谢谢。</p>
页:
[1]