Умная теплица с автоматическим климат-контролем на Arduino
Компоненты и материалы
| Arduino MKR1000 | ×1 |
| Arduino UNO | ×1 |
| Датчик температуры и влажности DHT22 | ×1 |
| Arduino WiFi Shield 101 | ×1 |
| Android-устройство | ×1 |
| Шилд управления двигателем DC Motor Control Shield с BTN8982 (Infineon) | ×1 |
Программное обеспечение и онлайн-сервисы
- Arduino IDE
- Eclipse
О проекте
Обзор
Условия окружающей среды в теплице постоянно меняются, поэтому необходим эффективный мониторинг. Автоматизированная система приносит множество преимуществ: сокращение ручного труда, возможность удалённого контроля инвестиций со смартфона или ПК, хранение данных в базе для аналитики и принятия решений, а также мгновенное реагирование на проблемы в реальном времени.
Система состоит из датчиков (считывающих параметры среды) и исполнительных механизмов — «Slave»-устройств. Они обмениваются данными по беспроводной сети с центральным устройством — «Master». Тот, в свою очередь, передаёт настройки порогов на Slave и отправляет данные на веб-сервер через WiFi.
1. Принцип работы
Проект разделён на три части:
- Master — Arduino MKR1000
- Slave — Arduino UNO
- Веб-сервер
Master общается с веб-сервером по WiFi (встроенный модуль WINC1500). Slave считывает температуру и влажность через DHT22 и передаёт данные Master по WiFi (WiFi Shield WINC1500).
2. Master
Master работает в режиме точки доступа (Access Point) и ожидает подключения Slave для приёма данных. Получив пакет, он вычисляет CRC-8 (по алгоритму Dallas/Maxim) для валидации настроек порогов, которые затем отправляются Slave. После выхода из режима AP Master подключается к WiFi-сети и передаёт данные на веб-сервер для построения графиков.
3. Slave
Slave каждые 5 минут считывает показания DHT22, формирует UDP-пакет и отправляет его Master. Затем ожидает возможные новые пороговые значения. Получив их, Slave пересчитывает CRC и сравнивает с полученным. При совпадении новые настройки сохраняются в EEPROM.
4. Веб-сервер
Сервер сохраняет все поступающие данные для истории. Используются PHP-скрипты: один для записи в БД, другой — для отображения графиков (библиотека JpGraph), третий — для выдачи JSON-данных Android-приложению.
5. Мобильное приложение
Приложение позволяет выбирать временной диапазон и запрашивать график. Оно обращается к PHP-скрипту getdati, получает JSON и строит график с помощью библиотеки GraphView.
Код
Генерация графика (PHP + JpGraph)
<?php
define('__ROOT__', dirname(dirname(__FILE__)));
require_once ('../jpgraph.php');
require_once ('../jpgraph_line.php');
require_once ('../jpgraph_error.php');
$parameter1 = $parameter2 = $time_axis = array();
$i = 0;
$con = mysqli_connect("localhost","user","pass","my_db");
if (mysqli_connect_errno()) die("Failed to connect: " . mysqli_connect_error());
$result = mysqli_query($con, "SELECT parameter1, parameter2, date FROM your_raw");
while($row = mysqli_fetch_array($result)) {
$parameter1[$i] = $row["parameter1"];
$parameter2[$i] = $row["parameter2"];
$time_axis[$i] = $row["date"];
$i++;
}
mysqli_close($con);
$graph = new Graph(300,250);
$graph->SetScale("textlin");
$graph->SetTheme(new UniversalTheme());
$graph->img->SetAntiAliasing(true);
$graph->title->Set('Greenhouse Climate');
$graph->SetBox(false);
$graph->yaxis->HideZeroLabel();
$graph->xgrid->Show();
$graph->xgrid->SetLineStyle("solid");
$graph->xaxis->SetTickLabels($time_axis);
$graph->xgrid->SetColor('#E3E3E3');
$graph->xaxis->SetLabelAngle(90);
$graph->legend->SetPos(0.5,0.08,'center','top');
$p1 = new LinePlot($parameter1);
$p1->SetColor("#6495ED");
$p1->SetLegend('Temperature');
$graph->Add($p1);
$p2 = new LinePlot($parameter2);
$p2->SetColor("#B22222");
$p2->SetLegend('Humidity');
$graph->Add($p2);
$graph->Stroke();
?>
Скрипт приёма данных (PHP)
<?php
$servername = "localhost"; $username = "user"; $password = "pass"; $dbname = "my_db";
$id = $_GET["name"]; $temp = $_GET["parameter1"]; $umid = $_GET["parameter2"];
$conn = mysqli_connect($servername, $username, $password, $dbname);
if (!$conn) die("Connection failed: " . mysqli_connect_error());
$sql = "INSERT INTO greenhouse (name, parameter1, parameter2) VALUES ('$id', '$temp', '$umid')";
if (mysqli_query($conn, $sql)) echo "New record created";
else echo "Error: " . $sql . "
" . mysqli_error($conn);
mysqli_close($conn);
?>
Создание таблицы (SQL)
CREATE TABLE IF NOT EXISTS `greenhouse` (
`id` int(11) NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`temp` float NOT NULL,
`umid` float NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Код Master (C/C++ для Arduino MKR1000)
/* Automation greenhouse project */
/* Authors: Antonio La Mura, Umberto Festa */
/* Date: 03/03/2016 */
#include
#include
#include
#include
char ssid[] = "SSID"; char pass[] = "pass"; int keyIndex = 0;
char ssid_ap[] = "Arduino"; char pass_ap[] = "";
int status = WL_IDLE_STATUS;
unsigned int localPort = 2390;
char server[] = "www.example.com";
WiFiServer server_ap(80); WiFiClient client_ap; WiFiUDP Udp; RTCZero rtc; WiFiClient client;
char packetBuffer[255]; char ReplyBuffer[255];
float humax=0, humin=0, tumax=0, tumin=0;
boolean flag = false; boolean threshold_available = false;
void setup() { Serial.begin(9600); while(!Serial); }
void loop() {
int packetSize; double temp, hum; int id; byte crc; String strURL;
if (WiFi.status() == WL_NO_SHIELD) { while(true); }
while (status != WL_CONNECTED) {
status = WiFi.beginAP(ssid_ap); delay(10000); server_ap.begin();
}
Udp.begin(localPort);
client_ap = server_ap.available();
if (client_ap) {
packetSize = Udp.parsePacket();
if (packetSize) {
int len = Udp.read(packetBuffer, 255);
if (len > 0) packetBuffer[len] = 0;
char* cmd = strtok((char*)packetBuffer, ";");
int count = 0;
while (cmd != 0) {
switch(count) {
case 0: id = atoi(cmd); break;
case 1: temp = atof(cmd)/10; break;
case 2: hum = atof(cmd)/10; break;
}
cmd = strtok(0, ";"); count++;
}
// CRC calculation for thresholds
byte bhmax=(byte)humax, bhmin=(byte)humin, btmax=(byte)tumax, btmin=(byte)tumin;
byte crc_arr[4] = {bhmax, bhmin, btmax, btmin};
crc = CRC8(crc_arr);
if (threshold_available) {
snprintf(ReplyBuffer, sizeof(ReplyBuffer), "%d;%d;%d;%d;%d;%d", id, (int)humax, (int)humin, (int)tumax, (int)tumin, (int)crc);
Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
Udp.write(ReplyBuffer); Udp.endPacket();
}
}
client_ap.stop(); flag = true;
}
if (flag) {
while (status != WL_CONNECTED) { status = WiFi.begin(ssid, pass); delay(10000); }
strURL = "GET /YourAddress.php?id=" + String(id) + "¶meter1=" + String(temp) + "¶meter2=" + String(hum) + " HTTP/1.1";
if (client.connect(server, 80)) {
client.println(strURL);
client.println("Host: www.example.com");
client.println("Connection: close"); client.println();
client.stop();
}
flag = false;
}
}
byte CRC8(const byte *data) {
byte crc = 0x00;
while (*data) {
byte extract = *data++;
for (byte i=8; i; i--) {
byte sum = (crc ^ extract) & 0x01;
crc >>= 1; if (sum) crc ^= 0x8C;
extract >>= 1;
}
} return crc;
}
Код Slave (C/C++ для Arduino UNO)
/* Slave code */
#include #include #include #include #include
#define HUMIDIFIER A4 #define HEATER A5 #define DHT22_PIN 4
#define IN_1 3 #define IN_2 11 #define INH_1 12 #define INH_2 13
#define TCONST 100
extern unsigned long timer0_millis;
int status = WL_IDLE_STATUS;
char ssid[] = "Arduino"; char pass[] = "";
unsigned int localPort = 2390; char packetBuffer[255];
WiFiUDP Udp; DHT22 myDHT22(DHT22_PIN);
float hmin=0, hmax=0, tmin=0, tmax=0; int duty_motor=0;
float humidity, temperature; unsigned long interval = 600000;
void setup() {
Serial.begin(9600); while(!Serial);
pinMode(HEATER, OUTPUT); digitalWrite(HEATER, LOW);
pinMode(HUMIDIFIER, OUTPUT); digitalWrite(HUMIDIFIER, LOW);
pinMode(IN_1, OUTPUT); pinMode(IN_2, OUTPUT);
pinMode(INH_1, OUTPUT); pinMode(INH_2, OUTPUT);
reset_ports(); digitalWrite(INH_1, 1); digitalWrite(INH_2, 1);
delay(2000);
}
void loop() {
int packetSize = Udp.parsePacket();
humidity = myDHT22.getHumidity()*10; temperature = myDHT22.getTemperatureC()*10;
char toSend[32]; DHT22_ERROR_t errorCode = myDHT22.readData();
byte crc; int crc_ric;
if (millis() > interval) {
// Connect to Master AP
while (status != WL_CONNECTED) { status = WiFi.begin(ssid); delay(10000); }
Udp.begin(localPort);
snprintf(toSend, sizeof(toSend), "clie1;%d;%d", (int)humidity, (int)temperature);
Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); Udp.write(toSend); Udp.endPacket();
resetMillis();
while (millis() < 10000) {
packetSize = Udp.parsePacket();
if (packetSize) {
int len = Udp.read(packetBuffer, 255); if (len>0) packetBuffer[len]=0;
char* cmd = strtok((char*)packetBuffer, ";"); int count=0;
while (cmd != 0) {
switch(count) {
case 1: hmax = atof(cmd)/10; break;
case 2: hmin = atof(cmd)/10; break;
case 3: tmax = atof(cmd)/10; break;
case 4: tmin = atof(cmd)/10; break;
case 5: crc_ric = atoi(cmd); break;
} cmd = strtok(0, ";"); count++;
}
// Verify CRC
byte bhmax=(byte)hmax, bhmin=(byte)hmin, btmax=(byte)tmax, btmin=(byte)tmin;
byte crc_arr[4] = {bhmax, bhmin, btmax, btmin};
crc = CRC8(crc_arr);
if (crc_ric == (int)crc) {
EEPROM_writeDouble(0, tmax); EEPROM_writeDouble(4, tmin);
EEPROM_writeInt(8, hmax); EEPROM_writeInt(10, hmin);
}
}
}
WiFi.disconnect();
}
// Control actuators
if (myDHT22.getTemperatureC() >= tmax) digitalWrite(HEATER, HIGH);
if (myDHT22.getTemperatureC() <= tmin+1) digitalWrite(HEATER, LOW);
if ((int)myDHT22.getHumidity() >= hmax) digitalWrite(HUMIDIFIER, HIGH);
if ((int)myDHT22.getHumidity() <= hmin+1) digitalWrite(HUMIDIFIER, LOW);
// Fan control logic...
delay(1000);
}
// EEPROM helpers, CRC8, resetMillis() — аналогичны Master
Получение данных для приложения (PHP)
<?php
$username="root"; $password=""; $hostname="localhost";
$dbhandle = mysql_connect($hostname, $username, $password) or die("Unable to connect");
mysql_select_db("serra",$dbhandle) or die("Could not select DB");
$datada = $_GET["datada"]; $dataa = $_GET["dataa"];
$result = mysql_query("SELECT hum,temp,date FROM record WHERE date >= '$datada' and date <= '$dataa'");
$records=array();
while ($row = mysql_fetch_array($result)) {
$tmp = array("hum"=>$row{'hum'}, "temp"=>$row{'temp'}, "date"=>$row{'date'});
array_push($records,$tmp);
}
echo json_encode($records); mysql_close($dbhandle);
?>
Производственный процесс
- Профессиональное руководство по анимации битмапов на TFT-экране ILI9341 2.4" с Visuino
- Веб-управляемая DMX-система освещения: Arduino Mega как мастер-контроллер
- Сборка Bluetooth-робота-шпиона на Arduino с управлением с телефона
- FlickMote: жестовое IR-пульт для умного дома на Arduino 101 и ультразвуковом датчике
- DIY Arduino TV-B-Gone: универсальный пульт для выключения любого телевизора
- Создание пользовательских LED-часов с будильником — видно с 12 метров
- HSV Clock: Яркие, настраиваемые часы для энтузиастов Arduino
- Android-управляемая робот-платформа на Raspberry Pi и Motor Shield
- Find Me: Умный поисковик вещей на Arduino и Bluetooth
- Оптимизированные решения питания для проектов на Arduino