
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.“
What lies behind us and what lies before us are tiny matters compared to what lies within us.
与我们内心的事物相比,身后和眼前的事物都是微不足道的。
—— 爱默生

