esphome / feature-requests

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

UART Peripheral timesharing #2560

Open RoganDawes opened 7 months ago

RoganDawes commented 7 months ago

Describe the problem you have/What new integration you would like Sometimes you need to communicate with more than 3 serial devices. When that communications is purely driven by the ESPHome as a master (e.g. modbusRTU request/response), it should theoretically be possible to "timeshare" one or more of the ESP32's 3 (or even 2) UART peripherals, simply by changing which pins are assigned, and the associated communications parameters, when a particular device needs to be communicated with.

Please describe your use case for this integration and alternatives you've tried: I need to communicate with 5 Growatt inverters using modbus over RS232 (I've tried RS485 with the thought that I could simply daisy chain each one, and got zero response from the inverters). I also need to talk to 4 Narada 48NPFC100 batteries using modbus over RS485. (This part is working fine, fortunately.)

That leaves me needing to talk to 6 UART's in total, which is currently not possible.

In theory, it should be feasible to define a timeshare or interlock object, that governs which pins and communications parameters are in force at any particular time. The real complication lies in deciding which defined uart actually has control over the UART peripheral, either by letting the uart's "client" reserve and release the uart, or by iterating through the defined uarts, and giving each a turn. Alternatively, if the client is a PollingComponent, one could make sure that the uart has been reserved/locked prior to the loop() being scheduled, and unlocked automatically some (configurable) time afterwards.

This is kind of analogous to the power_supply: component, in a way. (https://esphome.io/components/power_supply)

Additional context

There is another feature request for support of an I2C- or SPI-connected UART here (#2355). I imagine this might be an alternative to needing that component, but that also suggests a way to implement this, as a unique platform:

uart:
  - id: uart1_
    tx_pin: GPIO16
    rx_pin: GPIO17
    baud_rate: 19200
    data_bits: 8
    parity: none
    stop_bits: 2
  - id: uart2a
    platform: timeshare
    timeshare: timeshare_group1
    tx_pin: GPIO26
    rx_pin: GPIO27
    baud_rate: 115200
    data_bits: 8
    parity: none
    stop_bits: 1
  - id: uart2b
    platform: timeshare
    timeshare: timeshare_group1
    tx_pin: GPIO14
    rx_pin: GPIO15
    baud_rate: 57600
    data_bits: 8
    parity: none
    stop_bits: 1

This is somewhat analogous to the modbus: component's flow_control_pin: (https://esphome.io/components/modbus.html), which is asserted prior to transmitting, and de-asserted when receiving. In this case, the UARTComponent would attempt to reserve or lock the timeshare group prior to running a series of modbus queries, or whatever it needs, then unlock it when done. The act of locking the group would configure the UART peripheral to the required pins and parameters, and unlocking it would revert the TX pin to a GPIO set to OUTPUT HIGH (TX idle), and the RX pin to a GPIO set to INPUT LOW (RX idle).

RoganDawes commented 7 months ago

Depending on how long it actually takes to configure the UART peripheral, it could be done as part of the tx path, and unlocked automatically after a timeout.

latonita commented 3 months ago

@RoganDawes this is useful, yes, however all the components using UART shall be able to use this feature. I don't believe timeout will work properly for all devices. For example, I have implemented uart lock/unlock for my own custom component to be able to share UART between different electricity meters. Which work with 'sessions'. So basically I start working with UART by try_lock_session and then unlock it manually later.

maybe something like this might be thought trough and implemented in a component like session_uart, which only can be used after locking the resource.

RoganDawes commented 3 months ago

Yeah, an automatic timeout is probably not a good approach to this. And trying to make all possible users of a UARTComponent lock and unlock the UART is also probably not the right way. Providing a SharedUART type might be one approach, basically extending the UART interface to add a .lock() and .unlock() method.

It does feel like we would need to be very careful with the type hierarchy to make sure that it behaves in a sensible way, though!