Wstęp
Do napisania tego projektu zachęcił mnie podobny projekt kolegi, który był jednak napisany na Raspberry Pi.
Po co strzelać z armaty do muchy – pomyślałem.
Jako, że byłby to także fajny gadżet do wykorzystania na stronie internetowej, postanowiłem zamówić niezbędne podzespoły i w dwa dni powstał bazowy projekt, który pozwalał już komunikować się zdalnie z serwerem www.
Założenia
- Pomiar temperatury i wilgotności na zewnątrz pomieszczenia.
- Wysyłanie danych na serwer WWW – na początku była to komunikacja za pomocą protokołu UDP, ale żeby nie komplikować sprawy po stronie serwera, postanowiłem wykorzystać protokoł TCP.
- Odczyt danych i statusu pracy urządzenia możliwy także w aplikacji Bluetooth Terminal na Androida.
Podzespoły
Na początek należy zaopatrzyć się w podzespoły:
Arduino Nano 3.0 ATmega328P z CH340G
Moduł LAN ENC28J60
Czujnik temperatury i wilgotności
Osobiście zakupiłem dwa różne sensory, jeden do pomiarów wewnątrz pomieszczenia i drugi do pomiarów na zewnątrz.
Na potrzeby tego artykułu wystarczy zakupić jeden z poniższych czujników.
DHT11
Ze względu na swoje parametry, do zastosowania wyłącznie wewnątrz pomieszczeń.
| Parametr | Wartość |
|---|---|
| Zasilanie | 3-5V |
| Temperatura | 0-50° o dokładności ±2°C |
| Wilgotność | 20-90%RH o dokładności 5% |
DHT22
Do pomiarów na zewnątrz pomieszczeń.

| Parametr | Wartość |
|---|---|
| Zasilanie | 3-5V |
| Temperatura | -40-80° o dokładności ±0.5°C |
| Wilgotność | 0-100%RH o dokładności 2-5% |
Moduły, które zostały zakupione na Aliexpress:
- Nano 3.0 controller compatible for arduino nano CH340 USB driver NO CABLE
- ENC28J60 LAN Ethernet Network Board Module for arduino 25MHZ Crystal AVR 51 LPC STM32 3.3V
- Orignial-sensor Temperature and humidity sensor module Fapplication DHT-11 DHT11+PCB
- DHT22 Digital Temperature and Humidity Sensor AM2302 Module+PCB with Cable for Arduino
– został użyty w projekcie - HC-06 Bluetooth serial pass-through module wireless serial communication from machine Wireless HC06 for arduino
– opcjonalnie, jeśli zależy Ci na odczytywaniu statusu urządzenia na telefonie przez BT.
Schemat i podłączenie
Pierwszy raz mam styczność z programem Fritzing, więc schemat może nie jest za czytelny, ale wbrew pozorom, podłączenie jest bardzo proste.

Podłączenie
Arduino Nano przychodzi praktycznie złożone. Jedyne co musimy zrobić, to dolutować listwę z pinami.
Rezystor 4k7 widoczny na schemacie nie jest konieczny, gdyż wskazane przeze mnie sensory posiadają go wbudowanego.
Wszystkie moduły posiadają możliwość zasilania przez 3V3 i 5V, więc od Ciebie zależy na jakie zasilanie się zdecydujesz.
Moduł LAN podłączamy wg poniższej tabelki (lub schematu). Oznaczenia mogą się nieznacznie różnić w zależności od producenta modułu LAN.
| ENC28J60 | Nano |
|---|---|
| 5V | 5V |
| SO (MISO) | D12 |
| SCK | D13 |
| ST (MOSI) | D11 |
| RST | RST |
| CS | D10 |
Biblioteki
Z modułem ENC28J60 przetestowałem 3 biblioteki.
- Standardową bibl. Ethernet z Arduono – nie pobierała IP z DHCP ani statycznie.
- EtherCard – nie działało UDP.
- UIPEthernet – wszystko działało (przynajmniej to co mnie interesowało).
Z wiadomych względów, projekt został oparty o trzecią bibliotekę, chociaż podobno najbardziej zasobożerną.
Przydadzą się jeszcze oczywiście sterowniki do konwertera USB To RS232:
CH341SER.ZIP – uruchamiamy program SETUP.EXE z folderu CH341SER i postępujemy zgodnie z instrukcją.
Następną biblioteką jaką będziemy potrzebowali jest arduino-DHT.
Bardzo prosta, ładnie napisana biblioteka.
Serwer
Dane będą po prostu zapisywane (dodawane) do pliku w formacie CSV.
Nic nie stoi na przeszkodzie, abyś przerobił skrypt wg. własnego uznania tak, aby zapisywał dane w wybranym formacie lub w bazie danych.
Musisz oczywiście posiadać serwer WWW z zainstalowanym interpreterem PHP, ale to chyba oczywiste.
<?php
define('DEBUG', false);
// Lokalna strefa czasowa
date_default_timezone_set('Europe/Warsaw');
// Wybór źródła danych
if (constant('DEBUG')) {
$data = [
'Humidity' => 45.5,
'Temperature' => 23.1
];
file_put_contents('headers.txt', var_export($_SERVER, true), FILE_APPEND);
} else {
$data = &$_POST;
}
// Uwierzytelnienie
if (constant('DEBUG') || (!empty($_SERVER['HTTP_X_APIKEY']) && $_SERVER['HTTP_X_APIKEY'] == '4Jrt39dj')) {
// Dozwolona lista przesyłanych pól
$whiteList = [
'Temperature',
'Humidity'
];
// Ustaw wartości domyślne
$whiteList = array_fill_keys($whiteList, NULL);
// Dodaj lokalny znacznik czasu
$storeData['DateTime'] = date('Y-m-d H:i:s');
// Przygotuj tablicę do zapisu
$storeData = array_merge($storeData, $whiteList, array_intersect_key($data, $whiteList));
// Zapisz jako CSV
$fp = fopen('weather.txt', 'a');
fputcsv($fp, $storeData);
fclose($fp);
if (constant('DEBUG')) {
print_r($storeData);
}
}
?>
Wartym uwagi jest wspomnienie o warunku sprawdzającym istnienie poprawnego nagłówka X-ApiKey, który przechowuje klucz API, nasz sekretny kod/hasło, który pozwala nam sprawdzić, czy odebrane dane pochodzą z zaufanego źródła (naszego programu).
Dodatkowo, jeśli posiadasz stałe publiczne IP, możesz dodać dodatkowy warunek sprawdzający tenże adres.
Klient
Czas na główny program, który będzie przekazywał dane z sensorów na serwer metodą POST.
#include <DHT.h>
#include <UIPEthernet.h>
#define WS_HOST_NAME "ws.example.com"
#define WS_HOST_PORT 80
const static uint8_t REQUEST_BUFFER_SIZE = 250;
const static uint8_t mac[] = { 0x1A, 0x2B, 0x3C, 0x4D, 0x5E, 0x6F };
const static uint16_t timerInterval = 1000 * 60; // max. 65,535;
const static char wsHost[] PROGMEM = WS_HOST_NAME;
const static char wsUri[] PROGMEM = "/ws.php";
const static char wsAgent[] PROGMEM = "WeatherStation/1.0";
const static char wsApiKey[] PROGMEM = "4Jrt39dj";
const static char wsRequestHeaders[] PROGMEM = "POST %S HTTP/1.1\r\nUser-Agent: %S\r\nHost: %S\r\nAccept: */*\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: %d\r\nConnection: keep-alive\r\nX-ApiKey: %S\r\n\r\n";
static char requestBuffer[REQUEST_BUFFER_SIZE] = "";
static uint16_t connectRetryCount = 0;
static uint16_t timerLastMeasurement = 0;
EthernetClient client;
DHT outSensor;
void setup() {
Serial.begin(9600);
outSensor.setup(A0);
do
{
if (connectRetryCount++ > 0) Serial.println("FAIL");
Serial.print(F("Collect DHCP information..."));
} while (Ethernet.begin(mac) == 0);
Serial.println("DONE");
Serial.print(F("IP Address: "));
Serial.println(Ethernet.localIP());
Serial.print(F("Subnet Mask: "));
Serial.println(Ethernet.subnetMask());
Serial.print(F("Default Gateway: "));
Serial.println(Ethernet.gatewayIP());
Serial.print(F("DNS Server: "));
Serial.println(Ethernet.dnsServerIP());
}
void loop() {
uint16_t timerNow = millis();
if (timerNow - timerLastMeasurement >= timerInterval) {
timerLastMeasurement = timerNow;
timerCallback();
}
}
void timerCallback() {
//delay(outSensor.getMinimumSamplingPeriod());
Serial.print(F("Reading data from DHT..."));
float humidity = outSensor.getHumidity();
float temperature = outSensor.getTemperature();
Serial.println(outSensor.getStatusString());
if (outSensor.getStatus() != DHT::ERROR_NONE) return;
Serial.print("DHT22");
Serial.print("\t");
Serial.print(humidity, 1);
Serial.print("%\t");
Serial.print(temperature, 1);
Serial.write(176);
Serial.println("C");
//Serial.print("\t");
//Serial.println(outSensor.toFahrenheit(temperature), 1);
Serial.print(F("Connecting to server..."));
if (Ethernet.maintain() % 2 == 0 && client.connect(WS_HOST_NAME, WS_HOST_PORT) && client.connected()) {
Serial.println("DONE");
String data;
data.reserve(40);
data.concat("Temperature=");
data.concat(temperature);
data.concat("&Humidity=");
data.concat(humidity);
uint8_t len = data.length();
bool written;
sprintf_P(requestBuffer, wsRequestHeaders, wsUri, wsAgent, wsHost, len, wsApiKey);
Serial.print(F("Sending data..."));
written = client.write((const uint8_t *)requestBuffer, strlen(requestBuffer)) && client.write((const uint8_t *)data.c_str(), len);
Serial.println(written ? "DONE" : "FAIL");
client.stop();
} else {
Serial.println("FAIL(" + String(connectRetryCount++) + ")");
}
}
Gotowe urządzenie
Logi z Monitora szeregowego
Plik wynikowy
„2016-02-04 21:39:52”,11.00,40.60
„2016-02-04 21:40:52”,7.90,52.00
„2016-02-04 21:41:52”,5.20,61.80
„2016-02-04 21:42:52”,3.80,68.20
„2016-02-04 21:43:52”,2.80,74.70
„2016-02-04 21:44:52”,2.30,78.80
„2016-02-04 21:45:52”,1.90,81.90
„2016-02-04 21:46:52”,1.70,84.70
„2016-02-04 21:47:52”,1.50,86.30
„2016-02-04 21:48:52”,1.50,87.40
„2016-02-04 21:49:52”,1.40,88.50
Wizualizacja danych
Przykładowy wykres, sporządzony po wystawieniu czujnika za okno.
Widać spadek temperatury w czasie. Po ok. 10 minutach następuje ustabilizowanie się wyników.
Temperatura okazała się identyczna ze wskazywaną przez Google, po wpisaniu „Pogoda” w wyszukiwarkę.

Na zakończenie
Należy pamiętać o zmianie podstawowych stałych, jak nazwa hosta docelowego, ścieżki URI do skryptu, adresu MAC karty sieciowej!
Osobiście przepisałem po prostu MAC z mojego popsutego już laptopa (nie użyty w tym artykule).
Przy wszelkich zmianach, należy wziąć pod uwagę wielkość bufora ustalanego przez stałą REQUEST_BUFFER_SIZE.
O częstotliwości pomiarów decyduje stała timerInterval – maksymalna wartość to nieco ponad 1 minuta.
Kod źródłowy i niezbędne biblioteki
- https://github.com/fu-hsi/arduino-weather-station
- https://github.com/ntruchsess/arduino_uip
- https://github.com/markruys/arduino-DHT





Bardzo fajny i dobrze wytłumaczony kod i sposób działania.
Spoko opisane wszystko !!! Dobre fotki…
Bardzo ciekawy, solidnie napisany artykuł, zawierający dużo cennych informacji, uwag oraz wskazówek, które z pewnością się przydadzą w tym temacie 🙂