天天看點

基于esp8266網絡氣象時鐘(一)

成品效果

基于esp8266網絡氣象時鐘(一)
基于esp8266網絡氣象時鐘(一)
基于esp8266網絡氣象時鐘(一)

功能(視訊效果):

1、網絡自動校準時間(日期、星期、時間)

2、顯示目前城市的天氣情況

3、顯示最近三天的天氣預報

4、顯示目前室内的溫度

DIY esp8266迷你氣象台

準備

硬體

1、Esp8266 mini D1

2、1.3英寸Oled螢幕(SH1106/SSD1306) IIC 4線版(本程式僅适用于IIC 4線版本 SPI 7線版本需要改程式)

3、DS18B20 / 4.7k 上拉電阻

4、外殼(3D列印、紙皮、積木等)

軟體

Arduino 庫

1.自行設定安裝ESP8266開發闆

進入首選項(Preferences),找到附加開發闆管理器位址(Additional Board Manager URLs),并在其後添加如下資訊:

http://arduino.esp8266.com/stable/package_esp8266com_index.json

基于esp8266網絡氣象時鐘(一)

開發闆 - 開發闆管理器

搜尋ESP8266并安裝。

基于esp8266網絡氣象時鐘(一)

2.Library Manager中安裝

ArduinoJson庫 用于解析和風天氣API response的JSON

DS18B20庫 用于讀取DS18B20的溫度,檢測房間内實時溫度

ESP8266 and ESP32 OLED driver for SSD1306 displays庫 用于驅動OLED螢幕顯示内容

OneWire庫 用于連接配接DS18B20

連線

我的接線方法:(你也可以按你的喜好來,注意修改程式中的接口資訊)

基于esp8266網絡氣象時鐘(一)

3D列印外殼

模型是别人做的 作者Qrome 一個是0.96英寸版本的

一個是1.3英寸版本的

他的項目是用于顯示3D列印機列印狀态的

基于esp8266網絡氣象時鐘(一)
基于esp8266網絡氣象時鐘(一)
基于esp8266網絡氣象時鐘(一)

程式

#include <DS18B20.h>
#include <Arduino.h>

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
// time
#include <time.h>                       // time() ctime()
#include <sys/time.h>                   // struct timeval
#include <coredecls.h>                  // settimeofday_cb()


#include "SH1106Wire.h"//or #include "SSD1306Wire.h"
#include "OLEDDisplayUi.h"
#include "Wire.h"
#include "HeFeng.h"

#include "WeatherStationFonts.h"
#include "WeatherStationImages.h"


/***************************
 * Begin Settings
 **************************/

DS18B20 ds(D7);

//時區以及夏令時設定
#define TZ              8       // (utc+) TZ in hours
#define DST_MN          0      // use 60mn for summer time in some countries

// 第二屏實時天氣 第三屏預報的更新頻率
const int UPDATE_INTERVAL_SECS = 20 * 60; // Update every 20 minutes  online weather
// DS18B20更新頻率
const int UPDATE_CURR_INTERVAL_SECS = 10; // Update every 10 secs DS18B20

// OLED位址以及管腳資訊
const int I2C_DISPLAY_ADDRESS = 0x3c;
#if defined(ESP8266)
const int SDA_PIN = D2;
const int SDC_PIN = D5;
#endif


const String WDAY_NAMES[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
const String MONTH_NAMES[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

/***************************
 * End Settings
 **************************/
 // Initialize the oled display for address 0x3c
 // sda-pin=14 and sdc-pin=12
 SH1106Wire     display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);   // or SSD1306Wire  display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);
 OLEDDisplayUi   ui( &display );

HeFengCurrentData currentWeather;
HeFengForeData foreWeather[3];
 HeFeng HeFengClient;

//OpenWeatherMapForecastData forecasts[MAX_FORECASTS];
//OpenWeatherMapForecast forecastClient;

#define TZ_MN           ((TZ)*60)
#define TZ_SEC          ((TZ)*3600)
#define DST_SEC         ((DST_MN)*60)

const char* HEFENG_KEY="9d883c83e6064d63b1132b664ff45623";//和風天氣KEY 城市ID 自行去https://dev.heweather.com/ 注冊 城市代碼查詢https://dev.heweather.com/docs/refer/city
const char* HEFENG_LOCATION="shenzhen";//我的城市地點是深圳
time_t now;

// flag changed in the ticker function every 10 minutes
bool readyForWeatherUpdate = false;

String lastUpdate = "--";

long timeSinceLastWUpdate = 0;
long timeSinceLastCurrUpdate = 0;

String currTemp="-1.0";
//declaring prototypes
void drawProgress(OLEDDisplay *display, int percentage, String label);
void updateData(OLEDDisplay *display);
void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex);
void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
void setReadyForWeatherUpdate();


// Add frames
// this array keeps function pointers to all frames
// frames are the single views that slide from right to left
FrameCallback frames[] = { drawDateTime, drawCurrentWeather,drawForecast };
//drawForecast
int numberOfFrames = 3;

OverlayCallback overlays[] = { drawHeaderOverlay };
int numberOfOverlays = 1;



bool autoConfig()
{
  WiFi.mode(WIFI_STA);
  WiFi.begin();
  Serial.print("AutoConfig Waiting......");
   int counter = 0;
  for (int i = 0; i < 20; i++)
  {
    if (WiFi.status() == WL_CONNECTED)
    {
      Serial.println("AutoConfig Success");
      Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
      Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
      WiFi.printDiag(Serial);
      return true;
    }
    else
    {
       delay(500);
    Serial.print(".");
    display.clear();
    display.drawString(64, 10, "Connecting to WiFi");
    display.drawXbm(46, 30, 8, 8, counter % 3 == 0 ? activeSymbole : inactiveSymbole);
    display.drawXbm(60, 30, 8, 8, counter % 3 == 1 ? activeSymbole : inactiveSymbole);
    display.drawXbm(74, 30, 8, 8, counter % 3 == 2 ? activeSymbole : inactiveSymbole);
    display.display(); 
     counter++; 
    }
  }
  Serial.println("AutoConfig Faild!" );
  return false;
}
ESP8266WebServer server(80);
String str = "<!DOCTYPE html><html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\"><title>ESP8266網頁配網</title><script type=\"text/javascript\">function wifi(){var ssid = aa.value;var password = bb.value;var xmlhttp=new XMLHttpRequest();xmlhttp.open(\"GET\",\"/HandleWifi?ssid=\"+ssid+\"&password=\"+password,true);xmlhttp.send();xmlhttp.onload = function(e){alert(this.responseText);}}</script></head><body>hello,我是fxy,輸入wifi資訊給wifi時鐘配網:  <form>WiFi名稱:<input type=\"text\" placeholder=\"請輸入您WiFi的名稱\" id=\"aa\"><br>WiFi密碼:<input type=\"text\" placeholder=\"請輸入您WiFi的密碼\" id=\"bb\"><br><input type=\"button\" value=\"連接配接\" οnclick=\"wifi()\"></form></body></html>";
void handleRoot() {
  server.send(200, "text/html", str);
}
void HandleWifi()
{
    String wifis = server.arg("ssid"); //從JavaScript發送的資料中找ssid的值
    String wifip = server.arg("password"); //從JavaScript發送的資料中找password的值
    Serial.println("received:"+wifis);
    server.send(200, "text/html", "連接配接中..");
    WiFi.begin(wifis,wifip);
    
}

void handleNotFound() { 
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}

void htmlConfig()
{
    WiFi.mode(WIFI_AP_STA);//設定模式為AP+STA
    WiFi.softAP("wifi_clock");

    IPAddress myIP = WiFi.softAPIP();
  
    if (MDNS.begin("clock")) {
      Serial.println("MDNS responder started");
    }
    
    server.on("/", handleRoot);
    server.on("/HandleWifi", HTTP_GET, HandleWifi);
    server.onNotFound(handleNotFound);//請求失敗回調函數
    MDNS.addService("http", "tcp", 80);
    server.begin();//開啟伺服器
    Serial.println("HTTP server started");
    int counter = 0;
    while(1)
    {
        server.handleClient();
        MDNS.update();  
         delay(500);
          display.clear();
          display.drawString(64, 5, "WIFI AP:wifi_clock");
          display.drawString(64, 20, "192.168.4.1");
           display.drawString(64, 35, "waiting for config wifi.");
          display.drawXbm(46, 50, 8, 8, counter % 3 == 0 ? activeSymbole : inactiveSymbole);
          display.drawXbm(60, 50, 8, 8, counter % 3 == 1 ? activeSymbole : inactiveSymbole);
          display.drawXbm(74, 50, 8, 8, counter % 3 == 2 ? activeSymbole : inactiveSymbole);
          display.display();  
           counter++;
        if (WiFi.status() == WL_CONNECTED)
        {
            Serial.println("HtmlConfig Success");
            Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str());
            Serial.printf("PSW:%s\r\n", WiFi.psk().c_str());
            Serial.println("HTML連接配接成功");
            break;
        }
    }
       server.close();  
       WiFi.mode(WIFI_STA);
    
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println();

  // initialize dispaly
  display.init();
  display.clear();
  display.display();

  //display.flipScreenVertically();
  display.setFont(ArialMT_Plain_10);
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  display.setContrast(255);

 bool wifiConfig = autoConfig();
    if(wifiConfig == false){
        htmlConfig();//HTML配網
    }

  ui.setTargetFPS(30);

  ui.setActiveSymbol(activeSymbole);
  ui.setInactiveSymbol(inactiveSymbole);

  // You can change this to
  // TOP, LEFT, BOTTOM, RIGHT
  ui.setIndicatorPosition(BOTTOM);

  // Defines where the first frame is located in the bar.
  ui.setIndicatorDirection(LEFT_RIGHT);

  // You can change the transition that is used
  // SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN
  ui.setFrameAnimation(SLIDE_LEFT);

  ui.setFrames(frames, numberOfFrames);
  ui.setTimePerFrame(7500);
  ui.setOverlays(overlays, numberOfOverlays);

  // Inital UI takes care of initalising the display too.
  ui.init();

  Serial.println("");
  configTime(TZ_SEC, DST_SEC, "pool.ntp.org","0.cn.pool.ntp.org","1.cn.pool.ntp.org");
  updateData(&display);

}

void loop() {

  if (millis() - timeSinceLastWUpdate > (1000L*UPDATE_INTERVAL_SECS)) {
    setReadyForWeatherUpdate();
    timeSinceLastWUpdate = millis();
  }
 if (millis() - timeSinceLastCurrUpdate > (1000L*UPDATE_CURR_INTERVAL_SECS)) {
    if( ui.getUiState()->frameState == FIXED)
    {
    currTemp=String(ds.getTempC()-3, 1);//由于我的傳感器的檢測誤差,調整一下溫度
    timeSinceLastCurrUpdate = millis();
    }
  }




  if (readyForWeatherUpdate && ui.getUiState()->frameState == FIXED) {
    updateData(&display);
  }

  int remainingTimeBudget = ui.update();

  if (remainingTimeBudget > 0) {
    // You can do some work here
    // Don't do stuff if you are below your
    // time budget.
    delay(remainingTimeBudget);
  }


}

void drawProgress(OLEDDisplay *display, int percentage, String label) {
  display->clear();
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_10);
  display->drawString(64, 10, label);
  display->drawProgressBar(2, 28, 124, 10, percentage);
  display->display();
}


void updateData(OLEDDisplay *display) {
  drawProgress(display, 30, "Updating weather...");

for(int i=0;i<5;i++){
  HeFengClient.doUpdateCurr(&currentWeather, HEFENG_KEY, HEFENG_LOCATION);
  if(currentWeather.cond_txt!="no network"){
    break;}
 }
  drawProgress(display, 50, "Updating forecasts...");
  
 for(int i=0;i<5;i++){
  HeFengClient.doUpdateFore(foreWeather, HEFENG_KEY, HEFENG_LOCATION);
    if(foreWeather[0].datestr!="N/A"){
    break;}
 }
 
  readyForWeatherUpdate = false;
  drawProgress(display, 100, "Done...");
  delay(1000);
}



void drawDateTime(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
  now = time(nullptr);
  struct tm* timeInfo;
  timeInfo = localtime(&now);
  char buff[16];


  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_16);
  String date = WDAY_NAMES[timeInfo->tm_wday];
 
  sprintf_P(buff, PSTR("%04d-%02d-%02d, %s"), timeInfo->tm_year + 1900, timeInfo->tm_mon+1, timeInfo->tm_mday, WDAY_NAMES[timeInfo->tm_wday].c_str());
  display->drawString(64 + x, 5 + y, String(buff));
  display->setFont(ArialMT_Plain_24);

  sprintf_P(buff, PSTR("%02d:%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min, timeInfo->tm_sec);
  display->drawString(64 + x, 22 + y, String(buff));
  display->setTextAlignment(TEXT_ALIGN_LEFT);
}

void drawCurrentWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
  display->setFont(ArialMT_Plain_10);
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->drawString(64 + x, 38 + y, currentWeather.cond_txt+" | Wind: "+currentWeather.wind_sc);

  display->setFont(ArialMT_Plain_24);
  display->setTextAlignment(TEXT_ALIGN_LEFT);
  String temp = currentWeather.tmp + "°C" ;
  display->drawString(60 + x, 3 + y, temp);
  display->setFont(ArialMT_Plain_10);
  display->drawString(70 + x, 26 + y, currentWeather.fl+"°C | "+currentWeather.hum+"%");
  display->setFont(Meteocons_Plain_36);
  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->drawString(32 + x, 0 + y, currentWeather.iconMeteoCon);
}


void drawForecast(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) {
  drawForecastDetails(display, x, y, 0);
  drawForecastDetails(display, x + 44, y, 1);
  drawForecastDetails(display, x + 88, y, 2);
}

void drawForecastDetails(OLEDDisplay *display, int x, int y, int dayIndex) {

  display->setTextAlignment(TEXT_ALIGN_CENTER);
  display->setFont(ArialMT_Plain_10);
  display->drawString(x + 20, y, foreWeather[dayIndex].datestr);
  display->setFont(Meteocons_Plain_21);
  display->drawString(x + 20, y + 12, foreWeather[dayIndex].iconMeteoCon);

  String temp=foreWeather[dayIndex].tmp_min+" | "+foreWeather[dayIndex].tmp_max;
  display->setFont(ArialMT_Plain_10);
  display->drawString(x + 20, y + 34, temp);
  display->setTextAlignment(TEXT_ALIGN_LEFT);
}

void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) {
  now = time(nullptr);
  struct tm* timeInfo;
  timeInfo = localtime(&now);
  char buff[14];
  sprintf_P(buff, PSTR("%02d:%02d"), timeInfo->tm_hour, timeInfo->tm_min);

  display->setColor(WHITE);
  display->setFont(ArialMT_Plain_10);
  display->setTextAlignment(TEXT_ALIGN_LEFT);
  display->drawString(6, 54, String(buff));
  display->setTextAlignment(TEXT_ALIGN_RIGHT);
  String temp =currTemp +"°C";
  display->drawString(128, 54, temp);
  display->drawHorizontalLine(0, 52, 128);
}

void setReadyForWeatherUpdate() {
  Serial.println("Setting readyForUpdate to true");
  readyForWeatherUpdate = true;
}
           

注意

OLED螢幕一般有兩種驅動SSD1306和SH1106

根據你買的螢幕 更改如下兩行的資訊 用錯了會花屏

#include “SH1106Wire.h”//or #include “SSD1306Wire.h”

SH1106Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN); // or SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SDC_PIN);

配網

HTML配網 連接配接ESP8266建立的wifi 手機或電腦浏覽器通路http://192.168.4.1 即可配網

其他

和風天氣目前免費版API提供了實時天氣 天氣預報 市級的空氣品質等 可用的資訊很多 如果你想顯示其他資訊 請檢視API

https://dev.heweather.com/docs/api/

并更改HeFeng.app HeFeng.h中的結構體以及JSON解析資訊

如果你想用其他網站的API 請自行修改HeFeng.app HeFeng.h

歡迎QQ群交流:247620001 驗證資訊:esp8266

基于esp8266網絡氣象時鐘(二)

繼續閱讀