项目介绍
我完成的是项目6 制作一个本地气象台/温度计
-
利用OLED显示
-
显示当前本地的时间、温度和气象信息
设计思路
硬件介绍
使用的开发板搭载的是乐鑫ESP32-S2-mini-1模块,配备了Xtensa单核32位LX7微处理器,支持高达240MHz的时钟频率以及4MB嵌入式flash,功能很强大。
本次项目使用到了OLED显示屏,WIFI以及按键三个模块。
本次我使用的平台是arduino。
按键操作
本次我只使用了一个按键用于切换显示界面,一共设置了三个显示界面分别是时间、天气、IP定位。
功能展示
上电会自动连接给定的WiFi,连接成功后会自动获取网络时间并进行显示
按一下最左边的按键后切换到天气显示界面,显示当天的气温和天气
再次按下最左边的按键后切换到IP定位显示界面,显示当前网络的IP地址以及定位信息
代码分析
首先是WiFi连接部分,调用WiFi库,定义需要连接的wifi名称和wifi密码,启用WiFi.begin();来连接WiFi。
#include <WiFi.h>//调用WiFi库
const char* ssid="weather";//WiFi名称
const char* password="88888888";//WiFi密码
WiFi.begin(ssid, password);//连接WiFi
获取外网IP地址
引入HTTPClient库,通过http://ipinfo.io/ip这个链接来获取外网IP,刚开始我使用的是WiFi库里面的WiFi.localIP();来获取IP发现获取到的是局域网的IP,并不能用来定位,后来去查找了好多文档找到了这个方法。
#include <HTTPClient.h>
HTTPClient http;
//存放ip地址
String pageData = "";
//获取IP地址
void GetIP()
{
http.begin("http://ipinfo.io/ip");
int httpCode = http.GET();
if(httpCode == HTTP_CODE_OK){
pageData = http .getString();
Serial.print(pageData);
ip = pageData;
}else{
Serial.println("GET ERR");
}
http.end();
}
获取时间
获取网络时间服务器最常用的主机名是 pool.ntp.org;
通过网络时间服务器获得的时间是世界协调时间(UTC)/格林尼治时间(GMT),不同地区的时间可以通过时区换算, gmtOffset_sec 参数就是用来修正时区的,对于我们东八区(UTC/GMT+08:00)来说该参数需要填写 8 * 3600 ;
如果使用夏令时 daylightOffset_sec 就填写3600,否则就填写0;
最后的输出部分:%F 年-月-日,%T 显示时分秒:hh:mm:ss,%A 星期几的全称。
//时间部分
const char *ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
struct tm timeinfo;
void printLocalTime()
{
if (!getLocalTime(&timeinfo))
{
Serial.println("Failed to obtain time");
return;
}
Serial.println(&timeinfo, "%F %T %A");//格式化输出时间信息
}
根据IP获取定位
在IP定位中我使用的是高德地图提供的API接口,获取到的数据是Json格式所以要引入Json库,在使用前需要到高德地图申请个人认证开发者,创建自己的个人密匙后参考他给出的接口(https://restapi.amap.com/v3/ip?ip=用户的IP&output=xml&key=<用户的key>)利用GET请求获取到定位。
#include <ArduinoJson.h>
//获取定位部分,使用的是高德地图
const char *host_ip = "restapi.amap.com";
const char *privateKey_ip = "17efa72a2a0178d39f8407984bac9b99";
String ip;
//存放城市定位
struct IPData
{
char city_ip[32];
char province_ip[32];
};
struct IPData ipdata = {0};
//获取定位信息
void get_gps()
{
WiFiClient client;
if (!client.connect(host_ip, 80))
{
Serial.println("Connect host failed!");
return;
}
Serial.println("host Conected!");
String getUrl = "/v3/ip?ip=";
getUrl += ip;
getUrl += "&key=";
getUrl += privateKey_ip;
getUrl += "&coor=bd09ll";
client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host_ip + "\r\n" + "Connection: close\r\n\r\n");
Serial.println("Get send");
char endOfHeaders[] = "\r\n\r\n";
bool ok = client.find(endOfHeaders);
if (!ok)
{
Serial.println("No response or invalid response!");
}
Serial.println("Skip headers");
String line="";
line += client.readStringUntil('\n');
Serial.println(line);
DynamicJsonDocument doc(1400);
DeserializationError error = deserializeJson(doc, line);
if (error)
{
Serial.println("deserialize json failed");
return;
}
Serial.println("deserialize json success");
strcpy(ipdata.city_ip, doc["city"].as<const char*>());
strcpy(ipdata.province_ip, doc["province"].as<const char*>());
// 向串口发送城市信息,调试用
// Serial.print("市:");
// Serial.println(ipdata.city_ip);
// Serial.print("省:");
// Serial.println(ipdata.province_ip);
city = ipdata.city_ip;
province = ipdata.province_ip;
client.stop();
}
获取天气信息
在查询到IP并获取到定位后可以去查询天气信息了,我使用的是心知天气提供的API接口,同样也需要去申请一个并获取密匙,他有查询频率的限制(20次/分钟),查阅官网给出的文档,并根据他给的接口(https://api.seniverse.com/v3/weather/now.json?key=your_private_key&location=beijing&language=zh-Hans&unit=c)利用GET请求去查询天气。
//天气部分,使用的是心知天气
const char *host_weather = "api.seniverse.com";
const char *privateKey_weather = "SVieTap2mRssHyUHF";
String city = "";
String province = "";
const char *language = "zh-Hans";
//存放天气信息
struct WetherData
{
char city[32];
char weather[64];
char high[32];
char low[32];
char humi[32];
};
struct WetherData weatherdata = {0};
//获取天气信息
void get_weather()
{
WiFiClient client;
if (!client.connect(host_weather, 80))
{
Serial.println("Connect host failed!");
return;
}
Serial.println("host Conected!");
String getUrl = "/v3/weather/daily.json?key=";
getUrl += privateKey_weather;
getUrl += "&location=";
getUrl += city;
getUrl += "&language=";
getUrl += language;
client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host_weather + "\r\n" + "Connection: close\r\n\r\n");
Serial.println("Get send");
char endOfHeaders[] = "\r\n\r\n";
bool ok = client.find(endOfHeaders);
if (!ok)
{
Serial.println("No response or invalid response!");
}
Serial.println("Skip headers");
String line="";
line += client.readStringUntil('\n');
Serial.println(line);
DynamicJsonDocument doc(1400);
DeserializationError error = deserializeJson(doc, line);
if (error)
{
Serial.println("deserialize json failed");
return;
}
// Serial.println("deserialize json success");
strcpy(weatherdata.city, doc["results"][0]["location"]["name"].as<const char*>());
strcpy(weatherdata.weather, doc["results"][0]["daily"][0]["text_day"].as<const char*>());
strcpy(weatherdata.high, doc["results"][0]["daily"][0]["high"].as<const char*>());
strcpy(weatherdata.low, doc["results"][0]["daily"][0]["low"].as<const char*>());
strcpy(weatherdata.humi, doc["results"][0]["daily"][0]["humidity"].as<const char*>());
// 向串口发送天气信息,调试用
// Serial.print("城市:");
// Serial.println(weatherdata.city);
// Serial.print("天气状况:");
// Serial.println(weatherdata.weather);
// Serial.print("最高气温:");
// Serial.println(weatherdata.high);
// Serial.print("最低气温:");
// Serial.println(weatherdata.low);
// Serial.print("湿度:");
// Serial.println(weatherdata.humi);
//
// Serial.println("read json success");
// Serial.println();
// Serial.println("closing connection");
client.stop();
}
屏幕使用
该项目中我使用的是U8g2的屏幕库,屏幕是SSD1306,并对相应的管脚进行设置,我使用的字体是chinese2,刚开始使用的时候发现好多内容都不显示,查阅了资料后发现是库里面没有包含这些文字,按照教程把我需要使用的字都添加进去后就显示正常了。
#include <U8g2lib.h>//调用屏幕显示函数库
U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 36, /* data=*/ 35, /* cs=*/ 46, /* dc=*/ 33, /* reset=*/ 34);//设置屏幕
u8g2.begin(); //开始使用屏幕驱动
u8g2.enableUTF8Print();//启用UTF8支持函数(Unicode的一种可变长度字符编码函数)
u8g2.setFont(u8g2_font_unifont_t_chinese2); //设置显示中文字体2
//u8g2.setFontDirection(0);//设置字体方向
u8g2.firstPage();//第一页
do {
//显示天气状况
if(show_mode == 2)
{
sprintf(str,"城市: %s",weatherdata.city);
u8g2.setCursor(10, 15);//设置光标行与列
u8g2.print(str); //显示城市
sprintf(str,"温度: %s~%s",weatherdata.low,weatherdata.high);
u8g2.setCursor(10, 32);//设置光标行与列
u8g2.print(str);//显示温度
sprintf(str,"天气: %s",weatherdata.weather);
u8g2.setCursor(10, 49);//设置光标行与列
u8g2.print(str); //显示天气
}
//显示时间信息
if(show_mode == 1)
{
u8g2.setCursor(10, 15);//设置光标行与列
u8g2.print(&timeinfo, "%F"); //显示日期
u8g2.setCursor(10, 32);//设置光标行与列
u8g2.print(&timeinfo, "%T");//显示时间
u8g2.setCursor(10, 49);//设置光标行与列
u8g2.print(&timeinfo, "%A"); //显示星期
}
if(show_mode == 3)
{
u8g2.setCursor(0, 15);//设置光标行与列
u8g2.print(ip); //显示ip地址
sprintf(str,"省: %s",province);
u8g2.setCursor(0, 32);//设置光标行与列
u8g2.print(str); //显示省
sprintf(str,"市: %s",city);
u8g2.setCursor(0, 49);//设置光标行与列
u8g2.print(str);//显示市
}
} while ( u8g2.nextPage());//循环下一页
存在的问题
1.WiFi需要设置成2.4GHz频段的,设置为5GHz频段的开发板会连接不了。
2.从网络获取时间不一定每次都成功,如果没有成功获取会显示一个默认时间,并且其他功能无法使用,重新上电几次即可解决。
3.手机流量开的热点IP会定位到省会城市,家庭路由器的IP会定位到当前城市,需要使用的话尽量连接家庭路由器的WiFi。
未来计划
后面想实现一键播报天气的功能,并支持播报未来几天的天气。