diff --git a/platformio.ini b/platformio.ini index 7a8efcaf..6efabe2b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -120,6 +120,7 @@ build_flags = -D DECODE_SAMSUNG=true -D DECODE_LG=true -DWLED_USE_MY_CONFIG + ; -D USERMOD_SENSORSTOMQTT build_unflags = -Wall @@ -210,6 +211,10 @@ lib_deps = #milesburton/DallasTemperature@^3.9.0 #For BME280 sensor uncomment following #BME280@~3.0.0 + ; adafruit/Adafruit BMP280 Library @ 2.1.0 + ; adafruit/Adafruit CCS811 Library @ 1.0.4 + ; adafruit/Adafruit Si7021 Library @ 1.4.0 + lib_ignore = AsyncTCP diff --git a/usermods/sensors_to_mqtt/readme.md b/usermods/sensors_to_mqtt/readme.md new file mode 100644 index 00000000..7f2d6407 --- /dev/null +++ b/usermods/sensors_to_mqtt/readme.md @@ -0,0 +1,87 @@ +# Sensors To Home Assistant (or mqtt) + +This usermod will publish values of the BMP280, CCS811 and Si7021 sensors to Home Assistant via MQTT. + +Its using home assistant automatic device discovery feature. + +The use of Home Assistant is not mandatory; it will publish the sensor values via MQTT just fine without it. + +Its resusing the mqtt connection set in the WLED web user interface. + +## Maintainer + +twitter.com/mpronk89 + +## Features + +- Reads BMP280, CCS811 and Si7021 senors +- Publishes via MQTT, configured via webui of wled +- Announces device in Home Assistant for easy setup +- Efficient energy usage +- Updates every 60 seconds + +## Example mqtt topics: + +`$mqttDeviceTopic` is set in webui of WLED! + +``` +temperature: $mqttDeviceTopic/temperature +pressure: $mqttDeviceTopic/pressure +humidity: $mqttDeviceTopic/humidity +tvoc: $mqttDeviceTopic/tvoc +eCO2: $mqttDeviceTopic/eco2 +IAQ: $mqttDeviceTopic/iaq +``` + +# Installation + +## Hardware + +### Requirements + +1. BMP280/CCS811/Si7021 sensor. E.g. https://aliexpress.com/item/32979998543.html +2. A microcontroller which can talk i2c, e.g. esp32 + +### installation + +Attach the sensor to the i2c interface. + +Default PINs esp32: + +``` +SCL_PIN = 22; +SDA_PIN = 21; +``` + +Default PINs ESP8266: + +``` +SCL_PIN = 5; +SDA_PIN = 4; +``` + +## Enable in WLED + +1. Copy `usermod_v2_SensorsToMqtt.h` into the `wled00` directory. +2. Add to `build_flags` in platformio.ini: + +``` + -D USERMOD_SENSORSTOMQTT +``` + +3. And add to `lib_deps` in platformio.ini: + +``` + adafruit/Adafruit BMP280 Library @ 2.1.0 + adafruit/Adafruit CCS811 Library @ 1.0.4 + adafruit/Adafruit Si7021 Library @ 1.4.0 +``` + +The #ifdefs in `usermods_list.cpp` should do the rest :) + +# Credits + +- Aircoookie for making WLED +- Other usermod creators for example code +- Bouke_Regnerus for https://community.home-assistant.io/t/example-indoor-air-quality-text-sensor-using-ccs811-sensor/125854 +- You, for reading this diff --git a/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h new file mode 100644 index 00000000..dd7aedc1 --- /dev/null +++ b/usermods/sensors_to_mqtt/usermod_v2_SensorsToMqtt.h @@ -0,0 +1,284 @@ +#pragma once + +#include "wled.h" +#include +#include +#include +#include +#include +#include + +Adafruit_BMP280 bmp; +Adafruit_Si7021 si7021; +Adafruit_CCS811 ccs811; + +#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards +uint8_t SCL_PIN = 22; +uint8_t SDA_PIN = 21; +#else //ESP8266 boards +uint8_t SCL_PIN = 5; +uint8_t SDA_PIN = 4; +#endif + +class UserMod_SensorsToMQTT : public Usermod +{ +private: + bool initialized = false; + bool mqttInitialized = false; + float SensorPressure = 0; + float SensorTemperature = 0; + float SensorHumidity = 0; + char *SensorIaq = "Unknown"; + String mqttTemperatureTopic = ""; + String mqttHumidityTopic = ""; + String mqttPressureTopic = ""; + String mqttTvocTopic = ""; + String mqttEco2Topic = ""; + String mqttIaqTopic = ""; + unsigned int SensorTvoc = 0; + unsigned int SensorEco2 = 0; + unsigned long nextMeasure = 0; + + void _initialize() + { + initialized = bmp.begin(BMP280_ADDRESS_ALT); + bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */ + Adafruit_BMP280::SAMPLING_X16, /* Temp. oversampling */ + Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */ + Adafruit_BMP280::FILTER_X16, /* Filtering. */ + Adafruit_BMP280::STANDBY_MS_2000); /* Refresh values every 20 seconds */ + + initialized &= si7021.begin(); + initialized &= ccs811.begin(); + ccs811.setDriveMode(CCS811_DRIVE_MODE_10SEC); /* Refresh values every 10s */ + Serial.print(initialized); + } + + void _mqttInitialize() + { + mqttTemperatureTopic = String(mqttDeviceTopic) + "/temperature"; + mqttPressureTopic = String(mqttDeviceTopic) + "/pressure"; + mqttHumidityTopic = String(mqttDeviceTopic) + "/humidity"; + mqttTvocTopic = String(mqttDeviceTopic) + "/tvoc"; + mqttEco2Topic = String(mqttDeviceTopic) + "/eco2"; + mqttIaqTopic = String(mqttDeviceTopic) + "/iaq"; + + String t = String("homeassistant/sensor/") + mqttClientID + "/temperature/config"; + + _createMqttSensor("temperature", mqttTemperatureTopic, "temperature", "°C"); + _createMqttSensor("pressure", mqttPressureTopic, "pressure", "hPa"); + _createMqttSensor("humidity", mqttHumidityTopic, "humidity", "%"); + _createMqttSensor("tvoc", mqttTvocTopic, "", "ppb"); + _createMqttSensor("eco2", mqttEco2Topic, "", "ppm"); + _createMqttSensor("iaq", mqttIaqTopic, "", ""); + } + + void _createMqttSensor(const String &name, const String &topic, const String &deviceClass, const String &unitOfMeasurement) + { + String t = String("homeassistant/sensor/") + mqttClientID + "/" + name + "/config"; + + StaticJsonDocument<300> doc; + + doc["name"] = name; + doc["state_topic"] = topic; + doc["unique_id"] = String(mqttClientID) + name; + if (unitOfMeasurement != "") + doc["unit_of_measurement"] = unitOfMeasurement; + if (deviceClass != "") + doc["device_class"] = deviceClass; + doc["expire_after"] = 1800; + + JsonObject device = doc.createNestedObject("device"); // attach the sensor to the same device + device["identifiers"] = String("wled-sensor-") + mqttClientID; + device["manufacturer"] = "Aircoookie"; + device["model"] = "WLED"; + device["sw_version"] = VERSION; + device["name"] = mqttClientID; + + String temp; + serializeJson(doc, temp); + Serial.println(t); + Serial.println(temp); + + mqtt->publish(t.c_str(), 0, true, temp.c_str()); + } + + void _updateSensorData() + { + SensorTemperature = bmp.readTemperature(); + SensorHumidity = si7021.readHumidity(); + SensorPressure = (bmp.readPressure() / 100.0F); + ccs811.setEnvironmentalData(SensorHumidity, SensorTemperature); + ccs811.readData(); + SensorTvoc = ccs811.getTVOC(); + SensorEco2 = ccs811.geteCO2(); + SensorIaq = _getIaqIndex(SensorHumidity, SensorTvoc, SensorEco2); + + Serial.printf("%f c, %f humidity, %f hPA, %u tvoc, %u Eco2, %s iaq\n", + SensorTemperature, SensorHumidity, SensorPressure, + SensorTvoc, SensorEco2, SensorIaq); + } + + /** + * Credits: Bouke_Regnerus @ https://community.home-assistant.io/t/example-indoor-air-quality-text-sensor-using-ccs811-sensor/125854 + */ + char *_getIaqIndex(float humidity, int tvoc, int eco2) + { + int iaq_index = 0; + + /* + * Transform indoor humidity values to IAQ points according to Indoor Air Quality UK: + * http://www.iaquk.org.uk/ + */ + if (humidity < 10 or humidity > 90) + { + iaq_index += 1; + } + else if (humidity < 20 or humidity > 80) + { + iaq_index += 2; + } + else if (humidity < 30 or humidity > 70) + { + iaq_index += 3; + } + else if (humidity < 40 or humidity > 60) + { + iaq_index += 4; + } + else if (humidity >= 40 and humidity <= 60) + { + iaq_index += 5; + } + + /* + * Transform eCO2 values to IAQ points according to Indoor Air Quality UK: + * http://www.iaquk.org.uk/ + */ + if (eco2 <= 600) + { + iaq_index += 5; + } + else if (eco2 <= 800) + { + iaq_index += 4; + } + else if (eco2 <= 1500) + { + iaq_index += 3; + } + else if (eco2 <= 1800) + { + iaq_index += 2; + } + else if (eco2 > 1800) + { + iaq_index += 1; + } + + /* + * Transform TVOC values to IAQ points according to German environmental guidelines: + * https://www.repcomsrl.com/wp-content/uploads/2017/06/Environmental_Sensing_VOC_Product_Brochure_EN.pdf + */ + if (tvoc <= 65) + { + iaq_index += 5; + } + else if (tvoc <= 220) + { + iaq_index += 4; + } + else if (tvoc <= 660) + { + iaq_index += 3; + } + else if (tvoc <= 2200) + { + iaq_index += 2; + } + else if (tvoc > 2200) + { + iaq_index += 1; + } + + if (iaq_index <= 6) + { + return "Unhealty"; + } + else if (iaq_index <= 9) + { + return "Poor"; + } + else if (iaq_index <= 12) + { + return "Moderate"; + } + else if (iaq_index <= 14) + { + return "Good"; + } + else if (iaq_index > 14) + { + return "Excellent"; + } + } + +public: + void setup() + { + Serial.println("Starting!"); + Wire.begin(SDA_PIN, SCL_PIN); + Serial.println("Initializing sensors.. "); + _initialize(); + } + + // gets called every time WiFi is (re-)connected. + void connected() + { + nextMeasure = millis() + 5000; // Schedule next measure in 5 seconds + } + + void loop() + { + unsigned long tempTimer = millis(); + + if (tempTimer > nextMeasure) + { + nextMeasure = tempTimer + 60000; // Schedule next measure in 60 seconds + + if (!initialized) + { + Serial.println("Error! Sensors not initialized in loop()!"); + _initialize(); + return; // lets try again next loop + } + + if (mqtt != nullptr && mqtt->connected()) + { + if (!mqttInitialized) + { + _mqttInitialize(); + mqttInitialized = true; + } + + // Update sensor data + _updateSensorData(); + + // Create string populated with user defined device topic from the UI, + // and the read temperature, humidity and pressure. + // Then publish to MQTT server. + mqtt->publish(mqttTemperatureTopic.c_str(), 0, true, String(SensorTemperature).c_str()); + mqtt->publish(mqttPressureTopic.c_str(), 0, true, String(SensorPressure).c_str()); + mqtt->publish(mqttHumidityTopic.c_str(), 0, true, String(SensorHumidity).c_str()); + mqtt->publish(mqttTvocTopic.c_str(), 0, true, String(SensorTvoc).c_str()); + mqtt->publish(mqttEco2Topic.c_str(), 0, true, String(SensorEco2).c_str()); + mqtt->publish(mqttIaqTopic.c_str(), 0, true, String(SensorIaq).c_str()); + } + else + { + Serial.println("Missing MQTT connection. Not publishing data"); + mqttInitialized = false; + } + } + } +}; diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index 810127a7..c5f106b0 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -17,20 +17,26 @@ #ifdef USERMOD_BUZZER #include "../usermods/buzzer/usermod_v2_buzzer.h" #endif +#ifdef USERMOD_SENSORSTOMQTT +#include "usermod_v2_SensorsToMqtt.h" +#endif void registerUsermods() { - /* +/* * Add your usermod class name here * || || || * \/ \/ \/ */ - //usermods.add(new MyExampleUsermod()); - #ifdef USERMOD_DALLASTEMPERATURE +//usermods.add(new MyExampleUsermod()); +#ifdef USERMOD_DALLASTEMPERATURE usermods.add(new UsermodTemperature()); - #endif - //usermods.add(new UsermodRenameMe()); - #ifdef USERMOD_BUZZER +#endif +//usermods.add(new UsermodRenameMe()); +#ifdef USERMOD_BUZZER usermods.add(new BuzzerUsermod()); - #endif +#endif +#ifdef USERMOD_SENSORSTOMQTT + usermods.add(new UserMod_SensorsToMQTT()); +#endif } \ No newline at end of file