Szerverszoba hőmérséklet projekt v1: NodeMCU WiFi + 1.8″ TFT + DTH11

Arduino_NodeMCU_hofokA napokban többen osztottak meg Facebookon olyan hirdetéseket, ahol horror árakon árulnak IP-s szerverszoba hőmérséklet mérőket. Úgy döntöttem, hogy segítek azoknak, akik nem karnak százezreket költeni arra, hogy a szerverszoba hőmérsékletét tudják naplózni. Természetesen hasonló megoldással lehet kapcsolgatni 230V-ot, nyomkodni reset gombot. Nem űrtechnika ez, amit csak a szuper okos fejlesztőmérnökök tudhatnak magukénak.
A magyar webshopok általában ugyanarra az alaplapra épülő megoldást árulják, csak más (szabvány) dobozba csomagolva, bruttó 30-60eFt közötti áron. Sok esetben a szenzorokat még külön kell megvenni, ami sorkapcson (alföldiesen: csoki),  RJ11 vagy RJ45 aljzaton keresztül kapcsolódnak.


A feladat a megvásárolható termékek alapján:

  • DHCP kliens, esetleg statikus IP (állítási lehetőség)
  • hőmérséklet és páratartalom mérés (informatív és nem OMH pontossággal)
  • SNMP és/vagy web szerver (Cacti, Munin, stb plugin gyártáshoz)
  • IP-s elérhetőség dróton vagy WiFin

A felhasznált kütyük

DTH11A DTH11 az egyik legolcsóbb kombinált hőmérséklet és páratartalom szenzor. A hőmérsékleti része a gyártó szerint 2°C pontosságú, a páratartalom része meg csak úgy van. Persze szerverszobában már ez is elég. Ha magas a páratartalom, akkor bekrepált/kikapcsolt a klíma – de ez majd a hőmérsékletből is látszani fog. Ami nekem van DTH11-es modul 5V-ról használva +6°C-ot tévedett – valószínűleg a felhúzóellenállást moddingolni kellene -, viszont lineárisan, szal’ szoftveresen korrigálható volt, 3.3V-on egész hihető. A páratartalom értéket nem szabad komolyan venni. Nyilván az épített eszközét nem használja senki élesben azonnal.

Első lépések

NodeMCU2A NodeMCU modul előnye, hogy az Arduino kompatibilis eszköz és az ESP8266 WiFi modul össze van integrálva. Változattól függően viszonylag sok program memóriával lehet gazdálkodni és kompakt méretű. Hátránya, hogy sokan panaszkodnak az instabilitásra és a bugokkal teli firmware-re, amiből én semmit sem tapasztaltam még. Vásárlás után igényel némi előkészítést, mert ősi firmware-el szokott érkezni, amihez ki kell nyomozni, hogy melyik típust is sikerült megboltolni. Ha inkább nem kísérleteznél ezzel, akkor a szabadidőmben fogok készíteni egy Arduino Uno + (drótos) ethernet változatot is.

TFT_J1A kijelző nem kötelező eleme a szettnek, de látványos és egyáltalán nem drága (Kínából). Azért esett a választásom a NodeMCU-ra először, mert a WiFi kényelmes és kis méretű az alaplap. Általános játszadozásra az Arduino Uno (klón) alaplap alkalmasabb lehet, a drótos projektnél valószínűleg azzal oldom meg (vagy Arduino Nano 5V klónnal).

Az ilyen TFT kijelzők szerencsére 3.3V és 5V-os működésre is képesek. De az ezek közötti váltás sokszor egy forrasztást igényel. Az én kijelzőm alapból 3.3V-os, ami a NodeMCU számára ideális, de az Uno tönkreteszi. A 3.3V-os üzemmódhoz nálam a J1 forrpontokat rövidre kell zárni. Olvassuk el az eladó leírását vásárlás előtt!

A szoftveres alapok

Az 1.8″-os TFT meghajtásához kell pár dolog: ST7735_ESP8266Adafruit_GFXAdafruit_ST7735 . Az első kivételével telepíthető az Arduino környezetből (Vázlat-> Könyvtár tartalmazása -> Könyvtárak kezelése). A NodeMCU-hoz az ESP8266, a DHT11-hez pedig Adafruit DHT. A kézi telepítéshez a Program Files(86)\Arduino\Library mappába kell másolni, valamint az Arduino programot újraindítani.

Arduino_Adafruit_GFX

Vezetékelés

1.8″ SPI 128×160 TFT NodeMCU DTH11
D0
RESET D1
D2 S
D3
A0 D4
SCK D5
D6
SDA D7
CS D8
Vcc, LED 3.3V + (Vcc)
GND GND GND (-)

Munin plugin (PHP-vel)

nodemcu-day (1)

Nem valami elegáns plugin, de mutiba’ jó lesz.

#!/usr/bin/php
<?php

if ( count($argv) == 2 && $argv[1] == 'autoconf' ) {
  exit('yes');
}

if ( count($argv) ==2 && $argv[1] == 'config' ) {
  echo 'graph_title Temperature and humidity'.PHP_EOL;
  echo 'graph_vlabel Celsius and percent'.PHP_EOL;
  echo 'graph_category Sensors'.PHP_EOL;
  echo 'temperature.label Temperature'.PHP_EOL;
  echo 'temperature.type GAUGE'.PHP_EOL;
  echo 'temperature.colour COLOUR0'.PHP_EOL;
  echo 'humidity.label Humidity'.PHP_EOL;
  echo 'humidity.type GAUGE'.PHP_EOL;
  echo 'humidity.colour COLOUR1'.PHP_EOL;
  exit();
}

$json = file_get_contents('http://192.168.11.214');
$j = @json_decode( $json );

if ( isset( $j->temperature ) ) {
  echo 'temperature.value '.$j->temperature.PHP_EOL;
  echo 'humidity.value '.$j->humidity.PHP_EOL;
}
?>

Arduino forráskód (még alakítás alatt)

// License things

/***************************************************
This is a library for the Adafruit 1.8" SPI display.

This library works with the Adafruit 1.8" TFT Breakout w/SD card
----> http://www.adafruit.com/products/358
The 1.8" TFT shield
----> https://www.adafruit.com/product/802
The 1.44" TFT breakout
----> https://www.adafruit.com/product/2088
as well as Adafruit raw 1.8" TFT display
----> http://www.adafruit.com/products/618

Check out the links above for our tutorials and wiring diagrams
These displays use SPI to communicate, 4 or 5 pins are required to
interface (RST is optional)
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!

Written by Limor Fried/Ladyada for Adafruit Industries.
MIT license, all text above must be included in any redistribution
****************************************************/

/*
* ESP8266-12 HY-1.8 SPI
* GPIO5 D1 Pin 06 (RESET)
* GPIO2 D4 Pin 07 (A0)
* GPIO13 D7 (HSPID) Pin 08 (SDA)
* GPIO14 D5 (HSPICLK) Pin 09 (SCK)
* GPIO15 D8 (HSPICS) Pin 10 (CS)
*/
/*
#define TFT_PIN_CS 15
#define TFT_PIN_DC 2
#define TFT_PIN_RST 5
- See more at: 
http://www.esp8266.com/viewtopic.php?f=32&t=3668#sthash.ueG2ptmV.dpuf
src: http://www.esp8266.com/viewtopic.php?f=32&t=3668
*/

// Libraries
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>
#include <ESP8266WiFi.h>
//#include <ESP8266WiFiMulti.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include "DHT.h"

// DTH11
#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
int readyDTH = 0;
float lastTemperature = 0;
char lastTemperatureStr[4] = "---";
char lastHumidityStr[4] = "---";
float lastHumidity = 0;
int delaySensorRefresh = 0;
// WiFi
const char* ssid = "LegyesHome";
const char* password = "12345678";

ESP8266WebServer server(80);

#define TFT_CS 15
#define TFT_RST 5
#define TFT_DC 2
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

#define TFT_SCLK 13 // set these to be whatever pins you like!
#define TFT_MOSI 11 // set these to be whatever pins you like!
//Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);

void setup(void) {
  Serial.begin(9600);
// DTH
  dht.begin();

// LCD
  tft.initR(INITR_BLACKTAB); 
  // initialize a ST7735S chip, black tab, 
  // Use this initializer if you're using a 1.8" TFT
  uint16_t time = millis();
  tft.fillScreen(ST7735_BLACK);
  time = millis() - time;
  Serial.println("Initialized");
  Serial.println(time, DEC);
// WiFi
  printStatus("WiFi connecting...");
  WiFi.begin(ssid, password);

  Serial.println("");
  while (WiFi.status() != 6) {
    delay(500);
    Serial.print(".");
    printStatus("WiFi retry...");
  }
  printStatus("WiFi connected");
  printIP( WiFi.localIP() );

  if (MDNS.begin("esp8266")) {
    Serial.println("MDNS responder started");
  }

  server.on("/", handleRoot);
  server.onNotFound(handleNotFound);
  server.begin();

  readyDTH = dht.read(DHTPIN);
}

void loop() {

if ( delaySensorRefresh < 1 ) {

  delaySensorRefresh = 60;

  printStatus("Updating sensors...");

  readyDTH = dht.read(DHTPIN);

  Serial.println( readyDTH );

  if ( readyDTH == 1 ) {

    printTemperature();
    printHumidity();

    printStatus("Sensors updated");
  }
  else {
    printStatus("DHT not ready, retry");
  }

  printRSSI( WiFi.RSSI() );
  printSSID( WiFi.SSID() );

}

  printSensorDelay();
  delaySensorRefresh--;

  delay(500);

  server.handleClient();
}

// LCD based functions

void printSensorDelay() {
  char stringSensorDelayStr[3];

  tft.fillRect(0, 10, 50, 10, ST7735_BLACK);
  itoa(delaySensorRefresh, stringSensorDelayStr, 10);
  tft.setCursor(0, 10);
  tft.setTextColor(ST7735_YELLOW);
  tft.setTextWrap(false);
  tft.setTextSize(1);
  tft.print(stringSensorDelayStr);
}

void printTemperature() {

  float ft = dht.readTemperature();

  if( lastTemperature != ft ) {

    tft.fillRect(0, 20, 127, 42, ST7735_BLACK);

    char temperatureStr[15];
    dtostrf(ft,2, 0, temperatureStr);

    tft.setCursor(0, 20);
    tft.setTextSize(1);
    tft.setTextColor(ST7735_GREEN);
    tft.setTextWrap(false);
    tft.print("Temperature (Celsius):");

    tft.setCursor(50, 40);
    tft.setTextSize(3);

    char temperatureStrConcat[4];
    strcpy( temperatureStrConcat, temperatureStr );
    strcat( temperatureStrConcat, "C" );

    tft.print(temperatureStrConcat);
    dtostrf(ft, 0, 0, lastTemperatureStr);
  }
  lastTemperature = ft;
}

void printHumidity() {

  float fh = dht.readHumidity();

  if ( lastHumidity != fh) {

    tft.fillRect(0, 70, 127, 42, ST7735_BLACK);

    char humidityStr[15];
    dtostrf(fh,2, 0, humidityStr);

    tft.setCursor(0, 70);
    tft.setTextSize(1);
    tft.setTextColor(ST7735_CYAN);
    tft.setTextWrap(false);
    tft.print("Humidity (percent):");

    tft.setCursor(50, 90);
    tft.setTextSize(3);

    char humidityStrConcat[4];
    strcpy( humidityStrConcat, humidityStr );
    strcat( humidityStrConcat, "%" );

    tft.print(humidityStrConcat);
    dtostrf(fh, 0, 0, lastHumidityStr);

  }
  lastHumidity = fh;
}

void printIP(IPAddress ip) {

  static char stringIP[20];
  String labelIP = "IP:";
  String IP = ip.toString();
  labelIP.concat(IP);
  labelIP.toCharArray(stringIP, 20);

  tft.setCursor(0, 140);
  tft.setTextColor(ST7735_BLUE);
  tft.setTextWrap(false);
  tft.setTextSize(1);
  tft.print(stringIP);
}

void printSSID(String ssid) {

  tft.fillRect(0, 150, 80, 10, ST7735_BLACK);

  tft.setCursor(0, 150);
  tft.setTextColor(ST7735_MAGENTA);
  tft.setTextWrap(false);

  tft.setTextSize(1);
  tft.print(ssid);
}

void printRSSI(int rssi) {

  tft.fillRect(90, 150, 28, 10, ST7735_BLACK);
  char stringRSSI[3];
  dtostrf(rssi,3, 0, stringRSSI);
  //itoa(int, str, int length);
  tft.setCursor(90, 150);
  tft.setTextColor(ST7735_MAGENTA);
  tft.setTextWrap(false);

  char RSSIStrConcat[8];
  strcpy( RSSIStrConcat, stringRSSI );
  strcat( RSSIStrConcat, "dBm" );

  tft.setTextSize(1);
  tft.print(RSSIStrConcat);
}

void printStatus(char *text) {
  tft.fillRect(0, 0, 127, 19, ST7735_BLACK);
  tft.setCursor(0, 0);
  tft.setTextColor(ST7735_WHITE);
  tft.setTextWrap(true);
  tft.setTextSize(1);
  tft.print(text);
}

// WiFI server based functions
void handleRoot() {
  String s = "{\"temperature\":";
  s += lastTemperatureStr;
  s += ",\"humidity\":";
  s += lastHumidityStr;
  s +="}";

  server.send(200, "application/json", s);
}

void handleNotFound(){
  String s = "{\"temperature\":";
  s += lastTemperatureStr;
  s += ",\"humidity\":";
  s += lastHumidityStr;
  s +="}";

  server.send(200, "application/json", s);
}

#Update1:

A DTH11 meglehetősen érdekes viselkedése miatt később módosítanom kell a kódot, mert tüskéket csinál a grafikonon. Valószínűleg az lesz a megoldás, hogy 5 sikeres mérést fogok átlagolni. A páratartalomnál ez még szembetűnőbb. A DS18B20-szal ilyen gond nincs, szépen karcol és 1°C-ot ugrik. A DTH11 hülyeségeire már korábban is felfigyeltem. Nagyon sok fórumban írtak hasonlókat. Az is közrejátszhat, hogy csak a kínaiak tudják mi is van valójában a kis, kék dobozkában – lehet csak valami gagyi klónt fogtam ki. Az átlagolással minden bizonnyal jó lesz mindkét mért érték. Folytatás hétvégén…

nodemcu-day (2)

Update #2

A DHT szenzor hülyéskedései miatt kicsit módosítottam a kódon. Tudom, hogy nem szép, de az egész úgyis csak az amatőrködésről szól 😛

Letölthető a módosított (és csúnyább) kód innen: NodeMCU_18_TFT_DHT11.ino

Működés közben