用ESP32-S2模块制作一个本地气象台
使用乐鑫公司的ESP32-S2-Mini-1模块,通过连接WiFi获取外网ip,利用高德地图的API接口实现定位,使用心知天气根据定位获取天气信息。
标签
嵌入式系统
显示
网络与通信
ESP32
2022寒假在家练
1315zzc
更新2022-03-01
金陵科技学院
1551

项目介绍

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

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

设计思路

FnwRXhI8kLq6lYUGb6Yh5uRRdpYF

硬件介绍

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

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

本次我使用的平台是arduino。

按键操作

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

功能展示

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

FmbkYuaSEAr7_4Rx9_kI6f5-VOy1

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

FkVJIj6r2suSA5r-os5Lvdbcobh_

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

Fn5rDAa9zADWAKT_w8W1ka9PxYmm

代码分析

首先是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。

未来计划

后面想实现一键播报天气的功能,并支持播报未来几天的天气。

软硬件
元器件
ESP32-S2-MINI-1
2.4GHz Wi­Fi (802.11 b/g/n) 模组, 内置ESP32­S2系列芯片,Xtensa® 单核32位LX7微处理器, 内置芯片叠封4MB flash,可叠封2MB PSRAM, 37个GPIO,丰富的外设, 板载PCB天线或外部天线连接器
CH340C
USB总线的转接芯片,实现USB转串口或者USB转打印口,内置时钟,无需外部晶振
SSD1306
OLED控制器 - 支持128*32、128*64 OLED显示屏
附件下载
get_weather_V1.5.zip
arduino完整工程,手机开热点名称为weather,密码为88888888
get_weather_V1.5.ino.esp32s2.bin
已编译的二进制文件
团队介绍
金陵科技学院-机电工程学院-赵正创
团队成员
1315zzc
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号