Под Новый Год ко мне приходит желание разработать что-нибудь нестандартное. В этот раз я решил начать собирать и обрабатывать погодные данные возле своего дома. И, конечно, выбрал Arduino в качестве железа, а вот в качестве хранилища и инструмента просмотра и анализа — упоминавшийся недавно на Хабрахабре конструктор бизнес-приложений Orienteer. Тем что получилось, я поделюсь в этой заметке.



Почему именно эта связка, ведь можно сделать без Arduino на голом Atmel микроконтроллере и в качестве хранилища использовать связку PHP/MySQL? Всё просто: не хотелось возится с ЛУТом, ибо лазерного принтера под рукой небыло, да и PHP порядком надоел. А самое главное — хотелось попробовать этот самый Orienteer в боевых условиях, а не тыкать кнопки в демонстрационном приложении.

Принцип работы прост: опрашиваем датчики, формируем и отправляем REST запрос в Orienteer.

В связи с небольшим количеством железа под Arduino в моем арсенале была выбрана следующая связка железа:

  1. Arduino UNO R3 (точнее ее китайский клон с CH340)
  2. Датчик температуры и влажности DHT22
  3. Ethernet шилд
  4. Соединительные провода



Аппаратная часть




Как видно схема подключения очень проста, обвязки для датчика не требуется. Берем Arduino, подключаем Ethernet шилд, подключаем датчик. Я использовал датчик DHT22, он измеряет температуру и процент влажности, вы можете использовать любой, который есть у вас под рукой.

Перейдем к коду скетча для Arduino. В самом начале все стандартно: подключаются необходимые библиотеки. В моем случае потребовались: Adafruit Unified Sensor Driver и Adafruit DHT Humidity & Temperature Unified Sensor Library, а также библиотеки для работы с Ethernet.

Код с подключением необходимых библиотек
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>

#include <SPI.h>
#include <Ethernet.h>


Далее объявляются константы для быстрого редактирования, названия сами говорят за себя, но для новичков в Arduino поясню:
  • DHTPIN — разъем Arduino к которому подключен датчик
  • DHTTYPE — тип датчика, используется библиотекой Adafruit
  • DELAY_MILLIS — интервал отправки данных, по умолчанию значение 30000UL, что означает 30 секунд
  • REST — часть URL адреса для REST протокола
  • SERVER_NAME — IP адрес или хост сервера, где запущен Orienteer
  • SERVER_PORT — порт сервера
  • AUTH_BASE64 — закодированная в BASE64 кодировке строка login:password для Basic HTTP аутентификации (в нашем случае admin:admin)
  • DEVICE_ID — идентификатор устройства сбора данных, то есть нашей Arduino, предполагается сбор с нескольких точек (например: дом/улица)

Дальше также все стандартно — инициализация датчика, Ehternet шилда. Для отправки данных по REST в Orienteer нужно подготовить JSON с информацией — класс и данные. Код для отправки запроса я вынес в отдельную функцию. Стоит обратить внимание на отправку POST запроса и базовую авторизацию, если вы недавно изучаете Arduino.

Отправка HTTP заголовков
sprintf(outBuf,"POST %s HTTP/1.1",page);
client.println(outBuf);
sprintf(outBuf,"Authorization: Basic %s", AUTH_BASE64);
client.println(outBuf);
sprintf(outBuf,"Host: %s",domainBuffer);
client.println(outBuf);
client.println(F("Connection: close\r\nContent-Type: application/x-www-form-urlencoded"));


Генерация JSON для отправки данных формируется с помощью функции sprintf():

sprintf(params,"{'@class':'Weather', 'temperature':%i, 'humidity':%i, 'device':'%s'}", temperature, humidity, DEVICE_ID);

Интервал для отправки реализован с помощью функции millis(). В случае ошибки ругаемся в последовательный интерфейс.

Orienteer и его настройка


Вот и подошли к серверной части. Запустим и настроим платформу Orienteer для получения данных и вывода графиков.

Запустить платформу можно из исходников, а можно и Docker контейнер (подробнее в официальной документации). Спасибо авторам что упаковали все это дело в контейнер, очень удобный способ доставки приложения. Для разворачивания предположим что Docker у вас установлен и есть минимальные навыки работы с ним, такие как запуск и остановка контейнера. Если это не так, то вам сюда и сюда.

Для начала скачаем свежий образ контейнера с приложением, я работал на ОС Ubuntu:

sudo docker pull orienteer/orienteer

Вы увидите следующее:



Немного подождав, образ скачается и его можно будет запустить:

sudo docker run -p 8080:8080 orienteer/orienteer

Заходим на адрес localhost:8080 в браузере, входим с парой логин-пароль: admin/admin. Выберем в меню Schema > Classes > +Create для создания класса модели данных и укажем название класса — Weather. Не забудем сохранить новый класс:



Теперь создадим свойства класса. Нам нужно сохранять: температуру, процент влажности, имя устройства и время обращение устройства. На странице свойств класса (localhost:8080/class/Weather) добавим свойства:



У свойства dateTime в качестве значения по умолчанию укажем функцию sysdate() для получения текущего времени в момент прихода данных от устройства. Погрешность даже в несколько секунд не критична в нашем случае.

Готово, проверим — приходят наши данные? Все отлично, есть контакт!



Но, как мы видим — данные не в очень удобном виде нам подаются. Не беда, в Orienteer есть замечательные виджеты Pivot Table, HTML/JS и даже можно создать свою страницу для отображения, но остановимся на виджетах. Нам необходимо отображать последние пришедшие данные: температуру, процент влажности, время последнего обновления; средние показатели за дни.

Для добавления виджета перейдем Schema > Weather > Browse Class и нажмем на значок шестеренки в верхнем правом углу. Нажмем Add widget и добавим виджет Html Js Pane. Сохраним состояние виджета нажатием Save. Далее перейдем в настройки виджета по Actions > Settings, нажмем кнопку Edit.



Добавим ресурс для отображения — Weather(это значит, что данный виджет будет показываться только если мы находимся на странице просмотра объектов данного класса), укажем HTML и JS коды виджета.

HTML
<h1 id="last-temp">Update...</h1>
<h1 id="last-hum">Update...</h1>
<span id="last-datetime"></span>


JavaScript
function getWeather(){
var url = "/orientdb/query/db/sql/SELECT temperature, humidity, dateTime.format('dd.MM.yyyy HH:mm') AS dt FROM Weather ORDER BY dateTime.asLong() DESC LIMIT 1/1?rnd="+Math.random();
$.getJSON( url, {})
.done(function( data ) {
$('#last-temp').html(data.result[0].temperature + '°');
$('#last-hum').html(data.result[0].humidity+ '%');
$('#last-datetime').html(data.result[0].dt);
});
}
(function() {
getWeather();
})();


И сохраним — жмем Save.

Аналогичными действиями добавляем виджеты Pivot Table для отображения графиков. В одном для отображения средней температуры за сутки в настройках укажем SQL запрос для средней температуры:

SELECT dateTime, AVG(temperature) AS temp FROM Weather LET $day = dateTime.format('yyyy-MM-dd') GROUP BY $day

В режиме редактирования виджетов зададим конфигурацию графику:



А во втором виджете для отображения среднего процента влажности за сутки следующий SQL:

SELECT dateTime, AVG(humidity) AS hum FROM Weather LET $day = dateTime.format('yyyy-MM-dd') GROUP BY $day

Но в конфигурации графика укажем значение hum с результата SQL запроса:



И в итоге у нас получилась отличная система для сбора и отображения погодных данных.

Длинный скриншот


В планах:

  • Добавить дополнительные датчики: давления, CO2, уровня освещенности
  • Добавить часы реального времени(корректная реализация, когда у нас время считывания, а не время получения данных)
  • Предварительное кеширование на SD карту памяти для выгрузки среза данных
  • Вывод на дисплей погодных данных и сообщений об ошибках датчиков или проблем со связью
    настройка виджета

Все обновления будут на GitHub. А я тем временем полез на Aliexpress заказывать железо для совершенствования своей системы. Хочется еще попробовать Orienteer в качестве backend приложения для AngularJS или Android приложения, но это тема отдельной статьи.
Поделиться с друзьями
-->

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


  1. osipov_dv
    17.01.2017 15:29
    +2

    В планах:

    Добавить дополнительные датчики: давления, CO2, уровня освещенности
    Добавить часы реального времени(корректная реализация, когда у нас время считывания, а не время получения данных)
    Предварительное кеширование на SD карту памяти для выгрузки среза данных
    Вывод на дисплей погодных данных и сообщений об ошибках датчиков или проблем со связью
    настройка виджета


    Вот только на UNO это врят-ли получится, либы жрут очень много памяти. У меня IDE начал ругатся уже при подключении DHT22 + BMP180 + RTC + SD, памяти реально мало. В случае с ethernet либой думаю еще меньше останется. Если не секрет, при заливке что говорит IDE о свободной оперативке?


    1. IvanSCM
      17.01.2017 18:32
      +1

      Не секрет, 55% ПЗУ, 49% ОЗУ. Для экономии памяти можно попробовать использовать Serial to Ethernet, например на ENC28J60.


  1. FLABER
    17.01.2017 18:21
    +2

    Можно сэкономить как на памяти, так и на размере, если использовать в качестве датчика BME280 — довольно компактный, подключается по I2C или SPI, измеряет одновременно температуру, влажность и атмосферное давление.


    1. IvanSCM
      17.01.2017 18:22
      +1

      Спасибо за подсказку, добавил в корзину.


  1. crea7or
    17.01.2017 18:29
    +3

    Предлагаю ознакомиться с устройствами на народном мониторе погоды, там такого добра с софтом уже вагон и маленькая тележка (по кнопке устройства вверху).


    1. IvanSCM
      17.01.2017 18:41

      В виду того, что данное занятие хобби, хотелось самому разработать: научиться, вспомнить, размяться.


      1. crea7or
        17.01.2017 19:00
        +1

        Я к тому, что полезное что-нибудь найдёте.


  1. LumberJack
    17.01.2017 19:35

    Это даже не из пушки по воробьям, а «Солнцепёком» по комару. Я про тяжеловесность конструкции и явный overpower при наличии Ethernet шилда.


  1. OrienteerBAP
    18.01.2017 01:57
    +2

    Спасибо за интерес к Orienteer'у! Столкнулись ли с какими-то проблемами при реализации? Есть ли пожелания? И давайте к нам в облако перенесем:) Будет проще подключать других желающих использовать тот же подход, но не имеющих возможность запустить Ориентир у себя дома.


    1. IvanSCM
      18.01.2017 20:33
      +1

      С первой статьи наблюдаю за вашим продуктом, скоро попробую в качестве бекенда для Android приложения.
      По данной заметке: задача несложна, с Orienteer'ом проблем не испытал. С облаком отличное предложение, написал в ЛС.


  1. BurlakovSG
    18.01.2017 20:20
    +1

    SELECT dateTime, AVG(temperature) AS temp AS hum FROM Weather LET $day = dateTime.format('yyyy-MM-dd') GROUP BY $day

    Разве «AS hum» в этом запросе не лишнее?
    Я правильно понимаю, что LEТ из хранящегося в таблице «2017-01-02 12:01:57» превращает в «2017-01-02» и потом происходит получение среднего, где эта дата одинакова?
    А если мне нужно проссумировать данные по часам? Это будет выглядеть так: $day = dateTime.format('yyyy-MM-dd НН') GROUP BY $day


    1. IvanSCM
      18.01.2017 20:26

      Спасибо за внимательность, ошибка в запросе была, исправил.
      Для группировки по часам ваш запрос верен.