{"id":39595586,"url":"https://github.com/rosenrot00/esphome_modbus_bridge","last_synced_at":"2026-01-18T07:48:25.080Z","repository":{"id":303139812,"uuid":"1014523488","full_name":"rosenrot00/esphome_modbus_bridge","owner":"rosenrot00","description":"ESPHome Modbus TCP↔RTU Bridge – Transparent bridge for ESP8266/ESP32 that lets multiple Modbus TCP clients communicate with RTU slaves over UART/RS485.","archived":false,"fork":false,"pushed_at":"2026-01-10T12:47:51.000Z","size":380,"stargazers_count":25,"open_issues_count":2,"forks_count":6,"subscribers_count":7,"default_branch":"main","last_synced_at":"2026-01-11T03:57:50.705Z","etag":null,"topics":["esp32","esp8266","esphome","home-assistant","modbus","modbus-bridge","modbus-rtu","modbus-tcp","rs485","rs485-to-ethernet","rs485-to-wifi"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rosenrot00.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-05T22:28:35.000Z","updated_at":"2026-01-10T12:47:54.000Z","dependencies_parsed_at":"2025-12-15T02:07:42.029Z","dependency_job_id":null,"html_url":"https://github.com/rosenrot00/esphome_modbus_bridge","commit_stats":null,"previous_names":["rosenrot00/esphome_modbus_bridge"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rosenrot00/esphome_modbus_bridge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rosenrot00%2Fesphome_modbus_bridge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rosenrot00%2Fesphome_modbus_bridge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rosenrot00%2Fesphome_modbus_bridge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rosenrot00%2Fesphome_modbus_bridge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rosenrot00","download_url":"https://codeload.github.com/rosenrot00/esphome_modbus_bridge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rosenrot00%2Fesphome_modbus_bridge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28533172,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T00:39:45.795Z","status":"online","status_checked_at":"2026-01-18T02:00:07.578Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["esp32","esp8266","esphome","home-assistant","modbus","modbus-bridge","modbus-rtu","modbus-tcp","rs485","rs485-to-ethernet","rs485-to-wifi"],"created_at":"2026-01-18T07:48:22.902Z","updated_at":"2026-01-18T07:48:25.062Z","avatar_url":"https://github.com/rosenrot00.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ESPHome (ESP8266/ESP32) Modbus TCP to RTU Bridge\n\nThis ESPHome component provides a transparent Modbus TCP-to-RTU bridge, acting as a Modbus RTU master over UART on both ESP8266 and ESP32 platforms. It allows multiple Modbus TCP clients to communicate with Modbus RTU slaves via RS485 or other UART-compatible hardware.\n\n| Version   | Changes                                                                           |\n|-----------|-----------------------------------------------------------------------------------|\n| 2026.01.2 | Added separate RS-485 `de_pin` and `re_pin`; removed `flow_control_pin`           |\n| 2026.01.1 | TCP client drops, RTU timeouts, and others are now available to use as HA sensors |\n| 2025.12.3 | Added `uart_wake_loop_on_rx` to enable ESPHome’s low-latency UART flag            |\n| 2025.12.2 | Optimizations to recover after IP loss and tighten RTU frame detection            |\n| 2025.12.1 | For more compatibility a `crc_bytes_swapped` option was added                     |\n| 2025.11.1 | `enabled` was added to allow changing the bridges state during runtime            |\n| 2025.10.3 | Added ESPHome automations for tcp and rtu activities                              |\n| 2025.10.2 | Introduced T1.5 waiting time for better modbus rtu frame detection on lower bauds |\n| 2025.10.1 | Implemented support for multiple bridges to be used with multiple UART interfaces |\n| 2025.09.1 | Added configurable RS-485 `de_pin` / `re_pin` support (separate or shared GPIO)   |\n| 2025.08.2 | Improved RTU response handling (silence-based end detection)                      |\n| 2025.08.1 | Added support for multiple concurrent TCP clients with preemption logic           |\n| 2025.07.1 | Initial public README and Python `modbus_rw.py` tool                              |\n\n#### Features\n\nThe bridge listens on a configurable TCP port (default: 502) and expects standard Modbus TCP frames from clients. Each request is translated into a Modbus RTU frame, transmitted over UART, and the response is converted back into Modbus TCP and returned to the client.\n\n- Acts as a Modbus RTU master on UART\n- Multiple concurrent Modbus TCP clients (slot‑limited)\n- TCP↔RTU translation both ways\n- RTU end‑of‑frame via UART silence (no byte count needed)\n- Works with all Modbus function codes\n- Optional same‑IP preemption when slots are full\n- Compatible with Home Assistant and third‑party Modbus TCP tools\n- Supports RS-485 transceivers with separate DE and /RE pins or a single shared control GPIO\n\n#### Proven Compatibility\n- [nilan-cts600-homeassistant](https://github.com/frodef/nilan-cts600-homeassistant) thanks to @RichardIstSauer\n- [ha-solarman](https://github.com/davidrapan/ha-solarman) thanks to @davidrapan\n- [Marstek Venus Battery](https://github.com/ViperRNMC/marstek_venus_modbus) thanks to @ebbenberg\n- [homeassistant-solax-modbus](https://github.com/wills106/homeassistant-solax-modbus)\n\n#### Hardware Setup\nThe following diagram shows how an ESP32 is connected to an RS485 transceiver (e.g., MAX3485, SP3485, SN65HVD…) before the RS485 differential lines are attached to a Modbus bus.\n```\n             +--------------------+         +---------------------------+\n             |        ESP32       |         |      RS485 Transceiver    |\n             |       ESP8266      |         |   (e.g. MAX3485/SP3485)   |\n             +--------------------+         +---------------------------+\n             | GPIO TX (UART TX)  |--------\u003e| DI        (Data In)       |\n             | GPIO RX (UART RX)  |\u003c--------| RO        (Receiver Out)  |\n             | GPIO DE (Driver En)|--------\u003e| DE        (Driver Enable) |\n             | GPIO RE (Recv En)  |--------\u003e| /RE       (Recv Enable)   |\n             | GND                |---------| GND                       |\n             +--------------------+         +------------+--------------+\n                                                     |\n                                                     |\n                                                     |   RS485 differential pair\n                                                     |   (before connecting to Modbus)\n                                                     |\n                                              +------+------+ \n                                              |   A   |   B |\n                                              +------+------+\n```\n\n#### ESPHome Configuration Example\n\n```yaml\nesphome:\n  name: modbus-bridge\n  friendly_name: Modbus TCP-to-RTU bridge\n\n  # Run on boot: publish whether the bridge is enabled\n  on_boot:\n    priority: 600\n    then:\n      - lambda: |-\n          id(mb_bridge_enabled).publish_state(id(mb_bridge).is_enabled());\n\nesp32:\n  board: esp32dev\n  framework:\n    type: esp-idf                    # ESP-IDF recommended\n    #type: arduino                   # Arduino also works\n\n# Enable logging over UART\nlogger:\n\n# Enable Home Assistant API\napi:\n\n# Enable OTA updates\nota:\n  platform: esphome\n  password: !secret ota_password      # https://esphome.io/guides/security_best_practices/#using-secretsyaml\n\nwifi:\n  ssid: !secret wifi_ssid             # https://esphome.io/guides/security_best_practices/#using-secretsyaml\n  password: !secret wifi_password     # https://esphome.io/guides/security_best_practices/#using-secretsyaml\n  # min_auth_mode: WPA3               # Optional: Default is WPA2 on ESP32\n  # domain: .lan                      # Optional: Default is local \n\n  # Fallback hotspot if WiFi fails\n  ap:\n    ssid: \"Modbus TCP-to-RTU bridge Hotspot\"\n    password: !secret ap_password     # https://esphome.io/guides/security_best_practices/#using-secretsyaml\n\ncaptive_portal:\n\nexternal_components:\n  - source:\n      type: git\n      url: https://github.com/rosenrot00/esphome_modbus_bridge\n    components: [modbus_bridge]\n\n# UART hardware configuration: Modbus RTU (RS-485)\nuart:\n  id: uart_bus\n  tx_pin: GPIO17\n  rx_pin: GPIO16\n  baud_rate: 9600\n  # stop_bits: 1                 # Optional: Default is 1\n  # parity: NONE                 # Optional: Default is NONE\n  rx_buffer_size: 256            # minimum 256 recommended; increase for long RTU responses\n\n# Modbus bridge configuration: TCP server \u003c-\u003e UART RTU translator\nmodbus_bridge:\n  id: mb_bridge\n  uart_id: uart_bus\n  tcp_port: 502                  # TCP port to listen on\n  rtu_response_timeout: 3000     # ms, internally clamped to \u003e=10 ms\n  # tcp_client_timeout: 60000    # ms of inactivity before client is disconnected\n  # tcp_allowed_clients: 2       # number of simultaneous TCP clients (min 1)\n  # tcp_poll_interval: 50        # ms between TCP polls\n  # de_pin: GPIO18               # Optional: RS-485 Driver Enable (DE)\n  # re_pin: GPIO19               # Optional: RS-485 Receiver Enable (/RE) - de_pin and re_pin can be the same GPIO\n  # (DE and /RE may be the same GPIO if the transceiver ties them together)\n  # crc_bytes_swapped: false     # allows to swap CRC byte order LO/HI -\u003e HI/LO\n  # enabled: true                # allows to enable or disable during runtime\n  # uart_wake_loop_on_rx: true   # enable ESPHome's UART low latency setting (effects not yet tested)\n\n  # Event: triggered whenever number of TCP clients changes\n  on_tcp_clients_changed:\n    then:\n      - lambda: |-\n          id(tcp_clients) = count;\n      - logger.log:\n          format: \"TCP clients connected: %d\"\n          args: ['count']\n      - sensor.template.publish:\n            id: mb_tcp_clients\n            state: !lambda |-\n              return (int) count;\n\n  # Other available events (use similarly):\n  # on_rtu_send:       # (function_code, address) – triggered for every RTU command sent\n  # on_rtu_receive:    # (function_code, address) – triggered for every valid RTU response\n  # on_rtu_timeout:    # (function_code, address) – triggered for RTU timeouts\n  # on_tcp_started:    # () – triggered when TCP server successfully starts\n  # on_tcp_stopped:    # () – triggered when TCP server stops or IP is lost\n\n# Output pin for status LED\noutput:\n  - platform: gpio\n    id: output_led_status\n    pin: GPIO2\n\n# Binary LED light entity\nlight:\n  - platform: binary\n    id: led_status\n    name: \"Status LED\"\n    output: output_led_status\n\n# Global variable to store connected TCP client count\nglobals:\n  - id: tcp_clients\n    type: int\n    restore_value: no\n    initial_value: '0'\n\n# Every 3 seconds, blink the LED N times (N = connected TCP clients)\ninterval:\n  - interval: 3s\n    then:\n      - if:\n          condition:\n            lambda: 'return id(tcp_clients) \u003e 0;'\n          then:\n            - repeat:\n                count: !lambda 'return id(tcp_clients);'\n                then:\n                  - light.turn_on: led_status\n                  - delay: 100ms\n                  - light.turn_off: led_status\n                  - delay: 100ms  # short pause between blinks\n\nswitch:\n  # Switch: enable/disable verbose Modbus debugging\n  - platform: template\n    name: \"Modbus Bridge Debug\"\n    id: modbus_debug_switch\n    restore_mode: RESTORE_DEFAULT_OFF\n    turn_on_action:\n      - lambda: |-\n          id(mb_bridge).set_debug(true);\n          id(modbus_debug_switch).publish_state(true);\n    turn_off_action:\n      - lambda: |-\n          id(mb_bridge).set_debug(false);\n          id(modbus_debug_switch).publish_state(false);\n\n  # Switch: enable/disable the Modbus bridge itself\n  - platform: template\n    id: mb_bridge_enabled\n    name: \"Modbus Bridge Enabled\"\n    restore_mode: \"ALWAYS_ON\"\n    optimistic: true\n    turn_on_action:\n      - lambda: |-\n          id(mb_bridge).set_enabled(true);\n    turn_off_action:\n      - lambda: |-\n          id(mb_bridge).set_enabled(false);\n\nsensor:\n  - platform: template\n    name: \"TCP Clients\"\n    id: mb_tcp_clients\n    accuracy_decimals: 0\n    update_interval: never\n\n  #- platform: template\n  #  name: \"MB Frames In\"\n  #  accuracy_decimals: 0\n  #  update_interval: 10s\n  #  lambda: |-\n  #    return (float) id(mb_bridge).get_frames_in();\n\n  #- platform: template\n  #  name: \"MB Frames Out\"\n  #  accuracy_decimals: 0\n  #  update_interval: 10s\n  #  lambda: |-\n  #    return (float) id(mb_bridge).get_frames_out();\n\n  - platform: template\n    name: \"TCP Drops PID\"\n    accuracy_decimals: 0\n    update_interval: 10s\n    lambda: |-\n      return (int) id(mb_bridge).get_drops_pid();\n\n  - platform: template\n    name: \"Drops LEN\"\n    accuracy_decimals: 0\n    update_interval: 10s\n    lambda: |-\n      return (int) id(mb_bridge).get_drops_len();\n\n  - platform: template\n    name: \"RTU Timeouts\"\n    accuracy_decimals: 0\n    update_interval: 10s\n    lambda: |-\n      return (int) id(mb_bridge).get_timeouts();\n\n  - platform: template\n    name: \"TCP Clients Total\"\n    accuracy_decimals: 0\n    update_interval: 10s\n    lambda: |-\n      return (int) id(mb_bridge).get_clients_connected_total();\n\n  - platform: template\n    name: \"TCP No Slot Events\"\n    accuracy_decimals: 0\n    update_interval: 10s\n    lambda: |-\n      return (int) id(mb_bridge).get_noslot_events();\n\n  - platform: template\n    name: \"TCP Preempt Events\"\n    accuracy_decimals: 0\n    update_interval: 10s\n    lambda: |-\n      return (int) id(mb_bridge).get_preempt_events();\n```\n\n#### Modbus TCP Request Format\n\nEach Modbus TCP request must follow this format:\n\n- **Transaction ID**: 2 bytes (arbitrary, echoed back)\n- **Protocol ID**: 2 bytes (must be 0)\n- **Length**: 2 bytes (number of following bytes, typically `unit id` + `PDU`)\n- **Unit ID**: 1 byte (RTU slave address)\n- **PDU**: n bytes (Function code and data)\n\nExample (read holding registers, unit ID 1, starting at 0x0000, count 1):\n```\n00 01   - Transaction ID\n00 00   - Protocol ID\n00 06   - Length\n01      - Unit ID (RTU address)\n03      - Function code (Read Holding Registers)\n00 00   - Start address high/low\n00 01   - Register count high/low\n```\nThe response will match the Modbus TCP format and contain the same transaction ID.\n\n## modbus_rw.py – Modbus TCP Register Read/Write Tool\n\n`modbus_rw.py` is a simple command-line utility for reading and writing Modbus TCP registers using the `pymodbus` library.  \nIt supports reading Holding Registers (Function Code 0x03), Input Registers (0x04), and writing a single Holding Register (0x06).  \nThis tool is useful for testing, diagnostics, or integrating Modbus-capable devices in a network environment.\n#### Arguments\n```\n--host         Modbus TCP server IP address (required)\n--port         Modbus TCP port (default: 502)\n--unit         Modbus unit ID / slave ID (default: 1)\n--register     Register address to read/write (decimal or hex, e.g. 0x10) (required)\n--count        Number of registers to read (default: 1)\n--value        Value to write to register (used for write operation)\n--read         Read Holding Registers (Function Code 0x03)\n--read_input   Read Input Registers (Function Code 0x04)\n```\n#### Examples\n\n- Read Holding Registers (FC 0x03)\n```\npython modbus_rw.py --host 192.168.0.10 --register 0x0010 --count 2 --read\n```\n- Read Input Registers (FC 0x04)\n```\npython modbus_rw.py --host 192.168.0.10 --register 0x0010 --count 2 --read_input\n```\n- Write a Single Holding Register (FC 0x06)\n```\npython modbus_rw.py --host 192.168.0.10 --register 0x0010 --value 0x1234\n```\n- Read Holding Registers with USB/serial adapter (FC 0x03)\n```\npython modbus_rw.py --serial-port /dev/ttyUSB0 --baudrate 19200 --parity E --register 0x0010 --count 2 --read\n```\n\n#### Requirements\n- Python 3.x  \n- pymodbus ≤3.9.x library (let me know if you need it compatible with \u003e3.10)\n- Install via: `pip install \"pymodbus\u003c3.10\"`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frosenrot00%2Fesphome_modbus_bridge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frosenrot00%2Fesphome_modbus_bridge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frosenrot00%2Fesphome_modbus_bridge/lists"}