Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр Анемометр

Далее следует…

На этом история не закончилась, а все только начиналось, потому что когда прибор перешел на автономное питание ему резко поплохело и история его лечения заслуживает отдельного топика, вот осциллограмма из истории болезни:

UPD:Схема подключения типичная для указанных датчиков, более детально о распиновке в скетче.

Рабочий скетч для Arduino UNO

Скетч собирался (копипастился) с разных кусков, так что сильно не ругайте, надеюсь разобраться в нем можно

#include <SFE_BMP180.h>
#include <Wire.h>
#include "DHT.h"

#define PIN_SCE   7  // LCD CS  .... Pin 2
#define PIN_RESET 6  // LCD RST .... Pin 1
#define PIN_DC    5  // LCD Dat/Com. Pin 3
#define PIN_SDIN  4  // LCD SPIDat . Pin 4
#define PIN_SCLK  3  // LCD SPIClk . Pin 5
                     // LCD Gnd .... Pin 8
                     // LCD Vcc .... Pin 6
                     // LCD Vlcd ... Pin 7

#define LCD_C     LOW
#define LCD_D     HIGH

#define LCD_X     84
#define LCD_Y     48
#define LCD_CMD   0
SFE_BMP180 pressure;

#define DHTPIN 10
#define DHTTYPE DHT22 

DHT dht(DHTPIN, DHTTYPE);

#define ALTITUDE 113.0 // Altitude of SparkFun's HQ in Boulder, CO. in meters

int a = 0;
int sensorPin = A0;    // вход подключается к Aout модуля с датчиком СО2
int vBattPin = A1;    // вход используется для мониторинга за состояние батареи
int sensorValue = 0;  // variable to store the value coming from the sensor
float adc_step=5/1024.0f; //шаг измерения АЦП 
float a_k=5.0894E-7; //магическая константа модели датчика СО2
float b_k=6.7303E-4;//еще одна магическая константа модели датчика СО2
float v_0=1.09; //напряжение на выходе датчика на свежем воздухе
int ledPin=13;


static const byte ASCII[][5] =
{
 {0x00, 0x00, 0x00, 0x00, 0x00} // 20  
 ,{0xff, 0xff, 0xff, 0xff, 0xff} // 21 !
//,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 !
,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 "
,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 #
,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $
,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 %
,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 &
,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 '
,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 (
,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 )
,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a *
,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b  
,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c ,
,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d -
,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e .
,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f /
,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0
,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1
,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2
,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3
,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4
,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5
,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6
,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7
,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8
,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9
,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a :
,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ;
,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c <
,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d =
,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e >
,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ?
,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @
,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A
,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B
,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C
,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D
,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E
,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F
,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G
,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H
,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I
,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J
,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K
,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L
,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M
,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N
,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O
,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P
,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q
,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R
,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S
,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T
,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U
,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V
,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W
,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X
,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y
,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z
,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [
,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ¥
,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ]
,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^
,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _
,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 `
,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a
,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b
,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c
,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d
,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e
,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f
,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g
,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h
,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i
,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j 
,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k
,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l
,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m
,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n
,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o
,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p
,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q
,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r
,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s
,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t
,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u
,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v
,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w
,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x
,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y
,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z
,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b {
,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c |
,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d }
,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ←
,{0x00, 0x06, 0x09, 0x09, 0x06} // 7f →
};




void LcdCharacter(char character)
{
  LcdWrite(LCD_D, 0x00);
  for (int index = 0; index < 5; index  )
  {
    LcdWrite(LCD_D, ASCII[character - 0x20][index]);
  }
  LcdWrite(LCD_D, 0x00);
}

void LcdClear(void)
{
  for (int index = 0; index < LCD_X * LCD_Y / 8; index  )
  {
    LcdWrite(LCD_D, 0x00);
  }
}

void LcdInitialise(void)
{
  pinMode(PIN_SCE,   OUTPUT);
  pinMode(PIN_RESET, OUTPUT);
  pinMode(PIN_DC,    OUTPUT);
  pinMode(PIN_SDIN,  OUTPUT);
  pinMode(PIN_SCLK,  OUTPUT);

  digitalWrite(PIN_RESET, LOW);
 // delay(1);
  digitalWrite(PIN_RESET, HIGH);

  LcdWrite( LCD_CMD, 0x21 );  // LCD Extended Commands.
  LcdWrite( LCD_CMD, 0xBf );  // Set LCD Vop (Contrast). //B1
  LcdWrite( LCD_CMD, 0x04 );  // Set Temp coefficent. //0x04
  LcdWrite( LCD_CMD, 0x14 );  // LCD bias mode 1:48. //0x13
  LcdWrite( LCD_CMD, 0x0C );  // LCD in normal mode. 0x0d for inverse
  LcdWrite(LCD_C, 0x20);
  LcdWrite(LCD_C, 0x0C);
}

void LcdString(char *characters)
{
  while (*characters)
  {
    LcdCharacter(*characters  );
  }
}

void LcdWrite(byte dc, byte data)
{
  digitalWrite(PIN_DC, dc);
  digitalWrite(PIN_SCE, LOW);
  shiftOut(PIN_SDIN, PIN_SCLK, MSBFIRST, data);
  digitalWrite(PIN_SCE, HIGH);
}

// gotoXY routine to position cursor 
// x - range: 0 to 84
// y - range: 0 to 5

void gotoXY(int x, int y)
{
  LcdWrite( 0, 0x80 | x);  // Column.
  LcdWrite( 0, 0x40 | y);  // Row.  

}


char * floatToString(char * outstr, double val, byte precision, byte widthp){
  char temp[16];
  byte i;

  // compute the rounding factor and fractional multiplier
  double roundingFactor = 0.5;
  unsigned long mult = 1;
  for (i = 0; i < precision; i  )
  {
    roundingFactor /= 10.0;
    mult *= 10;
  }
  
  temp[0]='';
  outstr[0]='';

  if(val < 0.0){
    strcpy(outstr,"-");
    val = -val;
  }

  val  = roundingFactor;

  strcat(outstr, itoa(int(val),temp,10));  //prints the int part
  if( precision > 0) {
    strcat(outstr, "."); // print the decimal point
    unsigned long frac;
    unsigned long mult = 1;
    byte padding = precision -1;
    while(precision--)
      mult *=10;

    if(val >= 0)
      frac = (val - int(val)) * mult;
    else
      frac = (int(val)- val ) * mult;
    unsigned long frac1 = frac;

    while(frac1 /= 10)
      padding--;

    while(padding--)
      strcat(outstr,"0");

    strcat(outstr,itoa(frac,temp,10));
  }

  // generate space padding 
  if ((widthp != 0)&&(widthp >= strlen(outstr))){
    byte J=0;
    J = widthp - strlen(outstr);
    
    for (i=0; i< J; i  ) {
      temp[i] = ' ';
    }

    temp[i  ] = '';
    strcat(temp,outstr);
    strcpy(outstr,temp);
  }
  
  return outstr;
}


void drawLine(void)
{
  unsigned char  j;  
   for(j=0; j<84; j  ) // top
	{
          gotoXY (j,0);
	  LcdWrite (1,0x01);
  } 	
  for(j=0; j<84; j  ) //Bottom
	{
          gotoXY (j,5);
	  LcdWrite (1,0x80);
  } 	

  for(j=0; j<6; j  ) // Right
	{
          gotoXY (83,j);
	  LcdWrite (1,0xff);
  } 	
	for(j=0; j<6; j  ) // Left
	{
          gotoXY (0,j);
	  LcdWrite (1,0xff);
  }

}

float VoltageToPPM(float voltage)
{
  return pow(b_k, voltage)/a_k;
}


void setup(void)
{
//analogReference(INTERNAL);
 LcdInitialise();
  LcdClear();
  gotoXY(0,0);
    if (pressure.begin())
    LcdString("BMP180 init success");
  else
  {
    // Oops, something went wrong, this is usually a connection problem,
    // see the comments at the top of this sketch for the proper connections.

   LcdString("BMP180 init failnn");
    while(1); // Pause forever.
  }
  dht.begin(); 

}

void loop(void)
{
  sensorValue = analogRead(sensorPin);
  delay(50);
  sensorValue = analogRead(sensorPin);
  delay(50);
  char buf[20];
  float value=sensorValue*adc_step;
  gotoXY(0,0);
  LcdString("CO2:");
  LcdString(floatToString(buf, VoltageToPPM(value),1,5));
  LcdString("ppm");
  gotoXY(0,1);
   LcdString("CO2 V:");
  LcdString(floatToString(buf, value,4,5));
  
   gotoXY(0,2);
   delay(50);
  sensorValue = analogRead(vBattPin);
  delay(50);
  sensorValue = analogRead(vBattPin);
  value=sensorValue*adc_step;
  LcdString("V batt:");
  LcdString(floatToString(buf, value,3,4));
  
//  static bool led_on_off=true;
//    digitalWrite(ledPin, led_on_off);
//  led_on_off=!led_on_off;
  
    char status;
  double T,P,p0,a;

  // Loop here getting pressure readings every 10 seconds.

  // If you want sea-level-compensated pressure, as used in weather reports,
  // you will need to know the altitude at which your measurements are taken.
  // We're using a constant called ALTITUDE in this sketch:
 

  
  // If you want to measure altitude, and not pressure, you will instead need
  // to provide a known baseline pressure. This is shown at the end of the sketch.

  // You must first get a temperature measurement to perform a pressure reading.
  
  // Start a temperature measurement:
  // If request is successful, the number of ms to wait is returned.
  // If request is unsuccessful, 0 is returned.

  status = pressure.startTemperature();
  if (status != 0)
  {
    // Wait for the measurement to complete:
    delay(status);

    // Retrieve the completed temperature measurement:
    // Note that the measurement is stored in the variable T.
    // Function returns 1 if successful, 0 if failure.

    status = pressure.getTemperature(T);
    if (status != 0)
    {
      // Print out the measurement:
      //gotoXY(0,3);
     // LcdString("temp: ");
      //LcdString(floatToString(buf, T,1,4));
     // LcdString(" C");
      
      // Start a pressure measurement:
      // The parameter is the oversampling setting, from 0 to 3 (highest res, longest wait).
      // If request is successful, the number of ms to wait is returned.
      // If request is unsuccessful, 0 is returned.

      status = pressure.startPressure(2);
      if (status != 0)
      {
        // Wait for the measurement to complete:
        delay(status);

        // Retrieve the completed pressure measurement:
        // Note that the measurement is stored in the variable P.
        // Note also that the function requires the previous temperature measurement (T).
        // (If temperature is stable, you can do one temperature measurement for a number of pressure measurements.)
        // Function returns 1 if successful, 0 if failure.

        status = pressure.getPressure(P,T);
        if (status != 0)
        {
          // Print out the measurement:
          gotoXY(0,5);
          //lcd.print("ap ");
          LcdString(floatToString(buf, P*0.7501,1,4));
          //lcd.print(" mb");

          // The pressure sensor returns abolute pressure, which varies with altitude.
          // To remove the effects of altitude, use the sealevel function and your current altitude.
          // This number is commonly used in weather reports.
          // Parameters: P = absolute pressure in mb, ALTITUDE = current altitude in m.
          // Result: p0 = sea-level compensated pressure in mb

          p0 = pressure.sealevel(P,ALTITUDE); // we're at 1655 meters (Boulder, CO)
          LcdString("-");
          LcdString(floatToString(buf, p0*0.7501,1,4));
          //Serial.print(" mb, ");
          //Serial.print(p0*0.0295333727,2);
          //Serial.println(" inHg");

          // On the other hand, if you want to determine your altitude from the pressure reading,
          // use the altitude function along with a baseline pressure (sea-level or other).
          // Parameters: P = absolute pressure in mb, p0 = baseline pressure in mb.
          // Result: a = altitude in m.

          a = pressure.altitude(P,p0);

        }
        else LcdString("error retrieving pressure measurementn");
      }
      else LcdString("error starting pressure measurementn");
    }
    else LcdString("error retrieving temperature measurementn");
  }
  else LcdString("error starting temperature measurementn");

  float h = dht.readHumidity();
  float t = dht.readTemperature();
  gotoXY(0,4);
  if (isnan(t) || isnan(h)) {
   LcdString("Failed to read from DHT");
  } 
  else{
    LcdString(floatToString(buf, h,1,4));
    LcdString("%");
  }
     gotoXY(0,3);
     LcdString("temp: ");
     LcdString(floatToString(buf, t,1,4));
     LcdString(" C");
  delay(1000);
  
}

Про анемометры:  Устройство и принцип действия датчика давления - Авто Брянск

Датчик углекислого газа в воздухе (co2) "mh-z19b" – головоломка

Чтобы окончательно решить исход вековечной вражды в офисе между «теми, кому дует» и «теми, кому душно» решил разориться на бюджетный датчик содержания CO2 в атмосферном воздухе

и прикрутить к нему сирену

. Поскольку цена на такие датчики — совсем негуманная, выбрал вариант «MH-Z19B», который оказался самым бюджетным.

К сожалению, датчик подарил головоломку. Подробности — под катом.

(Внимание — в обзоре много «программизма», ругани в адрес китайских даташитов, присутствует шестнадцатеричная математика — так что если тема DIY вам не близка, проходите мимо, иначе будете разочарованы).

Датчик «MH-Z19B» сделан китайской компанией «Winsen» (даташит, PDF) и неоднократноупоминался на Habrahabr и Geektimes. Это вторая ревизия, с буквой «b» в названии, по результатам китайской «работы над ошибками». Первую ревизию одним неверным движением можно было ввести в режим калибровки, для которой требовалась атмосфера с нулевым содержанием CO2. Бедолагам, которые в это влетали, приходилось искать баллон с чистым азотом или кислородом, чтобы её организовать. В ревизии «b» китайцы сделали так, что модуль «эталонной» считает смесь с 400ppm углекислоты — то есть его можно, теоретически, перекалибровать просто в лесу или в парке.

Модуль пришёл в паре с небольшим шлейфом, основная плата имеет надпил, позволяющий отломать кусок с разъёмом и вместо этого припаять гребёнки. Модуль работает в двух режимах — UART (передавая показания по последовательному порту на 9600 бод) и PWM, контакты слева и справа, соответственно:

Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

Для удобства я обрезал шлейф и насадил на провода дюпоновские наконечники. Правда, выяснилось, что часть проводов вообще ни к чему не подключена. Напротив, PWM оказался не выведен на шлейф, к контакту пришлось дополнительно подпаиваться, завернув из предосторожности датчик в плёнку поплотнее:

Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

Датчик работает по следующему принципу — он получает по UART девятибайтовые команды (последний байт — CRC) и отвечает также девятибайтовыми пакетами. Замер концентрации СО2 выполняется командой с байтом 0x86:

Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

Также показания можно прочитать, померяв ширину PWM-сигнала:

Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

Тут подстерегала первая проблема — как узнать текущую размерность измерений? Даташит упоминает, что датчик может мерять в диапазаонах от нуля как до 2000, так и от нуля до 5000ppm:

Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

Запросить текущее значение у датчика нельзя, поэтому остаётся только явно задавать размерность при каждом старте. Даташит описывает формат команды, которая скажет модулю, в какой шкале работать:

Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

Почему-то ни одна инструкция в интернете этого вопроса не касается — никто толком не интересовался настройками датчика и просто принимают их как данность. Кряхтим и пишем код для Ардуино, который пошлёт модулю нужную команду:

(CRC вычисляем по формуле из того же даташита, (NOT(Byte1 Byte2 Byte3 Byte4 Byte5 Byte6 Byte7)) 1)

Пробуем и так, и эдак, но в результате получаем на выходе дикие неправдоподобные значения концентрации СО2, вылетающие за паспортные диапазоны показаний датчика в разы. Уже испугавшись, что запороли дорогой прибор, через какое-то время нагугливаем ссылку revspace.nl/MHZ19 со словами

According to the MH-Z19B datasheet, you can configure the measurement range by putting the desired range in byte 3 and 4. However, unlike what the MH-Z19B datasheet says, you can set the range using the following command (in this case 0x07d0 = 2000 ppm in byte 6 and 7)
Замечательные китайцы ухитрились ошибиться в даташите!

Материмся, и вместо третьего и четвёртого байта пишем в шестой и седьмой. Благодаря подсказке неизвестного голландца — модуль воскресает.

Поскольку теперь доверия модулю нет никакого, решил разобраться с его показаниями досконально и сравнить результаты по UART и PWM. Пишем код под Ардуино, который сначала, в блоке setup, даёт команду установки размерности, а потом в цикле loop делает замеры. Модуль располагаем на сквозняке у форточки.

Код (финальный, уже включающий все позднейшие *открытия*

#include <SoftwareSerial.h>
#define pwmPin 10
SoftwareSerial swSerial(A0, A1); // RX, TX

void setup() {
  Serial.begin(9600); 
  swSerial.begin(9600); 
  pinMode(pwmPin, INPUT);

  /*
  Источник - https://revspace.nl/MHZ19
   2000 ppm range: 0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x07, 0xD0, 0x8F
   5000 ppm range: 0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x13, 0x88, 0xCB
  */

  // Этот вариант ("A") с записью команды в 6й и 7й байт - работает
  //           bytes:                         3     4           6     7
  byte setrangeA_cmd[9] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x13, 0x88, 0xCB}; // задаёт диапазон 0 - 5000ppm
  unsigned char setrangeA_response[9]; 
  swSerial.write(setrangeA_cmd,9);
  swSerial.readBytes(setrangeA_response, 9);
  int setrangeA_i;
  byte setrangeA_crc = 0;
  for (setrangeA_i = 1; setrangeA_i < 8; setrangeA_i  ) setrangeA_crc =setrangeA_response[setrangeA_i];
  setrangeA_crc = 255 - setrangeA_crc;
  setrangeA_crc  = 1;
  if ( !(setrangeA_response[0] == 0xFF && setrangeA_response[1] == 0x99 && setrangeA_response[8] == setrangeA_crc) ) {
    Serial.println("Range CRC error: "   String(setrangeA_crc)   " / "  String(setrangeA_response[8])   " (bytes 6 and 7)");
  } else {
    Serial.println("Range was set! (bytes 6 and 7)");
  }
  delay(1000);

/*  
  // Этот вариант ("B") с записью команды в 3й и 4й байт, согласно даташиту - НЕ работает и поэтому закомментирован
  //           bytes:                         3     4           6     7
  byte setrangeB_cmd[9] = {0xFF, 0x01, 0x99, 0x00, 0x00, 0x00, 0x13, 0x88, 0xCB};
  unsigned char setrangeB_response[9]; 
  swSerial.write(setrangeB_cmd,9);
  swSerial.readBytes(setrangeB_response, 9);
  int setrangeB_i;
  byte setrangeB_crc = 0;
  for (setrangeB_i = 1; setrangeB_i < 8; setrangeB_i  ) setrangeB_crc =setrangeB_response[setrangeB_i];
  setrangeB_crc = 255 - setrangeB_crc;
  setrangeB_crc  = 1;
  if ( !(setrangeB_response[0] == 0xFF && setrangeB_response[1] == 0x99 && setrangeB_response[8] == setrangeB_crc) ) {
    Serial.println("Range CRC error: "   String(setrangeB_crc)   " / "  String(setrangeB_response[8])   " (bytes 3 and 4)");
  } else {
    Serial.println("Range was set! (bytes 3 and 4)");
  }
  delay(1000);
*/ 

}

void loop() {

  byte measure_cmd[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
  unsigned char measure_response[9]; 
  unsigned long th, tl, ppm = 0, ppm2 = 0, ppm3 = 0;

  // ***** узнаём концентрацию CO2 через UART: ***** 
  swSerial.write(measure_cmd,9);
  swSerial.readBytes(measure_response, 9);
  int i;
  byte crc = 0;
  for (i = 1; i < 8; i  ) crc =measure_response[i];
  crc = 255 - crc;
  crc  = 1;
  if ( !(measure_response[0] == 0xFF && measure_response[1] == 0x86 && measure_response[8] == crc) ) {
    Serial.println("CRC error: "   String(crc)   " / "  String(measure_response[8]));
  } 
  unsigned int responseHigh = (unsigned int) measure_response[2];
  unsigned int responseLow = (unsigned int) measure_response[3];
  unsigned int ppm = (256*responseHigh)   responseLow;

  // *****  узнаём концентрацию CO2 через PWM: ***** 
  do {
    th = pulseIn(pwmPin, HIGH, 1004000) / 1000;
    tl = 1004 - th;
    ppm2 =  2000 * (th-2)/(th tl-4); // расчёт для диапазона от 0 до 2000ppm 
    ppm3 =  5000 * (th-2)/(th tl-4); // расчёт для диапазона от 0 до 5000ppm 
  } while (th == 0);

  Serial.print(ppm);
  Serial.print(" <- ppm (UART) ");
  Serial.print((ppm/5)*2);
  Serial.println(" <- two fifths of it"); // Потом пришло озарение
  Serial.print(th);
  Serial.println(" <- Milliseconds PWM is HIGH");
  Serial.print(ppm2);
  Serial.println(" <- ppm2 (PWM) with 2000ppm as limit");
  Serial.print(ppm3);
  Serial.println(" <- ppm3 (PWM) with 5000ppm as limit");

  Serial.println("-----------");
  delay(5000);
}

Все замеры проводим, подключив модуль к питанию от 5 вольт; при 3.3 вольтах он выдаёт очевидно некорректные значения по верху диапазона.

В режиме PWM при заданном диапазоне значений от 0 до 2000ppm получаем, в условиях центра города, заполночь у форточки во двор, 1208ppm, что, безусловно, завышено. UART выдаёт нам близкое значение 1227ppm — различия вполне объяснимы ошибками оцифровки PWM-показаний.

Для сравнения, вот примерные дапазоны концентраций СО2, найденные в интернете:

— 350 — 450 ppm: Нормальный уровень на открытом воздухе.
— < 600 ppm: Приемлемые уровни. Уровень. рекомендованный для спален, детских садов и школ.
— 600 — 1000 ppm: Жалобы на несвежий воздух, возможно снижение концентрации внимания.
— 1000 ppm: Максимальный уровень стандартов ASHRAE (American Society of Heating, Refrigerating and Air-Conditioning Engineers) и OSHA (Occupational Safety & Health Administration).
— 1000 — 2500 ppm: Общая вялость, снижение концентрации внимания, возможна головная боль.
— 2500 — 5000 ppm: Возможны нежелательные эффекты на здоровье.

Переключаем датчик в диапазон 0 — 5000ppm. В режиме PWM у той же форточки во двор получаем 478ppm, что гораздо больше похоже на правду. Но вот в режиме UART наш датчик снова выдаёт совершенно неправдоподобное значение 1213ppm.

Десять раз перепроверив формулы, по близости показаний начинаю догадываться, что датчик считает с ошибками в арифметике. Модифицирую код, чтобы формула расчёта концентрации CO2 по данным PWM рассчитывалась с подстановкой всех вариантов верхнего предела значений. В момент какого-то озарения также дополнительно модифицирую код, чтобы значение, полученное по UART, дополнительно выводилось домноженным на 2000/5000:

диапазон 0 — 2000ppm

Range was set! (bytes 6 and 7)
1227 <- ppm (UART) 490 <- two fifths of it
606 <- Milliseconds PWM is HIGH
1208 <- ppm2 (PWM) with 2000ppm as limit
3020 <- ppm3 (PWM) with 5000ppm as limit

диапазон 0 — 5000ppm

Range was set! (bytes 6 and 7)
1213 <- ppm (UART) 484 <- two fifths of it
241 <- Milliseconds PWM is HIGH
478 <- ppm2 (PWM) with 2000ppm as limit
1195 <- ppm3 (PWM) with 5000ppm as limit

Делаем выводы:

После этого расследования датчик поселится в приборе в офисе, в компании с барометром, датчиком влажности воздуха, содержания пыли и ESP8266 для управления всем этим добром. Хоть я и вдохновился изначально приборчиками в переговорках московского Google, мой вариант не будет иметь цифр и экрана, а вместо этого получит более простую цветовую индикацию, как светофор — а подробные логи будет выкладывать по WiFi на сервер. Впрочем, это уже отдельная история.

Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

Исходный код программы (скетча)

Как выбрать измеритель качества воздуха

Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
Представляю вашему вниманию обзор на 12 гаджетов, которые позволят узнать качество воздуха, которым вы дышите. В основном это устройства, которые позволяют измерять концентрацию углекислого газа и количество микрочастиц пыли PM2.5. Но некоторые из представленных в обзоре устройств также могут измерять концентрацию угарного газа, озона и уровень шума (а также температуру, влажность и давление).

Но обо всём по порядку. Поехали!

Итак, сегодня речь пойдёт о следующих устройствах:

• Мастеркит MT8057
• Awair
• ТИОН MagicAir
• AirVisual Node/Pro
• iCeeO2-500 (-700)
• AZ Instruments AZ-7798/7788/7787
• HT-2000
• NetAtmo Weather Station
• uHoo Air Sensor
• Специализированные CO2-реле
• CleanSpace Tag
• Xiaomi PM2.5 Detector
• WP-6910/6912

Из этого списка я пользуюсь всем, кроме: AZ-7798, NetAtmo Weather Station, uHoo Air Sensor и HT-2000. В большей степени потому, что эти устройства имеют меньший функционал, чем аналоги. Все обозреваемые устройства (кроме одного, но его нет в этом списке) имеют настоящий NDIR детектор концентрации углекислого газа CO2 и/или качественный сенсор PM2.5. Также мы рассмотрим специализированные СО2-реле, позволяющие автоматизировать управление приточной вентиляцией.

Я сделал таблицу, в которой собрал основые характеристики устройств:

© anemometers.ru

Мастеркит MT8057
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
Начну с самого первого устройства, измеряющего концентрацию СО2, которое появилось у меня 4 года назад. Это простейший компактный измеритель концентрации СО2 и температуры от компании ZyAura (модель ZGm053U), продаваемый в России под торговой маркой Мастеркит МТ-8057 (Дaджeт). Недорогой измеритель, который имеет три цветных индикатора (пороговые значения можно настроить), питается от USB, а также имеет возможность подключения к компьютеру для записи статистики измерений. Хороший вариант, если вам нужно измерять только концентрацию углекислого газа СО2 в стационарных условиях. В устройстве используется NDIR сенсор собственной разработки компании, который показывает высокую точность измерений и имеет функцию автоматической калибровки (ABC). Купить устройство можно в России (одно время это было дешевле, чем на aliexpress).

ТИОН MagicAir
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
Базовая станция МеджикЭйр измеряет температуру, влажность и концентрацию углекислого газа в помещении. Питается от USB, имеет на борту Wi-Fi для подключения к сети и радиомодуль для связи с бризерами приточной вентиляции. Все измерения хранятся в облачном сервисе. Основная задача устройства — автоматизировать работу приточной вентиляции ТИОН. Устройство оснащено высококачественным сенсором концентрациии углекислого газа SenseAir S8. Базовая станция подходит только для стационарных измерений т.к. требует постоянного подключения к интернету. Идеальна для работы в комплексе с бризером. Мобильное приложение пока не имеет push-уведомлений. Идеальное устройство, если вы хотите автоматизировать работу приточной вентиляции в своей квартире. Купить можно на официальном сайте Тион.

Подробный обзор про ТИОН MagicAir можно посмотреть здесь.

Awair
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
Измеритель качества воздуха Awair имеет стильный внешний вид (корпус из натурального дерева) и достаточно неплохой функционал, включая мобильное приложение с push-уведомлениями. Из недостатков стоит отметить инфракрасный детектор частиц пыли (particle matter), который не может точно определить размер частиц, что приводит к достаточно большой погрешности измерений. Зато устройство имеет датчик летучей органики (VOC), который по моему мнению совершенно не нужен в бытовых условиях (об этом далее). Все измерения хранятся на сервере, поэтому устройство подходит только для стационарных измерений. Также данные нельзя получить в текстовом виде для самостоятельного анализа (это касается и станции ТИОН MagicAir). Кроме этого 12-вольтовое питание не позволит использовать внешний аккумулятор для носимых измерений. Купить устройство можно на Amazon с доставкой в Россию через посредника.

Подробный обзор про Awair можно посмотреть здесь.

AirVisual Node
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
По моему мнению это лучшее устройство на рынке, которое не имеет аналогов. Измеряет CO2 и PM2.5 (а также температуру и влажность). Имеет огромный цветной экран, встроенную память (4 Гб) и аккумулятор для автономной работы. Также имеется мобильное приложение с картой качества воздуха и push-уведомлениями (приложение можно использовать и без станции). Устройство может работать как в автономном режиме, так и подключенное к сети по Wi-Fi. Доступ с компьютера возможен по протоколу Samba (как на любой сетевой диск). Имеет датчик углекислого газа SenseAir S8 Extended Range и высокоточный детектор PM2.5 промышленного класса точности (подтверждено многочисленными тестами, результаты есть в интернете), собственной разработки компании.

Подробный обзор про AirVisual Node можно посмотреть здесь.

Данное устройство я регулярно использую для оценки качества воздуха в жилых и офисных помещениях, а также с помощью него я проводил исследование качества воздуха в общественном транспорте. В настоящий момент это устройство является публичной станцией по наблюдению за качеством воздуха в Москве (район м. Университет). Более продобнее про это можно прочитать здесь.
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

IQAir AirVisual Pro
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
Поскольку первая станция AirVisual Node теперь используется для наблюдений за уличным воздухом, то я купил вторую. Это обновлённая версия, с более качественным глянцевым экраном, улучшенным датчиком частиц PM2.5 (с абсолютно бесшумным вентилятором) и расширенным функционалом (возможность отключить Wi-Fi и настроить интервал измерений, например). Теперь бренд принадлежит всемирно известной компании IQAir. Повторюсь, что это лучшее устройство, которое можно использовать для того, чтобы отслеживать качество воздуха как внутри помещений (СО2) так и на улице (PM2.5). Купить устройство можно на официальном сайте (цена на сайте указана вместе с доставкой).

iCeeO2-500
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
Очень интересный и недорогой измеритель концентрации углекислого газа из Китая. Выделяется на фоне конкурентов тем, что имеет двухканальный сенсор СО2 (позволяет более точно проводить автоматическую калибровку). Предназначен не только для контроля СО2 в жилых помещениях, но также и в теплицах. Если для человека критичен высокий уровень СО2 во вдыхаемом воздухе, то для растений СО2 наоборот нужен для более продуктивного роста. Производитель подразумевает, что его можно использовать совместно с внешним аккумулятором. Также в ассортименте у производителя есть продвинутые и более дорогие модификации имеющие функцию логгера (на отдельно устанавливаемую micro sd карту памяти). Я считаю, что это лучшее устройство за минимальную стоимость, если вам нужно измерять только СО2. Купить устройство можно на TaoBao через посредника.

Ещё 4 устройства, про которые стоит упомянуть, но я лично ими не владею.
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

AZ Instruments AZ-7798/7788/7787
Это один из первых массовых сенсоров СО2, который можно было купить на Aliexpress. За несколько лет цена на него снизилась. Существует 3 модификации устройства: без памяти измерений и без реле, только с памятью, только с реле. С поставленной задачей по наблюдениям за СО2 справляется отлично. Дизайн устарел, функционал ограничен, носить с собой неудобно. Купить можно на Aliexpress.

HT-2000
Пожалуй самый дешёвый СО2 логгер на рынке. Оснащён либо датчиком SenseAir S8 LP, либо MH-Z19 (как повезёт). Имеет проблемы с автоматической калибровкой, можно подключить к компьютеру. Хороший вариант если вам нужно самый дешёвый логгер СО2. Купить можно на Aliexpress.

NetAtmo Weather Station
Персональная погодная станция оснащённая датчиком СО2 собственной разработки. Также измеряет давление и уровень шума. Есть неплохое мобильное приложение с push-уведомлениями. При этом очень медленно реагирует на изменение концентрации СО2. Купить можно в России.

uHoo Air Sensor
Устройство обладающее огромным количеством датчиков, по сравнению с конкурентами. Помимо общепринятых индикаторов качества воздуха CO2 и PM2.5, это устройство может измерять концентрацию угарного газа (СО), диоксид азота (NO2) и озон (O3). По этой же причине это самое дорогое устройство из обзора. Но при этом у него нет экрана (только мобильное приложение), аккумулятора (для автономной работы) и памяти. Также на мой взгляд устройство обладает избыточным набором датчиков. Объективно, для измерений внутри помещений достаточно одного датчика углекислого газа. Купить устройство можно на официальном сайте, доставка обойдётся ещё в 30 долларов.

ДОПОЛНЕНИЕ: Я приобрёл это устройство и разочаровавшись вернул обратно. К сожалению не удалось от него добиться ни точных показаний по СО2, ни тем более по PM2.5 (тем более, что у него не лазерный детектор). Плюс были проблемы с подключением к Wi-Fi. Не рекомендую.

Специализированные устройства: CO2-реле
Эти устройства нужны для автоматизации работы приточной вентиляцией в жилых и офисных помещениях. Они оснащаются датчиком СО2 и силовым реле, которое управляет включением/выключением приточной вентиляции по достижению определённого значения (порог срабатывания и гистерезис устанавливается пользователем).

На этом фото два таких CO2 реле от производителя Tongdy, они продаются по всему миру и под другими брендами. В устройстве слева используется датчик SenseAir K-30, а питается устройство от 24 вольт. Справа устройство с цветным экраном и питанием от 220 вольт. В нём используется датчик GE Telaire T6500. Купить в России эти устройства невозможно, я их покупал напрямую у производителя, заплатив в том числе за дорогую доставку. Устройство слева стоило 8 тысяч рублей, справа — 11 тысяч. Кстати, реле справа я готов продать т.к. для своих задач в загородном доме решил выбрать первый вариант (про это сделаю отдельную статью).
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

Доступной альтернативой для задач автоматизации работы приточной вентиляции можно рассмотреть устройства от AZ Instrument. Это AZ-7722/7721 (последний не имеет датчика влажности). Меня оно не устроило из-за своих габаритов и старомодного внешнего вида. Стоит такое устройство примерно 10 тысяч рублей, купить его можно на Aliexpress.
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр

Раз мы затронули СО2-реле, то естественно стоит упомянуть вот это устройство, которое наделало много шума в интернете прошлой осенью. Это Hessway KF-900F (на фото слева). Проблема заключается в том, что продавец разместивший лот на Aliexpress упомянул в тексте не только VOC, но и СО2. Любители халявы моментально набросились на устройство, которое продавалось менее, чем за 50 долларов, хотя любому здравомыслящему человеку очевидно, что за эти деньги невозможно купить устройство с настоящим NDIR-сенсором CO2.
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
И что же оказалось на самом деле? Нет, это не СО2-реле для приточной вентиляции. Это — VOC-реле для управления ВЫТЯЖНОЙ вентиляцией. Об этом во-первых, чётко написано в инструкции. А во-вторых, это очевидно из принципа работы. Устройство имеет на борту 4 реле: 3 из которых используются для включения вентилятора на разных скоростях, а ещё одно — для управления байпасным клапаном приточной вентиляции (на экране даже видна иконка). Следовательно это устройство предназначено для установки на вытяжку рабочей зоны (на кухне, например). Управление байпасом нужно потому что как мы знаем кухонная вытяжка не «дружит» с приточно-вытяжной вентиляцией. А в грязной зоне нет необходимости измерять конкретно СО2. Задача более глобальна — удалить запахи. Поэтому в устройстве и используется VOC сенсор Winsen MG-812. Использовать это устройство как СО2 измеритель или реле для управления приточной вентиляцией не получится.

Также стоит отметить различные самодельные устройства, которые люди пытаются сделать с помощью Ardiuno/Raspberry и самого дешёвого NDIR CO2 датчика Winsen MH-Z19(B). Во-первых, себестоимость устройства всё равно получится в районе 100 долларов (а значит какой смысл этим заниматься, если можно купить готовое устройство?). Во-вторых, надежность и точность самодельного измерителя будет гораздо ниже, чем заводское решение (все самоделки обычно заваливают автоматическую калибровку).

CleanSpace Tag
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
Это устройство с «вечным» аккумулятором выделяется из обзора тем, что оно не измеряет ни CO2, ни PM2.5. Зато оно может измерять концентрацию смертельно опасного для человека угарного газа (СО). Актуально оно только для измерений на улице (в пробках) или на подземных парковках. В квартиры (в особенности на высоких этажах) угарный газ обычно не попадает. Несмотря на то, что само устройство заряжается от радиосигнала GSM и Wi-Fi, его мобильное приложение для полноценной работы требует держать включенным GPS, что крайне негативно сказывается на времени работы вашего смартфона. Зато хочу отметить один интересный момент, который обнаружился в процессе эксплуатации — bluetooth-соединение со смартфоном поддерживается на расстоянии не менее 20 метров. Устройство производят в Англии и официально в России не продают, но мне удалось договориться о продаже в Россию.

Подробнее про CleanSpace Tag можно прочитать здесь.

Xiaomi PM2.5 Detector
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
Как следует из названия это детектор частиц PM2.5 от Xiaomi. Оснащён Wi-Fi (может управляться через фирменное приложение Mi Home). Имеет аккумулятор, на котором способен проработать около 2-3 часов. Предназначен для китайского рынка с зашитым часовым поясом, который нельзя сменить (во всём Китае используется один часовой пояс) — использовать как ночник/часы не получится. Очень компактное и удобное устройство, которое можно взять с собой. Купить можно в России или на Aliexpress.

WP-6910/6912 (на фото слева и справа)
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
Два самых доступных на рынке переносных детектора частиц PM2.5 и летучей органики VOC. Модели отличаются только экранами (с цветным экраном дороже). Имеют встроенный аккумулятор и позволяют проводить мобильные измерения частиц PM2.5, формальдегида и летучей органики. Со своими функциями справляются отлично. Купить можно на Aliexpress.

Так что же выбрать?
Датчик CO2 — прибор, который подскажет когда проветрить, чтобы думалось эффективнее / Хабр
Для начала стоит определиться, что именно вы хотите измерять и какую задачу решить.

Если вас беспокоит грязный воздух на улице, то нужен детектор частиц PM2.5. То есть очевидно, что любой детектор частиц PM2.5 должен быть мобильным. Ещё датчик частиц PM2.5 будет вам очень полезен, если вы пользуетесь печным отоплением/камином. Я рекомендую Xiaomi PM2.5 Detector.

Если в квартире/офисе душно и вы хотите отслеживать момент, когда надо открыть окно для проветривания — вам нужен датчик углекислого газа CO2. Я рекомендую iCeeO2-500.

Но на самом деле, измерения качества воздуха в помещениях упираются в то, что внутри жилых помещений обязательно необходима автоматизированная приточная вентиляционная система с фильтрацией.

Получается замкнутый круг: в комнате стало душно (высокая концентрация СО2) — вы открыли окно, чтобы подать в комнату свежий воздух, а на улице грязный воздух (частицы PM2.5) — окно нужно снова закрывать.

Конечно можно бороться со следствием (частицы PM2.5 попавшие в квартиру через окно с улицы) и установить мойку воздуха, но на самом деле нужно бороться с источником загрязнения (улица) и использовать приточную вентиляцию с фильтрацией. А ещё не забывайте, что если у вас нет приточной вентиляции, то окно фактически придётся постоянно держать открытым (либо открывать на 5 минут каждые 30 минут и делать так круглосуточно — я публиковал замеры ранее).

Это же касается формальдегида и летучей органики VOC. Они есть в любой квартире и офисе. Эмиссия вредных веществ из отделочных материалов и мебели имеет место было в течение всего срока службы. Это тот самый запах, который вы ощущаете когда входите в квартиру, в которой не было людей и были закрыты окна (вроде не душно, а есть какой-то запах). Но все эти воздушные загрязнители элементарно удаляются с помощью принудительной приточной вентиляции. А если у вас есть принудительная приточная вентиляция, то получается, что у вас нет необходимости в датчике СО2 (кроме как для отслеживания момента замены фильтра).

Если вы хотите измерять и CO2 и PM2.5, то альтернатив у AirVisual Pro нет. Поскольку ни Awair, ни uHoo Air Sensor нельзя взять с собой. И AirVisual фактически единственное устройство, которое имеет встроенную память измерений.

Берегите своё здоровье и здоровье ваших близких.
И не забывайте: одно дело кратковременное воздействие загрязнителей (пробка на улице, душный вагон), совсем другое — регулярное (ваша квартира, в которой вы проводите половину своей жизни).

Литература для самостоятельного изучения:
Сколько воздуха нужно человеку для комфорта?
К вопросу о нормировании воздухообмена по содержанию СО2 в наружном и внутреннем воздухе
Качество внутреннего воздуха в зданиях, построенных в холодном климате
Частицы РМ2.5: что это, откуда и почему об этом все говорят

Остались вопросы? Задавайте их в комментариях!

И не забудьте подписаться на мой блог, чтобы не пропустить новые статьи!

§

§

§

§

Оцените статью
Анемометры
Добавить комментарий