基于ESP32-S2制作的本地气象台温度计
本项目采用乐鑫官方模块ESP32-S2-Mini-1主控,制作一个本地气象台温度计。功能为通过网络获取天气数据且自动校时,在12864oled屏上显示地区、天气、温度、时间和星期,并且绘制相应的天气图标。
标签
嵌入式系统
显示
网络与通信
2022寒假在家练
co1
更新2022-03-01
南京工业大学
1338

平台介绍

本次使用的平台,是基于乐鑫官方模块ESP32-S2-Mini-1制作的。该模块由一颗ESP32S2的处理器,和一颗可以运行在80M的QPI上的Flash以及一些外围电路组成。ESP32S2是一颗单核的Xtensa32的处理器,能运行在240MHz的主频上。官方提供了esp-idf和Arduino等开发框架,本次我在使用arduino作为开发框架开发。

任务需求

制作一个本地气象台/温度计

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

设计思路

连接网络,通过网络获取时间和天气情况,处理之后在oled屏幕上进行信息显示。

FvjYB4GXB1BlV-1o2QyGqIFqVynp

实现代码

所需库

#include <Arduino.h>
#include <ArduinoJson.h>//进行json的序列化,和转换做数据通讯
#include <WiFi.h>
#include <U8g2lib.h>
#include <SPI.h>
#include <Wire.h>
#include "time.h"

链接wifi

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 httpURL= "/v3/weather/now.json?key=";
    httpURL += privateKey;httpURL += "&location=";httpURL += city;httpURL += "&language=";httpURL += language;
    client.print(String("GET ") + httpURL + " 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);
    Json(line);
    client.stop();
}
void Json(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*>());
    strcpy(weatherdata.code,doc["results"][0]["now"]["code"].as<const char*>());
    mystr=weatherdata.code;
    OLEDDisplay(weatherdata.city, weatherdata.weather, weatherdata.temperature,weatherdata.code);
}

天气图标绘制

#define SUN  0
#define SUN_CLOUD  1
#define CLOUD 2
#define RAIN 3
#define THUNDER 4

//int weathercode;
//weathercode =int(weatherdata.code);
//uint8_t getSymbol(int weathercode)
//{
//  if (weathercode == 0)
//    return SUN;
////  if (weathercode == "9")
////    return SUN_CLOUD;
////  if (weathercode == "13" || weathercode == "14" || weathercode == "15"|| weathercode == "16" || weathercode == "17"|| weathercode == "18")
////    return RAIN;
////  if (weathercode == "11" || weathercode == "12" )
////    return THUNDER;
//}

void drawWeatherSymbol(u8g2_uint_t x, u8g2_uint_t y, uint8_t symbol)
{ 
  switch(symbol)
  {
    case SUN:
      u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
          Serial.println("its sun");
      u8g2.drawGlyph(x, y, 69);  
      break;
    case SUN_CLOUD:
      u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
      u8g2.drawGlyph(x, y, 65); 
      break;
    case CLOUD:
      u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
      u8g2.drawGlyph(x, y, 64); 
      break;
    case RAIN:
      u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
      u8g2.drawGlyph(x, y, 67); 
      break;
    case THUNDER:
      u8g2.setFont(u8g2_font_open_iconic_embedded_6x_t);
      u8g2.drawGlyph(x, y, 67);
      break;      
  }
}

void OLEDDisplay(String cityName, String cityweather, String citytemperature,String code)
{   struct tm timeinfo;
    if(!getLocalTime(&timeinfo)){
      Serial.println("Failed to obtain time");
      return;
    }
 u8g2.setCursor(43 , 30 ); u8g2.println("天");  //国内城市白天晴
//    u8g2.drawXBM( 0, 0, 38, 24, FACE_Neutral);
u8g2.firstPage();
do{
    u8g2.setFont(u8g2_font_wqy12_t_gb2312);
    u8g2.setFontDirection(0);
    u8g2.clearBuffer();
    u8g2.setCursor(5,10);
    u8g2.print("城市: ");
    u8g2.print(cityName);
    //    u8g2.drawXBMP(5, 64, 64, 32, sunny); 
    u8g2.setCursor(5, 23);
    u8g2.print("天气: ");
    u8g2.print(cityweather);
    u8g2.setCursor(5, 36);
    u8g2.print("温度: ");
    u8g2.print(citytemperature);
    u8g2.print("'C");
    u8g2.setCursor(5, 50);
    u8g2.print(&timeinfo, "%F");
    u8g2.setCursor(5, 63);
    u8g2.print("时间:");
    u8g2.print(&timeinfo, "%T");
        u8g2.print(" ");
    u8g2.print(&timeinfo, "%A");
    u8g2.sendBuffer();

myint=mystr.toInt();
Serial.println("myint");
Serial.println(myint);
uint8_t symbol;//=SUN;
  if (myint == 0)
    symbol= SUN;
  if (myint == 9)
    symbol=  SUN_CLOUD;
  if (myint == 13 || myint == 14 || myint == 15|| myint == 16 || myint == 17|| myint == 18)
    symbol=  RAIN;
  if (myint == 11 || myint == 12 )
    symbol=  THUNDER;

drawWeatherSymbol(71, 48, symbol);

}while(u8g2.nextPage());
   // delay(1000);
}

结果展示

Fhfo5C-a5GdFRRguWD-cwsENBPeh

所遇问题及解决过程

  1. esp32的专业开发环境为idf,在使用idf跑helloworld时就不断报错,最终解决方案为采用纯净版python(python版本问题),以及重新添加path。最后还是放弃idf,用了vscode和arduino,idf在每次编译时都要重新导入,非常慢,在用arduino时,只要编译一次,再修改后编译起来会快很多,新版2.0也能够看一些底层的文件,稍微好用一点点。
  2. 一开始想查看tm结构体里的信息,没找到。tm结构体在time.c里,time.c在esp32的cores的hal库里。
  3. 网络时间服务器最常用的主机名是 pool.ntp.org,通过网络时间服务器获得的时间是世界协调时间(UTC)/格林尼治时间(GMT),不同地区的时间可以通过时区换算, gmtOffset_sec 参数就是用来修正时区的,比如对于我们东八区(UTC/GMT+08:00)来说该参数就需要填写 8 * 3600 ;如果使用夏令时 daylightOffset_sec 就填写3600,否则就填写0;u8g2.print(&timeinfo, “%F %A%T”); %F代表获取的年月日,%A代表获取的星期几的全称,%T表示获取的时分秒。
  4. esp32作客户端向心知天气API发送GET请求;然后,从心知天气响应报文体中的JSON数据解析出天气,气温数据,首先需要注册一个心知天气账号,然后申请一个免费版,然后就可以从网页获取天气了.
  5. 从心知API获取到一串数据后,内含天气状况代码,阅读心知使用手册对应天气,然后根据天气绘制图标。Fmx2AcVFS3Gl-tXdayPLR3arMcPR
  6. API的地址为api.seniverse.com/v3/weather/now.json?key=私钥(填写自己申请到的)&location=yanji(填写地点)&language=en&unit=c。服务器响应了一个JSON对象:
    {"results":[{"location":{"id":"WTSQQYHVQ973","name":"南京","country":"CN",
    "path":"南京,南京,江苏,中国","timezone":"Asia/Shanghai","timezone_offset":"+08:00"},
    "now":{"text":"晴","code":"0","temperature":"8"},"last_update":"2022-02-24T17:50:05+08:00"}]}
  7. 在绘制天气图标时,遇到了很多问题:
  8.  u8g2有两个库,Adafrui图形化方便,但是字库不圆滑,u8g2显示汉字方便,引脚配置也方便。
  9. 为了中文显示天气时间方便,用了u8g2lib库,导致画天气图标花了很久,一开始用drawXBMPdrawFrame,最后一个参数取字模的指针没搞明白,最后直接用了drawGlyph绘制字集里的符号(强烈推荐),又快又多,就是需要查一下字库,然后把编
    号对应图表。
  10. 调用函数drawweathersymbol时,一开始什么也不显示,后来发现在draw之前要先设置库,库设置错了。本项目中一共使用了u8g2_font_open_iconic_embedded_6x_t和u8g2_font_open_iconic_weather_6x_t两个库,里面图标特别全,比字模好用多了。
  11. 因为从知心API抓取到的天气代码是字符串的形式,我又想通过switch来赋值symbol然后draw,所以我直接用保存抓取到的天气数据的数组和天气代号进行比较,但是始终报错,说我没用定义类型或者找不到数组,最后分析应该是数组不能在新写的函数里读到,最后一同操作,连转两次,把天气代码转成了int型,再和天气代号比较,虽然能用了,但是代码实在太丑了。
  12. 使用u8g2lib库时总是需要在loop里用

    u8g.firstPage();
    
    do {undefined
    
    draw();
    
    } while( u8g.nextPage() );

    才能显示正确。原因是u8glib可能使用了缓存机制,有时候一次循环画不完arduino第一次编译会比较慢,后面再修改后编译就会快单个显示图标之后,怎么和文字信息一起显示又是一个问题。

心得体会

  1. 阅读examples的能力很重要!
  2. 多从网上检索、筛选资料。
  3. 多动手实践,有错误慢慢改。
  4. 除了敲代码,正确表达自己的错误,正确记录解决问题过程的能力也很重要。

最后附上几个好用的网站和帖子:

天气现象代码说明 | 心知天气文档 (seniverse.com)

Font Converter (squix.ch)

【新提醒】【原创】OLED屏-U8glib库 增强版 U8G2库。-Arduino中文社区 - Powered by Discuz!

软硬件
元器件
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转打印口,内置时钟,无需外部晶振
电路图
附件下载
weather.rar
团队介绍
南京工业大学计算机科学与技术学院
团队成员
co1
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号