Инструменты пользователя

Инструменты сайта


iot:firmware:arduino:amica:nodemcu:v3:esp12-e:bosch:bme680

Барометр на базе BME680 под Arduino

Почти год назад я купил плату разработки Amica NodeMCU V3 с ESP-12E, который ESP8266MOD, и BOSCH BME6801), чтобы сделать барометр. Но… В Tasmota нет нормального драйвера для этого датчика2)! А решиться и взяться за Arduino и его Arduino C, для которых доступна полноценная библиотека BSEC3), все не мог – никогда с этой средой разработки не работал, как и с C++ вообще. А тут…

Кстати, помимо датчика BME680, туда предполагалось подключить еще реле для управления светом на балконе, для чего тогда же был куплен безымянный пятивольтовый модуль.

:!: Облегченная, переработанная и дополненная версия скетча находится тут.

Оборудование

В общем итоге нужно:

  1. NodeMCU с ESP-12E;
  2. CJMCU-680 (Bosch BME680);
  3. 1ch LLT4) relay;
  4. PSU 220 VAC / 5 VDC.

Крупнее!

Подключение

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

NodeMCU BME680
D1 SCL
D2 SDA
3V3 VCC
GND GND
GND SDO
NodeMCU Relay
D5 IN
3V35) VCC
GND GND
NodeMCU Power
VIN +5V
GND GND

Важные замечания:

  1. Опять же, в Интернете бродят слухи, что эти платы разработки есть разные – некоторые развязаны с USB по питанию VIN, некоторые нет, из-за чего лучше не рисковать и не использовать сразу два источника – углубляться в этот вопрос не хотелось, поэтому как есть – VIN стоит задействовать после сборки и отладки, а до этого питаться от USB6).
  2. И еще нюанс – то ли BME680, то ли шина I²C, так и не понял, очень вредные – линии от одного метра и больше, экранированные и нет, витые и нет, не работают! Т.е. работают, но с разной степенью нестабильности – рано или поздно, но датчик отваливается с ошибкой «bme680 error code -2». Есть предположение, что виной всему блок питания, т.к. кажется, что при питании от USB, сбоев то ли нет, то ли сильно меньше, но я забил на эксперименты и остановился на линии в 30 сантиметров, которая работает стабильно!
  3. Ну и последнее – стоить помнить, что ESP-12E и PSU имеют тенденцию нещадно греться и размещать с ними в одном корпусе BME680 не стоит, как сперва по наивности сделал я!

Среда разработки

Первым делом необходимо загрузить Arduino IDE7), затем добавить в него поддержку плат ESP8266 и дополнительных библиотек.

Плата ESP8266

Краткое изложение этой статьи:

  1. перейти «Файл» → «Настройки» и добавить адрес http://arduino.esp8266.com/stable/package_esp8266com_index.json в «Дополнительные ссылки для Менеджера плат»;
  2. перейти «Инструменты» → «Плата: […]» → «Менеджер плат…», найти esp8266 и установить;
  3. перейти «Инструменты» → «Плата: […]» → «ESP8266 Boards (x.x.x)» и выбрать плату «NodeMCU 1.0 (ESP-12E Module)».

Дополнительно может потребоваться выбрать COM-порт в меню «Инструменты» → «Порт: […]».

Библиотеки

Собственно, нужны только две дополнительные библиотеки, остальное уже есть в комплекте с IDE и платой.

BSEC

Краткое изложение и перевод этой статьи:

  1. Найти и добавить библиотеку BSEC через «Инструменты» → «Управлять библиотекам…»;
  2. открыть «platform.txt»8) и после «compiler.elf2hex.extra_flags=» добавить compiler.libraries.ldflags=;
  3. ниже, в блоке «## Combine gc-sections, archives, and objects», после «{compiler.c.elf.libs}»9) добавить {compiler.libraries.ldflags}.

PubSubClient

Загрузить отсюда10) и через меню «Скетч» → «Подключить библиотеку» → «Добавить .ZIP библиотеку…».

Набросок

Ниже представлен рабочий11) скетч12), который подключается к Wi-Fi, передает данные с датчика и получает команды для реле по MQTT.

barometer-v1.0.0.ino
// Written by Nikolay Soloshin (nikolay@soloshin.su) for NodeMCU v3 with ESP-12E & BME680 @ 2022.02
 
/* Подключение библиотек */
#include <ESP8266WiFi.h> // v3.0.2 (ESP8266 Community)
#include <PubSubClient.h> // v2.8.0
#include <bsec.h> // v1.6.1480
 
/* Инвертирование значений*/
#define iHIGH LOW
#define iLOW HIGH
 
/* Пользовательские настройки */
const char* wifi_ssid = "SSiD";
const char* wifi_pwd =  "pWd";
 
const char* mqtt_server = "192.168.XXX.XXX";
const int mqtt_port = 1883;
const char* mqtt_login = "user";
const char* mqtt_pwd = "pwd";
const char* mqtt_clientid = "bme680_XXXXXX";
 
const char* mqtt_sub_power = "barometer_XXXXXX/cmnd/POWER";
const char* mqtt_pub_power = "barometer_XXXXXX/stat/POWER";
const char* mqtt_pub_sensor = "barometer_XXXXXX/tele/SENSOR";
const char* mqtt_lwt_topic = "barometer_XXXXXX/tele/LWT";
const char* mqtt_state_topic = "barometer_XXXXXX/tele/STATE";
 
// Частота публикации данных в MQTT (часы * минуты * секунды * 1000)
const int mqtt_pub_freq = 5 * 60 * 1000; // Каждые 2 часа = 2 * 60 * 60 * 1000
 
// Сдвиг для приведения к уровню моря в Па.
// Уменьшение давления на 100 Па с увеличением высоты на 8 м.
// https://www.fxyz.ru/формулы_по_физике/механика/аэростатика/атмосферное_давление/#
const int bme_p_offset = 2225; // Высота датчика (178 м.) / 8 * 100.
 
// Корректировка показаний температуры в °C
const int bme_t_offset = -1.1; // К примеру, -1.1, 0 или 1.15
 
// Константа для перевода Па в мм.р.с.
const float bme_mmHg = 0.0075;
 
// Состояние реле при загрузке
const uint8_t relay_state = iLOW; // iHIGH - включено, iLOW - выключено
 
// Назначение GPIO
const int bme_led = D0;
const int status_led = LED_BUILTIN;
const int relay = D5;
 
// Выключение светодиодов через некоторое время
const boolean leds_off = true; // false - не выключать
long leds_off_delay = 60 * 1000; // Через 2 минуты = 2 * 60 * 1000
 
/* Прочие нужные дела */
const char* sk_version = "1.0.0 (sk1)"; // Версия скетча
 
// Объявление функций
void checkIaqSensorStatus(void);
void BMEerrLeds(void);
void MQTTErrorLED (void);
 
// Создание объектов
WiFiClient espClient;
PubSubClient MQTT(espClient);
Bsec iaqSensor;
 
// Объявление служебных переменных
String bme_output;
long lastReconnectAttempt = 0;
unsigned long MQTTLastPubTime = 0;
boolean leds_off_status;
 
/* Настройка при запуске */
void setup() {
 
  // Инициализация GPIO
  pinMode(bme_led, OUTPUT);
  pinMode(status_led, OUTPUT);
  pinMode(relay, OUTPUT);
 
  // Выключение LED и реле при запуске
  digitalWrite(bme_led, iLOW);
  digitalWrite(relay, relay_state);
 
  // Инициализация COM
  Serial.begin(115200);
  delay(3000);
 
  // Инициализация Wi-Fi
  WiFi.begin(wifi_ssid, wifi_pwd);
  Serial.println(F("Hi guys and girls!"));
  Serial.print(F("Connecting to Wi-Fi"));
  while (WiFi.status() != WL_CONNECTED) {
    digitalWrite(status_led, iHIGH);
    delay(250);
    Serial.print(".");
    digitalWrite(status_led, iLOW);
    delay(250);
    }
  Serial.print(F("\nConnected to Wi-Fi: "));
  Serial.print(WiFi.SSID());
  Serial.print(F(" (IP: "));
  Serial.print(WiFi.localIP());
  Serial.println(F(")!"));
  digitalWrite(status_led, iLOW);
 
  // Инициализация MQTT
  MQTT.setServer(mqtt_server, mqtt_port);
  MQTT.setCallback(MQTTcallback);
  while (!MQTT.connected()) {
    Serial.println(F("Connecting to MQTT..."));
    if (MQTT.connect(mqtt_clientid, mqtt_login, mqtt_pwd)) {
      Serial.println(F("MQTT connected!"));
      MQTT.publish(mqtt_state_topic, "MQTT connected!");
      digitalWrite(status_led, iHIGH);
      } else {
        digitalWrite(status_led, iHIGH);
        delay(500);
        Serial.print(F("Failed with state "));
        Serial.println(MQTT.state());
        digitalWrite(status_led, iLOW);
        delay(500);
        }
      }
 
  // Подписка и публикация начальных значений
  MQTT.subscribe(mqtt_sub_power);
  MQTT.publish(mqtt_lwt_topic, "Online", true);
  MQTT.publish(mqtt_pub_power, digitalRead(relay) == iHIGH ? "ON" : "OFF");
 
  // Инициализация BSEC
  Wire.begin();
  iaqSensor.begin(BME680_I2C_ADDR_PRIMARY, Wire);
  bme_output = "BSEC library version " + String(iaqSensor.version.major) + "."
    + String(iaqSensor.version.minor) + "." + String(iaqSensor.version.major_bugfix)
    + "." + String(iaqSensor.version.minor_bugfix);
  checkIaqSensorStatus();
  bsec_virtual_sensor_t sensorList[10] = {
    BSEC_OUTPUT_RAW_TEMPERATURE,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_RAW_HUMIDITY,
    BSEC_OUTPUT_RAW_GAS,
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_STATIC_IAQ,
    BSEC_OUTPUT_CO2_EQUIVALENT,
    BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
    };
  iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP);
  checkIaqSensorStatus();
 
  digitalWrite(bme_led, iHIGH);
  bme_output += ", Sketch version " + String(sk_version) + ". Loading complete!";
  Serial.println(bme_output);
  MQTT.publish(mqtt_state_topic, bme_output.c_str());
  }
 
/* Функция MQTT для приема сообщений и работы реле */
void MQTTcallback(char* topic, byte* payload, unsigned int length) {
 
  // Преобразование нагрузки в строку
  String message;
  for (int i = 0; i < length; i++) {
    message = message + (char)payload[i];
    }
 
  // Переключение реле
  if (message == "ON") {
    digitalWrite(relay, iHIGH);
    }
    else if (message == "OFF") {
      digitalWrite(relay, iLOW);
      }
      else if (message == "TOGGLE") {
        if (digitalRead(relay) == iHIGH)
        {
          digitalWrite(relay, iLOW);
          } else {
            digitalWrite(relay, iHIGH);
            }
          }
 
  // Публикация статуса реле
  char* relay_status;
  if (digitalRead(relay) == iHIGH) {
    relay_status = "ON";
    } else {
      relay_status = "OFF";
      }
  MQTT.publish(mqtt_pub_power, relay_status);
  }
 
/* Основной цикл */
void loop() {
  unsigned long now = millis();
 
  // Чтение данных с датчика BME680
  if (iaqSensor.run()) { // Есть новые данные (раз в 3 секунды)
 
    // Сборка строки
    bme_output = "{\"pressure_st\":" + String(iaqSensor.pressure * bme_mmHg);
    bme_output += ",\"pressure_sl\":" + String((iaqSensor.pressure + bme_p_offset) * bme_mmHg);
    bme_output += ",\"temperature\":" + String(iaqSensor.temperature + bme_t_offset);
    bme_output += ",\"humidity\":" + String(iaqSensor.humidity);
    bme_output += ",\"pressure_raw\":" + String(iaqSensor.pressure);
    //bme_output += ",\"temperature_raw\":" + String(iaqSensor.rawTemperature);
    //bme_output += ",\"humidity_raw\":" + String(iaqSensor.rawHumidity);
    bme_output += ",\"iaq\":" + String(iaqSensor.iaq);
    bme_output += ",\"iaq_accuracy\":" + String(iaqSensor.iaqAccuracy);
    bme_output += ",\"static_iaq\":" + String(iaqSensor.staticIaq);
    bme_output += ",\"co2_equivalent\":" + String(iaqSensor.co2Equivalent);
    bme_output += ",\"breath_voc_equivalent\":" + String(iaqSensor.breathVocEquivalent);
    bme_output += "}";
    Serial.println(bme_output);
 
    // Сообщения для отладки
    //String bme_debug = "New data is available from ";
    //unsigned long time_trigger = millis();
    //bme_debug += String(time_trigger) + "!";
    //MQTT.publish(mqtt_state_topic, bme_debug.c_str());
    } else {
      checkIaqSensorStatus();
      }
 
  // Проверка соединения с брокером MQTT
  if (!MQTT.connected()) {
    if (now - lastReconnectAttempt > 5000) {
      lastReconnectAttempt = now;
      MQTTErrorLED();
      Serial.println(F("MQTT disconnected!"));
      if (MQTTreconnect()) {
        lastReconnectAttempt = 0;
        digitalWrite(status_led, iHIGH);
        Serial.println(F("MQTT reconnected!"));
        MQTT.publish(mqtt_state_topic, "MQTT reconnected!");
 
        // Сброс состояния выключения LED
        leds_off_status = false;
        leds_off_delay = now + 60000; // Выключить через минуту
        }
      }
    } else {
 
      // Публикация телеметрии датчика
      if (now - MQTTLastPubTime > mqtt_pub_freq || now - MQTTLastPubTime < 0) {
        MQTTLastPubTime = now;
        Serial.println(F("Publishing MQTT message..."));
        MQTT.publish(mqtt_pub_sensor, bme_output.c_str());
        }
      }
 
  // Выключение светодиодов
  if (leds_off && now > leds_off_delay && !leds_off_status) {
    Serial.println(F("Turning off LEDs by timeout!"));
    MQTT.publish(mqtt_state_topic, "Turning off LEDs by timeout!");
    digitalWrite(status_led, iLOW);
    digitalWrite(bme_led, iLOW);
    leds_off_status = true;
    }
 
  // Циклы библиотеки MQTT
  MQTT.loop();
  }
 
/* Функция проверки BSEC и датчика BME680 */
void checkIaqSensorStatus(void) {
  if (iaqSensor.status != BSEC_OK) {
    if (iaqSensor.status < BSEC_OK) {
      bme_output = "BSEC error code: " + String(iaqSensor.status);
      Serial.println(bme_output);
      MQTT.publish(mqtt_state_topic, bme_output.c_str());
      for (;;)
        BMEerrLeds(); // Остановка в случае сбоя
      } else {
        bme_output = "BSEC warning code: " + String(iaqSensor.status);
        Serial.println(bme_output);
        MQTT.publish(mqtt_state_topic, bme_output.c_str());
        }
      }
 
  if (iaqSensor.bme680Status != BME680_OK) {
    if (iaqSensor.bme680Status < BME680_OK) {
      bme_output = "BME680 error code: " + String(iaqSensor.bme680Status);
      Serial.println(bme_output);
      MQTT.publish(mqtt_state_topic, bme_output.c_str());
      for (;;)
        BMEerrLeds(); // Остановка в случае сбоя
    } else {
      bme_output = "BME680 warning code: " + String(iaqSensor.bme680Status);
      Serial.println(bme_output);
      MQTT.publish(mqtt_state_topic, bme_output.c_str());
      }
    }
  }
 
/* Функция сигнализации о сбое BME или BSEC */
void BMEerrLeds(void) {
  digitalWrite(bme_led, iLOW);
  delay(100);
  digitalWrite(bme_led, iHIGH);
  delay(100);
  }
 
/* Функция сигнализации о сбое Wi-Fi или MQTT */
void MQTTErrorLED (void) {
  digitalWrite(status_led, iHIGH);
  delay(100);
  digitalWrite(status_led, iLOW);
  delay(100);
  digitalWrite(status_led, iHIGH);
  delay(100);
  digitalWrite(status_led, iLOW);
  delay(100);
  digitalWrite(status_led, iHIGH);
  delay(100);
  digitalWrite(status_led, iLOW);
  }
 
/* Функция переподключения MQTT */
boolean MQTTreconnect() {
  if (MQTT.connect(mqtt_clientid, mqtt_login, mqtt_pwd)) {
    MQTT.publish(mqtt_lwt_topic, "Online", true);
    MQTT.subscribe(mqtt_sub_power);
    }
    return MQTT.connected();
    }
 
// With love from Vladivostok.

:!: Если будет терять соединение с Wi-Fi, смотреть сюда.


Дисклеймер

  • Использование материалов данной базы знаний разрешено на условиях лицензии, указанной внизу каждой страницы! При использовании материалов активная гиперссылка на соответствующую страницу данной базы знаний обязательна!
  • Автор не несет и не может нести какую либо ответственность за последствия использования материалов, размещенных в данной базе знаний. Все материалы предоставляются по принципу «как есть». Используйте их исключительно на свой страх и риск.
  • Все высказывания, мысли или идеи автора, размещенные в материалах данной базе знаний, являются исключительно его личным субъективным мнением и могут не совпадать с мнением читателей!
  • При размещении ссылок в данной базе знаний на интернет-страницы третьих лиц автор не несет ответственности за их техническую функциональность (особенно отсутствие вирусов) и содержание! При обнаружении таких ссылок, можно и желательно сообщить о них в комментариях к соответствующей статье.
1)
Даташит тут или локально тут.
2)
Есть некий дженерик, который выдает только сырые данные – температуру, влажность, давление и сопротивление газового сенсора.
3)
Зеркало версии 1.4.8.0 тут.
4)
Low Level Trigger.
5)
Это не ошибка! При подключении этого реле на 5 вольт, он залипает в каком-то из положений и не работает.
6)
Для подключения к плате после монтажа, я просто добавил микропереключатель, отключающий линию VIN.
7)
В моем случае, это версия 1.8.19 (Windows Store 1.8.57.0).
8)
Для версии Arduino IDE, установленной из Microsoft Store, он находится в папке %HOMEPATH%\Documents\ArduinoData\packages\esp8266\hardware\esp8266\x.x.x, а для версии, скачанной с сайта, тут – %HOMEPATH%\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\x.x.x, где «x.x.x» версия ядра ESP8266.
9)
Там очень длинная строка, где-то в конце.
10)
Локальное зеркало текущей на момент написания статьи версии 2.8 от 20 мая 2020.
11)
При условии использования вышеуказанных компонентов и подключений.
12)
При написании использована эта статья, примеры к библиотекам и просторы Интернета.

Обсуждение

Ваш комментарий:
X X W Y G J D I Y H B W M H Q G
 
Последнее изменение: 2022/06/30 09:06 — Николай Солошин