
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.
生活的讽刺之处在于:如果你只接受最好的,你经常会得到最好的。
—— 《刀锋》威廉·萨默塞特·毛姆


发表回复