这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【M5CoreS3测评】桌面天气日历摆件

共2条 1/1 1 跳转至

【M5CoreS3测评】桌面天气日历摆件

助工
2025-10-18 21:56:13     打赏

为了充分发挥ESP32的WiFi功能,很多开发者通常会制作桌面天气日历摆件,而M5CoreS3的造型方方正正的,正适合做一个桌面摆件

我个人比较手残,画不好比较漂亮的UI设计,而且我也不太熟悉图形化编程,因此本项目的开发基于MicroPython

首先为开发板烧录UIFlow2.0,这主要是为了引入M5GFX的库

image.png

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

image.png

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

image.png

为了实现这些信息的获取,我们需要使用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)

实现效果如下,可以看到字体有点小了。这个天气的时间并不是实时的,这个网站的信息大概半小时会更新一次

IMG_20251018_213150.jpg

工程代码如下

weather.zip




关键词: M5CoreS3     天气日历     桌面摆件    

院士
2025-10-19 19:55:15     打赏
2楼

这个太棒了!

不过,我手机上面可以显示按小时的天气状态。这个信息可以在哪里获取啊?


共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]