Класс Forecaster для метеостанции или Предсказатель погоды

в 7:32, , рубрики: arduino, метеостанция, прогноз погоды

Многие начинающие (и не только) Ардуинщики прошли через создание устройства с громким названием — Метеостанция, я в их числе.

Многие варианты, которые я изучал, интересны с точки зрения полета мысли создателя, из многих я почерпнул идеи, о чем и не скрываю.Помимо фиксации показаний с собственных датчиков, визуализации их через различные интерфейсы типа всевозможных Дисплеев и Веб-страничек, мне было интересно использовать данные о прогнозе погоды на некоторое время вперед.

Тут, на мой взгляд есть два пути: с первым я ознакомился в статье , но мне больше понравилась возможность получать прогноз из Интернета. Основную информацию об этом я почерпнул из статьи на Амперке и развил эту идею дальше, благо предложенный в статье ресурс, позволяет это сделать.

В результате моя версия погодной станции получила класс Forecaster, отвечающий за предсказание погоды. Вот данный класс, в контексте тестового проекта, я и хочу представить публике.

Сразу оговорюсь:
1. У меня серверная часть метеостанции построена на Arduino Mega 2560, поэтому нехватки памяти в проекте я не испытываю.
2. Мне пришлось внести некоторые изменения в стандартную библиотеку Ethernet, изменения коснулись количества сокетов и выделенной под них буферной памяти.

Исходная версия имеет 4 сокета по 2кБ на каждый сокет, я же сделал 2 сокета по 4кБ. Увеличить буферную память пришлось в связи с тем, что при получении с сервера информации о прогнозе погоды, объем информации может достигать 15-18кБ (при получении 3х часового прогноза). Т.е. при размере буфера в 2кБ — большая ее часть просто терялась. При 4кБ буфере удается получить прогноз на 2 дня с дискретностью 3 часа, что уже совсем не плохо. Все изменения библиотеки задокументированы прямо в коде самого класса. Если кто-нибудь подскажет как по дуругому решить данную проблему, не трогая сокеты, я буду только рад.

Иногда, при получении и разборе 3х часового прогноза удается обработать меньшее количество элементов (не 16, а всего 3-6), я так и не понял из-за чего это происходит, возможно Ардуинка не успевает выбрать данные из буфера. Для демонстрации работы Предсказателя я собрал тестовый проект, который опробовал на 2х устройствах:

1. Arduino Uno + Ethernet shield (w5100)
2. iBoard

В связи с нехваткой памяти в этих устройствах пришлось закомментить отдельные связанные функции. Внутри класса:

int FORECAST::GetForecastDays(FORECAST::_WeatherDay* wdp)     // Получение 4х дневного прогноза
int FORECAST::GetForecast(FORECAST::_WeatherPacket& whr)        // Получение текущей погоды

В теле основного цикла закомментирован код, вызывающий эти функции. Их работу можно проверить, по очереди комментируя и убирая комменты с соответствующих участков кода. Если же код залить в Mega2560, то необходимость комментирования отпадет и все будет работать. Проект собирается под IDE версии 1.6.5. (На 1.0.5r2 — тоже без проблем).

Так же хочу оговориться, я не являюсь адептом С (С++), поэтому частенько задаю вопросы на форумах как работать со строками и тому подобное. В связи с этим вполне вероятно, что какие то участки кода написаны странно или не оптимально, короче — здоровая критика приветствуется.

Город, для которого запрашиваем прогноз прописан в объявлениях класса:

const char p_request3Hour[] PROGMEM  = "GET /data/2.5/forecast?q=Krasnoyarsk&mode=xml&units=metric";
const char p_request4Day[]  PROGMEM  = "GET /data/2.5/forecast/daily?q=Krasnoyarsk&mode=xml&units=metric&cnt=4";
const char p_requestToDay[] PROGMEM  = "GET /data/2.5/weather?q=Krasnoyarsk&mode=xml&units=metric";

Объявление класса предсказания погоды:

FORECAST frc;
FORECAST::_WeatherPacket weather;      //  текущая погода с сайта
FORECAST::_WeatherDay wPack1Day;      // прогноз погоды с сайта на 4 дня (начиная с сегодня)
FORECAST::_WeatherThreeHour wPack3Hour;   // 3х часовой прогноз погоды с сайта на 2 дня

Инициализация:

frc.Init(client, 7);  // client  - это EthernetClient, 7 - это часовой пояс

Запрос погоды на сервере:

frc.GetForecast(weather)                    //структура weather - содержит данные о погоде
frc.GetForecast3Hour(&wPack3Hour)    //wPack3Hour - массив из 16 струкур с 3х часовым прогнозом
frc.GetForecastDays(&wPack1Day)      //wPack1Day - массив из 4 структур с прогнозом на 4 дня

Структуры с метеоданными имеют небольшую длину и поэтому без труда могут быть переданы через nRF24. Естественно что 1 структура содержит данные за 1 день или за 3 часа, и что бы передать все дынные полностью, потребуется соответствующее кол-во сеансов передачи. Для этого я создал специальный протокол обмена между дисплейным и серверным модулями. Ну и собственно сами исходники:

forecast.h

#ifndef FORECAST_H
#define FORECAST_H
#include "Arduino.h"
#include <Ethernet.h>
#include <Time.h>

// Прогноз погоды на сутки из 4х дневного прогноза
typedef struct OneDay
{
	time_t		Data;			//прогноз на Дату "20141010'
	int			TD;				//Температура днем
	int			TN;				//Температура ночью
	byte		H;				//Влажность
	int			P;				//Давление
	byte		WS;				//Скорость ветра
	char		WD[4];				//Направление ветра 
	byte		Cloud;			//Облачность в %
	int			RainVal;		//количество осадков (*100), 0 - если нет, - снег  + дождь
	char		Icon[4];		//Icon
} wdPack;  // = 23 byte
// Прогноз погоды на 3 часа из 2х дневного прогноза
typedef struct ThreeHour
{
  	time_t		Data;		//прогноз на Дату  "2014-10-10 21:00'
	int			T;				//Температура
	int			TT;				//Температура Min или Max
	byte		H;				//Влажность
	int			P;				//Давление
	byte		WS;				//Скорость ветра
	char		WD[4];			//Направление
	byte		Cloud;			//Облачность в %	
	int			RainVal;		//количество осадков (*100), 0 - если нет, - снег  + дождь
	char		Icon[4];		//Icon
} whPack; //=23 byte

class FORECAST {
  private :
		EthernetClient client;
		void clearStr (char* str);
		void addChar (char ch, char* str);
		void SubStrA(int Num,String& source, String& str);
		void SubStrB(int Num,String& source, String& str);
		time_t _ConvertDate(char _Data[10]);
		time_t _ConvertDateTime(char DatTim[16]);
    char* GetWord(uint8_t numWord);
		String dataString;
		boolean tagFlag;
		boolean dataFlag;
		int tZone;
		long tDelay;
  public :
		FORECAST();
		void Init (EthernetClient& clnt,int timeZone);

		struct _WeatherPacket
		{
			int		P;			//Давление
			int		T;			//Температура
			int		H;			//Влажность
			int		WS;			//Скорость ветра
			char    WD[4];		//Направление
			char    Icon[4];	//Icon
		};
		typedef  wdPack _WeatherDay[4];
		typedef  whPack _WeatherThreeHour[16];

		int GetForecast(_WeatherPacket& whr);
		int GetForecastDays(_WeatherDay *wdp);
		int GetForecast3Hour(_WeatherThreeHour *whp);
		int iPacket3H;
		time_t SunRise;
		time_t SunSet;
		boolean fDebug;
};

#endif 

forecast.cpp

#include "forecast.h"
#include "math.h"
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <Time.h>
#include <avr/pgmspace.h>
//////////////////////////////////////////////////////////////////////////
// По умолчанию в драйвере w5100 размер приемного буфера 2Кб (4 сокета по 2Кб)
// объем XML данных страницы с 3х часовым прогнозом составляет 15-18Кб
// Что бы минимизировать потерю информации необходимо увеличить размер приемного буфера
// до 4Кб (в идеале 1 сокет и 8Кб, но так не работает вообще).
//////////////////////////////////////////////////////////////////////////
// По этому вносим изменения в файле w5100.h
// MAX_SOCK_NUM 2
// SOCKETS = 2;
// SMASK = 0x0FFF; // Tx buffer MASK
// RMASK = 0x0FFF; // Rx buffer MASK
// SSIZE = 4096; // Max Tx buffer size
// RSIZE = 4096; // Max Rx buffer size
//////////////////////////////////////////////////////////////////////////
// В файле w5100.cpp
// TX_RX_MAX_BUF_SIZE 4096
// writeTMSR(0xAA);
// writeRMSR(0xAA);
//////////////////////////////////////////////////////////////////////////
// В файле Ethernet.h
// MAX_SOCK_NUM 2
//////////////////////////////////////////////////////////////////////////
// В файле Ethernet.cpp
// uint8_t EthernetClass::_state[MAX_SOCK_NUM] = { 0, 0 };
// uint16_t EthernetClass::_server_port[MAX_SOCK_NUM] = { 0 , 0 };
//////////////////////////////////////////////////////////////////////////
#include <Ethernet.h>
///////////////////////////////////////////////////////////////////////////////////////////////
// Особой необходимости использовать PROGMEM нет, но для разнообразия. В результате освободили 504 байта RAM
const char p_FCserver[]     PROGMEM  = "api.openweathermap.org";
const char p_APIID[]        PROGMEM  = "&APPID="; //Тут надо вписать свой API-ключ, но работает и без него
const char p_request3Hour[] PROGMEM  = "GET /data/2.5/forecast?q=Krasnoyarsk&mode=xml&units=metric";
const char p_request4Day[]  PROGMEM  = "GET /data/2.5/forecast/daily?q=Krasnoyarsk&mode=xml&units=metric&cnt=4";
const char p_requestToDay[] PROGMEM  = "GET /data/2.5/weather?q=Krasnoyarsk&mode=xml&units=metric";
const char p_ConnClose[]    PROGMEM  = "Connection: close";
const char p_HTTP[]         PROGMEM  = " HTTP/1.1";
PGM_P const string_table[]  PROGMEM  = {p_FCserver, p_APIID,p_request3Hour,p_request4Day,p_requestToDay,p_ConnClose,p_HTTP};
char words[80];
///////////////////////////////////////////////////////////////////////////////////////////////
#define MAX_STRING_LEN  100
char tagStr[MAX_STRING_LEN] = "";
char tmpStr[MAX_STRING_LEN] = "";
char endTag[3] = {'<', '/', ''};
char inChar;
// Конструктор
FORECAST::FORECAST()
{
	fDebug = false;
}
// В качестве инициализации передаем сссылку на EthernetClient, и часовой пояс
// часовой пояс нужен что бы скорректировать время в прогнозе, т.к. изночально дано время по гринвичу
void FORECAST::Init(EthernetClient& clnt, int timeZone)
{
	client = clnt;
	tZone = timeZone;
	tDelay = 1000;
}
// Получение 3х часового прогноза (2 дня с дискретностью 3 часа)
int FORECAST::GetForecast3Hour(FORECAST::_WeatherThreeHour *whp)
{
	int p1 = 0,p2 = 0;
	char TMP[20];
	String temp = "";
	float T;
  
  if (client.connected()) client.stop();
	if (client.connect(GetWord(0), 80))
	{
    temp  = GetWord(2);
    //temp += GetWord(1);
    temp += GetWord(6);
    //if (fDebug) Serial.println(temp);
		client.println(temp);
    client.print("Host: ");
    client.println(GetWord(0));
    client.println(GetWord(5));
    client.println();
    temp = "";
		// Ждем пока сервер ответит, если долго не отвечает - прервемся
		while (!client.available())
		{
			p1++;
			delay(50);
			if ( p1 > tDelay )
			{
				client.stop();
				return 0;
			}
		}
		iPacket3H = 0;
		while (client.available())
		{
			//-----------------------------------------------------------
			inChar = client.read();
			if (inChar == '<')	// начало строки
			{
				addChar(inChar, tmpStr);
				tagFlag = true;
			}
			else if (inChar == '>')	// конец строки
			{
				addChar(inChar, tmpStr);
				if (tagFlag)	// копируем временную строку в результирующую
				{
				  strncpy(tagStr, tmpStr, strlen(tmpStr)+1);
				}
				clearStr(tmpStr);
				tagFlag = false;
			}
			else if (inChar != 10)
			{
				if (tagFlag)
				{
					addChar(inChar, tmpStr);
					// Check for </XML> end tag, ignore it
					if ( tagFlag && strcmp(tmpStr, endTag) == 0 )
					{
						clearStr(tmpStr);
						tagFlag = false;
					}
				}
			}
			// если конец строки - обработаем ее
			if ((inChar == 10 ) || (inChar == '>'))
			{
				dataString = tagStr;
				if( !strncmp(tagStr,"<time from",10))
				{ //<time from="2014-11-26T03:00:00" to="2014-11-26T06:00:00">
          //if (fDebug) Serial.println(tagStr);
					p1 =  dataString.indexOf('"',1);
					p1++;
					p2 =  dataString.indexOf('"',p1);
					temp = dataString.substring(p1,p2-3);
					temp.replace("T"," ");
					temp.toCharArray(TMP,temp.length()+1);
					(*whp)[iPacket3H].Data = _ConvertDateTime(TMP);
				}
				else if( !strncmp(tagStr,"<symbol",7))
				{ // <symbol number="600" name="light snow" var="13d"/>
					p1 =  dataString.indexOf("var");
					SubStrA(p1,dataString,temp);
					temp.toCharArray((*whp)[iPacket3H].Icon,temp.length()+1);
				}
				else if( !strncmp(tagStr,"<precipitation",14))
				{ // <precipitation unit="3h" value="0.125" type="rain">
          //if (fDebug) Serial.println(tagStr);
					int sign;
					clearStr(TMP);
					if (dataString.indexOf("rain") > 0) sign = 1;		// Дождь
					else if (dataString.indexOf("snow") > 0 || dataString.indexOf("show") > 0) sign = -1;	// Снег //29.09.2015 - ошибка в тегах на сайте вместо snow стоит show 
					else	sign = 0;					// без осадков
          p1 =  dataString.indexOf("value");
					if (sign != 0)
					{
						SubStrA(p1,dataString,temp);
						temp.toCharArray(TMP,temp.length()+1);
						(*whp)[iPacket3H].RainVal = atof(TMP) * sign * 100;
					}
					else (*whp)[iPacket3H].RainVal = 0;
				}
				else if( !strncmp(tagStr,"<windDirection",14))
				{ //<windDirection deg="235" code="SW" name="Southwest"/>
					p1 =  dataString.indexOf("code");
					SubStrA(p1,dataString,temp);
					temp.toCharArray((*whp)[iPacket3H].WD,temp.length()+1);
				}
				else if( !strncmp(tagStr,"<windSpeed",10))
				{ //<windSpeed mps="4.62" name="Gentle Breeze"/>
					clearStr(TMP);
					SubStrA(1,dataString,temp);
					temp.toCharArray(TMP,temp.length()+1);
					T = atof(TMP);
					(*whp)[iPacket3H].WS = round(T);
				}
				else if( !strncmp(tagStr,"<temperature",12))
				{ //<temperature unit="celsius" value="-12.92" min="-17.91" max="-12.92"/>
					clearStr(TMP);
					p1 =  dataString.indexOf("value");
					SubStrA(p1,dataString,temp);
					temp.toCharArray(TMP,temp.length()+1);
					T = atof(TMP);
					(*whp)[iPacket3H].T = round(T);
					if (T < 0)
					p1 =  dataString.indexOf("min");
					else
					p1 =  dataString.indexOf("max");
					clearStr(TMP);
					SubStrA(p1,dataString,temp);
					temp.toCharArray(TMP,temp.length()+1);
					T = atof(TMP);
					(*whp)[iPacket3H].TT = round(T);
				}
				else if( !strncmp(tagStr,"<pressure",9))
				{ //<pressure unit="hPa" value="999.14"/>
					p1 =  dataString.indexOf("value");
					SubStrA(p1,dataString,temp);
					(*whp)[iPacket3H].P = temp.toInt();
					(*whp)[iPacket3H].P =((*whp)[iPacket3H].P * 0.75);    // - 17
				}
				else if( !strncmp(tagStr,"<humidity",9))
				{ //<humidity value="73" unit="%"/>
					SubStrA(1,dataString,temp);
					(*whp)[iPacket3H].H = temp.toInt();
				}
				else if( !strncmp(tagStr,"<clouds",7))
				{ // <clouds value="broken clouds" all="56" unit="%"/>
					p1 =  dataString.indexOf("all");
					SubStrA(p1,dataString,temp);
					(*whp)[iPacket3H].Cloud = temp.toInt();
					iPacket3H++;
				}
				clearStr(tmpStr);
				clearStr(tagStr);
				tagFlag = false;
			}
			//-----------------------------------------------------------
			if (iPacket3H==16) {break;}
		}
		client.stop();
		return 1;
	}
	else {return 0;}
}
/////////////////////////////////////////////// Получение 4х дневного прогноза///////////////////////////////////////////////////////
/*
int FORECAST::GetForecastDays(FORECAST::_WeatherDay* wdp)
{
	int iPack=0,p=0;
	char TMP[20];
	String temp;
	float T;
  if (client.connected()) client.stop();
	if (client.connect(GetWord(0), 80))
	{
    temp =  GetWord(3);
    //temp += GetWord(1);
    temp += GetWord(6);
    if (fDebug) Serial.println(temp);
		client.println(temp);
    client.print("Host: ");
    client.println(GetWord(0));
    client.println(GetWord(5));
    client.println();
    temp = "";
		while (!client.available())
		{
			p++;
			delay(50);
			if ( p > tDelay )
			{
				client.stop();
				return 0;
			}
		}
		while (client.available())
		{
			inChar = client.read();
			if (inChar == '<')	// начало строки
			{
				addChar(inChar, tmpStr);
				tagFlag = true;
			}
			else if (inChar == '>')	// конец строки
			{
				addChar(inChar, tmpStr);

				if (tagFlag)	// копируем временную строку в результирующую
				{
				  strncpy(tagStr, tmpStr, strlen(tmpStr)+1);
				}
				clearStr(tmpStr);
				tagFlag = false;
			}
			else if (inChar != 10)
			{
				if (tagFlag)
				{
					addChar(inChar, tmpStr);
					// Check for </XML> end tag, ignore it
					if ( tagFlag && strcmp(tmpStr, endTag) == 0 )
					{
						clearStr(tmpStr);
						tagFlag = false;
					}
				}
			}
			// если конец строки - обработаем ее
			if ((inChar == 10 )  || (inChar == '>'))
			{
				dataString = tagStr;

				if( !strncmp(tagStr,"<time day",9))
				{ //<time day="2014-10-09">
					SubStrA(1,dataString,temp);
					//temp.toCharArray((*wdp)[iPack].Data,temp.length()+1);
					temp.toCharArray(TMP,temp.length()+1);
					(*wdp)[iPack].Data = _ConvertDate(TMP);
         //if (fDebug) Serial.println(temp);
				}
				else if( !strncmp(tagStr,"<symbol",7))
				{ // <symbol number="600" name="light snow" var="13d"/>
					p =  dataString.indexOf("var");
					SubStrA(p,dataString,temp);
					temp.toCharArray((*wdp)[iPack].Icon,temp.length()+1);
				}
				else if( !strncmp(tagStr,"<precipitation",14))
				{ // <precipitation value="1.25" type="snow"/>
					int sign;
					clearStr(TMP);
					if (dataString.indexOf("rain") > 0) sign = 1;		// Дождь
					else if (dataString.indexOf("snow") > 0 || dataString.indexOf("show") > 0) sign = -1;	// Снег
					else	sign = 0;					// без осадков
					if (sign != 0)
					{
						p=1;
						SubStrA(p,dataString,temp);
						temp.toCharArray(TMP,temp.length()+1);
						(*wdp)[iPack].RainVal = atof(TMP) * sign * 100;
					}
					else (*wdp)[iPack].RainVal = 0;
				}
				else if( !strncmp(tagStr,"<windDirection",14))
				{ //<windDirection deg="235" code="SW" name="Southwest"/>
					p =  dataString.indexOf("code");
					SubStrA(p,dataString,temp);
					temp.toCharArray((*wdp)[iPack].WD,temp.length()+1);
				}
				else if( !strncmp(tagStr,"<windSpeed",10))
				{ //<windSpeed mps="4.62" name="Gentle Breeze"/>
					clearStr(TMP);
					SubStrA(1,dataString,temp);
					temp.toCharArray(TMP,temp.length()+1);
					T = atof(TMP);
					(*wdp)[iPack].WS = round(T);
				}
				else if( !strncmp(tagStr,"<temperature",12))
				{ //<temperature day="4.48" min="-1.12" max="4.48" night="-0.94" eve="-1.12" morn="2.26"/>
					clearStr(TMP);
					SubStrA(1,dataString,temp);
					temp.toCharArray(TMP,temp.length()+1);
					T = atof(TMP);
					(*wdp)[iPack].TD = round(T);
					clearStr(TMP);
					p =  dataString.indexOf("night");
					SubStrA(p,dataString,temp);
					temp.toCharArray(TMP,temp.length()+1);
					T = atof(TMP);
					(*wdp)[iPack].TN = round(T);
				}
				else if( !strncmp(tagStr,"<pressure",9))
				{ //<pressure unit="hPa" value="999.14"/>
					p =  dataString.indexOf("value");
					SubStrA(p,dataString,temp);
					(*wdp)[iPack].P = temp.toInt();
					(*wdp)[iPack].P =((*wdp)[iPack].P * 0.75 - 17);
				}
				else if( !strncmp(tagStr,"<humidity",9))
				{ //<humidity value="73" unit="%"/>
					SubStrA(1,dataString,temp);
					(*wdp)[iPack].H = temp.toInt();
				}
				else if( !strncmp(tagStr,"<clouds",7))
				{ // <clouds value="broken clouds" all="56" unit="%"/>
					p =  dataString.indexOf("all");
					SubStrA(p,dataString,temp);
					(*wdp)[iPack].Cloud = temp.toInt();
					iPack++;
				}
				clearStr(tmpStr);
				clearStr(tagStr);
				tagFlag = false;
			}
		}
		// останавливаем клиент
		client.stop();
		return 1;
	}
	else {return 0;}
}
*/
///////////////////////////// Получение текущей погоды //////////////////////////////////////////////////////////////
/*
int FORECAST::GetForecast(FORECAST::_WeatherPacket& whr)
{
	int p1 = 0;
	char TMP[20];
	String temp = "";
	float T;
	int step = 0;
  if (client.connected()) client.stop();
	if (client.connect(GetWord(0), 80))
	{
    temp = GetWord(4);
    //temp += GetWord(1);
    temp += GetWord(6);
    if (fDebug) Serial.println(temp);
    client.println(temp);
    client.print("Host: ");
    client.println(GetWord(0));
    client.println(GetWord(5));
    client.println();
    temp = "";
		while (!client.available())		// Ждем пока поступит ответ
		{
			p1++;
			delay(50);
			if ( p1 > tDelay )				// если ожидание затянулось - прервем силком
			{
				client.stop();
				return 0;
			}
		}
		while (client.available())	//Ответ получен - вычитываем информацию
		{
			// Read a char
			inChar = client.read();
			//if (fDebug) Serial.print(inChar);
			if (inChar == '<')
			{
				addChar(inChar, tmpStr);
				tagFlag = true;

			}
			else if (inChar == '>')
			{
				addChar(inChar, tmpStr);
				if (tagFlag)
				{
					strncpy(tagStr, tmpStr, strlen(tmpStr)+1);
				}
				clearStr(tmpStr);
				tagFlag = false;
				dataFlag = true;
			}
			else if (inChar != 10)
			{
				if (tagFlag)
				{
					addChar(inChar, tmpStr);
					// Check for </XML> end tag, ignore it
					if ( tagFlag && strcmp(tmpStr, endTag) == 0 )
					{
						clearStr(tmpStr);
						tagFlag = false;
						dataFlag = false;
					}
				}
			}
			// If a LF, process the line
			if ((inChar == 10 ) || (inChar == '>'))
			{
				dataString = tagStr;
				if( !strncmp(tagStr,"<sun",4))
				{ // <sun rise="2014-10-14T00:18:33" set="2014-10-14T10:51:01"/>
					SubStrA(1,dataString,temp);
					temp.replace("T"," ");
					temp.toCharArray(TMP,temp.length()+1);
					if (fDebug) {Serial.print("SunRise_w_");Serial.println(TMP);}
					SunRise = _ConvertDateTime(TMP);
					p1 =  dataString.indexOf("set");
					p1++;
					SubStrA(p1,dataString,temp);
					temp.replace("T"," ");
					temp.toCharArray(TMP,temp.length()+1);
					SunSet = _ConvertDateTime(TMP);
					step++;
				}

				else if( !strncmp(tagStr,"<temperature",12))
				{ //<temperature value="-9.86" min="-16" max="-6.3" unit="celsius"/>
					clearStr(TMP);
					SubStrA(1,dataString,temp);
					temp.toCharArray(TMP,temp.length()+1);
					T = atof(TMP);
					whr.T = round(T);
					step++;
          if (fDebug) Serial.println(tagStr);
				}
				else if( !strncmp(tagStr,"<humidity",9))
				{ //<humidity value="65" unit="%"/>
					SubStrA(1,dataString,temp);
					whr.H = temp.toInt();
					step++;
				}
				else if( !strncmp(tagStr,"<pressure",9)  )
				{ //<pressure value="1027" unit="hPa"/>
					SubStrA(1,dataString,temp);
					//      whr.P = round(temp.toInt() * 0.75 - 17);
					whr.P = temp.toInt() ;
					step++;
          if (fDebug) Serial.println(tagStr);
				}
				else if( !strncmp(tagStr,"<speed",6) )
				{ //<speed value="1.51" name=""/>
					clearStr(TMP);
					SubStrA(1,dataString,temp);
					temp.toCharArray(TMP,temp.length()+1);
					T = atof(TMP);
					whr.WS = round(T);
					step++;
          if (fDebug) Serial.println(tagStr);
				}
				else if( !strncmp(tagStr,"<direction",10)  )
				{ //<direction value="160.501" code="SSE" name="South-southeast"/>
					p1 =  dataString.indexOf("code");
					SubStrB(p1,dataString,temp);
					temp.toCharArray(whr.WD,temp.length()+1);
					step++;
          if (fDebug) Serial.println(tagStr);
				}
				else if( !strncmp(tagStr,"<weather",8)  )
				{ //<weather number="803" value="broken clouds" icon="04d"/>
					p1 =  dataString.indexOf("icon");
					SubStrA(p1,dataString,temp);
					temp.toCharArray(whr.Icon,temp.length()+1);
					step++;
          if (fDebug) {Serial.println(tagStr);Serial.println("-------------------");}
				}
				clearStr(tmpStr);
				clearStr(tagStr);
				tagFlag = false;
				dataFlag = false;
			}
		}
		// останавливаем клиент
		client.stop();
		if (step > 5) return 1; else return 0;
	}
	else {return 0;}
}
*/
//////////////////// Вспомогательные функции ////////////////////////////////////////////////////
void FORECAST::SubStrA(int Num,String& source, String& str)
{
	int p1 =  source.indexOf('"',Num);
	p1++;
	int p2 =  source.indexOf('"',p1);
	str = source.substring(p1,p2);
}
// переодически в прогнозе погоды направление ветра дают с ошибкой
void FORECAST::SubStrB(int Num,String& source, String& str)
{
	int p1 =  source.indexOf('"',Num);
	p1++;
	int p2 =  source.indexOf('"',p1+1);
	if (p2 - p1 < 1)
	  str = "WNW";
	else
	  str = source.substring(p1,p2);
}
//Function to add a char to a string and check its length
void FORECAST::addChar(char ch, char* str)
{
	char const  *tagMsg  = "!=!";
	if (strlen(str) > MAX_STRING_LEN - 2) {
		if (tagFlag)
		{
			clearStr(tagStr);
			strcpy(tagStr,tagMsg);
		}
		// Clear the temp buffer and flags to stop current processing
		clearStr(tmpStr);
		tagFlag = false;
	}
	else
	{
		// Add char to string
		str[strlen(str)] = ch;
	}
}
// Function to clear a string
void FORECAST::clearStr(char* str) {
	int len = strlen(str);
	for (int c = 0; c < len; c++) {
		str[c] = 0;
	}
}

time_t FORECAST::_ConvertDate(char _Data[11])
{
	int Y, M, D;
	TimeElements te;
	sscanf ( _Data,"%i-%d-%d", &Y, &M, &D);
	te.Year = Y -1900; te.Month = M; te.Day = D;
	te.Hour=0; te.Minute=0; te.Second=0;
	return makeTime(te);
}

time_t FORECAST::_ConvertDateTime(char _DateTime[17])
{
	int Y, M, D, hh, mm;
	TimeElements te;
	// Парсим строку (заполняем структуру датой из строки)
	sscanf ( _DateTime,"%i-%d-%d %d:%d", &Y, &M, &D, &hh, &mm );
	//  перводим время с учетом TimeZone
	hh = hh + tZone;
	if (hh > 24)
	{
		hh = hh - 24;
		if ( (M == 2 && D == 28 && Y%4 != 0)|| (M == 2 && D == 29 && Y%4 == 0)
		|| ((M == 1 || M == 3 || M == 5 || M == 7 || M == 8 || M == 10 || M == 12) && D == 31)
		|| ((M == 4 || M == 6 || M == 9 || M == 11) && D == 30) )
		{
			M++;
			D = 1;
			if (M==13) {M=1;Y++;}
		}
		else  D++;
	}

	te.Year = Y -1900; te.Month = M; te.Day = D;
	te.Hour = hh; te.Minute = mm; te.Second=0;

	return makeTime(te);
}
// извлечение строки из flash - памяти по №строки
char* FORECAST::GetWord(uint8_t numWord)
{
    strcpy_P(words, (PGM_P)pgm_read_word(&(string_table[numWord])));
  return words;
}

Forecast.ino

#include <Time.h>
#include <SPI.h>
#include <Ethernet.h>
#include "forecast.h"
///////////////////////////////////////////////////
byte mac[] = { 0xEA, 0xCD, 0xCE, 0x17, 0x19, 0x66 };
char server[] = "192.168.1.120";    // Адрес сервера куда сливаем данные
EthernetClient client;
//////////////////////////////////////////////////
// Класс предсказания погоды
FORECAST frc;
FORECAST::_WeatherPacket weather;      //  текущая погода с сайта
FORECAST::_WeatherDay wPack1Day;      // прогноз погоды с сайта на 4 дня (начиная с сегодня)
FORECAST::_WeatherThreeHour wPack3Hour;   // 3х часовой прогноз погоды с сайта на 2 дня

// Период запроса 4х дневного прогноза
const unsigned long dInterval = 300000;
unsigned long last_dForecast;
// Период запроса 3х часового прогноза
const unsigned long hInterval = 300000;   //
unsigned long last_hForecast;
// Устанавливаем период задержки между обращениями к сайту с прогнозом погоды
const unsigned long wInterval = 200000;  //~30 мин.
unsigned long last_wForecast;

//////////////////////////////////////
#define DEBUG_MODE  1
//////////////////////////////////////

void setup() 
{
  frc.fDebug = false;
  if (Ethernet.begin(mac) == 0)
  {
    while (1)
    {
      delay(1000);
    }
  }
  // Инициализируем предсказатель погоды
   unsigned long now = millis();
  frc.Init(client, 7);  
  last_wForecast = now - wInterval + 2000;
  last_hForecast = now - hInterval + 15000;
  last_dForecast = now - dInterval + 25000;
}

void loop() {
  unsigned long now = millis();
/*
 if ( now - last_wForecast > wInterval )
  {
    if (frc.GetForecast(weather) == 1)      // текущая погода из инета
    {
      last_wForecast =  now;
    }
  }
*/
  if ( now - last_hForecast > hInterval)
  {
    if (frc.GetForecast3Hour(&wPack3Hour) == 1)   // прогнох на 2 дня по 3 часа
    {
      if ( frc.iPacket3H > 2 )                      // если мало распарсили, повторим через 2мин
        last_hForecast =  millis();
      else
        last_hForecast = millis() - hInterval + 30000;
      PrintForecast3H();
    }
  }
/*
  if ( now - last_dForecast > dInterval)
  {
    if (frc.GetForecastDays(&wPack1Day) == 1)   // прогноз на 4 дня
    {
      last_dForecast =  millis();
      PrintForecastDay();
    }
  }
  */
}
void ConvertDT(time_t tt, char dt[17])
{
  TimeElements t_e;
  int Y, M, D, hh, mm;
  breakTime(tt, t_e);
  Y = t_e.Year + 1900;
  M = t_e.Month;
  D = t_e.Day;
  hh = t_e.Hour;
  mm = t_e.Minute;
  sprintf(dt, "%i.%i.%i %i:%i", D, M, Y, hh, mm);
}
void PrintForecast3H()
{
  char DT[17];
  for (int i = 0; i  < frc.iPacket3H; i++)
  {
    ConvertDT(wPack3Hour[i].Data, DT);
    Serial.print("Data= ");
    Serial.print(DT);
    Serial.print("; ");
    Serial.print(wPack3Hour[i].RainVal / 100.0);
    Serial.print("; ");
    Serial.print(wPack3Hour[i].WS);
    Serial.print("; ");
    Serial.print(wPack3Hour[i].WD);
    Serial.print("; ");
    Serial.print(wPack3Hour[i].T);
    Serial.print("; ");
    Serial.print(wPack3Hour[i].TT);
    Serial.print("; ");
    Serial.print(wPack3Hour[i].H);
    Serial.print("; ");
    Serial.print(wPack3Hour[i].P);
    Serial.print("; ");
    Serial.println(wPack3Hour[i].Icon);
  }
}
/*
void ConvertD(time_t tt, char dd[11])
{
  TimeElements t_e;
  int Y, M, D;
  breakTime(tt, t_e);
  Y = t_e.Year + 1900;
  M = t_e.Month;
  D = t_e.Day;
  sprintf(dd, "%i.%i.%i", D, M, Y);
}
void PrintForecastDay()
{
  char D[11];
  for (int i = 0; i  < 4; i++)
  {
    ConvertD(wPack1Day[i].Data, D);
    Serial.print("Data= ");
    Serial.print(D);
    Serial.print("; ");
    Serial.print(wPack1Day[i].RainVal / 100.0);
    Serial.print("; ");
    Serial.print(wPack1Day[i].WS);
    Serial.print("; ");
    Serial.print(wPack1Day[i].WD);
    Serial.print("; ");
    Serial.print(wPack1Day[i].TD);
    Serial.print("; ");
    Serial.print(wPack1Day[i].TN);
    Serial.print("; ");
    Serial.print(wPack1Day[i].P);
    Serial.print("; ");
    Serial.print(wPack1Day[i].H);
    Serial.print("; ");
    Serial.print(wPack1Day[i].Cloud);
    Serial.print("; ");
    Serial.println(wPack1Day[i].Icon);
  }
}
*/

Автор: avs24rus

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js