昨天搞定 SSD1306 OLED 顯示器的驅動程式問題後, 想要用它來顯示從 OpenWeatherMap 抓取的高雄氣象資料, 氣象爬蟲做法參考 :
本系列之前的文章參考 :
MicroPython 相關文章索引 :
SSD1306 教學文件參考 :
基於此項實驗使用較多記憶體, 因此使用 ESP32 開發板, 此次使用 LOLIN D32, 同時將連網 SSID 帳密與 OpenWeatherMap 的 API key 寫在 config.py 檔案內. 首先匯入 config 與 xtools 模組連網 :
MicroPython v1.23.0 on 2024-06-02; Generic ESP32 module with ESP32
Type "help()" for more information.
>>> import config
>>> import xtools
>>> ip=xtools.connect_wifi(config.SSID, config.PASSWORD)
Connecting to network...
network config: ('192.168.50.94', '255.255.255.0', '192.168.50.1', '192.168.50.1')
接著匯入 urequests 與 ujson 模組, 從 config.py 取出 OpenWeaherMap API key, 定義 city 與 country 變數將其嵌入 API 網址中, 然後呼叫 urequests.get() 傳入 API 網址即可取得氣象資料 :
>>> import urequests
>>> import ujson
>>> weather_api_key=config.WEATHER_API_KEY
>>> city='Kaohsiung'
>>> country='TW'
>>> url=f'https://api.openweathermap.org/data/2.5/weather?q={city},{country}&units=metric&lang=zh_tw&appid={weather_api_key}'
>>> r=urequests.get(url)
>>> data=ujson.loads(r.text) # 轉成字典
>>> data
{'timezone': 28800, 'cod': 200, 'dt': 1728572708, 'base': 'stations', 'weather': [{'id': 803, 'icon': '04n', 'main': 'Clouds', 'description': '\u591a\u96f2'}], 'sys': {'country': 'TW', 'sunrise': 1728510789, 'sunset': 1728553082, 'id': 2002588, 'type': 2}, 'name': 'Kaohsiung City', 'clouds': {'all': 75}, 'coord': {'lon': 120.3133, 'lat': 22.6163}, 'visibility': 10000, 'wind': {'speed': 2.57, 'deg': 40}, 'id': 1673820, 'main': {'feels_like': 26.33, 'pressure': 1012, 'temp_max': 25.97, 'temp': 25.58, 'temp_min': 25.58, 'humidity': 82, 'sea_level': 1012, 'grnd_level': 1011}}
我們感興趣的氣象資料是溫溼度與氣壓 :
>>> temperature=data['main']['temp']
>>> temperature
25.58
>>> humidity=data['main']['humidity']
>>> humidity
82
>>> pressure=data['main']['pressure']
>>> pressure
1012
氣候狀況描述會傳回該地區語言的 Unicode, 用 print() 即可解碼 :
>>> data['weather'][0]['description']
'\u591a\u96f2'
>>> print(data['weather'][0]['description'])
多雲
但中文無法在 SSD1306 顯示, 但可以用天氣圖示表示, OpenWeatherMap 定義了 17 種天氣概況圖示並與已命名, 放在 data['weather'][0]['icon'] :
>>> data['weather'][0]['icon']
'04n'
這個編號後續測試會用到.
接下來就可以將這些資料輸出至 SSD1306 顯示, 先匯入 machine 模組下的 Pin 與 I2C 類別, 以及 ssd1306 模組 (鑰先上傳開發板) :
>>> from machine import I2C, Pin
>>> import ssd1306
呼叫 I2C 類別之建構式 I2C() 並傳入 I2C 的兩個接腳 SDA 與 SCL 之 Pin 物件以建立 i2c 物件 :
>>> i2c=I2C(0, scl=Pin(22), sda=Pin(21)) # ESP32 I2C
建立代表 OLED 顯示器的 SSD1306_I2C 物件 :
>>> oled=ssd1306.SSD1306_I2C(128, 64, i2c) # 0.96 吋解析度 128*64
此物件有如下常用方法 :
SSD1306_I2C 物件的方法 | 說明 |
fill(col) | 將顯示記憶體全部畫素填入 col=1 (亮) 或 0 (暗) |
pixel(x, y, col) | 在顯示記憶體指定畫素位置 (x, y) 填入 col=1 (亮) 或 0 (暗) |
text(string, x, y, col=1) | 在顯示記憶體指定畫素位置 (x, y) 起填入預設 col=1 之字串 string |
show() | 將顯示記憶體內容輸出於面板顯示內容 |
scroll(dx, dy) | 將顯示記憶體畫素內容向上下 (dy) 或向左右 (dx) 捲動 |
invert(state) | state=True (反白), False (取消反白) |
先點亮 OLED 畫面 :
>>> oled.fill(1) # 將 1 填滿顯示緩衝器 (點亮畫素)
>>> oled.show()
然後呼叫 text(x, y, on) 在第一列顯示城市, 其中 x, y 為座標, on 表示畫素點亮與否, 1=點亮, 0= 熄滅, 因上面我們將 OLED 每個畫素都點亮, 故顯示文字時需將其熄滅 :
>>> oled.text(city, 0, 0, 0)
在第二列顯示縣在時間, 先呼叫 xtools.tw_now() 取得時間再傳給 oled.text() :
>>> now=xtools.tw_now()
Querying NTP server and set RTC time ... OK.
>>> now
'2024-10-10 23:39:13'
>>> oled.text(now, 0, 8, 0)
注意, SSD1306 解析度 128*64, text() 方法以 8*8 畫素繪製一個字元, 因此每列可顯示 128/8=16 個字元, 共可顯示 64/8=8 列文字, 往下移一列時 Y 座標加 8.
第 3~5 列分別顯示溫溼度與氣壓 :
>>> oled.text(f'Temperature:{temperature}', 0, 16, 0)
>>> oled.text(f'Humidity:{humidity}', 0, 24, 0)
>>> oled.text(f'Pressure:{pressure}', 0, 32, 0)
最後需呼叫 show() 方法才會顯示 :
>>> oled.show()
結果如下 :
不過亮背景方式較耗電, 應改採用暗背景亮文字方式較省電, 這時要先熄滅所有畫素, 呼叫 text() 方法時第三參數需傳入 1 點亮畫素 :
>>> oled.fill(0) # 將 0 填滿顯示緩衝器 (熄滅畫素)
>>> oled.text(city, 0, 0, 1)
>>> oled.text(now, 0, 8, 1)
>>> oled.text(f'Temperature:{temperature}', 0, 16, 1)
>>> oled.text(f'Humidity:{humidity}', 0, 24, 1)
>>> oled.text(f'Pressure:{pressure}', 0, 32, 1)
>>> oled.show()
結果如下 :
SSD1306 只能顯示 ASCII 字元, 因此天氣狀況描述 (中文 Unicode) 在缺乏中文字元集解碼情況下無法正常顯示 :
>>> oled.text(f'Pressure:{pressure}', 0, 40, 1)
>>> oled.show()
結果顯示一個長直條 :
以上測試之完整程式碼如下 :
# weather_ssd1306_1.py
import config
import xtools
import urequests
import ujson
from machine import I2C, Pin
import ssd1306
ip=xtools.connect_wifi(config.SSID, config.PASSWORD)
weather_api_key=config.WEATHER_API_KEY
city='Kaohsiung'
country='TW'
url=f'https://api.openweathermap.org/data/2.5/weather?q={city},{country}&units=metric&lang=zh_tw&appid={weather_api_key}'
r=urequests.get(url)
data=ujson.loads(r.text) # 轉成字典
temperature=data['main']['temp']
humidity=data['main']['humidity']
pressure=data['main']['pressure']
description=data['weather'][0]['description']
now=xtools.tw_now()
i2c=I2C(0, scl=Pin(22), sda=Pin(21)) # ESP32 I2C
oled=ssd1306.SSD1306_I2C(128, 64, i2c) # 0.96 吋解析度 128*64
oled.fill(0) # 將 0 填滿顯示緩衝器 (熄滅畫素)
oled.text(city, 0, 0, 1)
oled.text(now, 0, 8, 1)
oled.text(f'Temperature:{temperature}', 0, 16, 1)
oled.text(f'Humidity:{humidity}', 0, 24, 1)
oled.text(f'Pressure:{pressure}', 0, 32, 1)
oled.show()
但此程式只能顯示抓取一次氣象資料, 應該週期性地 (例如每分鐘) 去 OpenWeatherMap 抓資料後更新 SSD1306 才實用. 首先將之前寫的天氣爬蟲函式改寫如下 :
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
參考 :
先在互動環境測試此函式 :
>>> weather_api_key=config.WEATHER_API_KEY
>>> city='Kaohsiung'
>>> country='TW'
>>> data=get_weather(country, city, weather_api_key)
>>> data
{'icon': '04d', 'temperature': 25.62, 'geocode': 1673820, 'pressure': 1013, 'humidity': 87}
然後將輸出至顯示器的功能寫成一個 show_weather() 函式 :
def show_weather(oled, data):
oled.fill(0) # (填滿 0 熄滅畫素)
oled.text(data['city'], 0, 0, 1)
oled.text(data['time'], 0, 8, 1)
oled.text(f"Temperature:{data['temperature']}", 0, 16, 1)
oled.text(f"Humidity:{data['humidity']}", 0, 24, 1)
oled.text(f"Pressure:{data['pressure']}", 0, 32, 1)
oled.show()
此函式要傳入一個 SSD1306_I2C 物件 oled 與要顯示之資料字典 data, 這主要是在上面呼叫 get_weather() 時取得的傳回值上添加城市 city 與時間 time 這兩個鍵 :
>>> data['time']=xtools.tw_now()
Querying NTP server and set RTC time ... OK.
>>> data['city']=city
>>> data
{'pressure': 1012, 'time': '2024-10-11 08:25:10', 'temperature': 26.68, 'city': 'Kaohsiung', 'humidity': 84, 'geocode': 1673820, 'icon': '04d'}
呼叫 show_weather() 測試 OK :
>>> show_weather(oled, data)
接下來只要匯入 time 模組, 然後用一個無窮迴圈每 60 秒去 OpenWeatherMap 抓一次資料來更新 OLED 顯示器即可 :
>>> import time
>>> while True: # 每分鐘抓一次更新 OLED 顯示器
data=get_weather(country, city, weather_api_key)
data['time']=xtools.tw_now() # 新增或更新欄位
data['city']=city # 新增或更新欄位
show_weather(oled, data)
time.sleep(60)
完整程式碼如下 :
# weather_ssd1306_2.py
import config
import xtools
import urequests
import ujson
from machine import I2C, Pin
import ssd1306
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
def show_weather(oled, data):
oled.fill(0) # (填滿 0 熄滅畫素)
oled.text(data['city'], 0, 0, 1)
oled.text(data['time'], 0, 8, 1)
oled.text(f"Temperature:{data['temperature']}", 0, 16, 1)
oled.text(f"Humidity:{data['humidity']}", 0, 24, 1)
oled.text(f"Pressure:{data['pressure']}", 0, 32, 1)
oled.show()
def main():
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)
while True: # 每分鐘抓一次更新 OLED 顯示器
data=get_weather(country, city, weather_api_key)
data['time']=xtools.tw_now() # 新增或更新欄位
data['city']=city # 新增或更新欄位
show_weather(oled, data)
time.sleep(60)
else:
print('無法連線 WiFi')
if __name__ == '__main__':
main()
比較系統化的結構是把連線 WiFi 的功能寫在 main.py :
# main.py
import xtools
import config
import weather_ssd1306
ip=xtools.connect_wifi(config.SSID, config.PASSWORD)
mac=xtools.get_id()
print('ip: ', ip)
print('mac: ', mac)
if not ip: # 連線失敗傳回 None
print('無法連線 WiFi 基地台')
else: # 連線成功傳回 ip 字串: 執行 app
weather_ssd1306.main() # 連線成功才執行 App 程式
然後把上面的 weather_ssd1306_test_2.py 改寫為下面的 weather_ssd1306.py :
# weather_ssd1306.py
import config
import xtools
import urequests
import ujson
from machine import I2C, Pin
import ssd1306
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
def show_weather(oled, data):
oled.fill(0) # (填滿 0 熄滅畫素)
oled.text(data['city'], 0, 0, 1)
oled.text(data['time'], 0, 8, 1)
oled.text(f"Temperature:{data['temperature']}", 0, 16, 1)
oled.text(f"Humidity:{data['humidity']}", 0, 24, 1)
oled.text(f"Pressure:{data['pressure']}", 0, 32, 1)
oled.show()
def main():
# 設定氣象爬蟲參數
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)
while True: # 每分鐘抓一次更新 OLED 顯示器
data=get_weather(country, city, weather_api_key)
data['time']=xtools.tw_now() # 新增或更新欄位
data['city']=city # 新增或更新欄位
show_weather(oled, data) # 更新 OLED 顯示
time.sleep(60)
if __name__ == '__main__':
main()
注意, 此結構須按開發板的 Reset 鈕硬啟動才會執行, Thonny 的 "執行/重新啟動後端程式" 只是軟啟動 (soft restart) 不會執行 boot.py, 所以也不會去執行 main.py. 這些程式壓縮檔可從 GitHub 下載 :
注意, 上傳到 ESP32 開發板之前須先編輯 config.py, 填入 WiFi 基地台的 SSID 與密碼, 以及向 OpenWeatherMap 取得的 API key.
沒有留言:
張貼留言