Multiple functions on same uart bus

Hello everyone,

I’m using ESPHome quite some time for various tasks and also created custom components. And I really like it so first of all thanks for this great tool.

However, now I have a problem I really don’t seem get solved by myself:
I want to access some DIY hardware via Modbus with the standard approach using a chain of uart, modbus, modbus_controller and e.g. a sensor component which works great.
Additionally I want to be able to do firmware updates of the DIY hardware via the same serial connection (RS485) used by the Modbus link, however in this case I need a direct tunnel to the uart component from my development machine which can be done nicely via the stream server (GitHub - oxan/esphome-stream-server: Stream server (serial-to-wifi bridge) for ESPHome).

However having both of them active in the same ESPHome configuration simultaneously is not possible out of the box I guess. So one solution could be to reprogram the ESPHome node every time I want to do an update with either the configuration uart + stream server or uart + modbus, however it would be much more convenient to have something like a merger or switch for the data streams going to and from the uart component to allow the Modbus and stream server components to be simultaneously active on the ESPHome node.

So I tried to create a custom component accessing the uart component and representing itself also as two uart components which can be accessed by Modbus and stream sever later. So the custom component would sit between uart and Modbus / Stream server and select which one can access the bus.
However I’m stuck on how to integrate this components into the yaml configuration.

The header file of the custom components looks like this:

class UARTSlv : public UARTComponent, public Component {

    public:
        void setup() override { };
        float get_setup_priority() const override { return setup_priority::BUS; }
        void write_array(const uint8_t *data, size_t len) override;
        bool peek_byte(uint8_t *data) override;
        bool read_array(uint8_t *data, size_t len) override;
        int available() override;
        void flush() override;
 
    protected:
        void check_logger_conflict() override {};

};

class UARTSwitch : public Component {

    public:
        UARTSlv *uart_slv_0;
        UARTSlv *uart_slv_1;

        UARTSwitch(UARTComponent *uart_master, int flow_control_pin);
        ~UARTSwitch();

    protected:
        UARTComponent *uart_mst;
        int flow_pin;

};

And this is the relevant corresponding yaml configuration:

uart:
  - id: mst_uart
    tx_pin: 13
    rx_pin: 14
    baud_rate: 9600
    stop_bits: 1

custom_component:
  - lambda: |-
      auto sw_uart = new UARTSwitch(id(mst_uart), 2);
      App.register_component(sw_uart);
      return { sw_uart->uart_slv_0, sw_uart->uart_slv_1 };
    components:
      - id: mb_uart
      - id: net_uart

stream_server:
  uart_id: net_uart
  port: 6638

modbus:
  id: mb
  uart_id: mb_uart

However, I get an error while compiling:
ID ‘net_uart’ of type Component doesn’t inherit from uart::UARTComponent. Please double check your ID is pointing to the correct value.
ID ‘mb_uart’ of type Component doesn’t inherit from uart::UARTComponent. Please double check your ID is pointing to the correct value.
Seems like the type of the slave uart is removed when I assign an id to them in the custom_component section.

So maybe someone has an idea what I can do to make this thing compile or maybe has an even better idea to realize this uart switch function.

Many thanks,
Michael

Hi.

The issue you have is that the UARTSwitch is not inherent from UARTComponent

So to use it with the modbus it need to inherent from UARTComponent .

So UARTSwitch need look like this:


class UARTSwitch : public UARTComponent {
…

Maybe this will get you a little bit further.

/Mattias

Hi Mattias,

Thank you very much for your hint. However, using public UARTComponent instead of public Component or even deriving the new class from public UARTComponent and public Component I already tested and it doesn’t change the error message at all.
But I think this also makes sense as I return two objects of type UARTComponent created within the UARTSwitch object in the lambda expression of the custom_component section and not the UARTSwitch object itself:
return { sw_uart->uart_slv_0, sw_uart->uart_slv_1 };
And the ids assigned in the components section refer to the objects returned in the lambda expression as far as I understand.
Some time ago I created a custom cover component with a similar approach (returning sub objects created in the object created in the lambda expression) and could access these sub objects later.

Interestingly the error seems to occur even before the h or cpp file from UARTSwitch is even parsed (esphome run puts out the same error message even the header file contains syntax errors). So I think the yaml file is parsed first and the type of mb_uart and net_uart is derived from the location of their definition within custom_component as type Component rather than the type in the C++ code.
I’d probably need a way to change the object type of mb_uart and net_uart inside the custom_component section or a completely different approach to connect my UARTSwitch with the other components in the yaml file.

For completeness here is the part of the cpp file creating the sub objects within the UARTSwitch’s cpp file:

UARTSwitch::UARTSwitch(UARTComponent *uart_master, int flow_control_pin) {
    this->uart_mst = uart_master;
    this->flow_pin = flow_control_pin;
    this->uart_slv_0 = new UARTSlv();
    this->uart_slv_1 = new UARTSlv();
}

UARTSwitch::~UARTSwitch() {
    delete this->uart_slv_0;
    delete this->uart_slv_1;
}

Maybe you or someone else has a nice idea how to solve this puzzle.

Best regards and many thanks,
Michael

Hi

You are of course completely right that they are UARTComponent.

This defines the mb_uart and net_uart as components and as you point out it has nothing to do with the actual implementation. But in the yaml.

custom_component:
...
    components:   <--- this define is a Component ( I think)
      - id: mb_uart
      - id: net_uart

From Custom Sensor Component — ESPHome

sensor:
- platform: custom
  lambda: |-
    auto my_sensor = new MyCustomSensor();
    App.register_component(my_sensor);
    return {my_sensor};

  sensors:   <--- This define the custom component as a sensor. 
    name: "My Custom Sensor"
    unit_of_measurement: hPa
    accuracy_decimals: 2

I dont know if it possible but maybe like this.

custom_component:
  - lambda: |-
      auto sw_uart = new UARTSwitch(id(mst_uart), 2);
      App.register_component(sw_uart);
      return { sw_uart->uart_slv_0, sw_uart->uart_slv_1 };
    uart:   <-- Could this make it be a UARTComponent???
      - id: mb_uart
      - id: net_uart

/Mattias

Hi Mattias,

Thanks again. Unfortunately only components seems to be allowed in the custom_component section. Also using the keyword platform in the uart section to define the custom component similar to the sensor section seems not possible. :frowning:
Another idea I had was to create another “real” custom uart component providing two IDs with the help of the external_components section using the platform implementation as base and replace the standard uart. However I’m not sure how the actual UART platform implementation is selected and if I can modify this. Also I don’t know if there is a way to provide two uart “end points” or IDs from a single UART component.

Best regards and Thank you,
Michael