ESP8266开发之旅 应用篇② OLED显示天气屏
1.前言
这一篇,博主将教大家怎么去实现一个简易版本的天气助手。
先来一个博主已经实现功能的图片,如下:
1.1 知识储备
本篇需要用到以下知识点:
- 运用到ArduinoJson V5库,github传送门,请读者自行下载该库放到Arduino安装目录(这里直接使用,博主后面计划会详细讲解该库,敬请期待);
- 运用到TCP Client,请参考 ESP8266开发之旅 网络篇⑦ TCP Server & TCP Client
- 运用到STA模式,请参考 ESP8266开发之旅 网络篇④ Station——ESP8266WiFiSTA库的使用
- 运用到一键配网功能,请参考 ESP8266开发之旅 网络篇⑧ SmartConfig——一键配网
- 运用U8G2 OLED库,请参考 玩转u8g2 OLED库,一篇就够
2.实验内容
往心知天气平台请求当地城市天气情况,并在OLED上显示天气图标、温度值以及城市名称;
3.实验准备
- NodeMcu开发板;
- Android 手机 + 博主一键配网App;
- SSD1306 128X64 OLED显示屏;
- 若干线;
引脚连接:
- SSD1306直接连接到NodeMcu的IIC端口,请参考 ESP8266开发之旅 基础篇③ ESP8266与Arduino的开发说明
4.实验步骤
把以下代码烧录进NodeMcu:
/**
* 日期:2019/05/25
* 功能:OLED显示天气屏
* 作者:单片机菜鸟
**/
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <U8g2lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif
#define LED D4
#define DEBUG //是否开启debug功能
#ifdef DEBUG
#define DebugPrintln(message) Serial.println(message)
#else
#define DebugPrintln(message)
#endif
#ifdef DEBUG
#define DebugPrint(message) Serial.print(message)
#else
#define DebugPrint(message)
#endif
#define WEATHER_CODE_DAY_SUN "0" //晴(国内城市白天晴)
#define WEATHER_CODE_NIGHT_SUN "1" //晴(国内城市夜晚晴)
#define WEATHER_CODE_DAY_SUN1 "2" //晴(国外城市白天晴)
#define WEATHER_CODE_NIGHT_SUN2 "3" //晴(国外城市夜晚晴)
#define WEATHER_CODE_CLOUDY "4" //多云
#define WEATHER_CODE_DAY_PARTLY_CLOUDY "5" //白天晴间多云
#define WEATHER_CODE_NIGHT_PARTLY_CLOUDY "6" //夜晚晴间多云
#define WEATHER_CODE_DAY_MOSTLY_CLOUDY "7" //白天大部多云
#define WEATHER_CODE_NIGHT_MOSTLY_CLOUDY "8" //夜晚大部多云
#define WEATHER_CODE_OVERCAST "9" //阴
#define WEATHER_CODE_SHOWER "10" //阵雨
#define WEATHER_CODE_THUNDERSHOWER "11" //雷阵雨
#define WEATHER_CODE_THUNDERSHOWER_WITH_HAIL "12" //雷阵雨伴有冰雹
#define WEATHER_CODE_LIGHT_RAIN "13" //小雨
#define WEATHER_CODE_MODERATE_RAIN "14" //中雨
#define WEATHER_CODE_HEAVY_RAIN "15" //大雨
#define WEATHER_CODE_STORM "16" //暴雨
#define WEATHER_CODE_HEAVY_STORM "17" //大暴雨
#define WEATHER_CODE_SEVERE_STORM "18" //特大暴雨
#define WEATHER_CODE_ICE_RAIN "19" //冻雨
#define WEATHER_CODE_SLEET "20" //雨夹雪
#define WEATHER_CODE_SNOW_FLURRY "21" //阵雪
#define WEATHER_CODE_LIGHT_SNOW "22" //小雪
#define WEATHER_CODE_MODERATE_SNOW "23" //中雪
#define WEATHER_CODE_HEAVY_SNOW "24" //大雪
#define WEATHER_CODE_SNOW_STORM "25" //暴雪
#define SUN_DAY 0
#define SUN_NIGHT 1
#define SUN_CLOUD 2
#define CLOUD 3
#define RAIN 4
#define THUNDER 5
//声明方法
bool autoConfig();
void smartConfig();
bool sendRequest(const char* host, const char* cityid, const char* apiKey);
bool skipResponseHeaders();
void readReponseContent(char* content, size_t maxSize);
void stopConnect();
void clrEsp8266ResponseBuffer(void);
bool parseUserData(char* content, struct UserData* userData);
void drawWeatherSymbol(u8g2_uint_t x, u8g2_uint_t y, uint8_t symbol);
void drawWeather(uint8_t symbol, int degree);
const unsigned long BAUD_RATE = 115200;// serial connection speed
const unsigned long HTTP_TIMEOUT = 5000; // max respone time from server
const size_t MAX_CONTENT_SIZE = 500; // max size of the HTTP response
const char* host = "api.seniverse.com";
const char* APIKEY = "wcmquevztdy1jpca"; //API KEY
const char* city = "guangzhou";
const char* language = "zh-Hans";//zh-Hans 简体中文 会显示乱码
int flag = HIGH;//默认当前灭灯
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
WiFiClient client;
char response[MAX_CONTENT_SIZE];
char endOfHeaders[] = "\r\n\r\n";
long lastTime = 0;
// 请求服务间隔
long Delay = 20000;
// 我们要从此网页中提取的数据的类型
struct UserData {
char city[16];//城市名称
char weather_code[4];//天气现象code(多云...)
char temp[5];//温度
};
/**
* @Desc 初始化操作
*/
void setup() {
Serial.begin(BAUD_RATE);
pinMode(LED,OUTPUT);
digitalWrite(LED, HIGH);
WiFi.disconnect();
if(!autoConfig()){
smartConfig();
DebugPrint("Connecting to WiFi");//写几句提示,哈哈
while (WiFi.status() != WL_CONNECTED) {
//这个函数是wifi连接状态,返回wifi链接状态
delay(500);
DebugPrint(".");
}
}
delay(1000);
digitalWrite(LED, LOW);
DebugPrintln("IP address: ");
DebugPrintln(WiFi.localIP());//WiFi.localIP()返回8266获得的ip地址
lastTime = millis();
u8g2.begin();
u8g2.enableUTF8Print();
//使能软件看门狗的触发间隔
ESP.wdtEnable(5000);
}
/**
* @Desc 主函数
*/
void loop() {
while (!client.connected()){
if (!client.connect(host, 80)){
flag = !flag;
digitalWrite(LED, flag);
delay(500);
//喂狗
ESP.wdtFeed();
}
}
if(millis()-lastTime>=Delay){
//每间隔20s左右调用一次
lastTime = millis();
if (sendRequest(host, city, APIKEY) && skipResponseHeaders()) {
clrEsp8266ResponseBuffer();
readReponseContent(response, sizeof(response));
UserData userData;
if (parseUserData(response, &userData)) {
showWeather(&userData);
}
}
}
//喂狗
ESP.wdtFeed();
}
/**
* 自动连接20s 超过之后自动进入SmartConfig模式
*/
bool autoConfig(){
WiFi.mode(WIFI_AP_STA); //设置esp8266 工作模式
WiFi.begin();
delay(2000);//刚启动模块的话 延时稳定一下
DebugPrintln("AutoConfiging ......");
for(int index=0;index<10;index++){
int wstatus = WiFi.status();
if (wstatus == WL_CONNECTED){
DebugPrintln("AutoConfig Success");
DebugPrint("SSID:");
DebugPrintln(WiFi.SSID().c_str());
DebugPrint("PSW:");
DebugPrintln(WiFi.psk().c_str());
return true;
}else{
DebugPrint(".");
delay(500);
flag = !flag;
digitalWrite(LED, flag);
}
}
DebugPrintln("AutoConfig Faild!");
return false;
}
/**
* 开启SmartConfig功能
*/
void smartConfig()
{
WiFi.mode(WIFI_STA);
delay(1000);
DebugPrintln("Wait for Smartconfig");
// 等待配网
WiFi.beginSmartConfig();
while (1){
DebugPrint(".");
delay(200);
flag = !flag;
digitalWrite(LED, flag);
if (WiFi.smartConfigDone()){
//smartconfig配置完毕
DebugPrintln("SmartConfig Success");
DebugPrint("SSID:");
DebugPrintln(WiFi.SSID().c_str());
DebugPrint("PSW:");
DebugPrintln(WiFi.psk().c_str());
WiFi.mode(WIFI_AP_STA); //设置esp8266 工作模式
WiFi.setAutoConnect(true); // 设置自动连接
break;
}
}
}
/**
* @发送请求指令
*/
bool sendRequest(const char* host, const char* cityid, const char* apiKey) {
// We now create a URI for the request
//心知天气
String GetUrl = "/v3/weather/now.json?key=";
GetUrl += apiKey;
GetUrl += "&location=";
GetUrl += city;
GetUrl += "&language=";
GetUrl += language;
// This will send the request to the server
client.print(String("GET ") + GetUrl + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
DebugPrintln("create a request:");
DebugPrintln(String("GET ") + GetUrl + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n");
delay(1000);
return true;
}
/**
* @Desc 跳过 HTTP 头,使我们在响应正文的开头
*/
bool skipResponseHeaders() {
// HTTP headers end with an empty line
bool ok = client.find(endOfHeaders);
if (!ok) {
DebugPrintln("No response or invalid response!");
}
return ok;
}
/**
* @Desc 从HTTP服务器响应中读取正文
*/
void readReponseContent(char* content, size_t maxSize) {
size_t length = client.readBytes(content, maxSize);
delay(100);
DebugPrintln("Get the data from Internet!");
content[length] = 0;
DebugPrintln(content);
DebugPrintln("Read data Over!");
client.flush();//这句代码需要加上 不然会发现每隔一次client.find会失败
}
// 关闭与HTTP服务器连接
void stopConnect() {
client.stop();
}
void clrEsp8266ResponseBuffer(void){
memset(response, 0, MAX_CONTENT_SIZE); //清空
}
bool parseUserData(char* content, struct UserData* userData) {
// -- 根据我们需要解析的数据来计算JSON缓冲区最佳大小
// 如果你使用StaticJsonBuffer时才需要
// const size_t BUFFER_SIZE = 1024;
// 在堆栈上分配一个临时内存池
// StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;
// -- 如果堆栈的内存池太大,使用 DynamicJsonBuffer jsonBuffer 代替
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(content);
if (!root.success()) {
Serial.println("JSON parsing failed!");
return false;
}
//复制我们感兴趣的字符串
strcpy(userData->city, root["results"][0]["location"]["name"]);
strcpy(userData->weather_code, root["results"][0]["now"]["code"]);
strcpy(userData->temp, root["results"][0]["now"]["temperature"]);
// -- 这不是强制复制,你可以使用指针,因为他们是指向“内容”缓冲区内,所以你需要确保
// 当你读取字符串时它仍在内存中
return true;
}
/**
* 根据天气接口返回的数据判断显示
*/
void showWeather(struct UserData* userData){
if(strcmp(userData->weather_code,WEATHER_CODE_DAY_SUN) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_DAY_SUN1) == 0){
drawWeather(SUN_DAY,userData->temp,userData->city);
}else if(strcmp(userData->weather_code,WEATHER_CODE_NIGHT_SUN) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_NIGHT_SUN2) == 0 ){
drawWeather(SUN_NIGHT,userData->temp,userData->city);
}else if(strcmp(userData->weather_code,WEATHER_CODE_DAY_PARTLY_CLOUDY) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_NIGHT_PARTLY_CLOUDY) == 0 ){
drawWeather(SUN_CLOUD,userData->temp,userData->city);
}else if(strcmp(userData->weather_code,WEATHER_CODE_CLOUDY) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_DAY_MOSTLY_CLOUDY) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_NIGHT_MOSTLY_CLOUDY) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_OVERCAST) == 0){
drawWeather(CLOUD,userData->temp,userData->city);
}else if(strcmp(userData->weather_code,WEATHER_CODE_SHOWER) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_LIGHT_RAIN) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_MODERATE_RAIN) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_HEAVY_RAIN) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_STORM) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_HEAVY_STORM) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_SEVERE_STORM) == 0){
drawWeather(RAIN,userData->temp,userData->city);
}else if(strcmp(userData->weather_code,WEATHER_CODE_THUNDERSHOWER) == 0
|| strcmp(userData->weather_code,WEATHER_CODE_THUNDERSHOWER_WITH_HAIL) == 0){
drawWeather(THUNDER,userData->temp,userData->city);
}else{
drawWeather(CLOUD,userData->temp,userData->city);
}
}
void drawWeather(uint8_t symbol, char* degree,char* city)
{
DebugPrintln(city);
u8g2.clearBuffer(); // clear the internal memory
//绘制天气符号
drawWeatherSymbol(0, 48, symbol);
//绘制温度
u8g2.setFont(u8g2_font_logisoso32_tf);
u8g2.setCursor(48+3, 42);
u8g2.print(degree);
u8g2.print("°C"); // requires enableUTF8Print()
u8g2.setFont(u8g2_font_unifont_t_chinese3);
u8g2_uint_t strWidth = u8g2.getUTF8Width(city);
u8g2_uint_t displayWidth = u8g2.getDisplayWidth();
u8g2.setCursor(displayWidth - strWidth - 5, 60);
u8g2.print(city);
u8g2.sendBuffer(); // transfer internal memory to the display
}
/**
* 绘制天气符号
*/
void drawWeatherSymbol(u8g2_uint_t x, u8g2_uint_t y, uint8_t symbol)
{
// fonts used:
// u8g2_font_open_iconic_embedded_6x_t
// u8g2_font_open_iconic_weather_6x_t
// encoding values, see: https://github.com/olikraus/u8g2/wiki/fntgrpiconic
switch(symbol)
{
case SUN_DAY://太阳
u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
u8g2.drawGlyph(x, y, 69);
break;
case SUN_NIGHT://太阳
u8g2.setFont(u8g2_font_open_iconic_weather_6x_t);
u8g2.drawGlyph(x, y, 66);
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;
}
}
等待几秒之后,模块自动进入配网模式,请使用博主的app进行配置,配置成功后就会每隔一段时间去请求天气接口,然后处理显示在OLED。
实验结果:
5.总结
本篇博文内容简单,主要是整合了esp8266和oled的应用,为了优化请求网络以及显示时间,读者可自行加入闹钟功能,具体请参考 玩转 RTC时钟库 + DS3231
ESP8266开发之旅 应用篇② OLED显示天气屏的更多相关文章
- ESP8266开发之旅 网络篇⑯ 无线更新——OTA固件更新
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 进阶篇② 闲聊Arduino IDE For ESP8266烧录配置
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 基础篇① 走进ESP8266的世界
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 基础篇② 如何安装ESP8266的Arduino开发环境
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 基础篇③ ESP8266与Arduino的开发说明
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 基础篇④ ESP8266与EEPROM
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 基础篇⑥ Ticker——ESP8266定时库
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 网络篇⑦ TCP Server & TCP Client
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
- ESP8266开发之旅 网络篇⑧ SmartConfig——一键配网
授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...
随机推荐
- NPOI导出2007格式报错:文件损坏
这个问题,归根结底还是代码问题,如下修改了代码就可以了.左侧是会出现问题的代码,右侧是正确的代码,自己感受,我也是一口老血:
- Android的有序广播和无序广播(解决安卓8.0版本之后有序广播的接收问题)
前言 Google从Android8.0版本开始,对在清单文件中静态注册广播做了限制. *** 特殊广播(动态注册广播接收者) 说:有序广播和无序广播之前,咱们先来说下Android中一些特殊的广播如 ...
- [Linux] linux下vim对于意外退出的文档的再次开启
转载自博客:https://blog.csdn.net/ljp1919/article/details/48372615 1.对于同一个文件如果上次已经打开,而未关闭的情况下,又打开该文件进行编辑时, ...
- 【linux】【PostgreSQL】PostgreSQL安装
前言 PostgreSQL是一种特性非常齐全的自由软件的对象-关系型数据库管理系统(ORDBMS),是以加州大学计算机系开发的POSTGRES,4.2版本为基础的对象关系型数据库管理系统.POSTGR ...
- 设计模式笔记(一):Singleton 设计模式
今天开始学习设计模式,借此机会学习并整理学习笔记. 设计模式是一门不区分语言的课程,什么样的编程语言都可以用到设计模式.如果说java语法规则比作武功招式的话,那么设计模式就是心法. 设计模式共有23 ...
- 夯实Java基础系列10:深入理解Java中的异常体系
目录 为什么要使用异常 异常基本定义 异常体系 初识异常 异常和错误 异常的处理方式 "不负责任"的throws 纠结的finally throw : JRE也使用的关键字 异常调 ...
- [phyton]文件的简单读写练习
f.open() 用于打开一个文件. f=open("record.txt","w",encoding="utf-8")#打开文件,设置文件 ...
- vue 条件渲染方式
1.通过class绑定 <div :class="{'div-class': this.align == 'center'}"></div> 对应的css ...
- 教老婆学Linux运维(二)Linux常用命令指南【上】
目录 教老婆学Linux(二)Linux常用命令指南[上] 一.概述 二.常用命令 教老婆学Linux(二)Linux常用命令指南[上] 作者:姚毛毛的博客 tips:文章太长,分两篇发出,本篇发前三 ...
- POJ 3069——Saruman's Army(贪心)
链接:http://poj.org/problem?id=3069 题解 #include<iostream> #include<algorithm> using namesp ...