在前一篇測試中我們已經將從 OpenWeatherMap 取得的氣象資料用自訂像素圖顯示在 SSD1306 顯示器上, 本篇則是要來複製趙英傑老師 "超圖解 Python 物聯網實作入門" 這本書第 18-2 節的做法, 利用 OpenWeatherMap 傳回值中的 icon 編號來顯示天氣概況圖, 溫濕度, 與氣壓等氣象資訊 (氣壓是我添加的, 書上範例僅顯示溫溼度).
氣象爬蟲做法參考 :
本系列之前的文章參考 :
本實驗所需的 icon 圖檔可從該書範例壓縮檔中取得, 網址如下 :
本篇測試所需的向量圖示 .bin 檔放在壓縮檔解開後的 ch18/icons 資料夾內 (共 17 個) :
其中最後一個 na.bin 不是 OpenWeatherMap 定義的圖示名稱, 而是此書作者特製, 用在萬一找不到天氣的圖示時顯示用的. 這些 .bin 檔都是用 wb 模式將圖示的 bytes 類型資料寫入二進位檔而成. 可用 Thonny 將整個 icons 資料夾上傳到開發板 (全部約 4.78KB) :
注意, 這些點陣像素圖檔轉成 byte 陣列時都是用 MVLSB 方式排列的, 用 framebuf 讀取時須指定為此格式.
首先匯入要用的模組與類別 :
MPY: soft reboot
MicroPython v1.23.0 on 2024-06-02; Generic ESP32 module with ESP32
Type "help()" for more information.
>>> import framebuf
>>> from machine import I2C, Pin
>>> import ssd1306
其中 framebuf 用來操控顯示緩衝區以便顯示點陣像素圖檔, 這在上一篇測試中已熟悉其用法. 接著是建立 SSD1306_I2C 物件, 同樣是使用 LOLIN D32 的 ESP32 開發板 :
>>> i2c=I2C(0, scl=Pin(22), sda=Pin(21))
>>> oled=ssd1306.SSD1306_I2C(128, 64, i2c)
先來顯示 02d (晴有雲) 這張圖檔, 以 rb 模式讀取後傳給 bytearray() 轉成 bytearray 類型, 然後以MVLSB 格式寫入緩衝顯示區, 然後呼叫 FrameBuffer 類別的靜態方法 blit() 顯示於螢幕 :
>>> icon_file='/icons/02d.bin'
>>> with open(icon_file, 'rb') as f:
icon=f.read()
oled.fill(0)
fb=framebuf.FrameBuffer(bytearray(icon), 48, 48, framebuf.MVLSB)
oled.framebuf.blit(fb, 0, 0)
oled.show()
結果如下 :
接下來要用之前寫好的 OpenWeatherMap 氣象爬蟲來取得高雄的即時天氣資料 :
def get_weather(country, city, api_key):
url=f'https://api.openweathermap.org/data/2.5/weather?q={city},{country}&units=metric&lang=zh_tw&appid={api_key}'
try:
res=urequests.get(url)
data=ujson.loads(res.text)
if data['cod']==200: # 注意是數值
ret={'geocode': data['id'],
'icon': data['weather'][0]['icon'],
'temperature': data['main']['temp'],
'pressure': data['main']['pressure'],
'humidity': data['main']['humidity']}
return ret
else:
return None
except Exception as e:
return None
先匯入 xtools 函式庫, 氣象爬蟲需要的 urequests 與 ujson 模組, 以及前一篇測試中用來顯示 16*16 大字型數字圖檔的自訂 big_simbol 模組 :
>>> import config
>>> import xtools
>>> import urequests
>>> import ujson
>>> import big_symbol
連上網路 :
>>> ip=xtools.connect_wifi(config.SSID, config.PASSWORD)
network config: ('192.168.50.164', '255.255.255.0', '192.168.50.1', '192.168.50.1')
定義呼叫爬蟲函式須要的變數 :
>>> weather_api_key=config.WEATHER_API_KEY
>>> city='Kaohsiung'
>>> country='TW'
呼叫 get_weather() :
>>> data=get_weather(country, city, weather_api_key)
>>> data
{'icon': '04d', 'temperature': 28.32, 'geocode': 1673820, 'pressure': 1007, 'humidity': 79}
其中 icon 為 04d (多雲), 我們要利用此屬性來載入 /icons 目錄下的 04d.bin 圖檔於顯示緩衝區後顯示於螢幕上. 先清除螢幕 :
>>> oled.fill(0) # (填滿 0 熄滅畫素)
>>> oled.show()
用 f 字串製作圖檔路徑 :
>>> icon_file=f'/icons/{data["icon"]}.bin'
>>> icon_file
'/icons/04d.bin'
在座標 (0, 15) 處開始顯示此天氣概況圖示 (保留兩列, 第一列用來顯示城市名稱) :
>>> with open(icon_file, 'rb') as f:
icon=f.read()
oled.fill(0)
fb=framebuf.FrameBuffer(bytearray(icon), 48, 48, framebuf.MVLSB)
oled.framebuf.blit(fb, 0, 15)
注意此處要指定 icon 點陣圖的排列方式為 MVLSB.
接下來要用大字型模組 big_symbol 以圖檔方式來顯示氣象資料, 由於天氣概況圖示寬度為 48px, 因此氣象資料會從 X 座標 50 開始顯示, 溫度 Y 座標從 10 開始, 因每個大字型是 16*16 解析度, 因此濕度從 Y=28 開始顯示 (10+16+2, 留 2px 間隔), 氣壓從 Y=46 開始顯示 (28+16+2, 留 2px 間隔).
先建立一個 Symbol 物件 :
>>> sb=big_symbol.Symbol(oled) # 建立 Symbol 物件
因傳回之溫度是小數點後兩位之浮點數, 占用寬度太寬, 所以用 round() 取整數 :
>>> temperature=round(data["temperature"])
>>> temperature
28
>>> sb.text(f'{temperature}c', 50, 10)
>>> sb.text(f'{data["humidity"]}%', 50, 28)
>>> sb.text(f'{data["pressure"]}hPa', 50, 46)
呼叫 show() 將顯示緩衝區資料輸出至螢幕 :
>>> oled.show()
最後在預留的前兩列顯示區的第一列開頭之 (0, 0) 座標顯示城市 'Kaohsiung' :
>>> oled.text(city, 0, 0, 1) # 在第一列顯示城市名稱
>>> oled.show()
注意, 此處文字要放在最後才輸出, 如果放在前面不會隨圖檔輸出一起顯示. 結果如下 :
可見氣壓因字串太長, 後面的單位 hPa 僅顯示 h 而已, 若值為 3 位數就會顯示 hP.
完整程式碼如下 :
# weather_ssd1306_4.py
import config
import xtools
import urequests
import ujson
from machine import I2C, Pin
import ssd1306
import framebuf
import big_symbol
import time
def get_weather(country, city, api_key):
url=f'https://api.openweathermap.org/data/2.5/weather?q={city},{country}&units=metric&lang=zh_tw&appid={api_key}'
try:
res=urequests.get(url)
data=ujson.loads(res.text)
if data['cod']==200: # 注意是數值
ret={'geocode': data['id'],
'icon': data['weather'][0]['icon'],
'temperature': data['main']['temp'],
'pressure': data['main']['pressure'],
'humidity': data['main']['humidity']}
return ret
else:
return None
except Exception as e:
return None
ip=xtools.connect_wifi(config.SSID, config.PASSWORD)
if ip:
# 設定氣象爬蟲參數
weather_api_key=config.WEATHER_API_KEY
city='Kaohsiung'
country='TW'
# 建立 I2C 與 SSD1306_I2C 物件
i2c=I2C(0, scl=Pin(22), sda=Pin(21)) # ESP32 I2C
oled=ssd1306.SSD1306_I2C(128, 64, i2c)
sb=big_symbol.Symbol(oled)
while True: # 每分鐘抓一次更新 OLED 顯示器
data=get_weather(country, city, weather_api_key)
if data:
sb.clear()
icon_file=f'/icons/{data["icon"]}.bin'
with open(icon_file, 'rb') as f:
icon=f.read()
oled.fill(0)
fb=framebuf.FrameBuffer(bytearray(icon), 48, 48, framebuf.MVLSB)
oled.framebuf.blit(fb, 0, 15)
temperature=round(data["temperature"])
sb.text(f'{temperature}c', 50, 10)
sb.text(f'{data["humidity"]}%', 50, 28)
sb.text(f'{data["pressure"]}hPa', 50, 46)
oled.show()
oled.text(city, 0, 0, 1)
oled.show()
time.sleep(60)
else:
print('無法連線 WiFi')
以上實驗所有檔案 zip 壓縮檔可在 GitHub 下載 :
注意, 上傳開發板之前須先編輯 config.py 設定 SSID, PASSWORD, 以及 OpenWeatherMap 的 API Key 等資料.
2024-10-23 補充 :
上面的程式碼在 OpenWeatherMap 傳回的 icon 萬一不存在時會出現檔案讀取錯誤, 原作者為了避免此問題發生製作了一個 na.bin (找不到圖檔之圖示), 可以用 os.stat() 先檢查檔案路徑存不存在, 存在就傳回該天氣概況之圖檔路徑, 否則傳回 na.bin 的路徑, 程式碼修改如下 :
#
import config
import xtools
import urequests
import ujson
from machine import I2C, Pin
import ssd1306
import framebuf
import big_symbol
import time
import os
def get_weather(country, city, api_key):
url=f'https://api.openweathermap.org/data/2.5/weather?q={city},{country}&units=metric&lang=zh_tw&appid={api_key}'
try:
res=urequests.get(url)
data=ujson.loads(res.text)
if data['cod']==200: # 注意是數值
ret={'geocode': data['id'],
'icon': data['weather'][0]['icon'],
'temperature': data['main']['temp'],
'pressure': data['main']['pressure'],
'humidity': data['main']['humidity']}
return ret
else:
return None
except Exception as e:
return None
def check_icon(icon):
try:
os.stat(icon) # 檢查圖檔是否存在
return icon
except:
return '/icons/na.bin' # 圖
ip=xtools.connect_wifi(config.SSID, config.PASSWORD)
if ip:
# 設定氣象爬蟲參數
weather_api_key=config.WEATHER_API_KEY
city='Kaohsiung'
country='TW'
# 建立 I2C 與 SSD1306_I2C 物件
i2c=I2C(0, scl=Pin(22), sda=Pin(21)) # ESP32 I2C
oled=ssd1306.SSD1306_I2C(128, 64, i2c)
sb=big_symbol.Symbol(oled)
while True: # 每分鐘抓一次更新 OLED 顯示器
data=get_weather(country, city, weather_api_key)
if data:
sb.clear()
icon_file=check_icon(f'/icons/{data["icon"]}.bin')
with open(icon_file, 'rb') as f:
icon=f.read()
oled.fill(0)
fb=framebuf.FrameBuffer(bytearray(icon), 48, 48, framebuf.MVLSB)
oled.framebuf.blit(fb, 0, 15)
temperature=round(data["temperature"])
sb.text(f'{temperature}c', 50, 10)
sb.text(f'{data["humidity"]}%', 50, 28)
sb.text(f'{data["pressure"]}hPa', 50, 46)
oled.show()
oled.text(city, 0, 0, 1)
oled.show()
time.sleep(60)
else:
print('無法連線 WiFi')
黃底色為修改或新增的部分 (注意新增 import os). 另外溫度顯示也可以放寬到小數點後一位, 只要將 round(data["temperature"]) 改為 round(data["temperature"], 1) 即可.
沒有留言 :
張貼留言