esphome / feature-requests

ESPHome Feature Request Tracker
https://esphome.io/
415 stars 26 forks source link

Support Dylos DC1100 Series #2657

Open MaxRenaud opened 6 months ago

MaxRenaud commented 6 months ago

Describe the problem you have/What new integration you would like

I would like to build integration with the Dylos DC1100 series of air quality monitors. They have a DB9 serial port, which is very inconvenient for any type of automation. The solution is to use a serial converter paired with an ESP device and send the data via MQTT for home assistant to do something when the air quality changes.

I want to create and merge a component into the main branch called dc1100.

Please describe your use case for this integration and alternatives you've tried:

The alternative is a somewhat bad custom-made C++ program that takes data from the serial port and sends it via MQTT. Values are hard coded and the chip must be reflashed if the WiFi or MQTT details change. I unfortunately lost the source code to this bad program so I have to write something no matter what. If others can find a use for it, I will put in the effort to make it a proper esphome component.

Additional context

If I get a comment from a maintainer that this is a valid FR, I will follow up with a PR.

nagyrobi commented 6 months ago

According to the reviews this is a good quality device, go ahead! We need proper docs also describing the hardware connection.

MaxRenaud commented 1 week ago

I have fallen to the way too obvious trap of It works, I'll make it look good later and I don't think this will change anytime soon. I want to share my prototype here, which is really prod for my home. Hopefully this can help people coming here from a Google search and someone more familiar with the esphome framework can quickly turn the code into a proper module.

Here's a relevant excerpt from my dylos.yaml file:

uart:
  id: uart_bus
  tx_pin: D5
  rx_pin: D6
  baud_rate: 9600

sensor:
- platform: custom
  lambda: |-
    auto dyloss = new DC1100Pro(id(uart_bus));
    App.register_component(dyloss);
    return {dyloss->small_count, dyloss->large_count};

  sensors:
  - name: "Small particles per 0.01ft3 of air"
    unit_of_measurement: particles
    accuracy_decimals: 0
  - name: "Large particles per 0.01ft3 of air"
    unit_of_measurement: particles
    accuracy_decimals: 0
mqtt:
  broker: 192.168.1.130
  username: mqtt
  password: changeme
  discovery: true
  topic_prefix: dylos

And dylos.h

#include "esphome.h"

class DC1100Pro : public PollingComponent, public UARTDevice, public Sensor {
public:
DC1100Pro(UARTComponent *parent) : UARTDevice(parent) {}
  Sensor *small_count = new Sensor();
  Sensor *large_count = new Sensor();

  void setup() override { 
    this->set_update_interval(5000);
  }

  int readline(int readch, char *buffer, int len)
  {
    static int pos = 0;
    int rpos;

    if (readch > 0) {
      switch (readch) {
        case '\n': // Ignore new-lines
          break;
        case '\r': // Return on CR
          rpos = pos;
          pos = 0;  // Reset position index ready for next time
          return rpos;
        default:
          if (pos < len-1) {
            buffer[pos++] = readch;
            buffer[pos] = 0;
          }
      }
    }
    // No end of line has been found, so return -1.
    return -1;
  }

  void update() override {
    if (!available()) {
      ESP_LOGD("dylos", "No new serial data available.");
      return;
    }
  ESP_LOGD("custom", "Log data is available");
  //String serialData = Serial.readStringUntil('\n'); 
  const int max_line_length = 80;
  static char buffer[max_line_length];
  while (available()) {
    if(readline(read(), buffer, max_line_length) > 0) break;
  }
  String serialData(buffer);
  ESP_LOGD("custom", "Data has been read: %s", serialData.c_str());

  int commaIndex = serialData.indexOf(',');
  if (commaIndex != -1) {
    String smallCountStr = serialData.substring(0, commaIndex);
    String largeCountStr = serialData.substring(commaIndex + 1);

    float smallCount = smallCountStr.toFloat(); 
    float largeCount = largeCountStr.toFloat(); 

    // Example: Report values as text
    small_count->publish_state(smallCount);
    large_count->publish_state(largeCount);
    ESP_LOGD("dylos", "Sending small %.1f", smallCount);
  } else {
    ESP_LOGW("dylos", "Invalid data format received: '%s'", serialData.c_str());
  }

  }
};

Obviously code that was written by brute force rather than sophistication, and not refactored since I've been able to ingest data and moved on to other automation goals.

As for the hardware connection, it's dead simple: I used an Ultra Compact RS232 to TTL Converter with Male DB9 (3.3V to 5V). The specific model I used is no longer available but Amazon still has a product page for it under ID B00OPU2QJ4. The 3.3V and GND come directly from the NodeMCU. Ping D5 is connected to the RX pin and D6 to the TX one.

@nagyrobi what is the etiquette with this FR? Should I close it as unplanned or leave it up in case someone wants to take a stab at it?