Дешевый датчик качества воздуха на CCS811, TM1637 и ESP8266

Ответить
Аватара пользователя
KirAdmin
Администратор
Сообщения: 5
Зарегистрирован: 04 янв 2019, 15:13
Откуда: М.О., г.Фрязино
Контактная информация:

Дешевый датчик качества воздуха на CCS811, TM1637 и ESP8266

Сообщение KirAdmin » 10 янв 2019, 22:45

Гаджет для измерения качества воздуха. Измеряет eCO2 и TVOC.
Значение TVOC:
  • 0-60 отлично (целевое значение) без ограничения
  • 60-200 хорошо (рекомендуется вентиляция) без ограничения
  • 200-600 умерено (инт.вент., поиск источника) < 12 месяцев
  • 600-2000 плохо (инт.вент., поиск источник) < 1 месяца
  • 2000-5000 опасно (инт.вент., поиск источник) < часа
Значение CO2:
  • Нормальный уровень на открытом воздухе 350 - 450 ppm
  • Приемлемые уровни < 600 ppm
  • Жалобы на несвежий воздух 600 - 1000 ppm
  • Общая вялость 1000 - 2500 ppm
  • Возможны нежелательные эффекты на здоровье 2500 - 5000 ppm
  • Максимально допустимая концентрация в течение 8 часового рабочего дня 5000 ppm
C TVOC все понятно, а вот eCO2 немного сложнее, это не показатель углекислого газа, т.е. не CO2, но CO2 в этой прошивке рассчитано из TVOC как эквивалент CO2. Эти данные можно использовать для оценки уровня углекислого газа, но не является точным его значением.

Для отображения показаний используется LED модуль 7 сегментного индикатора TM1637. В качестве микроконтроллера проще использовать ESP8266 со стабилизатором AMS1117 3.3, т.к. питание и выходы CCS811 не рассчитаны на 5 Вольт, а модуль индикатора TM1637 работает и с 3,3 и 5 Вольт. У меня TM1637 работает даже без подключения вывода VCC. (проверьте сами).

Схема включения:
shem CJMCU-811 CCS811.jpg
Схема включения CCS811, TM1637 и ESP8266
shem CJMCU-811 CCS811.jpg (45.86 КБ) 2022 просмотра
Первый запуск CCS811 очень не точный и требует 48 часов работы, после чего показаниям можно верить. Все последующие включения требуют 30 минут прогрева для выхода датчик на нормальный режим работы.

Код заливается с помощью среды Arduino. Код выполняет считывания показания и индикацию их на дисплей в следующем бесконечном цикле:
  • 2 секунды eCO2
  • 2 секунды TVOC
  • 2 секунды eCO2
  • 2 секунды TVOC
  • ...
Можно добавить небольшую пищалку или светодиод для предупреждения о высокой концентрации, т.е. необходимость проветрить помещение. Пока это в мыслях, и логично, что прибор должен предупреждать. А сейчас о всех за и против:
Плюсы
  • компактный
  • дешевый ~11$
  • низкое потребление
  • легкая повторяемость
Минусы
  • CCS811 бывают глюки
  • это не концентрация CO2
  • CCS811 может умереть
  • измерение температуры в библиотеке CCS811 ±20°
Очень мало известно о датчике CCS811, я не наблюдал необходимости перезагрузки а при ее выполнении датчик не шлет данные, решается передергиванием питания, (этот момент надо еще перепроверить).
Есть отзывы о выходе из строя, может это один человек, который написал везде где мог, а у кого все хорошо и промолчали. Есть график сравнения точности показаний на стороннем форуме:
Изображение
  1. голубая линия CCS811
  2. зеленая линия MH-Z19B
  3. черная линия S8
После ночи работы в помещении со мной показания дошли до критических ~2000, но перезагрузка и 30 минут ожидания прогрева показали ~850. Чему верить и с чем его можно сравнить для собственного спокойствия не знаю, за ночь легко можно надышать до ~2000, но тогда время до выхода на эти показатели не 30 минут. Много факторов могли повлиять на такой исход, например я покинул помещение открыв дверь и смешав воздух с другой комнатой, мое движение по комнате и открытие холодильника. Например открытая рядом миска колбасой очень резко поднимает показания до опасного уровня, я х.з. что в этой колбасе, но от сыра такого нет.

Как всегда прикладываю свою быдлокодерскую "программу" ;) для успешного повторения:

Код: Выделить всё

#include "Adafruit_CCS811.h"
#include "TM1637.h"
#define CLK 13 // D7 ESP8266
#define DIO 12 // D6 ESP8266
#define LED 16 // D0 ESP8266
TM1637 disp(CLK, DIO);
Adafruit_CCS811 ccs;

void setup() {
  pinMode(LED, OUTPUT); // выход под LED
  disp.init();  // инициализация
  disp.set(1);  // яркость, 0 - 7 (минимум - максимум)
  
  if(!ccs.begin()){ 
      disp.displayByte(_dash, _dash, _dash, _dash);
    while(1);
  }
}

void loop() {
  if(ccs.available()){
    float temp = ccs.calculateTemperature();
    if(!ccs.readData()){
      disp.clearDisplay();
      disp.displayInt(ccs.geteCO2());
      delay(2000);
      disp.clearDisplay();
      disp.displayInt(ccs.getTVOC());
      delay(2000);
      // если опасная концентрация включаем LED
      if(ccs.geteCO2() >= 2200 || ccs.getTVOC() >= 250) {
        digitalWrite(LED, HIGH);  // LED вкл если порог больше
        }
      else if(ccs.geteCO2() <= 1000 || ccs.getTVOC() <= 150) {
        digitalWrite(LED, LOW);  // LED выкл если порог ниже
        }
    }
    else{
      disp.displayByte(_E, _r, _r, _empty);
      while(1);
    }
  }
}
Среда Arduino 1.6.7 + дополнение ESP8266 2.4.2 + библиотеки:
TM1637.zip
TM1637
(8.38 КБ) 88 скачиваний
Adafruit_CCS811-master.zip
Adafruit CCS811 master
(9.61 КБ) 75 скачиваний

Аватара пользователя
KirAdmin
Администратор
Сообщения: 5
Зарегистрирован: 04 янв 2019, 15:13
Откуда: М.О., г.Фрязино
Контактная информация:

Re: Дешевый датчик качества воздуха на CCS811, TM1637 и ESP8266

Сообщение KirAdmin » 14 янв 2019, 10:46

Прошла неделя стабильной работы. Время выхода на нормальный режим больше 30 минут, целенаправленно не засекал, редко выключается между компьютером и блоком питания. Работает практически 24/7.

Датчик CCS811 оказался интереснее чем просто CO2, и по началу это меня вводило заблуждение. Один день наблюдал показания ppm > 5000, ожидая что датчик умрет, но это был нарезанный неподалеку лук.

Долгое проветривание помещения может уменьшить показания ppm до 400 и TVOC до 0 (это минимум для датчик), но 3 человека для 1 комнатной квартиры это много и уже 2500 ppm через час.

Не всем знают какая норма для проветриваемого помещения. Поэтому был добавлен мигающий светодиод (LED), который напрямую подключен к выводу D0 ESP8266 а другим выводом к GND. В код добавлена простая логика включения при показаниях ppm >= 2200 или TVOC >= 250. Отключение LED произойдет если уровень упадет <= 1000 и <= 150 соответственно.

В шапке темы схема включения и прошивка именно под этот случай и она основная v1.0 как самая простая и достаточная для большинства пользователей.

Время показало что включать LED лучше при ppm >= 1200 и TVOC >= 220 и отключать при ppm <= 500 и TVOC <= 11, это увеличит качество проветривания помещения.

Telegram уведомления и управление.
Бесплатно расширяем функционал и будем слать сообщения в телеграм через бота Telegram bot.
Оф. API для разработчиков: Telegram Bot API.
ВНИМАНИЕ!!!
Официально telegram заблокирован на территории РФ, но всегда есть способы обхода. Я рекомендую проверить, будет ли это работать у вас. Для этого в браузере компьютера или телефона подключенный к этому же Wi-Fi что и предполагаемый модуль и перейти по ссылке: https://api.telegram.org/bot123456:ABC- ... ew11/getMe
Если в открытой странице:

Код: Выделить всё

{"ok":false,"error_code":401,"description":"Unauthorized"}
Отлично! Вы можете смело работать с Telegram. А если не грузится или доступ закрыт, значит ваш провайдер заблокировал.
Обход блокировки
Для обхода использую роутре ASUS, другого у меня нет, поднимаю на нем VPN клиент, есть бесплатный OpneVPN
здесь файл конфигурации: https://www.vpngate.net/en/
Я использую Японию, скорость нам не важна, как и время отклика. Создаю правило для IP модуля ESP8266, что бы не замедлять другие устройства в сети и подключать их напрямую без VPN.

Для тестирования телеграмм бота использую готовый код:

Код: Выделить всё

#include "Adafruit_CCS811.h"
#include "TM1637.h"
#include <ESP8266WiFi.h> 
#include <WiFiClientSecure.h> 
#include <UniversalTelegramBot.h>
#define CLK 13 // D7 ESP8266
#define DIO 12 // D6 ESP8266
#define ledPin 16 // D0 ESP8266
int ledStatus = 0;

int i = 0;
bool alarm = true;
bool sms = true; // false or true
bool co2triger = true;
bool tvoctriger = true;
int pause = 5;
int pausestatus = 0;


TM1637 disp(CLK, DIO);
Adafruit_CCS811 ccs;
const char* ssid     = "xxxxxxx"; 
const char* password = "xxxxxxx"; 
// инициализация бота Telegram 
const char BotToken[] = "xxxxxxxxxx:xxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

WiFiClientSecure client;
UniversalTelegramBot bot(BOTtoken, client);

int Bot_mtbs = 1000; //mean time between scan messages
long Bot_lasttime;   //last time messages' scan has been done
bool Start = false;



// управление Telegram
void handleNewMessages(int numNewMessages) {
  Serial.println("handleNewMessages");
  Serial.println(String(numNewMessages));

  for (int i=0; i<numNewMessages; i++) {
    String chat_id = String(bot.messages[i].chat_id);
    String text = bot.messages[i].text;

    String from_name = bot.messages[i].from_name;
    if (from_name == "") from_name = "Guest";

    if (text == "/pause") { // задаем количество циклов пропуска для чтения сообщения Telegram
      if (pause == 5){
        pause = 10;
        bot.sendMessage(chat_id, "Twix 10sec.", "");
      }
      else if (pause == 10){
        pause = 15;
        bot.sendMessage(chat_id, "Twix 15sec.", "");
      }
      else {
        pause = 5;
        bot.sendMessage(chat_id, "Twix 5sec.", "");
      }
    }

    if (text == "/sms") {
      sms = !sms;
      if(sms){
        bot.sendMessage(chat_id, "SMS ON", "");
      } else {
        bot.sendMessage(chat_id, "SMS OFF", "");
      }
    }

    if (text == "/alarm") {
      alarm = !alarm;
      if(alarm){
        bot.sendMessage(chat_id, "Alarm ON", "");
      } else {
        bot.sendMessage(chat_id, "Alarm OFF", "");
      }
    }

    if (text == "/start" || text == "/info") {
      String welcome = "\xd0\xaf \xd0\xb1\xd0\xbe\xd1\x82 \xd0\xba\xd0\xb0\xd1\x87\xd0\xb5\xd1\x81\xd1\x82\xd0\xb2\xd0\xb0 \xd0\xb2\xd0\xbe\xd0\xb7\xd0\xb4\xd1\x83\xd1\x85\xd0\xb0. \xd0\x9e\xd1\x82\xd0\xb2\xd0\xb5\xd1\x87\xd0\xb0\xd1\x8e \xd0\xbd\xd0\xb0 \xd0\xba\xd0\xbe\xd0\xbc\xd0\xb0\xd0\xbd\xd0\xb4\xd1\x8b\x3a\x0d\x0a\n";

      welcome += "/alarm : to switch the Led ON\n";
      welcome += "/sms : to switch the Led OFF\n";
      welcome += "/pause : Returns current status of LED\n\n";
      if(alarm){
        welcome += "Alarm ON & ";
      } else {
        welcome += "Alarm OFF & ";
      }
      if(sms){
        welcome += "SMS ON & ";
        //bot.sendMessage(chat_id, "SMS ON", "");
      } else {
        welcome += "SMS OFF & ";
      }
      if(pause == 5){
        welcome += "5sec\n\n";
      } else if (pause == 10){
        welcome += "10sec\n\n";
      }
      else {
        welcome += "15sec\n\n";
      }
      welcome += "eCO2=";
      welcome += ccs.geteCO2();
      welcome += "ppm & TVOC=";
      welcome += ccs.getTVOC();
      welcome += "ppb\n\n";
      welcome += "\xd0\x9c\xd0\xbe\xd0\xb9 \xd1\x81\xd0\xbe\xd0\xb7\xd0\xb4\xd0\xb0\xd1\x82\xd0\xb5\xd0\xbb\xd1\x8c\x3a llirikks\x40mail.ru.\x0d\x0a\n\n";  // Мой создатель: llirikks@mail.ru.
      bot.sendMessage(chat_id, welcome, "Markdown");
    }
  }
}



void setup() {
  pinMode(ledPin, OUTPUT); // выход под LED
  disp.init();  // инициализация
  disp.set(1);  // яркость, 0 - 7 (минимум - максимум)

  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(100);
  WiFi.begin(ssid, password);

  // если датчик не исправен выводим "----"
  if(!ccs.begin()){ 
      disp.displayByte(_dash, _dash, _dash, _dash);
      delay(500);
      disp.clearDisplay(); // очистка дисплея
      delay(500);
    while(1);
    }
      //calibrate temperature sensor
      while(!ccs.available());
      float temp = ccs.calculateTemperature();
      ccs.setTempOffset(temp - 25.0);
   
   
   
   
   // подключение Wi-Fi

    while (WiFi.status() != WL_CONNECTED) { //|| i != 10
    disp.displayByte(_dash, _empty, _empty, _empty);
    delay(200);
    disp.displayByte(_dash, _dash, _empty, _empty);
    delay(200);
    disp.displayByte(_dash, _dash, _dash, _empty);
    delay(200);
    disp.displayByte(_dash, _dash, _dash, _dash);
    delay(200);
    disp.clearDisplay();
    delay(300);
    //i++;
  }
  // если подключение есть, на дисплее 1111
        if(WiFi.status() == WL_CONNECTED) {
        disp.displayInt(1111);   
        delay(300);
        }

        if(WiFi.status() == WL_CONNECTED) { bot.sendMessage(String(272561288), "\xd0\x97\xd0\xb0\xd0\xbf\xd1\x83\xd1\x89\xd0\xb5\xd0\xbd \xd0\xb1\xd0\xbe\xd1\x82 CSS811." , ""); } // Запущен бот CSS811.
}

void loop() {

/* Считываем данные с датчика и выводим на экран по очереди на 2 сек */

  if(ccs.available()){
    float temp = ccs.calculateTemperature();
    if(!ccs.readData()){
      disp.clearDisplay();
      disp.displayInt(ccs.geteCO2());
      delay(2000);
      disp.clearDisplay();
      disp.displayInt(ccs.getTVOC());
      delay(2000);
      // управление LED и сообщениями
      if(alarm) { // если alarm ON разрешаем LED и SMS
      if(ccs.geteCO2() >= 1200 || ccs.getTVOC() >= 220) { // если порог превышен
        if(co2triger == true) { // выполняем первое SMS и вкл. LED
                  digitalWrite(ledPin, HIGH); 
                  if(WiFi.status() == WL_CONNECTED && sms ) { bot.sendMessage(String(272561288), "\xE2\x9A\xA0 \xd0\x9f\xd1\x80\xd0\xbe\xd0\xb2\xd0\xb5\xd1\x82\xd1\x80\xd0\xb8\xd1\x82\xd1\x8c! " + String(ccs.geteCO2()) + "ppm & " + String(ccs.getTVOC()) + "ppb", ""); }
                  co2triger = false; // запрещаем повторять SMS о превышении
                  tvoctriger = true; // разрешаем SMS о снижении
                  }
                  }

      else if(ccs.geteCO2() <= 500 || ccs.getTVOC() <= 11 && tvoctriger == true) { // если показания в норме
        if(tvoctriger == true) { // выполняем первое SMS и выкл. LED
                  digitalWrite(ledPin, LOW); 
                  if(WiFi.status() == WL_CONNECTED && sms ) { bot.sendMessage(String(272561288), "\xF0\x9F\x8D\x80 \xd0\xa5\xd0\xbe\xd1\x80\xd0\xbe\xd1\x88\xd0\xbe " + String(ccs.geteCO2()) + "ppm & " + String(ccs.getTVOC()) + "ppb", ""); }
                  tvoctriger = false; // запрещаем повторять SMS о превышении
                  co2triger = true;   // разрешаем SMS о снижении
                  }
                  }
      }
      else { // если alarm OFF отключаем LED
      digitalWrite(ledPin, LOW); //выкл LED

}

pausestatus++;

        if(pause == pausestatus && WiFi.status() == WL_CONNECTED) { // если есть подключение и кол-во циклов = паузе
pausestatus = 0;
// чтение новых сообщений Telegramm;
              if (millis() > Bot_lasttime + Bot_mtbs)  {
                  int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
              
                  while(numNewMessages) {
                    Serial.println("got response");
                    handleNewMessages(numNewMessages);
                    numNewMessages = bot.getUpdates(bot.last_message_received + 1);
                  }
                    Bot_lasttime = millis();
              }
         }  
    }
      }
    else{
      disp.displayByte(_E, _r, _r, _empty);
      while(1);
    }
  }
Для работы нужна библиотека ArduinoJson и Universal Arduino Telegram Bot.
Для русских символах в сообщениях Telegram использую Кодер/Декодер Url. Для примера пишем Привет и получаем %cf%f0%e8%e2%e5%f2, с помощью блокнота меняем % на \x, так же знак + меняем на пробел. В итоге получается: /xcf/xf0/xe8/xe2/xe5/xf2.

Ответить