闲的没事AI做了一个代理收益统计

纯测试AI能力,不要在评论里开喷拉屎

先看效果图:

你需要的材料:

  1. ESP32 - S3
  2. 一个TFT屏幕(st7789)
  3. 一个2.4G的WIFI
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <esp_freertos_hooks.h>
#include "ChineseFont16.h"

constexpr int LCD_CS = 13;
constexpr int LCD_DC = 12;
constexpr int LCD_RST = 11;
constexpr int LCD_MOSI = 10;
constexpr int LCD_SCK = 9;

SPIClass lcdSpi(FSPI);
Adafruit_ST7789 lcd(&lcdSpi, LCD_CS, LCD_DC, LCD_RST);

// 修改成你的WiFi名称和密码
const char* ssid = "你的WiFi名称";//需要2.4G WiFi,ESP32不支持5G WiFi
const char* password = "你的WiFi密码";

const char* userApiUrl = "https://api.v2.rainyun.com/user/";
const char* rainyunApiKey = "你的雨云API Key";
constexpr unsigned long RESELL_REFRESH_INTERVAL = 30000;
constexpr unsigned long SERIAL_HEARTBEAT_INTERVAL = 1000;
constexpr unsigned long WIFI_CHECK_INTERVAL = 1000;
constexpr unsigned long WIFI_RECONNECT_INTERVAL = 5000;
constexpr unsigned long WIFI_HARD_RECONNECT_AFTER = 20000;
constexpr unsigned long WIFI_CONNECT_TIMEOUT = 20000;
constexpr unsigned long CPU_SAMPLE_INTERVAL = 1000;
constexpr unsigned long SYSTEM_RESTART_INTERVAL = 5UL * 60UL * 1000UL;
constexpr uint8_t CPU_CALIBRATION_SAMPLES = 5;
constexpr uint16_t HTTP_TIMEOUT = 8000;
unsigned long lastResellRequest = 0;
unsigned long resellRequestCount = 0;
unsigned long lastSerialHeartbeat = 0;
unsigned long lastWiFiCheck = 0;
unsigned long lastWiFiReconnectAttempt = 0;
unsigned long wifiDisconnectedSince = 0;
unsigned long lastCpuSample = 0;
volatile uint32_t cpuIdleCounters[portNUM_PROCESSORS] = {};
uint32_t previousCpuIdleCounters[portNUM_PROCESSORS] = {};
uint32_t maximumIdleRates[portNUM_PROCESSORS] = {};
uint8_t cpuUsagePercent = 0;
uint8_t cpuCalibrationSamples = 0;
bool wasWiFiConnected = false;
bool cpuMonitorReady = false;
bool cpuMonitorCalibrated = false;
bool dashboardVisible = false;

struct DashboardData
{
    String name;
    double stockDaily;
    double stockMonthly;
    double monthlyIncome;
    uint8_t vipLevel;
    bool isAgent;
};

void drawChineseText(int16_t x, int16_t y, const char* text, uint16_t color, uint8_t scale = 1);

const uint8_t* getStatusAsciiGlyph(char character)
{
    static const uint8_t glyphC[] = {0x00,0x00,0x78,0x44,0x42,0x02,0x02,0x02,0x02,0x44,0x38,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyphP[] = {0x00,0x00,0x3E,0x44,0x44,0x44,0x3C,0x04,0x04,0x04,0x0E,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyphU[] = {0x00,0x00,0xEE,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x38,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyph0[] = {0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyph1[] = {0x00,0x10,0x1C,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyph2[] = {0x00,0x38,0x44,0x44,0x40,0x20,0x20,0x10,0x08,0x44,0x7C,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyph3[] = {0x00,0x38,0x44,0x44,0x40,0x30,0x40,0x40,0x44,0x44,0x38,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyph4[] = {0x00,0x20,0x20,0x30,0x28,0x28,0x24,0x7C,0x20,0x20,0x70,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyph5[] = {0x00,0x7C,0x04,0x04,0x3C,0x44,0x40,0x40,0x44,0x44,0x38,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyph6[] = {0x00,0x18,0x24,0x02,0x3A,0x46,0x42,0x42,0x42,0x44,0x38,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyph7[] = {0x00,0x7C,0x44,0x20,0x20,0x20,0x10,0x10,0x10,0x10,0x10,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyph8[] = {0x00,0x3C,0x42,0x42,0x42,0x3C,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyph9[] = {0x00,0x1C,0x22,0x42,0x42,0x62,0x5C,0x40,0x40,0x24,0x18,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyphColon[] = {0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyphPercent[] = {0x00,0x24,0x2A,0x1A,0x1A,0x2A,0x54,0x58,0x58,0x54,0x24,0x00,0x00,0x00,0x00,0x00};
    static const uint8_t glyphDash[] = {0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

    switch (character)
    {
        case '0': return glyph0;
        case '1': return glyph1;
        case '2': return glyph2;
        case '3': return glyph3;
        case '4': return glyph4;
        case '5': return glyph5;
        case '6': return glyph6;
        case '7': return glyph7;
        case '8': return glyph8;
        case '9': return glyph9;
        case 'C': return glyphC;
        case 'P': return glyphP;
        case 'U': return glyphU;
        case ':': return glyphColon;
        case '%': return glyphPercent;
        case '-': return glyphDash;
        default: return nullptr;
    }
}

void drawStatusAscii(int16_t x, int16_t y, const char* text, uint16_t color)
{
    while (*text != '\0')
    {
        const uint8_t* glyph = getStatusAsciiGlyph(*text++);
        if (glyph != nullptr)
        {
            for (uint8_t row = 0; row < 16; ++row)
            {
                for (uint8_t column = 0; column < 8; ++column)
                {
                    if (glyph[row] & (1U << column))
                    {
                        lcd.drawPixel(x + column, y + row, color);
                    }
                }
            }
        }
        x += 9;
    }
}

bool countCore0Idle()
{
    ++cpuIdleCounters[0];
    return false;
}

#if portNUM_PROCESSORS > 1
bool countCore1Idle()
{
    ++cpuIdleCounters[1];
    return false;
}
#endif

void drawCpuUsage()
{
    if (!dashboardVisible)
    {
        return;
    }

    lcd.fillRect(16, 188, 90, 22, ST77XX_WHITE);
    char cpuText[12];
    if (cpuMonitorCalibrated)
    {
        snprintf(cpuText, sizeof(cpuText), "CPU:%u%%", cpuUsagePercent);
    }
    else
    {
        snprintf(cpuText, sizeof(cpuText), "CPU:--");
    }
    drawStatusAscii(20, 190, cpuText, ST77XX_BLACK);
}

void drawStatusBar()
{
    lcd.fillRect(14, 184, 292, 34, ST77XX_WHITE);
    lcd.drawFastVLine(111, 188, 22, 0xC618);
    lcd.drawFastVLine(209, 188, 22, 0xC618);

    drawCpuUsage();

    const uint32_t heapSize = ESP.getHeapSize();
    const uint8_t memoryUsed = heapSize == 0 ? 0 : 100 - (ESP.getFreeHeap() * 100UL / heapSize);
    drawChineseText(119, 190, "内存", ST77XX_BLACK);
    char memoryText[8];
    snprintf(memoryText, sizeof(memoryText), ":%u%%", memoryUsed);
    drawStatusAscii(153, 190, memoryText, ST77XX_BLACK);

    drawChineseText(216, 190, "状态", ST77XX_BLACK);
    drawChineseText(252, 190, WiFi.status() == WL_CONNECTED ? "正常" : "断开",
                    WiFi.status() == WL_CONNECTED ? 0x0400 : ST77XX_RED);
}

void initializeCpuMonitor()
{
    bool hooksRegistered = esp_register_freertos_idle_hook_for_cpu(countCore0Idle, 0) == ESP_OK;
#if portNUM_PROCESSORS > 1
    hooksRegistered = hooksRegistered &&
                      esp_register_freertos_idle_hook_for_cpu(countCore1Idle, 1) == ESP_OK;
#endif
    for (uint8_t core = 0; core < portNUM_PROCESSORS; ++core)
    {
        previousCpuIdleCounters[core] = cpuIdleCounters[core];
    }
    lastCpuSample = millis();
    cpuMonitorReady = hooksRegistered;
    Serial.println(cpuMonitorReady ? "[CPU] 双核占用监测已启动" : "[CPU] 空闲任务回调注册失败");
}

void updateCpuUsage(unsigned long now)
{
    if (!cpuMonitorReady || now - lastCpuSample < CPU_SAMPLE_INTERVAL)
    {
        return;
    }

    const unsigned long elapsed = now - lastCpuSample;
    uint8_t busiestCoreUsage = 0;
    for (uint8_t core = 0; core < portNUM_PROCESSORS; ++core)
    {
        const uint32_t currentCounter = cpuIdleCounters[core];
        const uint32_t idleDelta = currentCounter - previousCpuIdleCounters[core];
        previousCpuIdleCounters[core] = currentCounter;
        const uint32_t idleRate = static_cast<uint32_t>(
            static_cast<uint64_t>(idleDelta) * 1000ULL / elapsed);

        if (!cpuMonitorCalibrated && idleRate > maximumIdleRates[core])
        {
            maximumIdleRates[core] = idleRate;
        }

        if (cpuMonitorCalibrated && maximumIdleRates[core] > 0)
        {
            const uint32_t cappedIdleRate = min(idleRate, maximumIdleRates[core]);
            const uint32_t busyRate = maximumIdleRates[core] - cappedIdleRate;
            const uint8_t coreUsage = min<uint32_t>(
                100, (static_cast<uint64_t>(busyRate) * 100ULL + maximumIdleRates[core] - 1) /
                         maximumIdleRates[core]);
            busiestCoreUsage = max(busiestCoreUsage, coreUsage);
        }
    }

    if (!cpuMonitorCalibrated)
    {
        ++cpuCalibrationSamples;
        if (cpuCalibrationSamples >= CPU_CALIBRATION_SAMPLES)
        {
            cpuMonitorCalibrated = true;
            Serial.println("[CPU] 空闲基准校准完成");
        }
    }
    else
    {
        cpuUsagePercent = (cpuUsagePercent * 3 + busiestCoreUsage + 2) / 4;
    }

    lastCpuSample = now;
    drawCpuUsage();
}

const ChineseGlyph16* findChineseGlyph(uint16_t codepoint)
{
    for (size_t index = 0; index < CHINESE_FONT_16_COUNT; ++index)
    {
        if (pgm_read_word(&CHINESE_FONT_16[index].codepoint) == codepoint)
        {
            return &CHINESE_FONT_16[index];
        }
    }
    return nullptr;
}

void drawChineseGlyph(int16_t x, int16_t y, uint16_t codepoint, uint16_t color, uint8_t scale = 1)
{
    const ChineseGlyph16* glyph = findChineseGlyph(codepoint);
    if (glyph == nullptr)
    {
        lcd.drawRect(x, y, 16 * scale, 16 * scale, color);
        return;
    }

    for (uint8_t row = 0; row < 16; ++row)
    {
        const uint16_t rowBits = pgm_read_byte(&glyph->bitmap[row * 2]) |
                                 (pgm_read_byte(&glyph->bitmap[row * 2 + 1]) << 8);
        for (uint8_t column = 0; column < 16; ++column)
        {
            if (rowBits & (1U << column))
            {
                lcd.fillRect(x + column * scale, y + row * scale, scale, scale, color);
            }
        }
    }
}

uint16_t readUtf8Codepoint(const char*& text)
{
    const uint8_t first = static_cast<uint8_t>(*text++);
    if (first < 0x80)
    {
        return first;
    }
    if ((first & 0xE0) == 0xC0)
    {
        const uint8_t second = static_cast<uint8_t>(*text++);
        return ((first & 0x1F) << 6) | (second & 0x3F);
    }

    const uint8_t second = static_cast<uint8_t>(*text++);
    const uint8_t third = static_cast<uint8_t>(*text++);
    return ((first & 0x0F) << 12) | ((second & 0x3F) << 6) | (third & 0x3F);
}

void drawChineseText(int16_t x, int16_t y, const char* text, uint16_t color, uint8_t scale)
{
    while (*text != '\0')
    {
        const uint16_t codepoint = readUtf8Codepoint(text);
        if (codepoint < 0x80)
        {
            lcd.setTextColor(color);
            lcd.setTextSize(scale * 2);
            lcd.setCursor(x, y);
            lcd.write(static_cast<uint8_t>(codepoint));
        }
        else
        {
            drawChineseGlyph(x, y, codepoint, color, scale);
        }
        x += 16 * scale;
    }
}

void drawCenteredValue(int16_t centerX, int16_t y, double value)
{
    char valueText[18];
    snprintf(valueText, sizeof(valueText), "%.2f", value);
    const int16_t width = strlen(valueText) * 12;
    lcd.setTextColor(ST77XX_BLACK);
    lcd.setTextSize(2);
    lcd.setCursor(centerX - width / 2, y);
    lcd.print(valueText);
}

void initializeLcd()
{
    Serial.println("[LCD] 配置控制引脚");
    pinMode(LCD_CS, OUTPUT);
    pinMode(LCD_DC, OUTPUT);
    pinMode(LCD_RST, OUTPUT);
    digitalWrite(LCD_CS, HIGH);
    digitalWrite(LCD_DC, HIGH);

    Serial.println("[LCD] 执行硬件复位");
    digitalWrite(LCD_RST, HIGH);
    delay(20);
    digitalWrite(LCD_RST, LOW);
    delay(100);
    digitalWrite(LCD_RST, HIGH);
    delay(150);

    Serial.print("[LCD] 启动 SPI,SCK=");
    Serial.print(LCD_SCK);
    Serial.print(", MOSI=");
    Serial.print(LCD_MOSI);
    Serial.print(", CS=");
    Serial.print(LCD_CS);
    Serial.print(", DC=");
    Serial.print(LCD_DC);
    Serial.print(", RST=");
    Serial.println(LCD_RST);
    lcdSpi.begin(LCD_SCK, -1, LCD_MOSI, LCD_CS);

    Serial.println("[LCD] 初始化 ST7789 240x320");
    lcd.init(240, 320);
    lcd.setRotation(1);
    lcd.invertDisplay(false);
    lcd.fillScreen(ST77XX_BLACK);
    Serial.println("[LCD] 初始化完成");
}

void showWiFiInfo()
{
    dashboardVisible = false;
    lcd.fillScreen(ST77XX_BLACK);
    lcd.setCursor(12, 16);
    lcd.setTextColor(ST77XX_GREEN);
    lcd.setTextSize(2);
    lcd.println("WiFi connected");

    lcd.setTextColor(ST77XX_WHITE);
    lcd.setCursor(12, 56);
    lcd.print("IP: ");
    lcd.println(WiFi.localIP());

    lcd.setCursor(12, 88);
    lcd.print("RSSI: ");
    lcd.print(WiFi.RSSI());
    lcd.println(" dBm");
}

void showDashboard(const DashboardData& data)
{
    dashboardVisible = true;
    lcd.fillScreen(ST77XX_WHITE);
    lcd.setTextWrap(false);
    lcd.drawRect(4, 4, 312, 232, ST77XX_BLACK);
    lcd.drawRect(5, 5, 310, 230, ST77XX_BLACK);

    lcd.fillCircle(48, 50, 34, 0x9CF3);
    lcd.setTextColor(ST77XX_BLACK);
    lcd.setTextSize(4);
    lcd.setCursor(36, 37);
    lcd.write(data.name.length() > 0 ? data.name.charAt(0) : '?');

    lcd.setTextSize(3);
    lcd.setCursor(92, 15);
    lcd.print(data.name.substring(0, 11));

    if (data.isAgent)
    {
        lcd.fillTriangle(94, 48, 101, 59, 108, 48, ST77XX_BLUE);
        lcd.fillCircle(101, 63, 9, 0xFD20);
        lcd.drawCircle(101, 63, 6, 0xFBE0);
        drawChineseText(116, 48, "高级代理", ST77XX_RED, 2);
    }
    else
    {
        lcd.setTextColor(0xFD20);
        lcd.setTextSize(3);
        lcd.setCursor(112, 52);
        lcd.print(data.vipLevel);
        drawChineseText(132, 52, "级会员", 0xFD20, 2);
    }

    drawChineseText(24, 105, "今日进货", ST77XX_BLACK);
    drawChineseText(128, 105, "本月收益", ST77XX_BLACK);
    drawChineseText(232, 105, "本月进货", ST77XX_BLACK);
    drawCenteredValue(56, 136, data.stockDaily);
    drawCenteredValue(160, 136, data.monthlyIncome);
    drawCenteredValue(264, 136, data.stockMonthly);

    lcd.drawFastHLine(14, 174, 292, 0xC618);
    drawStatusBar();
}

bool readDashboardData(DashboardData& dashboard)
{
    const unsigned long requestNumber = ++resellRequestCount;
    const unsigned long requestStartedAt = millis();

    Serial.println();
    Serial.println("========== 分销数据请求开始 ==========");
    Serial.print("请求编号:");
    Serial.println(requestNumber);
    Serial.print("请求时间:");
    Serial.print(requestStartedAt / 1000);
    Serial.println(" 秒");
    Serial.print("请求地址:");
    Serial.println(userApiUrl);
    Serial.print("WiFi 状态:");
    Serial.println(WiFi.status() == WL_CONNECTED ? "已连接" : "未连接");
    Serial.print("RSSI:");
    Serial.print(WiFi.RSSI());
    Serial.println(" dBm");

    WiFiClientSecure client;
    client.setInsecure();
    client.setTimeout(HTTP_TIMEOUT);

    HTTPClient http;
    if (!http.begin(client, userApiUrl))
    {
        Serial.println("无法创建 HTTPS 请求");
        Serial.print("本次请求耗时:");
        Serial.print(millis() - requestStartedAt);
        Serial.println(" ms");
        return false;
    }

    http.setConnectTimeout(HTTP_TIMEOUT);
    http.setTimeout(HTTP_TIMEOUT);
    http.setReuse(false);
    http.addHeader("x-api-key", rainyunApiKey);
    const int httpCode = http.GET();
    if (httpCode <= 0)
    {
        Serial.print("请求失败:");
        Serial.println(http.errorToString(httpCode));
        Serial.print("本次请求耗时:");
        Serial.print(millis() - requestStartedAt);
        Serial.println(" ms");
        http.end();
        return false;
    }

    const String response = http.getString();
    Serial.print("HTTP 状态码:");
    Serial.println(httpCode);
    Serial.print("响应长度:");
    Serial.print(response.length());
    Serial.println(" 字节");
    http.end();

    if (httpCode != HTTP_CODE_OK)
    {
        Serial.println("HTTP 状态不是 200,跳过 JSON 数据读取");
        Serial.println("========== 分销数据请求结束(失败) ==========");
        return false;
    }

    DynamicJsonDocument document(4096);
    const DeserializationError error = deserializeJson(document, response);
    if (error)
    {
        Serial.print("JSON 解析失败:");
        Serial.println(error.c_str());
        Serial.print("本次请求耗时:");
        Serial.print(millis() - requestStartedAt);
        Serial.println(" ms");
        Serial.println("========== 分销数据请求结束(失败) ==========");
        return false;
    }

    JsonObject data = document.as<JsonObject>();
    if (document["data"].is<JsonObject>())
    {
        data = document["data"].as<JsonObject>();
    }

    if (data["Name"].isNull() ||
        data["StockDaily"].isNull() ||
        data["StockMonthly"].isNull() ||
        data["ResellPointsMonthly"].isNull())
    {
        Serial.println("响应中缺少仪表盘字段");
        Serial.println("需要字段:Name、StockDaily、StockMonthly、ResellPointsMonthly");
        Serial.print("本次请求耗时:");
        Serial.print(millis() - requestStartedAt);
        Serial.println(" ms");
        Serial.println("========== 分销数据请求结束(失败) ==========");
        return false;
    }

    dashboard.name = data["Name"].as<String>();
    dashboard.stockDaily = data["StockDaily"].as<double>();
    dashboard.stockMonthly = data["StockMonthly"].as<double>();
    dashboard.monthlyIncome = data["ResellPointsMonthly"].as<double>() / 2000.0;
    dashboard.vipLevel = data["VipLevel"] | 1;
    dashboard.isAgent = data["IsAgent"] | false;

    Serial.println("解析后的仪表盘数据:");
    Serial.print("  名称:");
    Serial.println(dashboard.name);
    Serial.print("  今日进货:");
    Serial.println(dashboard.stockDaily, 4);
    Serial.print("  本月收益:");
    Serial.println(dashboard.monthlyIncome, 4);
    Serial.print("  本月进货:");
    Serial.println(dashboard.stockMonthly, 4);
    Serial.print("本次请求耗时:");
    Serial.print(millis() - requestStartedAt);
    Serial.println(" ms");
    Serial.println("========== 分销数据请求结束(成功) ==========");
    return true;
}

void updateResellData()
{
    if (WiFi.status() != WL_CONNECTED)
    {
        Serial.println("[API] WiFi 未连接,跳过本次数据请求");
        return;
    }

    Serial.println("开始更新 LCD 分销数据...");
    DashboardData dashboard;
    if (readDashboardData(dashboard))
    {
        showDashboard(dashboard);
        Serial.println("LCD 仪表盘更新完成");
    }
    else
    {
        Serial.println("[API] 本次更新失败,保留 LCD 上一次成功数据");
    }
}

void maintainWiFi(unsigned long now)
{
    if (now - lastWiFiCheck < WIFI_CHECK_INTERVAL)
    {
        return;
    }
    lastWiFiCheck = now;

    const bool isConnected = WiFi.status() == WL_CONNECTED;
    if (isConnected)
    {
        wifiDisconnectedSince = 0;
        if (!wasWiFiConnected)
        {
            Serial.println("[WiFi] 已恢复连接");
            Serial.print("[WiFi] IP 地址:");
            Serial.println(WiFi.localIP());
            Serial.print("[WiFi] RSSI:");
            Serial.print(WiFi.RSSI());
            Serial.println(" dBm");
            lastResellRequest = now - RESELL_REFRESH_INTERVAL;
            drawStatusBar();
        }
        wasWiFiConnected = true;
        return;
    }

    if (wasWiFiConnected)
    {
        Serial.println("[WiFi] 检测到连接已断开");
        drawStatusBar();
    }
    wasWiFiConnected = false;
    if (wifiDisconnectedSince == 0)
    {
        wifiDisconnectedSince = now;
    }

    if (now - lastWiFiReconnectAttempt >= WIFI_RECONNECT_INTERVAL)
    {
        lastWiFiReconnectAttempt = now;
        if (now - wifiDisconnectedSince < WIFI_HARD_RECONNECT_AFTER)
        {
            Serial.println("[WiFi] 正在尝试软重连...");
            WiFi.reconnect();
        }
        else
        {
            Serial.println("[WiFi] 持续断线,重新启动 STA 连接...");
            WiFi.disconnect(false, false);
            delay(50);
            WiFi.begin(ssid, password);
            wifiDisconnectedSince = now;
        }
    }
}

void setup()
{
    Serial.begin(115200);
    const unsigned long serialWaitStartedAt = millis();
    while (!Serial && millis() - serialWaitStartedAt < 3000)
    {
        delay(10);
    }
    delay(200);
    Serial.println();
    Serial.println("[BOOT] Serial monitor started at 115200 baud");
    Serial.flush();

    initializeLcd();

    lcd.fillScreen(ST77XX_BLACK);
    lcd.setCursor(12, 16);
    lcd.setTextColor(ST77XX_YELLOW);
    lcd.setTextSize(2);
    lcd.println("Connecting WiFi...");

    Serial.println();
    Serial.println("========== ESP32 启动 ==========");
    Serial.println("ESP32开始连接WiFi...");

    WiFi.mode(WIFI_STA);
    WiFi.setSleep(false);
    WiFi.setAutoReconnect(true);
    WiFi.persistent(false);
    Serial.println("[WiFi] 省电模式已关闭,自动重连已启用,凭据不重复写入 Flash");
    // No WiFi.config() call: obtain IP address from the router via DHCP.
    WiFi.begin(ssid, password);

    const unsigned long connectStartedAt = millis();
    while (WiFi.status() != WL_CONNECTED && millis() - connectStartedAt < WIFI_CONNECT_TIMEOUT)
    {
        delay(500);
        Serial.print(".");
    }

    Serial.println();
    Serial.print("分销数据刷新间隔:");
    Serial.print(RESELL_REFRESH_INTERVAL / 1000);
    Serial.println(" 秒");

    wasWiFiConnected = WiFi.status() == WL_CONNECTED;
    if (wasWiFiConnected)
    {
        Serial.println("连接成功!");
        Serial.print("IP地址:");
        Serial.println(WiFi.localIP());
        Serial.print("信号强度RSSI:");
        Serial.print(WiFi.RSSI());
        Serial.println(" dBm");
        showWiFiInfo();
        updateResellData();
    }
    else
    {
        Serial.println("[WiFi] 首次连接超时,将在后台继续重连");
    }
    lastResellRequest = millis();
    lastWiFiReconnectAttempt = millis();
    initializeCpuMonitor();
}

void loop()
{
    const unsigned long now = millis();
    if (now >= SYSTEM_RESTART_INTERVAL)
    {
        Serial.println("[SYSTEM] 已运行 5 分钟,正在执行软件复位...");
        Serial.flush();
        delay(50);
        ESP.restart();
    }

    maintainWiFi(now);
    updateCpuUsage(now);

    if (now - lastSerialHeartbeat >= SERIAL_HEARTBEAT_INTERVAL)
    {
        lastSerialHeartbeat = now;
        Serial.print("[HEARTBEAT] uptime=");
        Serial.print(now / 1000);
        Serial.print("s, WiFi=");
        Serial.print(WiFi.status() == WL_CONNECTED ? "connected" : "disconnected");
        Serial.print(", CPU=");
        if (cpuMonitorCalibrated)
        {
            Serial.print(cpuUsagePercent);
            Serial.println('%');
        }
        else
        {
            Serial.println("calibrating");
        }
    }

    if (WiFi.status() == WL_CONNECTED && now - lastResellRequest >= RESELL_REFRESH_INTERVAL)
    {
        Serial.println();
        Serial.println("达到刷新间隔,准备请求最新数据");
        updateResellData();
        lastResellRequest = millis();
        Serial.print("下一次刷新倒计时:");
        Serial.print(RESELL_REFRESH_INTERVAL / 1000);
        Serial.println(" 秒");
    }
}
//命名为ChineseFont16.h,这个是字模
#pragma once

#include <Arduino.h>

struct ChineseGlyph16
{
    uint16_t codepoint;
    uint8_t bitmap[32];
};

const ChineseGlyph16 CHINESE_FONT_16[] PROGMEM = {
    {0x4ECA, {0x80,0x00,0x80,0x00,0x40,0x01,0x20,0x02,0x10,0x04,0x48,0x08,0x84,0x10,0x83,0x60,0x00,0x00,0xf8,0x0f,0x00,0x08,0x00,0x04,0x00,0x04,0x00,0x02,0x00,0x01,0x80,0x00}},
    {0x65E5, {0x00,0x00,0xf8,0x0f,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0xf8,0x0f,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0xf8,0x0f,0x08,0x08}},
    {0x8FDB, {0x00,0x09,0x04,0x09,0x08,0x09,0xc8,0x3f,0x00,0x09,0x00,0x09,0x0f,0x09,0xe8,0x7f,0x08,0x09,0x08,0x09,0x88,0x08,0x88,0x08,0x48,0x08,0x14,0x00,0xe2,0x7f,0x00,0x00}},
    {0x8D27, {0x10,0x01,0x10,0x01,0x08,0x19,0x0c,0x07,0xea,0x21,0x09,0x21,0x08,0x3e,0x00,0x00,0xf8,0x0f,0x08,0x08,0x88,0x08,0x88,0x08,0x88,0x08,0x40,0x06,0x30,0x18,0x0e,0x20}},
    {0x672C, {0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xfe,0x3f,0xc0,0x01,0xa0,0x02,0xa0,0x02,0x90,0x04,0x88,0x08,0x84,0x10,0xf2,0x27,0x81,0x40,0x80,0x00,0x80,0x00,0x80,0x00}},
    {0x6708, {0xe0,0x3f,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xe0,0x3f,0x20,0x20,0x20,0x20,0x20,0x20,0xe0,0x3f,0x20,0x20,0x20,0x20,0x10,0x20,0x10,0x20,0x08,0x28,0x04,0x10}},
    {0x6536, {0x10,0x02,0x10,0x02,0x12,0x02,0x12,0x01,0x12,0x7f,0x92,0x10,0x52,0x11,0x12,0x11,0x12,0x11,0x1a,0x0a,0x16,0x0a,0x12,0x04,0x10,0x0a,0x10,0x11,0x90,0x20,0x50,0x40}},
    {0x76CA, {0x08,0x08,0x10,0x04,0x00,0x00,0xff,0x7f,0x00,0x00,0x10,0x04,0x08,0x08,0x04,0x10,0x02,0x20,0xfc,0x1f,0x24,0x12,0x24,0x12,0x24,0x12,0x24,0x12,0xff,0x7f,0x00,0x00}},
    {0x5185, {0x80,0x00,0x80,0x00,0x80,0x00,0xfe,0x3f,0x82,0x20,0x82,0x20,0x82,0x20,0x42,0x21,0x42,0x22,0x22,0x24,0x12,0x28,0x0a,0x28,0x02,0x20,0x02,0x20,0x02,0x28,0x02,0x10}},
    {0x5B58, {0x20,0x00,0x20,0x00,0xff,0x7f,0x10,0x00,0x10,0x00,0xc8,0x1f,0x08,0x08,0x0c,0x04,0x0a,0x02,0xe9,0x7f,0x08,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0x88,0x02,0x08,0x01}},
    {0x72B6, {0x10,0x02,0x10,0x12,0x10,0x22,0x12,0x22,0x14,0x02,0xf4,0x7f,0x10,0x02,0x10,0x02,0x18,0x02,0x14,0x05,0x13,0x05,0x10,0x09,0x90,0x08,0x90,0x10,0x50,0x20,0x30,0x40}},
    {0x6001, {0x80,0x00,0x80,0x00,0xfe,0x3f,0x80,0x00,0x40,0x01,0x20,0x02,0x50,0x04,0x8c,0x18,0x03,0x60,0x80,0x00,0x10,0x11,0x12,0x21,0x12,0x48,0x12,0x48,0xe1,0x0f,0x00,0x00}},
    {0x6B63, {0x00,0x00,0xfe,0x3f,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x88,0x00,0x88,0x1f,0x88,0x00,0x88,0x00,0x88,0x00,0x88,0x00,0x88,0x00,0x88,0x00,0xff,0x7f,0x00,0x00}},
    {0x5E38, {0x80,0x00,0x88,0x08,0x90,0x04,0xfe,0x7f,0x02,0x40,0xf9,0x2f,0x08,0x08,0xf8,0x0f,0x80,0x00,0xfc,0x1f,0x84,0x10,0x84,0x10,0x84,0x14,0x84,0x08,0x80,0x00,0x80,0x00}},
    {0x65AD, {0x20,0x00,0x20,0x20,0xaa,0x1e,0x72,0x02,0x22,0x02,0xfe,0x02,0x22,0x7e,0x72,0x12,0xaa,0x12,0xa6,0x12,0x22,0x12,0x22,0x12,0x02,0x12,0xfe,0x11,0x00,0x11,0x80,0x10}},
    {0x5F00, {0x00,0x00,0xfe,0x3f,0x10,0x04,0x10,0x04,0x10,0x04,0x10,0x04,0x10,0x04,0xff,0x7f,0x10,0x04,0x10,0x04,0x10,0x04,0x10,0x04,0x08,0x04,0x08,0x04,0x04,0x04,0x02,0x04}},
    {0x9AD8, {0x40,0x00,0x80,0x00,0xff,0x7f,0x00,0x00,0xf0,0x07,0x10,0x04,0x10,0x04,0xf0,0x07,0x00,0x00,0xfe,0x3f,0x02,0x20,0xf2,0x27,0x12,0x24,0x12,0x24,0xf2,0x27,0x02,0x30}},
    {0x7EA7, {0x08,0x00,0xc8,0x3f,0x04,0x21,0x04,0x11,0x12,0x11,0x1f,0x09,0x08,0x39,0x04,0x21,0x82,0x22,0x9f,0x22,0x82,0x14,0x80,0x14,0x58,0x08,0x47,0x14,0x22,0x22,0x80,0x41}},
    {0x4EE3, {0x10,0x09,0x10,0x11,0x10,0x11,0x08,0x01,0x08,0x7d,0xec,0x03,0x0c,0x01,0x0a,0x02,0x09,0x02,0x08,0x02,0x08,0x04,0x08,0x44,0x08,0x48,0x08,0x50,0x08,0x60,0x08,0x40}},
    {0x7406, {0x00,0x00,0x80,0x3f,0xbf,0x24,0x88,0x24,0x88,0x3f,0x88,0x24,0x88,0x24,0xbe,0x3f,0x08,0x04,0x08,0x04,0x88,0x3f,0x08,0x04,0x38,0x04,0x07,0x04,0xc2,0x7f,0x00,0x00}},
    {0x4F1A, {0x80,0x00,0x80,0x00,0x40,0x01,0x20,0x02,0x10,0x04,0x0c,0x18,0xf3,0x67,0x00,0x00,0x00,0x00,0xfe,0x3f,0x40,0x00,0x20,0x00,0x10,0x04,0x08,0x08,0xfc,0x1f,0x08,0x10}},
    {0x5458, {0x00,0x00,0xf8,0x0f,0x08,0x08,0x08,0x08,0xf8,0x0f,0x00,0x00,0xfc,0x1f,0x04,0x10,0x84,0x10,0x84,0x10,0x84,0x10,0x84,0x10,0x44,0x13,0x20,0x0c,0x18,0x10,0x06,0x20}}
};

constexpr size_t CHINESE_FONT_16_COUNT = sizeof(CHINESE_FONT_16) / sizeof(CHINESE_FONT_16[0]);

主要通过雨云API接口实现,有能力可自行二改开板

2 个赞

这个好玩欸