WeatherStation. Часть 2: Ethernet модуль

Часть 0: Описание и концепция
Часть 1: Основной модуль

Ethernet модуль — далеко не последняя по важности деталь в проекте WeatherStation (кто не в курсе — погодная станция, отображающая дату, время, погоду\влажность за окном и прогноз погоды на утро, день, вечер и ночь). В этой статье я опишу, как и с каких ресурсов я беру время и погоду, в каком формате читаю, как обрабатываю и как передаю основному модулю.

Ethernet модуль
Модуль состоит из Arduino Uno с Ethernet-шилдом. Была мысль сделать его на МК AVR-семейства (типа ATmega8) и сетевого адаптера на ENC, и даже было заказано все необходимое, но из-за скорости работы доставки (спасибо Почте России за пока так и не доставленную за 2 месяца посылку) пришлось делать из того, что есть. Как только все придет — обязательно сделаю более дешевый аналогичный модуль.

Назначение этого «бутерброда» из Ардуины и шилда — лежать около роутера и обрабатывать запросы, поступающие с основного модуля.

Коммуникация с основным модулем
Двусторонняя связь с ним, как я уже говорил в предыдущих статьях, обеспечивается трансивером nRF24L01+. Причем размер сообщения (т.е. размер payload) я выбрал 16 байт — не много, но и не мало, половина от максимального. Каждое сообщение будет стандартизировано — каждый байт будет строго иметь свое предназначение. Сделано это для возможности легкого добавления модулей — мало ли, приспичит сделать что-нибудь еще… Пока я пришел к такому шаблону:
Байт 0 — «адрес» модуля-отправителя (0=основной, 1=Ethernet, 2=DHT и т.д.).
Байт 1 — тип сообщения — запрос (и его код), ответ или просто полезная ниформация.
Байт 2 — флаг ошибки. Если этот байт равен 1, то полезная информация = описание ошибки.
Байты 3-15 — полезная информация.

Какие запросы может послать основной модуль? Пока в планах только три: запрос точного времени, запрос прогноза погоды и информация о состоянии сети. Также он может информировать Ethernet модуль о данных, приходящих с температурного датчика — в будущем планируется сделать простенькую Web-админку с актуальными значениями и настройками всей системы.

Работа с Интернет (алгоритм)
Для того, чтобы узнать время и погоду, нужно сделать запрос в Сеть. Я решил считывать данные, выводимые PHP-скриптами (тем самым разгрузив медленную Ардуину от лишних вычислений — PHP выполняется на сервере), благо бесплатный хостинг и домен (даже второго уровня) сейчас — не проблема, да и у меня как раз работает один (заметили, какой адрес у больших картинок и схем в предыдущей статье?). Я нашел сервисы, с которых можно будет легко брать время и погоду, написал два PHP-скрипта (time.php и weather.php соотвественно) и теперь обращаюсь к ним Ардуинкой.

А алгоритм работы такой: Arduino принимает сигнал от nRF24L01+ (в этом случае, в отличии от основного модуля, удобнее ждать прерывание от трансивера, нежели опрашивать его каждые N секунд) генерирует нужный HTTP-запрос, посылает его на Web-сервер, ждет ответа, считывает его по 1 байту, отделяет полезную информацию («тело») от HTTP-заголовков и посылает обратно основному модулю. Вот так все несложно.

Время и дата
Время и дату я решил брать прямо с сервера — благо оно оказалось довольно точным. К тому же, в языке PHP есть отличная функция date(), которая выдает нужную информацию в форматированном виде.

Итак, был написан простейший скрипт:

<?php
if ($_GET['utc']!=""){ $offset=$_GET['utc']; } else { $offset=0; }
$offset+=4; // time to UTC (time on server - UTC-4)

echo date("|Hisdmyw", strtotime("$offset hour"));

die();
?>

Посмотреть на его вывод можно здесь. Особенность этого маленького скрипта в том, что ему можно указывать необходимый часовой пояс. Вот так: time.php?utc=-2 или time.php?utc=+4 (МСК), а если не указать ничего, он выведет время UTC — по Гринвичу.
Разберу код по частям:
• Теги <php и ?> уведомляют сервер, что все что между ними есть PHP
• Строка 2 — проверка наличия параметра utc, если нет — то 0
• Строка 3 — компенсация часового пояса сервера (на этом сервере оказалось время UTC-4)
• Строка 4 — вывод даты и времени с учетом часового пояса
• Строка 5 заставляет сервер сразу закончить обработку скрипта. Как выяснилось, без нее Ардуина впустую ждет несколько секунд, что неприятно.
Формат вывода: |ччммссДДММГГд. чч — часы, мм — минуты, сс — секунды, и далее день, месяц, год. Последняя цифра — день недели, 1-7. Палочка в начале (|) нужна для легкого отсечения Ардуиной важной информации от HTTP-заголовков.

Погода
А вот с погодой все оказалось несколько сложнее… Функции weather() в PHP нет :). Пришлось брать ее (погоду) со стороннего ресурса. Парсить голый HTML не хотелось, а следовательно, Яндекс.Погода и Гисметео отпадали. И вот каким-то чудом был найден ресурс www.weather.ua (не реклама :) ), который позволяет бесплатно брать информацию о погоде в формате XML! Причем она полностью кастомизабельна — количество дней, город и многое другое, да и прогноз дает как раз на утро, день, вечер и ночь! Ура, то что нужно, подумал я! В срочном порядке был изучен синтаксис обращения к этому сайту, его вывод и XML-парсер simplexml. И тут встал еще один вопрос — показывать прогноз на один день или со сдвигом? Что показывать в прогнозе на утро, если уже вечер? Было решено, что показываться всегда будет актуальный прогноз, то есть вечером показывать прогноз на утро следующего дня.

Итак, решено: запрашиваем у сайта прогноз погоды на 2 дня, сортируем (информация с сайта приходит не всегда «утро, день, вечер, ночь» — может прийти «вечер, ночь, утро, день»), считаем среднее значение температуры (в XML выводится максимальная и минимальная температура), конвертируем состояние погоды (cond) из их 100-бальной шкалы в мою, с 4 состояниями (ясно, облачно, дождь, снег).
Вот так выглядит у меня запрос к сайту:
http://xml.weather.co.ua/1.2/forecast/$city?dayf=2&userid=$user&lang=ru

Где:
• $city — ID города. Узнать его можно так: заходим на weather.ua, переходим на страницу города (найдя его в поиске) и смотрим в адресную строку. Все что после знака вопроса — и есть ID города. Для Москвы это 27, для меня (Чебоксары) — 1501.
• $user — какой-то идентификатор пользователя, видимо им нужен для статистики. Я указываю домен, с которого запрашиваю данные — naboko.tk
Пример ссылки (при переходе можно посмотреть вывод): http://xml.weather.co.ua/1.2/forecast/1501?dayf=2&userid=naboko.tk&lang=ru

Я написал вот такой скрипт (он уже менее красив, чем предыдущий...):

<?php
function WC2DEC($tmp){
	if ($tmp<10) return 1;
	else if ($tmp<40) return 2;
	else if ($tmp<80) return 3;
	else return 4;
}

if ($_GET['city']!=""){ $city=$_GET['city']; } else { $city=1501; }

// Ссылка запроса - город, прогноз на 2 дня
$url="http://xml.weather.co.ua/1.2/forecast/$city?dayf=2&userid=naboko.tk&lang=ru";

/* Загружаем XML и преобразуем в объект */
$xmlstr = @file_get_contents($url);
if ( $xmlstr===false ) die('|ERR0'); // Error connect to XML
$xml = simplexml_load_string($xmlstr);
if ( $xml===false ) die('|ERR1'); // Error parse XML
$day=$xml->forecast->day; // Чтобы путь короче был

/* Сортировка в порядке ночь - утро - день - вечер */
switch ($day[0]['hour']){
 case 3:
 	$arr = array(4,1,2,3); break;
 case 9:
 	$arr = array(3,4,1,2); break;
 case 15:
 	$arr = array(2,3,4,1); break;
 case 21:
 	$arr = array(1,2,3,4); break;
}
echo "|";
/* Вывод погоды в формате sAAsBBsCCsDD, s - знак, AA..DD - погода (с лид. нулем) */
for ($i=0; $i<4; $i++){
    $t_min=$day[$arr[$i]]->t->min;
    $t_max=$day[$arr[$i]]->t->max;  

    $t=($t_min+$t_max)/2;
    if($t>=0) { echo "+"; } else { echo "-"; }
    if (abs($t)<10) { echo "0"; }
    echo abs($t_min);
}
/* Вывод погоды в формате abcd, 1..4 (ясно, облачно, дождь, снег) */
for ($i=0; $i<4; $i++){
    echo WC2DEC($day[$arr[$i]]->cloud);
}
die(); // Чтобы клиент сразу отцепился
?>

В работе скрипт можно посмотреть здесь. Как и прошлый скрипт, вывод можно подстроить под себя, не меняя исходник. ID города можно передать через параметр city, вот так: weather.php?city=27 (Москва). Без параметра скрипт выводит погоду для Чебоксар.
На этот раз поясню смысл лишь кратко:
• Строки 2-7 — функция, которая, получая на вход данные о состоянии погоды по 100-бальной шкале, выводит цифру от 1 до 4 — ясно, облачно, дождь, снег
• Строки 15-19 — забираю XML и превращаю его в объект, с которым удобно работать, при этом проверяя процесс на наличие ошибок
• Строки 22-31 создают массив с индексами дней в нужной последовательности
• Строки 34-42 считают среднее арифметическое температуры и выводят знак и значение (причем в обязательном порядке, и если число будет меньше 10, скрипт выведет лидирующий ноль)
• Строки 44-46 — вывод состояния погоды.

Скачать PHP скрипты (один архив) — *click* (мало ли, вдруг парсер на сайте съест пару тегов...)

Состояние проекта
Времени на все, к сожалению, не хватает. Надеюсь, сегодня я сделал все хорошо, остались еще 30 мая и 3, 6 июня… Так что разработка проекта немного подтормаживает: корпус основного модуля так и не готов, температурный модуль собран пока только на макетке… Вот начнется лето, и сразу все сделаю :)

Комментарии (2)

RSS свернуть / развернуть
+
0
У меня вопрос, как я понял, вы на Ардуино вешаете одновременно Ethernet-шилд и модуль nRF24L01+. Но судя по инструкции Ethernet-шилд использует пины 10, 11, 12, 13 для обмена данными между шилдом и Ардуино и пользователей настоятельно просят не использовать их для других целей. Но для работы с модулем nRF24L01+ по средствам библиотеки RF24 или Mirf так или иначе эти пины используются тоже. Не вызывает ли такое подключения конфликта или вы используете другое подключение и библиотеки.
avatar

mkokorev

  • 30 июля 2013, 14:30
+
0
Я не использовал готовые библиотеки, а портировал свою, написанную для AVR-GCC (Arduino-вариант не сохранился, оригинал брать тут. Для работы нужна либа по ссылке ниже, одна из версий)
А чтобы решить конфликт с Ethernet-шилдом, запустил софтварный SPI, опять-таки портированный с моей либы для AVR-GCC (так же, оригинал лежит тут, брать версию Software)
avatar

shadowalker

  • 30 июля 2013, 16:43

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.