Многие начинающие (и не только) Ардуинщики прошли через создание устройства с громким названием — Метеостанция, я в их числе.
Многие варианты, которые я изучал, интересны с точки зрения полета мысли создателя, из многих я почерпнул идеи, о чем и не скрываю. Помимо фиксации показаний с собственных датчиков, визуализации их через различные интерфейсы типа всевозможных Дисплеев и Веб-страничек, мне было интересно использовать данные о прогнозе погоды на некоторое время вперед.
Тут, на мой взгляд есть два пути: с первым я ознакомился в этой статье, но мне больше понравилась возможность получать прогноз из Интернета. Основную информацию об этом я почерпнул из статьи на Амперке и развил эту идею дальше, благо предложенный в статье ресурс, позволяет это сделать, причем совершенно бесплатно.
В результате моя версия погодной станции получила класс 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
В связи с нехваткой памяти в этих устройствах пришлось закомментить отдельные связанные функции. Внутри класса:
В теле основного цикла закомментирован код, вызывающий эти функции. Их работу можно проверить, по очереди комментируя и убирая комменты с соответствующих участков кода. Если же код залить в Mega2560, то необходимость комментирования отпадет и все будет работать. Проект собирается под IDE версии 1.6.5. (На 1.0.5r2 — тоже без проблем).
Так же хочу оговориться, я не являюсь адептом С (С++), поэтому частенько задаю вопросы на форумах как работать со строками и тому подобное. В связи с этим вполне вероятно, что какие то участки кода написаны странно или не оптимально, короче — здоровая критика приветствуется.
Город, для которого запрашиваем прогноз прописан в объявлениях класса:
Объявление класса предсказания погоды:
Инициализация:
Запрос погоды на сервере:
Структуры с метеоданными имеют небольшую длину и поэтому без труда могут быть переданы через nRF24. Естественно что 1 структура содержит данные за 1 день или за 3 часа, и что бы передать все дынные полностью, потребуется соответствующее кол-во сеансов передачи. Для этого я создал специальный протокол обмена между дисплейным и серверным модулями. Ну и собственно сами исходники:
Многие варианты, которые я изучал, интересны с точки зрения полета мысли создателя, из многих я почерпнул идеи, о чем и не скрываю. Помимо фиксации показаний с собственных датчиков, визуализации их через различные интерфейсы типа всевозможных Дисплеев и Веб-страничек, мне было интересно использовать данные о прогнозе погоды на некоторое время вперед.
Тут, на мой взгляд есть два пути: с первым я ознакомился в этой статье, но мне больше понравилась возможность получать прогноз из Интернета. Основную информацию об этом я почерпнул из статьи на Амперке и развил эту идею дальше, благо предложенный в статье ресурс, позволяет это сделать, причем совершенно бесплатно.
В результате моя версия погодной станции получила класс 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 дня
Описание структур с метеоданными
// текущая погода
struct _WeatherPacket
{
int P; //Давление
int T; //Температура
int H; //Влажность
int WS; //Скорость ветра (м/сек)
char WD[4]; //Направление
char Icon[4]; //Иконка отражающая текущую погоду
};
// Прогноз погоды на сутки из 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;
// Прогноз погоды на 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;
Структуры с метеоданными имеют небольшую длину и поэтому без труда могут быть переданы через 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] = {'<', '/', '\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;
}
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
P.S. Раньше (до 09.10.2015) для получения прогноза даже регистрация и APIID key были не нужны, теперь без APIID key выдается ошибка и прогноз погоды не отдают, но получить ключ можно за пару минут, просто зарегистрировавшись на сайте.