Здравствуйте уважаемые пользователи сайта «паяльник», сегодня мне хотелось бы поделиться своим опытом проектирования и разработки устройства под управлением Arduino, основная задача которого сбор данных с датчиков и отправка их на полноценный WEB-сервер с PHP/MySQL.
Просмотрев большое количество ресурсов по данной тематике, я так и не нашел более менее похожей статьи на данную тему, в связи с чем пришлось самому поразмыслить над некоторыми вещами. Чтобы данный труд не прошел даром, решил поделиться с вами накопленными знаниями.
Описание устройства
Вычислительным мозгом устройства является платформа Arduino UNO, для связи с сетью используется Ethernet Shield, помимо этого, я установил LCD KeyPad Shield для вывода информации о состоянии различных устройств и команд, также установил все возможные датчики и реле.
Полный список используемых компонентов:
- Arduino UNO – мозг системы;
- Ethernet Shield – связь с сетью;
- LCD KeyPad Shield – дисплей для вывода информации;
- DHT11 – датчик температуры и влажности;
- Датчик движения;
- Реле-модуль;
- Пульт ДУ.
Вам не обязательно иметь полный список комплектующих, код написан таким образом, что его можно легко переписать под свои нужды.
Клиентская часть на Arduino
Усвойте как незыблемую истину, что чудес в мире информационных технологий не бывает, и если устройство работает не так, как вы задумывали, значит, Вы где-то ошиблись.
Итак, давайте для начала разберемся, как вообще послать какой-либо запрос к Web-серверу. Если Вы думаете, что это слишком сложно, то Вы ошибаетесь. Человек так устроен, что просто не способен создавать что-то невообразимое, в чем он сам же не смог разобраться.
В нашем распоряжении имеется устройство на базе Arduino с которого необходимо отправить запрос на WEB-сервер. Инициатором обмена данными обычно выступает браузер, в нашем случае – Arduino. Web-сервер никому и никогда просто так ничего не пошлет, чтобы он что-нибудь отправил клиенту надо, чтобы клиент его об этом попросил. Простейший HTTP запрос может выглядеть, например, так:
GET http://www.php.net/ HTTP/1.0\r\n\r\n GET - тип запроса, тип запроса может быть разным, например POST, HEAD, PUT, DELETE. http://www.php.net/ - URI от которого мы хотим получить хоть какую-нибудь информацию. HTTP/1.0 - тип и версия протокола, который мы будем использовать в процессе общения с сервером. \r\n - конец строки, который необходимо повторить два раза.
В нашем случае запрос к серверу выглядит следующим образом:
GET /add.php?k=asREb25C&t=24.00&h=35.00 HTTP/1.1 Host: site.ru Connection: close
Скетч программы представлен ниже:
#include <SPI.h> #include <Ethernet.h> #include "DHT.h" //Константы #define DHTPIN 2 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // Мак адрес EthernetClient client; //Переменные unsigned long int timeConn = millis(); // Частота отправки данных о t/h на сервер float h; // Значение температуры float t; // Значение влажности char server[] = "site.ru"; /*-------------------------------------------------------------- Необходимые настройки --------------------------------------------------------------*/ void setup() { //Старт Serial.begin(9600); Ethernet.begin(mac); dht.begin(); } /*-------------------------------------------------------------- Основное тело программы --------------------------------------------------------------*/ void loop() { h = dht.readHumidity(); t = dht.readTemperature(); if (millis() - timeConn > 2000) { sendData(t,h); timeConn = millis(); Serial.println("CONNECT SERVER: Send temp/hum"); } } /*-------------------------------------------------------------- Функция отправляет данные о температуре и влажности на WEB сервер. --------------------------------------------------------------*/ void sendData(float t, float h) { client.connect(server, 80); client.print( "GET /add.php?"); client.print("k="); // Специальный код, например asREb25C client.print("&"); client.print("t="); client.print(t); client.print("&"); client.print("h="); client.print(h); client.println(" HTTP/1.1"); client.print( "Host: " ); client.println(server); client.println( "Connection: close" ); client.println(); client.println(); client.stop(); client.flush(); }
Как было сказано выше, в своем устройстве я использовал также и другие датчики, в связи с чем скетч моего устройства выглядит следующим образом:
/*-------------------------------------------------------------- Программа: Arduino Home Server Автор: С.С. Гранкин, http://www.factoblog.ru --------------------------------------------------------------*/ #include <SPI.h> #include <Ethernet.h> #include "DHT.h" #include <LiquidCrystal.h> #include <IRremote.h> //Константы #define DHTPIN 2 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); LiquidCrystal lcd(8, 9, 4, 5, 6, 7 ); byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // Мак адрес EthernetClient client; //Переменные long unsigned int lowIn; // Время, в которое был принят сигнал отсутствия движения(LOW) boolean lockLow = true; // Флаг. false = значит движение уже обнаружено, true - уже известно, что движения нет boolean takeLowTime; // Флаг. Сигнализирует о необходимости запомнить время начала отсутствия движения boolean PIR = false; // Вкл./Выкл. датчик движения unsigned long int timeConn = millis(); // Частота отправки данных о t/h на сервер unsigned long int sendSens = millis(); // Частота отправки данных о датчиках на сервер int h; // Значение температуры int t; // Значение влажности int codeLCD = 0; // Код для LCDisplay() boolean REBOOT = true; // Перезагрузка (не менять!) IRrecv irrecv(19); // Пин, к которому подключен приемник decode_results results; char server[] = "site.ru"; byte load[8] = { 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111 }; byte gradus[8] = { 0b00110, 0b01001, 0b01001, 0b00110, 0b00000, 0b00000, 0b00000, 0b00000 }; /*-------------------------------------------------------------- Вводим необходимые настройки --------------------------------------------------------------*/ void setup() { //Пины pinMode(3, OUTPUT); // Подсветка LCD pinMode(14, OUTPUT); // Реле-1 pinMode(18, INPUT); // Датчик движения //Значения digitalWrite(14, HIGH); // Отключаем реле-1 //Старт Serial.begin(9600); irrecv.enableIRIn(); Ethernet.begin(mac); dht.begin(); lcd.begin(16, 2); lcd.createChar(0,load); lcd.createChar(1,gradus); display("Calibrating PIR","sensor - 10 sec."); digitalWrite(3, HIGH); lcd.clear(); for(int i = 0; i < 16; i++) { lcd.setCursor(0,0); lcd.print("Calibrating PIR"); lcd.setCursor(i,2); lcd.write(byte(0)); delay(1500); } digitalWrite(3, LOW); //Приветствие LCD display("Arduino Server","Status: is RUN"); } /*-------------------------------------------------------------- Основное тело программы --------------------------------------------------------------*/ void loop() { h = dht.readHumidity(); t = dht.readTemperature(); motion(PIR); if (millis() - sendSens > 120000 || REBOOT==true) { sensor(PIR, REBOOT); sendSens = millis(); display("CONNECT SERVER","Sending data"); } if (millis() - timeConn > 14400000 || REBOOT == true) { sendData(t,h); timeConn = millis(); REBOOT = false; display("CONNECT SERVER","Send temp/hum"); } LCDispay(codeLCD); if (irrecv.decode(&results)) { switch (results.value) { case 16738455: // 1 LCDispay(0); codeLCD = 0; break; case 16750695: // 2 LCDispay(1); codeLCD = 1; break; case 16712445: //ОК digitalWrite(3, HIGH); delay(1500); digitalWrite(3, LOW); break; case 16728765: //* PIR = true; display("MOTION SENSOR","Status: ON"); break; case 16732845: //# PIR = false; lockLow = true; digitalWrite(14, HIGH); display("MOTION SENSOR","Status: OFF"); break; } irrecv.resume(); } } /*-------------------------------------------------------------- Функция вывода информации на LCD KeyPad Shield Принемает параметр id. --------------------------------------------------------------*/ void LCDispay(int id) { switch(id) { case 1: lcd.clear(); lcd.setCursor(0,0); lcd.print("Rele-1:OFF Rele-2:ON"); lcd.setCursor(0,2); lcd.print("Light:1950"); break; default: lcd.clear(); lcd.setCursor(0,0); lcd.print("Temp:"); lcd.setCursor(5,0); lcd.print(t); lcd.write(byte(1)); lcd.setCursor(9,0); lcd.print("Hum:"); lcd.setCursor(13,0); lcd.print(h); lcd.print("%"); lcd.setCursor(0,2); lcd.print("Motion:"); if (PIR == true) { lcd.print("ON"); lcd.setCursor(7,2); } else { lcd.print("OFF"); lcd.setCursor(8,2); } break; } } /*-------------------------------------------------------------- Функция отправляет данные о сотоянии датчиков, реле и тд. на WEB сервер. --------------------------------------------------------------*/ void sensor(int p1, int rb) { client.connect(server, 80); client.print( "GET /add.php?"); client.print("k="); // Специальный код, например asREb25C client.print("&"); client.print("p1="); client.print(p1); client.print("&"); client.print("rb="); client.print(rb); client.print("&"); client.print("ram="); client.print(freeRam()); client.println(" HTTP/1.1"); client.print( "Host: " ); client.println(server); client.println( "Connection: close" ); client.println(); client.println(); client.stop(); client.flush(); } /*-------------------------------------------------------------- Функция датчика движения (PIR-sensor) --------------------------------------------------------------*/ void motion(int PIR) { if (PIR==true) { if(digitalRead(18) == HIGH) { if(lockLow) { lockLow = false; digitalWrite(14, LOW); display("MOTION DETECTED","RELE-1 is ON"); Serial.println(lowIn); Serial.println("MOTION DETECTED RELE-1 is ON"); } takeLowTime = true; } else { if(takeLowTime) { lowIn = millis(); takeLowTime = false; } if(!lockLow && millis() - lowIn > 5000) { lockLow = true; digitalWrite(14, HIGH); display("MOTION FINISH","RELE-1 is OFF"); Serial.println(lowIn); Serial.println("MOTION FINISH RELE-1 is OFF"); } } } } /*-------------------------------------------------------------- Функция отправляет данные о температуре и влажности на WEB сервер. --------------------------------------------------------------*/ void sendData(float t, float h) { client.connect(server, 80); client.print( "GET /add.php?"); client.print("k="); // Специальный код, например asREb25C client.print("&"); client.print("t="); client.print(t); client.print("&"); client.print("h="); client.print(h); client.println(" HTTP/1.1"); client.print( "Host: " ); client.println(server); client.println( "Connection: close" ); client.println(); client.println(); client.stop(); client.flush(); } /*-------------------------------------------------------------- Функция вывода информации на LCD KeyPad Shild Принемает параметр title1 - первая строка (max 16 символов), title2 - вторая сторока. --------------------------------------------------------------*/ void display(String title1, String title2) { lcd.clear(); lcd.setCursor(0,0); digitalWrite(3, HIGH); lcd.print(title1); lcd.setCursor(0,2); lcd.print(title2); delay(1600); digitalWrite(3, LOW); } /*-------------------------------------------------------------- Функция выводит количество свободных байт RAM --------------------------------------------------------------*/ int freeRam () { extern int __heap_start, *__brkval; int v; return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); }
Серверная часть
Наше устройство на базе Arduino будет посылать на сервер различные данные с разной периодичностью, сделано это для того, чтобы просто не засорять базу данных MySQL излишней информацией. Для примера установим, что данные о состоянии датчиков (датчиков движения, реле и тд) буду отправляться с периодичностью в 2 мин., а данные о температуре и влажности каждые 4 часа.
Арендовать полноценный Web сервер мы, конечно, не будем, потому что это дорого, да и попросту неразумно, обойдемся услугой хостинга. Хостинг – это не компьютер и не программа, а услуга по предоставлению вебмастеру для его сайта места на своих серверах. Требования к хостингу минимальны: php не ниже 5.3, MySQL с субд.
Код серверной части прикреплен к данной статье. Для удобства он разбит на несколько файлов:
- /system/core.php – «ядро» нашего сервера, здесь прописываются данные для подключения к базе данных.
- /system/functions.php – здесь содержатся различные пользовательские функции, которые нам понадобятся при работе.
- /style/ - стили CSS.
- .htaccess – Содержит настройки для сервера. По умолчанию выставлен часовой пояс +3 (Москва).
- _sever.sql – дамп БД. Данный файл необходимо импортировать в базу данных.
- Index.php – наш главный файл (страница), который будет выводить информацию о состоянии датчиков.
- Add.php – данный файл будет принимать и обрабатывать данные от Arduino.
Для того, чтобы только наше устройство могло отправлять данные на сервер, мы в GET-запросе будем передавать специальный код (ключ), как бы подтверждая тот факт, что данные действительно передает Arduino.
Заключение
В статье я не коснулся вопроса, как обрабатывать ответ от сервера (если мы хотим управлять устройством через интернет), сделал я так потому, что наша серверная часть никак не защищена, т.е. любой желающий может зайти на сайт и посмотреть информацию о датчиках движения и т.д. В связи с этим, необходимо организовывать полноценную аунтификацию на PHP и MySQL, а это уже никак не подходит под тематику данного ресурса.
Всем, кто захочет собрать нечто подобное самостоятельно, я желаю удачи! Задавайте свои вопросы в комментариях, с радостью на них отвечу.
P.S. Живой пример: http://home.telwik.ru/
Прикрепленные файлы:
- sever.zip (76 Кб)
- arduino.zip (3 Кб)
- example.rar (1 Кб)
Комментарии (62) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация
[Автор]
[Автор]
Я понимаю, что, возможно, позно, но возникла проблема.
На картинки изображение как выглядит страничка
[Автор]
[Автор]
[Автор]
[Автор]
на месте графика ] }, yAxis: { title: { text: 't °C/ h %' } }, plotOptions: { line: { dataLabels: { enabled: true }, enableMouseTracking: false } }, series: [{ name: 'Температура', data: [
Warning: Invalid argument supplied for foreach() in A:\home\kontroller.ua\www\index.php on line 307
] }, { name: 'Влажность', data: [
Warning: Invalid argument supplied for foreach() in A:\home\kontroller.ua\www\index.php on line 314
] }] }); });
[Автор]
[Автор]
Вот скорее всего висяк:
unsigned long int sendSens = millis();
unsigned int: 0...65535 (= 216−1), что ближе похоже к правде
[Автор]
unsigned long
unsigned long int
У меня не только отправка данных, но и прием с сервера команд каждую секунду. Прием данных с другого контроллера по радио и т.п. Работать перестает все.
Вчера заменил на уно, не изменяя код... посмотрим, ну и для сравнения стабильности запрограммировал ESP-12e, на тот же сервер отсылает millis().
Кстати, если
client.print( "GET /add.php?");
client.print("k="); /
client.print("&");
client.print("t=");
.......
client.print(String("GET ") + "add.php?" + "&&K=" + "&&T=" + t +.......
" HTTP/1.1\r\n" +
"Host: " + server+ "\r\n" +
"Connection: close\r\n\r\n");
И не понял про uptime 99.99%, короче надо думать?????
[Автор]
[Автор]
php_flag display_errors on
DirectoryIndex index.php
Options -Indexes
AddDefaultCharset utf-8
После чего загрузите в папку, в которой данный скрипт. Перейдите в index.php, если есть ошибка, то она должна отобразиться.
Скорей всего пути неправильно прописаны
require_once 'system/core.php';
require_once 'system/functions.php';
Попробуйте абсолютные пути прописать.
[Автор]
Так как на PHP5.4 всё работает.
[Автор]
Только класс mysql больше неподдерживается в PHP7. Вместо неё теперь mysqli
к примеру:
в настройках (коре.пхп)
есть подключение к базе вида: mysql_connect
mysql_connect(DBHOST, DBUSER, DBPASS)
в пхп 5 оно работает, в 7 пхп уже нужно добавить "i" и постоянный коннект к базе "mysql_pconnect" не используется, надо в 7рке писать так: mysqli_connect('p:'. DBHOST, DBUSER, DBPASS, DBNAME)
- заметьте добавляется "DBNAME".
Я весь день менял менял все переписывал, вое как веб морда заработала, но гет запросы ардуина не может доставить до базы данных и записать в неё, причем ЛОГИ негде не увидишь... х..рен его знает почему.
Мне кажется автор конечно молодец, но синтаксис написания программы у него не универсальный, по этому очень трудно его адаптировать к универсальному синтаксису написания процедур для 7-рки php.
Весь день я убил, ничего не получилось, похоже для php7 надо с нуля писать и код для ардуино и сам скрипт на сайт.
есть строчка:
$total = mysql_result(mysql_query("SELECT count(*) FROM `stat`"),0);
Данное расширение (mysql_result) устарело, начиная с версии PHP 5.5.0, и удалено в PHP 7.0.0.
меняем на
$total = mysqli_data_seek(mysqli_query($connect,"SELECT count(*) FROM `stat`"),0);
График рисуется, данные в базу льются. но график заполнил ячейки и дальше не обновляется , то есть весит со старыми данными... Подскажите куда копать ? Когда удаляю данные из базы график с новым временем рисует график
второй вопрос
Notice: Undefined variable: t in /srv/www/arduino/htdocs/index.php on line 303
ссылаеться на эту строчку ... но я не нашел $t
foot($t);
[Автор]
По поводу переменной: удалите просто $t -> foot();
Просто много где скрипт использовался, поэтому некоторые вещи здесь не нужны. а вообще, если скрипт в паблике, то отключите все нотисы и воринги в php, это не безопасно.
У меня GET запрос не поступает к серверу, то есть данные не добавляются в базу. Если в браузере набрать:
http://localhost/add.php?t=25.10&h=53.30 - то данные добавляются в базу, то есть файл add.php работает как надо.
Сервер как вы поняли локальный. Может быть езернет шилд не умеет отправлять GET запросы локальному серверу, а работает только с сервером с которым можно соединится только через интернет? Если так, то можно ли как то обойти или может есть решение чтобы ардуино с езернет шилдом работал с локальным сервером?
[Автор]
У меня Debian 8.2. Создал новый хост, привязал IP 192.168.1.10 и сервером указал этот же адрес. Соответственно add.php перенес в новый хост и все заработало! Спасибо!
Пишите новые такие же интересные статьи!
Также как вариант еще можно жестко в файле hosts сопоставить имя и IP адрес
Да, в файле hosts привязал 192.168.1.10 к новому хосту типа mysite.ru. После этого заработал.
[Автор]
[Автор]
Поменяйте значение false на true и проверьте результат. а вообще, нужно в таких случаях выкладывать свой скетч для проверки
[Автор]
С ардуино никак не получается передать данные. В браузере все получается, через командную строку. ПОМОГИТЕ! В Чем причина? Файлы прилагаю.
[Автор]
[Автор]
Подскажите куда копать: данные на сервере пишутся с разницей в 3 часа. Я так понимаю, где-то нужно настроить часовой пояс, но я совсем не силен в вебе.
[Автор]
date_default_timezone_set('Europe/Moscow');
подскажите почему при гет запросе ответ возвращается в виде
nginx/1.10.1
busy s...
Выдаёт вот такую ошибку
первые 2 строки коментируем
//require_once '/system/functions.php'; // стартуем функции
@ini_set ( 'html_errors', false );
define ( 'ROOT_DIR', dirname ( __FILE__ ) );
require_once ROOT_DIR . '/system/core.php';// стартуем ядро двигателя
require_once ROOT_DIR . '/system/functions.php'; // стартуем функции
Запросы никак не отправляются на сервер. Обычным get запросом проверяю через браузер сервер ок, но Arduino никак не посылает запросы, как найти в чем проблема. Или каким образом можно debug делать?
Буду благодарен если кто нибудь поможет мне.