用ESP32-S2做一个本地气象台|CSDN创作打卡

这段时间在做“硬禾学堂”推出的寒假一起练活动,只要完成了对应的项目就可以免费获得开发板。

下面是我本次完成项目的一些分享:

项目介绍

我完成的项目是 制作一个本地气象台/温度计

  • 利用OLED显示

  • 显示当前本地的时间、温度和气象信息

硬件介绍

使用的开发板搭载的是乐鑫ESP32-S2-mini-1模块,配备了Xtensa单核32位LX7微处理器,支持高达240MHz的时钟频率以及4MB嵌入式flash,功能很强大。

本次项目使用到了OLED显示屏,WIFI以及按键三个模块。

本次我使用的平台是arduino。

按键操作

本次我只使用了一个按键用于切换显示界面,一共设置了三个显示界面分别是时间、天气、IP定位。

功能展示

上电会自动连接给定的WiFi,连接成功后会自动获取网络时间并进行显示

437a5c06f732f442a508e9de0bb388a0.png

按一下最左边的按键后切换到天气显示界面,显示当天的气温和天气

2507a0071fd2892aec6a91acf29d311c.png

再次按下最左边的按键后切换到IP定位显示界面,显示当前网络的IP地址以及定位信息

7b2bc51334753ef21154ab073233ef21.png

代码分析

首先是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 = "********";//输入申请到的密匙
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
" + "Host: " + host_ip + "
" + "Connection: close

");
    Serial.println("Get send");

    char endOfHeaders[] = "

";
    bool ok = client.find(endOfHeaders);
    if (!ok)
    {
        Serial.println("No response or invalid response!");
    }
    Serial.println("Skip headers");

    String line="";

    line += client.readStringUntil('
'); 
 
    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 = "******";//输入申请到的密匙
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
" + "Host: " + host_weather + "
" + "Connection: close

");
    Serial.println("Get send");

    char endOfHeaders[] = "

";
    bool ok = client.find(endOfHeaders);
    if (!ok)
    {
        Serial.println("No response or invalid response!");
    }
    Serial.println("Skip headers");

    String line="";

    line += client.readStringUntil('
'); 
 
    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());//循环下一页

完整代码展示

其中我把天气、IP、定位的刷新频率做了设置,一分钟刷新一次。

#include <Arduino.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <WiFi.h>//调用WiFi库

#include <U8g2lib.h>//调用屏幕显示函数库
U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 36, /* data=*/ 35, /* cs=*/ 46, /* dc=*/ 33, /* reset=*/ 34);//设置屏幕

const char* ssid="weather";//WiFi名称
const char* password="88888888";//WiFi密码

HTTPClient http;

//存放ip地址
String pageData = "";

//天气部分,使用的是心知天气
const char *host_weather = "api.seniverse.com";
const char *privateKey_weather = "******";//输入申请到的密匙
//char *city = NULL;
String city = "";
String province = "";
const char *language = "zh-Hans";

//获取定位部分,使用的是高德地图
const char *host_ip = "restapi.amap.com";
const char *privateKey_ip = "******";//输入申请到的密匙
//String *ip = NULL;
String ip;

//时间部分
const char *ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
struct tm timeinfo;

//按键部分
int button1Pin = 1;    
int button2Pin = 2;    
int button3Pin = 3;     
int button4Pin = 6;   

//屏幕显示状态
int show_mode = 1;

//屏幕显示
char str[64] = {0};

//天气刷新时间存放
unsigned long get_time = 0;

//存放天气信息
struct WetherData
{
    char city[32];
    char weather[64];
    char high[32];
    char low[32];
    char humi[32];
};

//存放城市定位
struct IPData
{
    char city_ip[32];
    char province_ip[32];
};

struct WetherData weatherdata = {0};
struct IPData ipdata = {0};

//获取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();
}

//获取定位信息
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
" + "Host: " + host_ip + "
" + "Connection: close

");
    Serial.println("Get send");

    char endOfHeaders[] = "

";
    bool ok = client.find(endOfHeaders);
    if (!ok)
    {
        Serial.println("No response or invalid response!");
    }
    Serial.println("Skip headers");

    String line="";

    line += client.readStringUntil('
'); 
 
    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();
}

void printLocalTime()
{
    if (!getLocalTime(&timeinfo))
    {
        Serial.println("Failed to obtain time");
        return;
    }
    Serial.println(&timeinfo, "%F %T %A");//格式化输出时间信息
}

//获取天气信息
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
" + "Host: " + host_weather + "
" + "Connection: close

");
    Serial.println("Get send");

    char endOfHeaders[] = "

";
    bool ok = client.find(endOfHeaders);
    if (!ok)
    {
        Serial.println("No response or invalid response!");
    }
    Serial.println("Skip headers");

    String line="";

    line += client.readStringUntil('
'); 
 
    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();
}

void setup()
{
    // put your setup code here, to run once:
    u8g2.begin(); //开始使用屏幕驱动 
    u8g2.enableUTF8Print();//启用UTF8支持函数(Unicode的一种可变长度字符编码函数)
    Serial.begin(115200);

    //按键初始化
    pinMode(button1Pin, INPUT_PULLUP); 
    pinMode(button2Pin, INPUT_PULLUP);
    pinMode(button3Pin, INPUT_PULLUP);
    pinMode(button4Pin, INPUT_PULLUP);

    Serial.printf("Connecting to %s",ssid);
    //Serial.println(ssid);//打印wifi名称

    WiFi.begin(ssid, password);//连接WiFi

    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");
    }
//    打印WiFi连接信息,IP地址,调试用    
//    Serial.println("");
//    Serial.println("WiFi connected");
//    Serial.println("IP address: ");
//    Serial.println(WiFi.localIP());
//
//    Serial.println("");
//    Serial.println("WiFi Conected!");

     // 从网络时间服务器上获取并设置时间
    // 获取成功后芯片会使用RTC时钟保持时间的更新
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
    printLocalTime();
    GetIP();    
    get_gps();
    get_weather();
}

void loop()
{
    // put your main code here, to run repeatedly:

   u8g2.setFont(u8g2_font_unifont_t_chinese2); //设置显示中文字体2
  //u8g2.setFontDirection(0);//设置字体方向
   u8g2.firstPage();//第一页
  do {
    
     unsigned long now_time = millis();//获取系统运行时间
     
    //60秒刷新一次气象等信息
    if((now_time - get_time) >= 60000)
    {
      GetIP();
      get_gps();
      get_weather();
      get_time = millis();
    }
    printLocalTime();

    //按键控制部分
    if(digitalRead(button1Pin) == LOW)
    {
      delay(100);
      if(digitalRead(button1Pin) == LOW)
      {
        switch(show_mode)
        {
           case 1:show_mode = 2;break;

           case 2:show_mode = 3;break;

           case 3:show_mode = 1;break;
           default:break;
        }
      }
    }

    //显示天气状况
    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)
    {
//      sprintf(str,"IP:%s",ip);
      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。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寻梦旅程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值