天气时钟

最近在玩esp32,手边正好还有一块16bit的TFT_LCD屏幕,大小只有135×240,思来想去还是用来显示时间和天气吧🤔花费一番小心思,还添上了一个圆形带指针的可以转动的表😓,天气用的心知天气提供的api,方便且免费且贴心的送精美天气图标一份😻

成品去仓库看吧,就不贴图了🤭

Gitee: 天气时钟

连WiFi

过程基本是不变的,记录一下方便copy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <WiFi.h>

//这里改用char*变量也可以,但是Ardunio会有警告似乎是让改成String,改了之后报错(烦),现在就直接定义成宏,以后再看看。
#define ssid "balabala" // 修改wifi名称
#define password "*******" // 修改wifi密码


void setup() {
...
WiFi.begin(ssid, password);
delay(1000);
while (WiFi.status() != WL_CONNECTED) {
// 等待连接,loading是一个加载动画,一会儿说
//for (byte n = 0; n < 10; n++) {
// loading("Connecting Wifi", 50);
//}
}
//连接成功
if (WiFi.status() == WL_CONNECTED) {
Serial.println("wifi is connected!");
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
}
...
}

连上WiFi,就可以用它上网了(连的WiFi确保能上网🫵

这里照着别人做了个动画,等待连接WiFi就不会无聊了(确信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <TFT_eSPI.h>

TFT_eSPI tft = TFT_eSPI();
TFT_eSprite clk = TFT_eSprite(&tft);
byte loadNum = 6; //进度条长度
void loading(String msg, byte delayTime) {
clk.createSprite(200, 50);
clk.fillSprite(0x0000);
clk.drawRoundRect(0, 0, 200, 16, 8, 0xFFFF);
clk.fillRoundRect(3, 3, loadNum, 10, 5, 0xFFFF);
clk.setTextColor(TFT_GREEN, 0x0000);
clk.setTextDatum(CC_DATUM);
clk.drawString(msg, 100, 40, 2);
clk.pushSprite(20, 30);
clk.deleteSprite();
loadNum += 1;
if (loadNum >= 195) {
loadNum = 195;
}
delay(delayTime);
}
//加载成功后过渡(动画)
void successload(String msg) {
loadNum = 6;
clk.createSprite(200, 50);
clk.fillSprite(0x0000);
clk.setTextDatum(CC_DATUM);
clk.setTextColor(TFT_GREEN, 0x0000);
clk.drawString(msg, 100, 10, 2);
clk.pushSprite(20, 30);
clk.deleteSprite();
delay(1000);
tft.fillScreen(0x0000);
}

时钟

有了网就可以获取网络时间了,一顿搜索就被我找到啦,整理一下如何获取实时网络时间,也方便以后copy🤭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <WiFiUdp.h>
#include <NTPClient.h>

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);


void setup() {
...//初始化网络,tft等
timeClient.setTimeOffset(28800);//设置时区偏差
timeClient.begin();
...
}
// 获取年月日时分秒,并调用显示函数
void updateDate(){
timeClient.update();
unsigned long int epochTime = timeClient.getEpochTime();
int hour = timeClient.getHours();
int minu = timeClient.getMinutes();
int secs = timeClient.getSeconds();
int weekday = timeClient.getDay();
struct tm* ptm = gmtime((time_t*)&epochTime);
int day = ptm->tm_mday;
int month = ptm->tm_mon + 1;
int year = ptm->tm_year+1900;
//圆表盘
showClock(hour,minu,secs,60,100,60);
//文字
showDate(hour, minu,secs,year,month, day, weekday);
}

显示文字没啥好说的,就是调调位置,改改颜色,变变大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

void showText(int16_t x, int16_t y, uint8_t font, uint8_t s, uint16_t fg, uint16_t bg, const String str) {
tft.setCursor(x, y, font);
tft.setTextColor(fg, bg);
tft.setTextSize(s);
tft.println(str);
}
//数字转成对应的文字
String getWeekdays(int weekday) {
String res;
switch (weekday) {
case 1: res = "Mon. "; break;
case 2: res = "Tues."; break;
case 3: res = "Wed. "; break;
case 4: res = "Thur."; break;
case 5: res = "Fri. "; break;
case 6: res = "Sat. "; break;
case 7: res = "Sun. "; break;
}
return res;
}

void showDate(int hou, int minu, int secs,int year, int mon, int days, int weekday) {
String shour,sminu,ssecs;
if (hou < 10) {
shour = "0"+(String)hou+":";
} else shour = (String)hou+":";
if (minu < 10) {
sminu = "0"+(String)minu;
} else sminu = (String)minu;
if (secs < 10) {
ssecs = "0"+(String)secs;
} else ssecs = (String)secs;
showText(15, 30, 2, 2, TFT_WHITE, TFT_BLACK, shour+sminu);
showText(90, 40, 1, 2, TFT_WHITE , TFT_BLACK, ssecs);
showText(10, 10, 2, 1, TFT_CYAN , TFT_BLACK, (String)mon + "/" + (String)days+"/"+(String)year+" "+getWeekdays(weekday));
}

圆标盘的显示原理也非常简单。以显示秒针为例,做个简单说明。

pCfDn9f.jpg

根据时间确定两个端点即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <math.h>

void showClock(int hou, int minu, int secs,int cx, int cy, int size){
int sr = size/2;
int mr = sr*3/4;
int hr = sr/2;

int sx = (int)(sr+sr*sin((double)secs*M_PI/30));
int sy = (int)(sr-sr*cos((double)secs*M_PI/30));
int mx = (int)(sr+mr*sin((double)minu*M_PI/30));
int my = (int)(sr-mr*cos((double)minu*M_PI/30));
int hx = (int)(sr+hr*sin((double)hou*M_PI/6));
int hy = (int)(sr-hr*cos((double)hou*M_PI/6));

clk.createSprite(size+6, size+6);
clk.fillSprite(TFT_BLACK);
clk.drawCircle(sr,sr,sr,TFT_WHITE);

clk.fillRect(sr-2,0,4,6,TFT_WHITE);
clk.fillRect(size-6,sr-2,6,4,TFT_WHITE);
clk.fillRect(sr-2,size-6,4,6,TFT_WHITE);
clk.fillRect(0,sr-2,6,4,TFT_WHITE);

clk.drawLine(sr,sr,sx,sy,TFT_RED);
clk.drawLine(sr,sr,mx,my,TFT_WHITE);
clk.drawLine(sr,sr,hx,hy,TFT_YELLOW);
clk.pushSprite(cx-size/2, cy-size/2);
clk.deleteSprite();
}

天气

心知天气注册账号后就能免费申请产品使用api了(白嫖党狂喜🤭
除了发送http请求外,还要解析返回的json字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "image.h"

DynamicJsonDocument doc(512);

//发送请求,解析响应,调用显示函数。 key city 记得替换
void updateWeather() {
HTTPClient http;
http.begin("https://api.seniverse.com/v3/weather/now.json?key="+key+"&location="+city+"&language=en&unit=c");
int httpCode = http.GET();
if (httpCode > 0) {
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
deserializeJson(doc, payload);
Serial.println(payload);
JsonObject root = doc.as<JsonObject>();
JsonObject result = root["results"][0];
JsonObject location = result["location"];
String name = location["name"];
String country = location["country"];
JsonObject now = result["now"];
String text = now["text"];
String code = now["code"];
String temperature = now["temperature"];
String last_update = result["last_update"];

showWeather(name,country,text,code.toInt(),temperature);
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
//weatherIcons二维数组,存储天气图标,sheshidu存储℃符号,均在image.h中定义
void showWeather(String name,String country,String text,int code,String temperature){
clk.createSprite(110, 135);
clk.fillSprite(TFT_BLACK);
clk.setSwapBytes(true);
clk.setTextColor(TFT_CYAN, TFT_BLACK);
clk.setCursor(0, 10, 1);
clk.setTextSize(2);
clk.println(name+" "+country);
clk.setTextColor(TFT_WHITE, TFT_BLACK);
if(code==99) code = 39;
clk.pushImage(0,40,50,50,weatherIcons[code]);
clk.setCursor(5, 95, 1);
clk.setTextSize(2);
clk.println(text);
clk.setCursor(70, 40, 1);
clk.setTextSize(2);
clk.println(temperature);
clk.pushImage(70,60,20,20,sheshidu);
clk.pushSprite(130, 0);
clk.deleteSprite();
}

综合

两个功能模块都实现了,现在就是任务调度的问题,又是一顿搜,我觉得TaskScheduler这个库不错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <TaskScheduler.h>

Scheduler runner;
void updateDate();
void updateWeather();
//时间每0.1ms刷新一次
Task dateTask(100, TASK_FOREVER, &updateDate);
//天气每分钟更新一次
Task weatherTask(60000, TASK_FOREVER, &updateWeather);

void setup() {
...
runner.init();
runner.addTask(dateTask);
runner.addTask(weatherTask);
dateTask.enable();
weatherTask.enable();
...
}
void loop() {
runner.execute();
}

记录完毕。


天气时钟
https://zy946.github.io/posts/2581b914/
作者
zy
发布于
2023年7月12日
许可协议