from machine import Pin, I2C,unique_id
import usocket as socket
import utime
import ubinascii
import sys, os
import ujson
import ssd1306 # OLED
import wlan
try:
import ussl as ssl
except:
import ssl
INTERVAL = 1000 # millisecond
oled_width = 128
oled_height = 64
# === API 設定 ===
host = "api.open-meteo.com" # 伺服器網址,不可動
port = 443 # https
lat = None
lon = None
time = None
temp = None
wind_speed = None
if(sys.platform=="esp8266"):
led_pin = 2 # GPIO2(D4)
I2C_SDA =4 # GPIO4(D2)
I2C_SCL =5 # GIPO5(D1)
i2c = I2C(scl=Pin(I2C_SCL), sda=Pin(I2C_SDA), freq=10000)
button_pin = 4 # GPIO4(D2)
elif(sys.platform=="esp32"):
led_pin = 2
I2C_SDA =21 # GPIO21(I2C SDA)
I2C_SCL =22 # GPIO22(I2C SCL)
i2c = I2C(0, scl=Pin(I2C_SCL), sda=Pin(I2C_SDA), freq=400000)
button_pin = 35
elif(sys.platform=="rp2"):
led_pin = "LED"
I2C_SDA =16 # GPIO16(I2C SDA)
I2C_SCL =17 # GPIO17(I2C SCL)
i2c = I2C(0, scl=Pin(I2C_SCL), sda=Pin(I2C_SDA), freq=400000)
button_pin = 21
# === 取得 API JSON ===
def get_api_json(url):
request = "GET {} HTTP/1.0\r\nHost: {}\r\n\r\n".format(url, host)
try:
addr = socket.getaddrinfo(host, port)[0][-1] # 取得連線到伺服器的相關訊息
Socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create STREAM TCP socket
Socket1.connect(addr) # 與伺服器進行連線
utime.sleep_ms(1000)
ssl_sock = ssl.wrap_socket(Socket1) # SSL wrap
ssl_sock.write(request.encode()) # send data
utime.sleep_ms(1000)
# 讀取回應
response = b""
while True:
data = ssl_sock.read(1024)
if not data:
break
response += data
ssl_sock.close()
# 解析 HTTP header 與 JSON
response = response.decode()
json_start = response.find("{")
json_data = response[json_start:]
# print(response)
return ujson.loads(json_data)
except Exception as e:
print("讀取失敗:", e)
return None
def print_location(data):
# 檢查是否有搜尋結果
if "results" in data and len(data["results"]) > 0:
first_result = data["results"][0]
lat = first_result["latitude"]
lon = first_result["longitude"]
name = first_result["name"]
country = first_result["country"]
if lat is not None and lon is not None:
print("-----------------------------------")
print(f"地名 (Name): {name} ({country})")
print(f"緯度 (Latitude): {lat}")
print(f"經度 (Longitude): {lon}")
print("-----------------------------------")
else:
print("Failed to get coordinates.")
return lat, lon, name, country
else:
print("No location results found.")
return None, None, None, None
# === 解析並顯示天氣資訊 ===
def print_weather(data):
global time,temp,wind_speed,weather_code
if not data:
print("沒有資料可顯示")
return
if data and "current_weather" in data:
current = data["current_weather"]
time = current['time']
temp = current['temperature']
wind_speed = current['windspeed']
weather_code = current['weathercode']
weather_desc = decode_weathercode(weather_code)
print("-----------------------------------")
print(f"目前溫度 (Temperature): {temp} °C")
print(f"風速 (Windspeed): {wind_speed} km/h")
print(f"風向 (Winddirection): {current['winddirection']}°")
print(f"天氣現象 (Weathercode): {weather_desc} (Weathercode: {weather_code})")
print(f"更新時間 (Time): {time}")
print("-----------------------------------")
else:
print("Failed to retrieve weather data.")
def decode_weathercode(code):
weather_map = {
0: "晴朗無雲",
1: "晴時多雲",
2: "局部多雲",
3: "陰天",
45: "有霧",
48: "霧淞",
51: "輕微毛毛雨",
53: "毛毛雨",
55: "濃密毛毛雨",
56: "輕微凍毛毛雨",
57: "凍毛毛雨",
61: "小雨",
63: "中雨",
65: "大雨",
66: "輕微凍雨",
67: "凍雨",
71: "小雪",
73: "中雪",
75: "大雪",
77: "雪粒",
80: "輕微陣雨",
81: "陣雨",
82: "強烈陣雨",
85: "輕微陣雪",
86: "陣雪",
95: "雷雨",
96: "雷雨伴隨輕微冰雹",
99: "雷雨伴隨大冰雹"
}
return weather_map.get(code, f"未知代碼 ({code})")
def print_AQI(data):
if data and "current" in data:
current = data["current"]
us_aqi = current.get("us_aqi", "N/A")
pm2_5 = current.get("pm2_5", "N/A")
pm10 = current.get("pm10", "N/A")
eu_aqi = current.get("european_aqi", "N/A")
print("-----------------------------------")
print(f"PM 2.5 濃度: {pm2_5} μg/m³")
print(f"PM 10 濃度: {pm10} μg/m³")
print(f"美國 AQI 指數: {us_aqi} -> {get_us_aqi_level(us_aqi) if us_aqi != 'N/A' else ''}")
print(f"歐洲 AQI 指數: {eu_aqi}")
print(f"數據時間: {current.get('time', 'N/A')}")
print("-----------------------------------")
else:
print("Failed to retrieve air quality data.")
# ---------------------------------------------------------------------
# 美國 AQI 等級判定輔助函式
# ---------------------------------------------------------------------
def get_us_aqi_level(aqi):
if aqi <= 50:
return "良好 (Good)"
elif aqi <= 100:
return "普通 (Moderate)"
elif aqi <= 150:
return "對敏感族群不健康 (Unhealthy for Sensitive Groups)"
elif aqi <= 200:
return "不健康 (Unhealthy)"
elif aqi <= 300:
return "非常不健康 (Very Unhealthy)"
else:
return "危害 (Hazardous)"
# Function to handle button press interrupt (optional)
def button_press(pin):
global button_down
button_down = True
# Start Function
if __name__ == '__main__':
print(os.uname())
print("Hello, " + os.uname().sysname + "!")
# Unique ID
CLIENT_ID = ubinascii.hexlify(unique_id())
print("Unique ID: ", end="")
print(CLIENT_ID)
# Define GPIO pins for LED and push button
led = Pin(led_pin, Pin.OUT)
led.off()
button = Pin(button_pin, Pin.IN, Pin.PULL_UP) # 按鍵接在 GPxx, Internal pull-up resistor
button_down = False
# Attach interrupt to the push button pin
button.irq(trigger=Pin.IRQ_FALLING, handler=button_press)
devices = i2c.scan() # I2C scanning
if len(devices) != 0:
print('Number of I2C devices found=',len(devices))
for device in devices:
print("Device Hexadecimel Address= ",hex(device))
if devices.count(0x3c) > 0:
oled = ssd1306.SSD1306_I2C(oled_width, oled_height, i2c)
print(f"SSD1306 found at I2C address {0x3c:#x}")
OLED_Found = True
else:
OLED_Found = False
else:
OLED_Found = False
wlan.connect_wifi() # Connecting to WiFi Router
# https://geocoding-api.open-meteo.com/v1/search?name=Taipei&count=1&language=zh&format=json
host = "geocoding-api.open-meteo.com" # 伺服器網址,不可動
path = f"/v1/search?name=Taipei&count=1&language=zh&format=json"
data = get_api_json(path)
lat, lon, name, country = print_location(data)
lastTime = utime.ticks_ms()
print("Please press the button to obtain Open-Meteo weather data.")
while True:
try:
currTime = utime.ticks_ms()
if (currTime - lastTime > INTERVAL):
led.value(not led.value())
# led.toggle()
lastTime = currTime
if button_down == True:
led.on()
print("\nFetching weather data from Open-Meteo...")
wlan.connect_wifi() # Connecting to WiFi Router
# https://air-quality-api.open-meteo.com/v1/air-quality?latitude=25.03&longitude=121.56¤t=pm2_5,pm10,us_aqi,european_aqi&timezone=Asia/Taipei
host = "air-quality-api.open-meteo.com"
# 抓取即時 PM2.5, PM10, 美國 AQI, 歐洲 AQI
path = f"/v1/air-quality?latitude={lat}&longitude={lon}¤t=pm2_5,pm10,us_aqi,european_aqi&timezone=Asia/Taipei"
data = get_api_json(path)
print_AQI(data)
# http://api.open-meteo.com/v1/forecast?latitude=25.03&longitude=121.56¤t_weather=true&timezone=Asia/Taipei
host = "api.open-meteo.com" # 伺服器網址,不可動
path = f"/v1/forecast?latitude={lat}&longitude={lon}¤t_weather=true&timezone=Asia/Taipei"
data = get_api_json(path)
print_weather(data)
if (OLED_Found == True):
oled.contrast(255)
oled.text("Latitude:"+str(lat),0,0)
oled.text("Longitude:"+str(lon),0,10)
oled.text("Time:"+str(time),0,20)
oled.text("Temp:"+str(temp),0,30)
oled.text("Wind Speed:"+str(wind_speed),0,40)
oled.show()
button_down = False
led.off()
# Do other tasks while waiting for button press
utime.sleep_ms(100)
except Exception as e:
print(e)
except KeyboardInterrupt: # 使用者中斷執行(通常是輸入^C)
print("Stop")