2022年寒假在家练硬禾项目--本地气象台/温度计 一、项目描述 1.1项目介绍
本项目基于ESP32-S2模块,联网后通过http协议访问“知心天气”获得本地气象数据和温度并在模块上的OLED屏上对获得的数据进行显示,展示本地时间、温度和气象信息,实现一个简单的本地气象台/温度计.
1.2设计思路(框图)
通过网上搜寻资料知道,要设计一个气象台,过程本质就是ESP32-S2模块通过API获取气象数据然后把数据解析出来并在OLED上显示即可。在这个过程中,ESP32-S2模块通过WIFI连接获得本地IP地址,然后作为客户端向“知心天气”(目的服务器)请求数据,然后模块解析收到响应数据并将数据在OLED上显示。
1.3硬件介绍
ESP32-S2-MINI-1:ESP32-S2-FH4芯片
ESP32-S2-FH4芯片搭载Xtensa®32位LX7单核处理器,工作频率高达240 MHz。芯片支持二次开发,无需使用其他微控制器或处理器。ESP32-S2芯片包括一个功能完备的Wi-Fi子系统,符合IEEE 802.11b/g/n协议。集成了Wi-Fi MAC、Wi-Fi射频和基带、天线开关、射频Balun、功率放大器、低噪声放大器等,提供了一个完整的Wi-Fi解决方案。ESP32-S2-FH4还集成了丰富的外设接口。用户可以关闭CPU的电源,利用低功耗协处理器监测外设的状态变化或某些模拟量是否超出阈值。
1.4实现的功能以及图片展示
在OLED上显示了本地位置、天气、温度以及时间
1.5主要代码片段以及说明
1.5.1 WIFI连接模块
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)//如果连接失败,则延迟并不断打印.,直到连接成功
{
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");//WIFI连接成功
Serial.println("IP address: ");
Serial.println(WiFi.localIP());//显示本地IP地址
1.5.2 HTTP请求模块
WiFiClient client;
if (!client.connect(host, 80))//如果访问失败,串口打印连接失败并跳出该函数
{
Serial.println("Connect host failed!");
return;
}
Serial.println("host Conected!");
String getUrl = "/v3/weather/now.json?key=";
getUrl += privateKey;
getUrl += "&location=";
getUrl += city;
getUrl += "&language=";
getUrl += language;
client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");//访问API接口
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="";//设置存储所需ROW Data的字符串
line += client.readStringUntil('\n');
Serial.println(client.readStringUntil('\n'));
Serial.println(line);
parseJson(line);//调用解析函数,对JSON数据进行解析
client.stop();
1.5.3 数据解析模块
DynamicJsonDocument doc(1400);//设置解析JSON所需内存大小为1400
DeserializationError error = deserializeJson(doc, line);//解析JSON数组信息
struct WeatherData weatherdata = {0};
Serial.println(doc["results"][0]["daily"][0].as<const char*>());
strcpy(weatherdata.city, doc["results"][0]["location"]["name"].as<const char*>());
strcpy(weatherdata.weather, doc["results"][0]["now"]["text"].as<const char*>());
strcpy(weatherdata.temperature, doc["results"][0]["now"]["temperature"].as<const char*>());
OLEDDisplay(weatherdata.city, weatherdata.weather, weatherdata.temperature);
Serial.println("City");
Serial.println(weatherdata.city);//串口打印本地城市信息
Serial.println("textDay");
Serial.println(weatherdata.weather);//串口打印当前本地气象信息
Serial.println("temperature");
Serial.println(weatherdata.temperature);//串口打印当前本地城市温度
Serial.println("read json success");
Serial.println();
Serial.println("closing connection");//串口打印读取并解析JSON成功,关闭http连接
1.5.4 OLED展示模块
struct tm timeinfo;//定义struct tm结构体 地址&timeinfo
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
return;
}//如果getLocalTime从网络时间服务器中获取时间失败,在串口打印获取时间失败
u8g2.setFont(u8g2_font_5x7_tr);//设置OLED显示的字体
u8g2.setFontDirection(0);//设置OLED字体展示方式为横向展示
u8g2.clearBuffer();//清除缓存
u8g2.setCursor(5,8);
u8g2.print("City: ");
u8g2.print(cityName);//OLED展示本地城市信息
u8g2.setCursor(5, 18);
u8g2.print("Weather: ");
u8g2.print(cityweather);//OLED展示本地气象信息
u8g2.setCursor(5, 28);
u8g2.print("temperature: ");
u8g2.print(citytemperature);//OLED展示本地温度
u8g2.print("'C");
u8g2.setCursor(5, 38);
u8g2.print(&timeinfo, "%F");//OLED展示本地时间中的年-月-日
u8g2.print(" ");
u8g2.print(&timeinfo, "%A");//OLED展示本地时间中的具体星期
u8g2.setCursor(5, 48);
u8g2.print("Time Now: ");
u8g2.print(&timeinfo, "%T");//OLED展示本地时间中的具体时间
u8g2.sendBuffer();
delay(1000);
1.6遇到的主要问题和解决办法
遇到问题1:使用Ardunio作为编辑器,在配置ESP32-S2环境的时候,一直报找不到xtensa-esp32s2-elf-g++的错误
解决办法1:通过上网查找资料,发现自己在配置环境的时候少了一步,忘记将xtensa-esp32s2-elf和mkspiffs-0.2.3-arduino-esp32-win32导入到文件中。
遇到问题2:在使用串口打印JSON数据时,发现一直打印的是HTTP /200啥的,一直不是想要数据。
解决办法2:询问好朋友,他说这个打印的是报头,得进行剔除操作,才能获取想要的数据。然后通过网上找资料,成功解决问题。
1.7未来计划和建议
因为自己现在做的这个项目是要求里最简单的那一个,实现起来并不复杂,所以未来计划尝试利用ESP32-S2实现一个收音机,继续发挥这个板子的强大之处。使用Ardunio开发ESP32-S2,虽然配置上相对简单,但是编辑环境很不友好,函数不能折叠,报错提示不明显,但是串口调试窗口是真的香,我没有什么建议。
二、可编译下载代码
#include <Arduino.h>
#include <ArduinoJson.h>
#include <WiFi.h>
#include <U8g2lib.h>
#include <SPI.h>
#include "time.h"
//--------------Wifi信息--------------//
const char* ssid = "1234";//wifi名字
const char* password = "a66778899";//wifi密码
//----------------------------------//
const char* host = "api.seniverse.com";//服务器地址
const char* privateKey = "SaJ24zif0X_09MWYz";//知心天气API私钥
const char* city = "nanning";//城市 要获取本地得在代码中自己修改本地城市信息
const char *language = "en";//语言
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 36, /* data=*/ 35, /* cs=*/ 46, /* dc=*/ 33, /* reset=*/ 34);//给OLED屏分配管脚
void OLEDDisplay(String cityName, String cityweather, String temperature, String cityhumi);//OLED屏显示
void Wifi_connect();//Wifi连接模块
void httpRequest();//http请求模块
void parseJson(String line);//解析Json模块
struct WeatherData //天气数据结构体
{
char city[32];
char weather[64];
char temperature[32];
};
void setup()
{
Serial.begin(115200);
u8g2.begin();
u8g2.enableUTF8Print();
Wifi_connect();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}
void loop()
{
httpRequest();
delay(1000);
}
void Wifi_connect()
{
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void httpRequest()
{
WiFiClient client;
if (!client.connect(host, 80))
{
Serial.println("Connect host failed!");
return;
}
Serial.println("host Conected!");
String getUrl = "/v3/weather/now.json?key=";
getUrl += privateKey;
getUrl += "&location=";
getUrl += city;
getUrl += "&language=";
getUrl += language;
client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\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(client.readStringUntil('\n'));
Serial.println(line);
parseJson(line);
client.stop();
}
void parseJson(String line)
{
DynamicJsonDocument doc(1400);
DeserializationError error = deserializeJson(doc, line);
struct WeatherData weatherdata = {0};
Serial.println(doc["results"][0]["daily"][0].as<const char*>());
strcpy(weatherdata.city, doc["results"][0]["location"]["name"].as<const char*>());
strcpy(weatherdata.weather, doc["results"][0]["now"]["text"].as<const char*>());
strcpy(weatherdata.temperature, doc["results"][0]["now"]["temperature"].as<const char*>());
OLEDDisplay(weatherdata.city, weatherdata.weather, weatherdata.temperature);
Serial.println("City");
Serial.println(weatherdata.city);
Serial.println("textDay");
Serial.println(weatherdata.weather);
Serial.println("temperature");
Serial.println(weatherdata.temperature);
Serial.println("read json success");
Serial.println();
Serial.println("closing connection");
}
void OLEDDisplay(String cityName, String cityweather, String citytemperature)
{
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
return;
}
u8g2.setFont(u8g2_font_5x7_tr);
u8g2.setFontDirection(0);
u8g2.clearBuffer();
u8g2.setCursor(5,8);
u8g2.print("City: ");
u8g2.print(cityName);
u8g2.setCursor(5, 18);
u8g2.print("Weather: ");
u8g2.print(cityweather);
u8g2.setCursor(5, 28);
u8g2.print("temperature: ");
u8g2.print(citytemperature);
u8g2.print("'C");
u8g2.setCursor(5, 38);
u8g2.print(&timeinfo, "%F");
u8g2.print(" ");
u8g2.print(&timeinfo, "%A");
u8g2.setCursor(5, 48);
u8g2.print("Time Now: ");
u8g2.print(&timeinfo, "%T");
u8g2.sendBuffer();
delay(1000);
}