Почему именно эта связка, ведь можно сделать без Arduino на голом Atmel микроконтроллере и в качестве хранилища использовать связку PHP/MySQL? Всё просто: не хотелось возится с ЛУТом, ибо лазерного принтера под рукой небыло, да и PHP порядком надоел. А самое главное — хотелось попробовать этот самый Orienteer в боевых условиях, а не тыкать кнопки в демонстрационном приложении.
Принцип работы прост: опрашиваем датчики, формируем и отправляем REST запрос в Orienteer.
В связи с небольшим количеством железа под Arduino в моем арсенале была выбрана следующая связка железа:
- Arduino UNO R3 (точнее ее китайский клон с CH340)
- Датчик температуры и влажности DHT22
- Ethernet шилд
- Соединительные провода
Аппаратная часть
Как видно схема подключения очень проста, обвязки для датчика не требуется. Берем 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.
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 коды виджета.
<h1 id="last-temp">Update...</h1>
<h1 id="last-hum">Update...</h1>
<span id="last-datetime"></span>
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)
FLABER
17.01.2017 18:21+2Можно сэкономить как на памяти, так и на размере, если использовать в качестве датчика BME280 — довольно компактный, подключается по I2C или SPI, измеряет одновременно температуру, влажность и атмосферное давление.
crea7or
17.01.2017 18:29+3Предлагаю ознакомиться с устройствами на народном мониторе погоды, там такого добра с софтом уже вагон и маленькая тележка (по кнопке устройства вверху).
LumberJack
17.01.2017 19:35Это даже не из пушки по воробьям, а «Солнцепёком» по комару. Я про тяжеловесность конструкции и явный overpower при наличии Ethernet шилда.
OrienteerBAP
18.01.2017 01:57+2Спасибо за интерес к Orienteer'у! Столкнулись ли с какими-то проблемами при реализации? Есть ли пожелания? И давайте к нам в облако перенесем:) Будет проще подключать других желающих использовать тот же подход, но не имеющих возможность запустить Ориентир у себя дома.
IvanSCM
18.01.2017 20:33+1С первой статьи наблюдаю за вашим продуктом, скоро попробую в качестве бекенда для Android приложения.
По данной заметке: задача несложна, с Orienteer'ом проблем не испытал. С облаком отличное предложение, написал в ЛС.
BurlakovSG
18.01.2017 20:20+1SELECT 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 $dayIvanSCM
18.01.2017 20:26Спасибо за внимательность, ошибка в запросе была, исправил.
Для группировки по часам ваш запрос верен.
osipov_dv
Вот только на UNO это врят-ли получится, либы жрут очень много памяти. У меня IDE начал ругатся уже при подключении DHT22 + BMP180 + RTC + SD, памяти реально мало. В случае с ethernet либой думаю еще меньше останется. Если не секрет, при заливке что говорит IDE о свободной оперативке?
IvanSCM
Не секрет, 55% ПЗУ, 49% ОЗУ. Для экономии памяти можно попробовать использовать Serial to Ethernet, например на ENC28J60.