ESP8266电子时钟

ESP8266电子时钟

功能:自定义API访问
显示天气
显示时间

材料
  • 一块大小不符合的面包板
  • NodeMCU ESP-12E(ESP8266 WiFi 开发板)
  • 0.96 寸 I2C 接口 SSD1306 OLED 显示屏
  • 有源蜂鸣器
  • 几根杜邦线
arduino 代码
#include <U8g2lib.h>
#include <Wire.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>

// ==================== OLED配置 ====================
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 5, /* data=*/ 4);
// ==================== 蜂鸣器配置 ====================
#define BUZZER_PIN 0  // D3 (GPIO0)


// ==================== WiFi配置 ====================
const char* ssid = "N/A";
const char* password = "N/A";

// ==================== API配置 ====================
const char* timeUrl = "N/A";
const char* serverUrl = "N/A";

// 心知天气配置(免费注册:https://www.seniverse.com/)
const char* xinzhiKey = "N/A";
const char* city = "N/A";
const char* weatherUrl = "https://api.seniverse.com/v3/weather/now.json";

WiFiClientSecure client;

// ==================== 全局变量 ====================
int currentHour = 0;
int currentMinute = 0;
int currentSecond = 0;
int currentWeekday = 0;
String currentDateFormatted = "";

// 天气
String weatherText = "";
String weatherTemp = "";

// 通知
String notifications[30];
int notificationCount = 0;
int currentNotificationIndex = 0;

// 定时
unsigned long lastTimeUpdate = 0;
unsigned long lastWeatherUpdate = 0;
unsigned long lastDataUpdate = 0;
unsigned long lastModeSwitch = 0;

int displayMode = 0;  // 0:时间界面, 1:通知界面
bool notificationJustShown = false;

// ==================== 辅助函数 ====================
String getWeekdayCN(int w) {
  String weekdays[] = {"周一", "周二", "周三", "周四", "周五", "周六", "周日"};
  if (w >= 0 && w <= 6) return weekdays[w];
  return "周一";
}

String getWeatherCN(String weather) {
  if (weather.indexOf("Clear") >= 0) return "晴";
  if (weather.indexOf("Cloud") >= 0) return "多云";
  if (weather.indexOf("Overcast") >= 0) return "阴";
  if (weather.indexOf("Light Rain") >= 0) return "小雨";
  if (weather.indexOf("Moderate Rain") >= 0) return "中雨";
  if (weather.indexOf("Heavy Rain") >= 0) return "大雨";
  if (weather.indexOf("Rain") >= 0) return "雨";
  if (weather.indexOf("Snow") >= 0) return "雪";
  if (weather.indexOf("Fog") >= 0) return "雾";
  return weather;
}

void beepLight() {
  digitalWrite(BUZZER_PIN, LOW);
  delay(80);
  digitalWrite(BUZZER_PIN, HIGH);
}

// ==================== 获取数据 ====================
void updateTime() {
  if (WiFi.status() != WL_CONNECTED) return;
  
  HTTPClient https;
  client.setInsecure();
  https.setTimeout(5000);
  
  if (!https.begin(client, timeUrl)) return;
  
  int httpCode = https.GET();
  if (httpCode == HTTP_CODE_OK) {
    String payload = https.getString();
    
    DynamicJsonDocument doc(512);
    deserializeJson(doc, payload);
    
    currentHour = doc["hour"];
    currentMinute = doc["minute"];
    currentWeekday = doc["weekday"];
    
    String timeStr = doc["time"].as<String>();
    if (timeStr.length() >= 19) {
      currentSecond = timeStr.substring(17, 19).toInt();
    }
    
    String dateStr = doc["date"].as<String>();
    if (dateStr.length() >= 10) {
      String month = dateStr.substring(5, 7);
      String day = dateStr.substring(8, 10);
      currentDateFormatted = month + "月" + day + "日";
    }
  }
  https.end();
}

void updateWeather() {
  if (WiFi.status() != WL_CONNECTED) return;
  if (String(xinzhiKey).length() == 0) return;
  
  String url = String(weatherUrl) + "?key=" + xinzhiKey + "&location=" + city + "&language=en&unit=c";
  
  HTTPClient https;
  client.setInsecure();
  https.setTimeout(5000);
  
  if (!https.begin(client, url)) return;
  
  int httpCode = https.GET();
  if (httpCode == HTTP_CODE_OK) {
    String payload = https.getString();
    
    DynamicJsonDocument doc(1024);
    deserializeJson(doc, payload);
    
    if (doc.containsKey("results")) {
      JsonObject now = doc["results"][0]["now"];
      weatherText = now["text"].as<String>();
      float temp = now["temperature"];
      weatherTemp = String(temp, 0);
    }
  }
  https.end();
}

void fetchNotifications() {
  if (WiFi.status() != WL_CONNECTED) return;
  
  HTTPClient https;
  client.setInsecure();
  https.setTimeout(10000);
  
  if (!https.begin(client, serverUrl)) return;
  
  int httpCode = https.GET();
  if (httpCode == HTTP_CODE_OK) {
    String payload = https.getString();
    
    DynamicJsonDocument doc(4096);
    deserializeJson(doc, payload);
    
    if (doc["status"] == "success") {
      notificationCount = 0;
      JsonArray notificationsArr = doc["notifications"];
      for (JsonObject n : notificationsArr) {
        if (notificationCount < 30) {
          notifications[notificationCount] = n["content"].as<String>();
          notificationCount++;
        }
      }
    }
  }
  https.end();
}

// ==================== 手表界面 ====================
void displayWatchFace() {
  u8g2.firstPage();
  do {
    // ========== 顶部区域 ==========
    u8g2.setFont(u8g2_font_wqy12_t_chinese3);
    u8g2.drawHLine(0, 15, 128);  // 顶部分割线
    
    // 日期(左上角)
    u8g2.setCursor(5, 12);
    if (currentDateFormatted.length() > 0) {
      u8g2.print(currentDateFormatted);
    } else {
      u8g2.print("03月24日");
    }
    
    // 星期(右上角)
    u8g2.setCursor(95, 12);
    u8g2.print(getWeekdayCN(currentWeekday));
    
    // ========== 中间时间区域 ==========
    // 时间往下移(从y=42移到y=48)
    u8g2.setFont(u8g2_font_logisoso24_tf);
    u8g2.setCursor(25, 48);
    char timeBuf[6];
    sprintf(timeBuf, "%02d:%02d", currentHour, currentMinute);
    u8g2.print(timeBuf);
    
    // 秒数(放在时间右侧)
    u8g2.setFont(u8g2_font_helvB08_tf);
    u8g2.setCursor(105, 38);
    char secBuf[3];
    sprintf(secBuf, "%02d", currentSecond);
    u8g2.print(secBuf);
    
    // ========== 底部区域 ==========
    
    // 底部文字(放在线下方,y=63)
    u8g2.setCursor(10, 63);
    if (weatherText.length() > 0 && weatherTemp.length() > 0) {
      u8g2.print(getWeatherCN(weatherText));
      u8g2.print(" ");
      u8g2.print(weatherTemp);
      u8g2.print("°C");
    } else if (String(xinzhiKey).length() > 0) {
      u8g2.print("天气加载中");
    } else {
      u8g2.print("WiFi:");
      u8g2.print(WiFi.RSSI());
      u8g2.print("dBm");
    }
    
    // 通知数量(右侧)
    if (notificationCount > 0) {
      u8g2.setCursor(100, 63);
      u8g2.print("[");
      u8g2.print(notificationCount);
      u8g2.print("]");
    }
    
  } while (u8g2.nextPage());
}

// ==================== 通知界面 ====================
void displayNotification() {
  u8g2.firstPage();
  do {
    u8g2.setFont(u8g2_font_wqy12_t_chinese3);
    
    u8g2.drawHLine(0, 15, 128);
    u8g2.setCursor(45, 12);
    u8g2.print("通 知");
    u8g2.drawHLine(0, 22, 128);
    
    if (notificationCount > 0 && currentNotificationIndex < notificationCount) {
      String content = notifications[currentNotificationIndex];
      
      int lineHeight = 12;
      int startPos = 0;
      int lines = 0;
      
      while (startPos < content.length() && lines < 3) {
        int endPos = startPos + 18;
        if (endPos > content.length()) endPos = content.length();
        
        u8g2.setCursor(5, 32 + lines * lineHeight);
        u8g2.print(content.substring(startPos, endPos));
        
        startPos = endPos;
        lines++;
      }
      
      u8g2.setCursor(100, 63);
      u8g2.print(currentNotificationIndex + 1);
      u8g2.print("/");
      u8g2.print(notificationCount);
      
    } else {
      u8g2.setCursor(35, 40);
      u8g2.print("暂无通知");
    }
    
    
  } while (u8g2.nextPage());
}

// ==================== 初始化 ====================
void setup() {
  Serial.begin(115200);
  delay(100);
  
  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, HIGH);
  
  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setFont(u8g2_font_wqy12_t_chinese3);
  
  u8g2.firstPage();
  do {
    u8g2.setCursor(35, 30);
    u8g2.print("连接WiFi");
  } while (u8g2.nextPage());
  
  WiFi.begin(ssid, password);
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 25) {
    delay(500);
    Serial.print(".");
    attempts++;
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nWiFi已连接");
    u8g2.firstPage();
    do {
      u8g2.setCursor(40, 30);
      u8g2.print("WiFi已连");
    } while (u8g2.nextPage());
    delay(1000);
  }
  
  client.setInsecure();
  
  updateTime();
  updateWeather();
  fetchNotifications();
  
  beepLight();
}

// ==================== 主循环 ====================
void loop() {
  unsigned long now = millis();
  
  if (now - lastTimeUpdate >= 1000) {
    updateTime();
    lastTimeUpdate = now;
  }
  
  if (now - lastWeatherUpdate >= 300000) {
    updateWeather();
    lastWeatherUpdate = now;
  }
  
  // 每15分钟检查通知
  if (now - lastDataUpdate >= 900000) {
    fetchNotifications();
    lastDataUpdate = now;
    
    if (notificationCount > 0) {
      displayMode = 1;
      lastModeSwitch = now;
      currentNotificationIndex = 0;
      notificationJustShown = true;
      beepLight();
    }
  }
  
  // 通知界面显示10秒后切回
  if (displayMode == 1 && notificationJustShown) {
    if (now - lastModeSwitch >= 10000) {
      displayMode = 0;
      notificationJustShown = false;
      lastModeSwitch = now;
    }
  }
  
  // 通知界面自动翻页
  if (displayMode == 1 && notificationCount > 0 && notificationJustShown) {
    static unsigned long lastNotiSwitch = 0;
    if (now - lastNotiSwitch >= 3000) {
      currentNotificationIndex = (currentNotificationIndex + 1) % notificationCount;
      lastNotiSwitch = now;
    }
  }
  
  if (displayMode == 0) {
    displayWatchFace();
  } else {
    displayNotification();
  }
  
  delay(50);
}
C++

“记住他人名字带给他人被记住的感觉是这个时代所稀缺的,因为效率至上的时代,每个人都忽视了彼此包括自己”

Making someone feel remembered just by knowing their name—that’s something rare in today’s world. In an era that prioritizes efficiency, people often neglect others, and even themselves.

It’s a funny thing about life; if you refuse to accept anything but the best, you very often get it.

生活的讽刺之处在于:如果你只接受最好的,你经常会得到最好的。

—— 《刀锋》威廉·萨默塞特·毛姆

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注