Подключаем к ESP8266 датчик SenseAir S8-0053

Сегодня ко мне приехал новый датчик измерения уровня CO2: SenseAir S8-0053.

SenseAir S8-0053 (front)
SenseAir S8-0053: вид спереди
SenseAir S8-0053 (back)
SenseAir S8-0053: вид сзади

Читал о нем достаточно, что бы предположить, что он будет лучше, чем имеющийся у меня на данный момент MH-Z19B, который и сам по себе неплох, по крайней мере уровень CO2 он действительно измеряет, а не угадывает, как делают многие более бюджетные варианты. К сожалению его функция автокалибровки, если не отключить ее вовремя, через некоторое время благополучно начнет занижать полученные значения, поэтому приходится либо проветривать чаще, либо жить с теми значениями, которые он выдает. Автокалибровка в S8 устроена по другому, и по даташиту она учитывает последние 15 дней измерений (а по факту отвечает, что 180 часов), что в свою очередь, при условии обычного проветривания помещения, уже будет выдавать более правдивые значения гораздо чаще.

Но в общем датчик то приехал, а как оказалось библиотек для него в интернет не подвезли еще. Поэтому поискав некоторое время, понял, что прийдется писать под него обработчик самому. Был конечно вариант использовать ESPEasy, в котором, как я понял, поддержка этого датчика присутствует, но это значит мне потребуется еще одна ESP, а это не рационально, учитывая, что у меня практически без дела запущен D1 Mini для проброса мультикастов от Xiaomi Gateway в MQTT.

В результате, практически на коленке был написан такой скетч. Его основная задача была дать мне понять, смогу с работать с этим датчиком или нет, поэтому данные в нем никуда не передаются, а просто выводятся на консоль, для тестов. Тем не менее, возможно кому-нибудь это будет интересно или полезно, как база для своих дальнейших проектов.

#include <ESP8266WiFi.h>
#include <SoftwareSerial.h>

#include <arduino_secrets.h>
#include "arduino_secrets.h"

const char* ssid          = SECRET_SMARTHOME_WIFI_SSID;
const char* password      = SECRET_SMARTHOME_WIFI_PASSWORD;

#define D7 (13)
#define D8 (15)

SoftwareSerial s8Serial(D7, D8);

int s8_co2;
int s8_co2_mean;
int s8_co2_mean2;

float smoothing_factor = 0.5;
float smoothing_factor2 = 0.15;

byte cmd_s8[]       = {0xFE, 0x04, 0x00, 0x03, 0x00, 0x01, 0xD5, 0xC5};
byte abc_s8[]       = {0xFE, 0x03, 0x00, 0x1F, 0x00, 0x01, 0xA1, 0xC3};
byte response_s8[7] = {0, 0, 0, 0, 0, 0, 0};

const int r_len = 7;
const int c_len = 8;

boolean wifi_reconnect() {
  Serial.printf("\nConnecting to %s ", ssid);
  WiFi.hostname("TestUnit");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.printf("\nConnected to the WiFi network: %s\n", ssid);
  return true;
}

void s8Request(byte cmd[]) {
  s8Serial.begin(9600);
  while(!s8Serial.available()) {
    s8Serial.write(cmd, c_len); 
    delay(50); 
  }
  int timeout=0;
  while(s8Serial.available() < r_len ) {
    timeout++; 
    if(timeout > 10) {
      while(s8Serial.available()) {
        s8Serial.read();
        break;
      }
    } 
    delay(50); 
  } 
  for (int i=0; i < r_len; i++) { 
    response_s8[i] = s8Serial.read(); 
  }
  
  s8Serial.end();
}                     

unsigned long s8Replay(byte rc_data[]) { 
  int high = rc_data[3];
  int low = rc_data[4];
  unsigned long val = high*256 + low;
  return val; 
}

void co2_measure() {
  s8Request(cmd_s8);
  s8_co2 = s8Replay(response_s8);
  
  if (!s8_co2_mean) s8_co2_mean = s8_co2;
  s8_co2_mean = s8_co2_mean - smoothing_factor*(s8_co2_mean - s8_co2);
  
  if (!s8_co2_mean2) s8_co2_mean2 = s8_co2;
  s8_co2_mean2 = s8_co2_mean2 - smoothing_factor2*(s8_co2_mean2 - s8_co2);

  Serial.printf("CO2 value: %d, M1Value: %d, M2Value: %d\n", s8_co2, s8_co2_mean, s8_co2_mean2);
  return;
}

void get_abc() {
  int abc_s8_time;
  s8Request(abc_s8);
  abc_s8_time = s8Replay(response_s8);

  Serial.printf("ABC Time is: %d hours\n", abc_s8_time);
  return;
}

void setup() {
  Serial.begin(115200);
  delay(10);

  if(WiFi.status() != WL_CONNECTED) {
    wifi_reconnect();
  }

  get_abc();
}

void loop() {
  co2_measure();
  delay(10 * 1000L);
}

Ссылка на GitHub репозиторий – тут.

Распиновка датчика выглядит вот так:

SenseAir S8-0053 pin assignment
Распиновка S8-0053

G+ – Power supply plus terminal.
Unprotected against reverse connection!
4.5 V to 5.25 V

G0 – Power supply minus terminal
Sensor’s reference (ground) terminal

UART_RxD – UART data receive line Configured as digital input

UART_TxD – UART data transmission line Configured as digital output

Соответственно, в скетче используются пины
D7 – UART_TxD
D8 – UART_RxD

Ссылка на даташит:
http://www.co2meters.com/Documentation/Datasheets/DS-S8-3.2.pdf

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

Ну и общие мои впечатления от датчика: понравился. Удобный форм-фактор, что бы сделать из него и D1 Mini этакий бутербродик, главное не забыть отверстием для забора воздуха наверх это сделать, точно показывает (хотя конечно в качестве эталона использую показания MH-Z19B), быстро реагирует на повышение CO2 около него, ну и в целом как то он поприятнее вышеназванного MH-Z19B.

Ссылки на страницы, которые были полезны при изучении процесса работы работы с этим датчиком:
https://mysku.ru/blog/china-stores/75322.html
https://www.instructables.com/id/Weather-and-Air-Quality-Monitor/
https://github.com/miaoski/esp8266-co2

https://rmtplusstoragesenseair.blob.core.windows.net/docs/Dev/publicerat/TDE2067.pdf (не без опечаток, как и положено хорошей документации)

6 ответов к «Подключаем к ESP8266 датчик SenseAir S8-0053»

  1. код вызывает ряд вопросов
    например
    while(s8Serial.available()) {
    s8Serial.read();
    break;
    }
    почему вместо While не написать просто
    if(s8Serial.available())
    s8Serial.read();
    ?
    да и внешний while(s8Serial.available() < r_len )
    – что за проверка на < r_Len, в чем ее физический смысл?

    1. по первому вопросу -потому что читаем побитно, в случае if мы прочитаем только один бит

      по второму из той же серии – читаем столько сколько мы ожидаем получить

  2. А где вы взяли адреса регистров, номера команд и вообще особенности протокола? Везде пишут уже готовый код, а откуда берутся данные нигде не написано

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *