[Из песочницы] Класс Forecaster для метеостанции или Предсказатель погоды
#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] = {'<', '/', '\0'};
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;
}