为了充分发挥ESP32的WiFi功能,很多开发者通常会制作桌面天气日历摆件,而M5CoreS3的造型方方正正的,正适合做一个桌面摆件
我个人比较手残,画不好比较漂亮的UI设计,而且我也不太熟悉图形化编程,因此本项目的开发基于MicroPython
首先为开发板烧录UIFlow2.0,这主要是为了引入M5GFX的库

烧录完成后打开Thonny,可以看到识别成功(事实上,使用UiFlow2.0进行开发其背后同样是基于MicroPython的)

有一个开源网址http://t.weather.itboy.net/api/weather/city/+你的城市编号,可以得到当地的天气和天气预报,以上海为例

为了实现这些信息的获取,我们需要使用WiFi;由于我们地处东八区,因此时间计算的时候需要在格林尼治时间的基础上加上8小时,编写基础信息参数如下
WIFI_SSID = "******" WIFI_PSWD = "******" CITY_CODE = "101020100" # 上海 WEATHER_URL = "http://t.weather.itboy.net/api/weather/city/" + CITY_CODE TZ_OFFSET = 8 * 3600 # 上海 UTC+8 CLOCK_REFRESH_MS = 1000 WEATHER_REFRESH_MS = 15 * 60 * 1000
编写全局状态用于后续修改和调用
g_weather = {"ok": False, "city": "", "weatherCN": "", "tempC": "",
"humidity": "", "aqi": -1, "updateTime": ""}
last_weather_ms = 0
last_clock_ms = 0编写工具函数用于WiFi连接、时间计算、天气转换、字符颜色随天气特征变化、天气参数获取等
def wifi_connect(ssid, pswd, timeout_ms=15000):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, pswd)
t0 = time.ticks_ms()
while not wlan.isconnected() and time.ticks_diff(time.ticks_ms(), t0) < timeout_ms:
time.sleep_ms(250)
return wlan.isconnected()
def setup_ntp():
try:
import ntptime
ntptime.host = "ntp1.aliyun.com"
ntptime.settime() # 设置 RTC(UTC)
except:
pass
def localtime():
return time.localtime(time.time() + TZ_OFFSET)
def weekday_name(wd):
return ("Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday")[wd % 7]
def is_wifi_ok():
try:
return network.WLAN(network.STA_IF).isconnected()
except:
return False
def map_cn_to_en(cn):
t = cn or ""
if "晴" in t: return "Sunny"
if "多云" in t: return "Cloudy"
if "阴" in t: return "Overcast"
if "雷" in t: return "Thunderstorm"
if "雪" in t: return "Snow"
if "雨夹雪" in t: return "Sleet"
if "雨" in t: return "Rain"
return "—"
def color_from_aqi(aqi):
if aqi < 0: return 0xAAAAAA
if aqi <= 50: return 0x55FF55
if aqi <= 100: return 0xFCE000
if aqi <= 150: return 0xFF9900
if aqi <= 200: return 0xFF4444
if aqi <= 300: return 0x9955FF
return 0x663300
def to_int(val, default=None):
try:
# 兼容 "27" / 27 / None / "" 等
return int(str(val).strip())
except:
return default
def fetch_weather():
global g_weather, last_weather_ms
try:
resp = requests2.get(WEATHER_URL)
data = resp.json()
resp.close()
d = data.get("data", {}) or {}
fc = d.get("forecast", [{}])
fst = fc[0] if isinstance(fc, list) and fc else {}
# 先从 data.aqi 取;没有再尝试 forecast[0].aqi;还没有就保持 None
aqi_val = to_int(d.get("aqi"), default=None)
if aqi_val is None:
aqi_val = to_int(fst.get("aqi"), default=None)
g_weather["city"] = (data.get("cityInfo", {}) or {}).get("city", "")
g_weather["tempC"] = str(d.get("wendu", ""))
g_weather["humidity"] = str(d.get("shidu", ""))
g_weather["aqi"] = aqi_val if aqi_val is not None else -1
g_weather["quality"] = str(d.get("quality", "")) # 例如 “优/良/轻度污染”
g_weather["weatherCN"] = str(fst.get("type", ""))
g_weather["updateTime"] = str(data.get("time", ""))
g_weather["ok"] = True
last_weather_ms = time.ticks_ms()
return True
except:
g_weather["ok"] = False
return False设计UI界面
def draw_hline(y, color):
M5.Display.fillRect(0, y, M5.Display.width(), 1, color)
def draw_header():
M5.Display.fillRect(0, 0, M5.Display.width(), 26, 0x102030)
M5.Display.setTextColor(0xFFFFFF, 0x102030)
M5.Display.setCursor(8, 4)
M5.Display.print("CoreS3 Weather Clock (MicroPython)")
def draw_clock_area():
y0, h = 26, 74
M5.Display.fillRect(0, y0, M5.Display.width(), h, 0x000000)
lt = localtime()
hhmmss = "%02d:%02d:%02d" % (lt[3], lt[4], lt[5])
ymdw = "%04d-%02d-%02d %s" % (lt[0], lt[1], lt[2], weekday_name(lt[6]))
# 时间
M5.Display.setTextColor(0xFFFFFF, 0x000000)
M5.Display.setCursor(80, y0 + 16)
M5.Display.print(hhmmss)
# 日期
M5.Display.setTextColor(0xCCCCCC, 0x000000)
M5.Display.setCursor(80, y0 + 36)
M5.Display.print(ymdw)
# 联网状态
M5.Display.setTextColor(0x55FF55 if is_wifi_ok() else 0xFF5555, 0x000000)
M5.Display.setCursor(8, y0 + h - 18)
M5.Display.print("Wi-Fi OK" if is_wifi_ok() else "Offline")
def draw_weather_icon(x, y, size, cn):
import math
def sun(cx, cy, r):
M5.Display.fillCircle(cx, cy, r, 0xFDB813)
for i in range(8):
a = i * math.pi / 4
x1 = int(cx + math.cos(a) * (r + 2))
y1 = int(cy + math.sin(a) * (r + 2))
x2 = int(cx + math.cos(a) * (r + 10))
y2 = int(cy + math.sin(a) * (r + 10))
M5.Display.drawLine(x1, y1, x2, y2, 0xFDB813)
def cloud(cx, cy, w, h, col):
M5.Display.fillEllipse(cx, cy, w//2, h//2, col)
M5.Display.fillEllipse(cx - w//3, cy + 2, w//3, h//3, col)
M5.Display.fillEllipse(cx + w//3, cy + 2, w//3, h//3, col)
M5.Display.fillRect(cx - w//2, cy, w, h//2, col)
def drop(rx, ry, r, col):
M5.Display.fillTriangle(rx, ry - r, rx - r//2, ry, rx + r//2, ry, col)
M5.Display.fillCircle(rx, ry, r//2, col)
def bolt(lx, ly, s, col):
M5.Display.fillTriangle(lx, ly, lx + s, ly + s//2, lx + s//2, ly + s//2, col)
M5.Display.fillTriangle(lx + s//2, ly + s//2, lx + s*3//2, ly + s*3//2, lx + s, ly + s*3//2, col)
t = cn or ""
cloudCol = 0xB0B8C0
if "晴" in t:
sun(x + size//2, y + size//2, size//3)
elif "多云" in t:
sun(x + size//3, y + size//3, size//5)
cloud(x + size//2, y + size//2 + 6, size, size//2, cloudCol)
elif "阴" in t:
cloud(x + size//2, y + size//2 + 6, size, size//2, 0x808890)
elif "雷" in t:
cloud(x + size//2, y + size//2, size, size//2, cloudCol)
bolt(x + size//2 + 8, y + size//2 + 4, 10, 0xFCE000)
elif "雪" in t:
cloud(x + size//2, y + size//2, size, size//2, cloudCol)
for i in (-1,0,1):
sx = x + size//2 + i*12
sy = y + size - 8 + (2 if i==0 else 0)
M5.Display.drawLine(sx - 4, sy, sx + 4, sy, 0xFFFFFF)
M5.Display.drawLine(sx, sy - 4, sx, sy + 4, 0xFFFFFF)
elif "雨" in t:
cloud(x + size//2, y + size//2, size, size//2, cloudCol)
drop(x + size//2 - 12, y + size - 6, 6, 0x55AAFF)
drop(x + size//2, y + size - 2, 6, 0x55AAFF)
drop(x + size//2 + 12, y + size - 6, 6, 0x55AAFF)
else:
cloud(x + size//2, y + size//2, size, size//2, cloudCol)
def draw_weather_area():
y0 = 110
h = M5.Display.height() - y0
M5.Display.fillRect(0, y0, M5.Display.width(), h, 0x000000)
card = 0x081018
M5.Display.fillRoundRect(10, y0 + 6, M5.Display.width()-20, h-12, 10, card)
if not g_weather["ok"]:
M5.Display.setTextColor(0xFCE000, card)
M5.Display.setCursor(20, y0 + 14)
M5.Display.print("Weather: unavailable")
M5.Display.setTextColor(0xFFFFFF, card)
M5.Display.setCursor(20, y0 + 36)
M5.Display.print("Tap right side to retry.")
return
M5.Display.setTextColor(0xFFFFFF, card)
M5.Display.setCursor(20, y0 + 10)
M5.Display.print("{} | {}".format(g_weather["city"], g_weather["updateTime"]))
draw_weather_icon(30, y0 + 38, 40, g_weather["weatherCN"])
M5.Display.setTextColor(0xFFFFFF, card)
M5.Display.setCursor(90, y0 + 44)
M5.Display.print("Temperature: {} C {}".format(g_weather["tempC"], map_cn_to_en(g_weather["weatherCN"])))
M5.Display.setTextColor(0xA0D8F0, card)
M5.Display.setCursor(90, y0 + 69)
M5.Display.print("Humidity: {}".format(g_weather["humidity"]))
M5.Display.setCursor(90, y0 + 94)
M5.Display.setTextColor(color_from_aqi(g_weather["aqi"]), card)
M5.Display.print("AQI: {}".format(g_weather["aqi"] if g_weather["aqi"] >= 0 else "--"))
M5.Display.setTextColor(0x888888, card)
M5.Display.setCursor(M5.Display.width() - 180, y0 + h - 22)主流程和入口函数
def setup():
M5.begin()
Widgets.fillScreen(0x222222)
M5.Display.setRotation(1) # 横屏
M5.Display.setBrightness(180)
draw_header()
draw_hline(26, 0x334455)
draw_hline(140, 0x334455)
M5.Display.setTextColor(0xFFFFFF, 0x000000)
M5.Display.setCursor(8, 30)
M5.Display.print("Connecting Wi-Fi...")
if wifi_connect(WIFI_SSID, WIFI_PSWD):
M5.Display.print("\nWi-Fi connected.")
setup_ntp()
else:
M5.Display.print("\nWi-Fi failed. Offline mode.")
fetch_weather()
draw_clock_area()
draw_weather_area()
def loop():
global last_clock_ms, last_weather_ms
M5.update()
if time.ticks_diff(time.ticks_ms(), last_clock_ms) >= CLOCK_REFRESH_MS:
last_clock_ms = time.ticks_ms()
draw_clock_area()
if time.ticks_diff(time.ticks_ms(), last_weather_ms) >= WEATHER_REFRESH_MS:
if is_wifi_ok() and fetch_weather():
draw_weather_area()
# 右侧触摸立即刷新(兼容 getDetail/getCount)
try:
t = M5.Touch.getDetail()
if t.state in (2, 10, 14): # touch_end / flick_end / drag_end
if t.x > (M5.Display.width() * 0.6):
M5.Display.setTextColor(0xFCE000, 0x102030)
M5.Display.setCursor(M5.Display.width() - 110, 4)
M5.Display.print("Refreshing...")
if fetch_weather():
draw_weather_area()
M5.Display.fillRect(M5.Display.width() - 112, 2, 110, 22, 0x102030)
M5.Display.setTextColor(0xFFFFFF, 0x102030)
except:
if M5.Touch.getCount() > 0:
if fetch_weather():
draw_weather_area()
setup()
while True:
loop()
time.sleep_ms(10)实现效果如下,可以看到字体有点小了。这个天气的时间并不是实时的,这个网站的信息大概半小时会更新一次

工程代码如下
我要赚赏金
