Опыт проектирования и разработки устройства под управлением 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 запрос может выглядеть, например, так:

1
2
3
4
5
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 - конец строки, который необходимо повторить два раза.

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

1
2
3
GET /add.php?k=asREb25C&t=24.00&h=35.00 HTTP/1.1
Host: site.ru
Connection: close

Скетч программы представлен ниже:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#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();
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
/*--------------------------------------------------------------
 Программа:    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 с субд.

Код серверной части прикреплен к данной статье. Для удобства он разбит на несколько файлов:

  1. /system/core.php – «ядро» нашего сервера, здесь прописываются данные для подключения к базе данных.
  2. /system/functions.php – здесь содержатся различные пользовательские функции, которые нам понадобятся при работе.
  3. /style/ - стили CSS.
  4. .htaccess – Содержит настройки для сервера. По умолчанию выставлен часовой пояс +3 (Москва).
  5. _sever.sql – дамп БД. Данный файл необходимо импортировать в базу данных.
  6. Index.php – наш главный файл (страница), который будет выводить информацию о состоянии датчиков.
  7. Add.php – данный файл будет принимать и обрабатывать данные от Arduino.

Для того, чтобы только наше устройство могло отправлять данные на сервер, мы в GET-запросе будем передавать специальный код (ключ), как бы подтверждая тот факт, что данные действительно передает Arduino.

Заключение

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

Всем, кто захочет собрать нечто подобное самостоятельно, я желаю удачи! Задавайте свои вопросы в комментариях, с радостью на них отвечу. 

P.S. Живой пример: http://home.telwik.ru/

Get YouTube Transcripts

Прикрепленные файлы: