Мониторинг воздуха в Zabbix с помощью NodeMCU, MQ-135 и DHT-11/22 без агента

Один из факторов влияющих на эффктивность работы является концентрация CO2 в воздухе. Для оценки качества воздуха в помещениях есть готовые решения, но нам было интересно разработать свое решение и интегрировать его в используемую систему мониторинга Zabbix.

Графики показаний датчиков MQ135 и DHT-11/22, полученных от NodeMCU

За основу была взята плата NodeMCU на базе микроконтроллера ESP8266. Данное решение "из коробки" позволяет подключитсья к сети Wi-Fi и организовать прием/передачу данных.

Для определения CO2 используется недорогое [и не точное] решение - датчик MQ-135. Данный датчик чуствителен к ряду газов в т.ч. и к CO2, библиотека для Arduino IDE содержит в себе функции для пересчета показаний датчика в ppm. Изыскания показали, что вычисляемые значения ppm с реальной концентрацией ничего общего не имеет, соответсвенно для оценки качества воздуха целесрообразно использовать значения на аналоговом выходе модуля MQ-135, которые растут по мере повышения концентрации газов в воздухе. Показания этого датчика чувствительны к питанию, датчик необходимо продержать включенным не менее суток для прокаливания и есть основания предполагать, что выдаваемые значения будут различными для разных экземпляров датчика. Так же показания датчика зависят от температуры и влажности окружающей среды.

Датчика качества воздуха MQ-135 Датчик температуры и влажности DHT-11 NodeMCU ESP8266

Для передачи данных в Zabbix без использования агента используется функция мониторинга веб-страниц, которая позволяет обратиться по заданному URL, получить код ответа и проверить наличие на странице определенного текста. При этом производится замер времени передачи данных и скорость. Единственный простой способ передачи данных от NodeMCU без использования агента на отдельном ПК, это передача значений в коде ответа веб-страницы:

  1. http://ip/ - URL возвращает HTML-страницу с текущими значениями параметров, страница автоматически обновляется с заданным интервалом;
  2. http://ip/a - URL возращает значение с датчика MQ135;
  3. http://ip/t - URL возращает значение с датчика DHT11/22;
  4. http://ip/h - URL возращает значение с датчика DHT11/22.
Код ответа "HTTP/1.1 [значение] OK"
HTTP/1.1 235 OK

Что позволило нам построить графики и поставить триггеры на выход параметров за пределы пороговых.

Zabbix NodeMCU Response Code

Подключение MQ135 и DHT-11 к NodeMCU

Изначально стоит определится с питанием. Исходя из информации в Сети и опыта работы MQ135 в силу необходимости нагрева чувствительного элемента потребляет ток до 800 мА, при этом его рабочее напряжение 5 В. NodeMCU работает с напряжением в 3.3 В, использует 3 В логику и вдает максимум 12 мА на пин. Текущая реализация показала, что используемые модули толерантны к логике на 3 В.

Распиновка NodeMCU

Приведенный ниже код основан на примере NodeMCU Server.

HTML-страница NodeMCU с текущими значениями датчиков MQ135 и DHT-11

Библиотека MQ135 содержит функцию расчета скорректированого значения показаний датчика с поправкой на влажность и температуру. При реалном использовании выяснилось что привключении увложнителя в помещении с увеличением влажности росли и показания датчика, что приводило к срабатыванию триггера в Zabbix. Расчет поправончого коэффициента прозводится по формуле:
k=CORA * t * t - CORB * t + CORC - (h-33.)*CORD, где CORA, CORB, CORC и CORD постоянные, заданные в начале программы.

Код:

#include 
#include 
#include "DHT.h"
#include "Wire.h"

#define CORA 0.00035
#define CORB 0.02718
#define CORC 1.39538
#define CORD 0.0018

#define DHTPIN 4
#define DHTTYPE DHT22
#define MQ135APIN A0
#define SOUNDPIN 5
#define LIMIT 360
DHT dht(DHTPIN, DHTTYPE);

const char* ssid = "SSID";
const char* password = "PASSWORD";

const boolean debug = 1;

float t = 0;
float h = 0;
float ppmRaw = 0;
int timeOut = 0;
int count = 0;

String header = "";
String footer = "";
String s = "";


WiFiServer server(80);

extern "C" {
#include "user_interface.h"
  bool wifi_set_sleep_type(sleep_type_t);
  sleep_type_t wifi_get_sleep_type(void);
}

void setup() {
  pinMode(SOUNDPIN, OUTPUT);
  if (debug==1) {
    Serial.begin(115200);
    delay(10);
  };
  Wire.begin(2, 0);
  delay(10);
  dht.begin();
  delay(10);
    
  if (debug==1) {
    Serial.println();
    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(ssid);
  };

  WiFi.mode(WIFI_STA);
  wifi_set_sleep_type(NONE_SLEEP_T);
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    if (debug==1) Serial.print(".");
  }

  server.begin();
  if (debug==1) {
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("Server started");
    Serial.println(WiFi.localIP());
  };
  header = "HTTP/1.1 200 OK\r\n";
  header = header + "Content-Type: text/html\r\n\r\n";
  header = header + "\r\n";
  header = header + "\r\n";
  header = header + "\r\n";
  header = header + "";
  header = header + "NodeMCU\r\n";
  header = header + "\r\n";
  header = header + "\r\n";
  footer = "\r\n";
  footer = footer + "\r\n";
}

void loop() {

  h = dht.readHumidity();
  t = dht.readTemperature();
  if (h == 0.00 or isnan(h)) {
    h = dht.readHumidity();
  };
  if (t == 0.00 or isnan(t)) {
    t = dht.readTemperature();
  };
  ppmRaw = analogRead(MQ135APIN)*(CORA * t * t - CORB * t + CORC - (h-33.)*CORD);
  if (ppmRaw>LIMIT) {
    tone(SOUNDPIN, 100, 10);
  };
  if (debug==1) {
    Serial.print("H: ");
    Serial.println(h);
    Serial.print("t: ");
    Serial.println(t);
    Serial.print("Air: ");
    Serial.println(ppmRaw);
    Serial.println(WiFi.status());
  };

  WiFiClient client = server.available();
  if (!client) {
    delay(1000);
    return;
  };

  if ( debug == 1 ) Serial.println("new client");

  while(!client.available()){
    delay(1);
    timeOut = timeOut +1;
    if (timeOut>=15) { // 500
      client.stop();
      client.flush();
      timeOut = 0;
      return; // break
    };
  }

  String req = client.readStringUntil('\r');
  if (debug==1) {
    Serial.println(req);
  }
  client.flush();
  float heap = ESP.getFreeHeap();
  
  if (req.indexOf("/favicon.ico") != -1) {
    s = "HTTP/1.1 404 Not found\r\n";
    client.print(s);
  } else if (req.indexOf("/t") != -1) {
    String answer="HTTP/1.1 " + String(t) + " OK\r\n";
    client.print(answer);
  }
  else if (req.indexOf("/h") != -1) {
    String answer="HTTP/1.1 " + String(h) + " OK\r\n";
    client.print(answer);
  } else if (req.indexOf("/a") != -1) {
    String answer="HTTP/1.1 " + String(ppmRaw) + " OK\r\n";
    client.print(answer);
  } else {
    client.print(header);
    client.print(t);
    client.println("°");
    if (h<40) {
	    client.print("");
	    client.print(h);
	    client.print("");
    } else {
      client.print(h);
    };
    client.println("%");
    client.print(" Air ");
    client.println(ppmRaw);
    client.println(footer);
    client.stop();
    client.flush();
    return;
  }
  delay(1);
  if (debug==1) Serial.println("Client disonnected");
};
			

Скачать (исходный код скетча под Arduino IDE)

7 мая 2017
Версия 0.3