一、准备工作

1.在和风天气官网注册,获取账号的key。

2.Arduino安装ArduinoUZlib库,用于解压和风天气返回的数据流。由于和风天气采用Gzip压缩方式返回数据流,返回的数据需要解压。以及与HTTPS、JSON等相关库。

二、相关代码

为了方便理解代码功能,不同功能的代码,各采用1个.h和.cpp文件。

1.获取数据流。和风天气API采用HTTPS方式获取相关天气数据,并且返回数据采用GZIP压缩方式,因此先要建立HTTPS链接,获取数据后,使用UZlib库里的 int result=ArduinoUZlib::decompress(inbuff, size, outbuf,outsize);函数进行解压。相关代码如下:

HttpsGetUtils.h代码

#ifndef _HTTPS_GET_UTILS_H_
#define _HTTPS_GET_UTILS_H_

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>

class HttpsGetUtils {  
  public:
    HttpsGetUtils();
    static bool getString(const char* url, uint8_t*& outbuf, size_t &len);
    static const char  *host;		// 服务器地址
  private:
    static bool fetchBuffer(const char* url);
    static uint8_t _buffer[2048]; //gzip流最大缓冲区
    static size_t _bufferSize;

};

#endif

HttpsGetUtils.cpp代码

#include "HttpsGetUtils.h"
#include "ArduinoUZlib.h" // gzip库

uint8_t HttpsGetUtils::_buffer[2048];
const char* HttpsGetUtils::host = "https://devapi.qweather.com"; //服务器地址,这是免费用户的地址,如果非免费用户,改为:https://api.qweather.com
size_t HttpsGetUtils::_bufferSize=0;

HttpsGetUtils::HttpsGetUtils() {
}

bool HttpsGetUtils::getString(const char* url, uint8_t *& outbuf, size_t &outlen) {
  fetchBuffer(url);        //HTTPS获取数据流
  if(_bufferSize) {
    ArduinoUZlib::decompress(_buffer, _bufferSize,outbuf, outlen);//GZIP解压
    _bufferSize=0;
    return true;
  }
  return false;
}


bool HttpsGetUtils::fetchBuffer(const char *url) {
	_bufferSize=0;
	std::unique_ptr<WiFiClientSecure> client(new WiFiClientSecure);
	client->setInsecure();
	HTTPClient https;
	if (https.begin(*client, url)) {
		https.addHeader("Accept-Encoding", "gzip");
		int httpCode = https.GET();
		if (httpCode > 0) {
			if (httpCode == HTTP_CODE_OK) {
				int len = https.getSize();				// get length of document (is -1 when Server sends no Content-Length header)
				static uint8_t buff[200] = { 0 };	// create buffer for read
				int offset=0;			// read all data from server
				while (https.connected() && (len > 0 || len == -1)) {
					size_t size = client->available();   // get available data size         
					if (size) {
						int c = client->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));              
						memcpy(_buffer+offset, buff, sizeof(uint8_t)*c);
						offset+=c;
						if (len > 0) {
							len -= c;
						}
					}
					delay(1);
				}
				_bufferSize=offset;
			}
		} else {
			Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
		}
		https.end();
	} else {
		Serial.printf("Unable to connect\n");
	}
	return _bufferSize>0;;
}

2.获取实时天气数据

WeatherNow.h

#ifndef _WEATHERNOW_H_
#define _WEATHERNOW_H_

#include <Arduino.h>
#include <ArduinoJson.h>

class WeatherNow {
  public:
    WeatherNow();
    void config(String userKey, String location);//获取城市的天气
    void config_Grid(String userKey, String Grid_location);//获取经纬度地点的天气
    bool get();
    String getServerCode();
    String getLastUpdate();
    int getTemp();
    int getFeelLike();
    int getIcon();
    String getWeatherText();
    String getWindDir();
    int getWindScale();
    float getHumidity();
    float getPrecip();

  private:
    void _parseNowJson(char* input, size_t inputLength);  // 解析json信息
    String _url;
    String _response_code =  "";  // API状态码
    String _last_update_str = ""; // 当前API最近更新时间
    int _now_temp_int = 999;             // 实况温度
    int _now_feelsLike_int = 999;        // 实况体感温度
    int _now_icon_int = 999;             // 当前天气状况和图标的代码
    String _now_text_str = "no_init";    // 实况天气状况的文字描述
    String _now_windDir_str = "no_init"; // 实况风向
    int _now_windScale_int = 999;        // 实况风力等级
    float _now_humidity_float = 999;     // 实况相对湿度百分比数值
    float _now_precip_float = 999;       // 实况降水量,毫米
};

#endif

WeatherNow.cpp

#include "WeatherNow.h"
#include "HttpsGetUtils.h"
WeatherNow::WeatherNow() {
}

//例如 location="101010100",城市相关ID从https://github.com/qwd/LocationList下载。
void WeatherNow::config(String userKey, String location) {
    _url =  String(HttpsGetUtils::host) +  "/v7/weather/now?location=" + location +
             "&key=" + userKey + "&unit=m&lang=zh";
}

//以英文逗号分隔的经度,纬度坐标(十进制,支持小数点后两位)例如Grid_location="116.41,39.92"
void WeatherNow::config_Grid(String userKey, String Grid_location) {
    _url =  String(HttpsGetUtils::host) +  "/v7/grid-weather/now?location=" + Grid_location +"&key=" + userKey + "&unit=m&lang=zh";
}

bool WeatherNow::get() {
   uint8_t *outbuf=NULL;
  size_t len=0;
  Serial.println("Get WeatherNow...");
  bool result = HttpsGetUtils::getString(_url.c_str(), outbuf, len);
  if(outbuf && len){
      _parseNowJson((char*)outbuf,len);
  } else {
    Serial.println("Get WeatherNow failed");
  }
  //一定要记得释放内存
  if(outbuf!=NULL) {
    free(outbuf);
    outbuf=NULL;
  }
  return result;
  
}

// 解析Json信息
void WeatherNow::_parseNowJson(char* input, size_t inputLength) {
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, input, inputLength);

  JsonObject now = doc["now"];

  _response_code = doc["code"].as<String>();            // API状态码
  _last_update_str = doc["updateTime"].as<String>();    // 当前API最近更新时间
  _now_temp_int = now["temp"].as<int>();                // 实况温度
  _now_feelsLike_int = now["feelsLike"].as<int>();      // 实况体感温度
  _now_icon_int = now["icon"].as<int>();                // 当前天气状况和图标的代码
  _now_text_str = now["text"].as<String>();             // 实况天气状况的文字描述
  _now_windDir_str = now["windDir"].as<String>();       // 实况风向
  _now_windScale_int = now["windScale"].as<int>();      // 实况风力等级
  _now_humidity_float = now["humidity"].as<float>();    // 实况相对湿度百分比数值
  _now_precip_float = now["precip"].as<float>();        // 实况降水量,毫米
}

// API状态码
String WeatherNow::getServerCode() {
  return _response_code;
}

// 当前API最近更新时间
String WeatherNow::getLastUpdate() {
  return _last_update_str;
}

// 实况温度
int WeatherNow::getTemp() {
  return _now_temp_int;
}

// 实况体感温度
int WeatherNow::getFeelLike() {
  return _now_feelsLike_int;
}

// 当前天气状况和图标的代码
int WeatherNow::getIcon() {
  return _now_icon_int;
}

// 实况天气状况的文字描述
String WeatherNow::getWeatherText() {
  return _now_text_str;
}

// 实况风向
String WeatherNow::getWindDir() {
  return _now_windDir_str;
}

// 实况风力等级
int WeatherNow::getWindScale() {
  return _now_windScale_int;
}

// 实况相对湿度百分比数值
float WeatherNow::getHumidity() {
  return _now_humidity_float;
}
// 实况降水量,毫米
float WeatherNow::getPrecip() {
  return _now_precip_float;
}

3.使用示例

.ino文件

#include "WeatherNow.h"
#include "HttpsGetUtils.h"

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

String UserKey = "394809a78ca744fcb0fc020fxxxxxxxx";   //和风天气的key
String gridLocation ="113.25,23.14"; //经纬度取2位小数
String Location = "101280107";// 城市代码 https://github.com/heweather/LocationList,表中的 Location_ID
float ROUND = 5;             // 更新间隔<分钟>实时天气API 1~20分钟更新一次

WeatherNow weatherNow;

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

  ConnectWiFi(); // 连接WiFi
  //使用城市ID或者经纬度,选用以下2个函数中的一个
  weatherNow.config(UserKey, Location); // 配置请求信息
  //weatherNow.config_Grid(UserKey, gridLocation); // 配置请求信息
}

void loop(){
  if(weatherNow.get()){ // 获取天气更新
    Serial.println(F("======Weahter Now Info======"));
    Serial.print(F("Last Update: "));
    Serial.println(weatherNow.getLastUpdate());  // 获取服务器更新天气信息时间
    Serial.print(F("Temperature: "));
    Serial.print(weatherNow.getTemp());        // 获取实况温度
    Serial.print(F("    FeelsLike: "));
    Serial.println(weatherNow.getFeelLike());    // 获取实况体感温度
    Serial.print(F("Weather Now: "));
    Serial.println(weatherNow.getWeatherText()); // 获取实况天气状况的文字描述
    Serial.print(F("windDir: "));
    Serial.print(weatherNow.getWindDir());     // 获取实况风向
    Serial.print(F("    WindScale: "));
    Serial.println(weatherNow.getWindScale());   // 获取实况风力等级
    Serial.print(F("Humidity: "));
    Serial.print(weatherNow.getHumidity());    // 获取实况相对湿度百分比数值
    Serial.print(F("    Precip: "));
    Serial.println(weatherNow.getPrecip());      // 获取实况降水量,毫米
    Serial.println(F("========================"));
  }
  else {    // 更新失败
    Serial.println("Update Failed...");
    Serial.print("Server Response: ");
    Serial.println(minutely5m.getServerCode());
  }
  delay(ROUND * 60000);
}

// 连接WiFi
void ConnectWiFi(){
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.print("Connecting to ");
  Serial.print(ssid);

  int i = 0;
  while (WiFi.status() != WL_CONNECTED && (i < 10)) {
    delay(1000);
    Serial.print('.');
    i++;
  }
  if (i == 10) { // 10s失败
    Serial.println("WiFi Connection Failed");
  } else { // 成功
    Serial.println("");
    Serial.println("WiFi Connection Successful!");
    Serial.print("IP address:   ");
    Serial.println(WiFi.localIP());
  }
}

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐