Skip to content

[e131] Fix E1.31 on ESP8266 and RP2040 by restoring WiFiUDP support#14086

Merged
bdraco merged 1 commit intodevfrom
fix-e131-esp8266-udp
Feb 19, 2026
Merged

[e131] Fix E1.31 on ESP8266 and RP2040 by restoring WiFiUDP support#14086
bdraco merged 1 commit intodevfrom
fix-e131-esp8266-udp

Conversation

@bdraco
Copy link
Member

@bdraco bdraco commented Feb 19, 2026

What does this implement/fix?

The socket abstraction layer on ESP8266/RP2040 (USE_SOCKET_IMPL_LWIP_TCP) only supports TCP. When E1.31 was migrated from WiFiUDP to the socket abstraction in #4832, it silently broke on these platforms — SOCK_DGRAM requests get TCP sockets that never receive UDP data.

This restores WiFiUDP as the UDP transport on LWIP_TCP platforms while keeping the socket-based implementation on BSD/LWIP socket platforms (ESP32, etc.). This follows the same dual-path pattern already used by the udp and wake_on_lan components.

Changes:

  • e131.h: Conditionally include socket/socket.h or WiFiUdp.h and declare the appropriate member (socket_ vs udp_)
  • e131.cpp: Add #elif dual paths in destructor, setup(), and loop() for socket vs WiFiUDP. The WiFiUDP path uses a while (parsePacket()) loop to drain all pending packets per loop call, matching the original pre-Migrate e131 to use socket instead of WiFiUDP arduino library #4832 behavior.
  • e131_packet.cpp: Guard socket_ == nullptr check so it only compiles on platforms that have the socket_ member

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Developer breaking change (an API change that could break external components)
  • Code quality improvements to existing code or addition of tests
  • Other

Related issue or feature (if applicable):

Pull request in esphome-docs with documentation (if applicable):

  • N/A (no user-facing configuration changes)

Test Environment

  • ESP32
  • ESP32 IDF
  • ESP8266
  • RP2040
  • BK72xx
  • RTL87xx
  • LN882x
  • nRF52840

Example entry for config.yaml:

esphome:
  name: e131-test

esp8266:
  board: d1_mini

wifi:
  ssid: MySSID
  password: password1

e131:
  method: multicast

light:
  - platform: neopixelbus
    id: led_strip
    type: GRB
    variant: ws2812
    pin: GPIO2
    num_leds: 50
    default_transition_length: 0s
    effects:
      - e131:
          universe: 1
          channels: RGB

Test sender script

Install sacn (pip install sacn) and run to verify E1.31 reception. Sweeps red, green, blue at 50 FPS:

"""Simple E1.31 (sACN) test sender for testing the ESPHome e131 component.

Usage:
    pip install sacn
    python test_e131_sender.py [--universe 1] [--unicast 192.168.1.100] [--leds 50]
"""

import argparse
import time

import sacn


def main():
    parser = argparse.ArgumentParser(description="E1.31 test sender")
    parser.add_argument("--universe", type=int, default=1)
    parser.add_argument("--unicast", type=str, default=None, help="Device IP for unicast (default: multicast)")
    parser.add_argument("--leds", type=int, default=50)
    args = parser.parse_args()

    sender = sacn.sACNsender()
    sender.start()
    sender.activate_output(args.universe)

    if args.unicast:
        sender[args.universe].destination = args.unicast
    else:
        sender[args.universe].multicast = True

    channels = args.leds * 3  # RGB
    print(f"Sending to universe {args.universe}, {args.leds} LEDs, {'unicast ' + args.unicast if args.unicast else 'multicast'}")
    print("Ctrl+C to stop")

    try:
        while True:
            # Red sweep
            for i in range(256):
                data = tuple([i, 0, 0] * args.leds)[:channels]
                sender[args.universe].dmx_data = data
                time.sleep(0.02)
            # Green sweep
            for i in range(256):
                data = tuple([0, i, 0] * args.leds)[:channels]
                sender[args.universe].dmx_data = data
                time.sleep(0.02)
            # Blue sweep
            for i in range(256):
                data = tuple([0, 0, i] * args.leds)[:channels]
                sender[args.universe].dmx_data = data
                time.sleep(0.02)
    except KeyboardInterrupt:
        print("\nStopping")
    finally:
        sender.stop()


if __name__ == "__main__":
    main()

Checklist:

  • The code change is tested and works locally.
  • Tests have been added to verify that the new code works (under tests/ folder).

If user exposed functionality or configuration variables are added/changed:

@github-actions
Copy link
Contributor

github-actions bot commented Feb 19, 2026

To use the changes from this PR as an external component, add the following to your ESPHome configuration YAML file:

external_components:
  - source: github://pr#14086
    components: [e131]
    refresh: 1s

(Added by the PR bot)

@codecov-commenter
Copy link

codecov-commenter commented Feb 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.11%. Comparing base (4d05e4d) to head (fe57153).

Additional details and impacted files
@@           Coverage Diff           @@
##              dev   #14086   +/-   ##
=======================================
  Coverage   74.11%   74.11%           
=======================================
  Files          55       55           
  Lines       11590    11590           
  Branches     1578     1578           
=======================================
  Hits         8590     8590           
  Misses       2598     2598           
  Partials      402      402           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bdraco bdraco force-pushed the fix-e131-esp8266-udp branch from 8a72b8f to 5209440 Compare February 19, 2026 05:59
@github-actions
Copy link
Contributor

github-actions bot commented Feb 19, 2026

Memory Impact Analysis

Components: e131
Platform: esp8266-ard

Metric Target Branch This PR Change
RAM 28,772 bytes 28,792 bytes 📈 🔸 +20 bytes (+0.07%)
Flash 346,883 bytes 346,195 bytes 📉 ✅ -688 bytes (-0.20%)
📊 Component Memory Breakdown
Component Target Flash PR Flash Change
[esphome]socket 2,699 bytes 89 bytes 📉 🎉 -2,610 bytes (-96.70%)
[lib]esp8266wifi 454 bytes 2,179 bytes 📈 +1,725 bytes (+379.96%)
network_stack 22,958 bytes 21,646 bytes 📉 -1,312 bytes (-5.71%)
wifi_config 4,432 bytes 5,146 bytes 📈 +714 bytes (+16.11%)
other 7,903 bytes 8,575 bytes 📈 +672 bytes (+8.50%)
[esphome]e131 2,898 bytes 2,669 bytes 📉 🎉 -229 bytes (-7.90%)
arduino_core 2,569 bytes 2,736 bytes 📈 +167 bytes (+6.50%)
rom_functions 6,004 bytes 6,143 bytes 📈 +139 bytes (+2.32%)
misc_system 3,994 bytes 3,998 bytes 📈 +4 bytes (+0.10%)
🔍 Symbol-Level Changes (click to expand)

Changed Symbols

Symbol Target Size PR Size Change
esphome::e131::E131Component::setup() 209 bytes 75 bytes 📉 -134 bytes (-64.11%)
esphome::e131::E131Component::E131Component() 52 bytes 83 bytes 📈 +31 bytes (+59.62%)
esphome::e131::E131Component::loop() 146 bytes 160 bytes 📈 +14 bytes (+9.59%)
esphome::e131::E131Component::join_igmp_groups_() 157 bytes 149 bytes 📉 -8 bytes (-5.10%)
esphome::e131::E131Component::setup()::__pstr__ 32 bytes 30 bytes 📉 -2 bytes (-6.25%)

New Symbols (top 15)

Symbol Size
hostByNameImpl(char const*, IPAddress&, unsigned int, DNSResolveType) [$constprop$0] 315 bytes
WiFiUDP::write(unsigned char const*, unsigned int) 223 bytes
WiFiUDP::parsePacket() 206 bytes
WiFiUDP::endPacket() 154 bytes
S2Stream::read(unsigned char*, unsigned int) 137 bytes
vtable for WiFiUDP 128 bytes
String::remove(unsigned int, unsigned int) 123 bytes
IPAddress::fromString4(char const*) 114 bytes
UdpContext::unref() 104 bytes
WiFiUDP::read(unsigned char*, unsigned int) 101 bytes
WiFiUDP::begin(unsigned short) 96 bytes
IPAddress::toString() const 95 bytes
WiFiUDP::beginPacketMulticast(IPAddress, unsigned short, IPAddress, int) 89 bytes
vtable for StreamString 88 bytes
S2Stream::read() 86 bytes
42 more new symbols... Total: 3,273 bytes

Removed Symbols (top 15)

Symbol Size
tcp_write 622 bytes
tcp_listen_with_backlog_and_err 228 bytes
esphome::socket::LWIPRawImpl::read(void*, unsigned int) 227 bytes
tcp_bind 193 bytes
esphome::socket::LWIPRawListenImpl::s_accept_fn(void*, tcp_pcb*, int) 159 bytes
esphome::socket::LWIPRawListenImpl::accept(sockaddr*, unsigned int*) 152 bytes
esphome::socket::LWIPRawImpl::getsockopt(int, int, void*, unsigned int*) 131 bytes
esphome::socket::LWIPRawImpl::bind(sockaddr const*, unsigned int) 129 bytes
tcp_shutdown 113 bytes
esphome::socket::LWIPRawImpl::writev(iovec const*, int) 107 bytes
esphome::socket::socket(int, int, int) 107 bytes
esphome::socket::LWIPRawImpl::getpeername(sockaddr*, unsigned int*) 103 bytes
esphome::socket::LWIPRawImpl::getsockname(sockaddr*, unsigned int*) 103 bytes
esphome::socket::LWIPRawImpl::internal_write(void const*, unsigned int) 98 bytes
esphome::socket::LWIPRawImpl::shutdown(int) 94 bytes
36 more removed symbols... Total: 3,868 bytes

Note: This analysis measures static RAM and Flash usage only (compile-time allocation).
Dynamic memory (heap) cannot be measured automatically.
⚠️ You must test this PR on a real device to measure free heap and ensure no runtime memory issues.

This analysis runs automatically when components change. Memory usage is measured from a representative test configuration.

The socket abstraction layer on ESP8266/RP2040 (USE_SOCKET_IMPL_LWIP_TCP)
only supports TCP. When E1.31 was migrated from WiFiUDP to sockets, it
silently broke on these platforms since SOCK_DGRAM requests get TCP sockets
that never receive UDP data.

Restore WiFiUDP as the transport on LWIP_TCP platforms while keeping the
socket-based implementation on BSD/LWIP socket platforms (ESP32, etc.).
This follows the same dual-path pattern used by the udp and wake_on_lan
components.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a critical bug where E1.31 (sACN) stopped working on ESP8266 and RP2040 platforms after PR #4832 migrated the component from WiFiUDP to the socket abstraction layer. The socket implementation on these platforms (USE_SOCKET_IMPL_LWIP_TCP) only supports TCP, causing E1.31 UDP packets to never be received. The fix restores WiFiUDP for LWIP_TCP platforms while keeping the socket-based implementation for platforms with full socket support (ESP32, etc.), following the established dual-path pattern used by the udp and wake_on_lan components.

Changes:

  • Added conditional compilation to use WiFiUDP on ESP8266/RP2040 (LWIP_TCP) and socket API on other platforms (BSD_SOCKETS/LWIP_SOCKETS)
  • Modified destructor, setup(), and loop() methods to handle both transport mechanisms
  • Guarded socket_ nullptr check to only compile on platforms with socket support

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
esphome/components/e131/e131.h Added conditional includes for socket.h vs WiFiUdp.h and conditional member variable (socket_ vs udp_) based on platform
esphome/components/e131/e131.cpp Added dual-path implementation in destructor, setup(), and loop() for socket vs WiFiUDP transport
esphome/components/e131/e131_packet.cpp Added preprocessor guard around socket_ nullptr check so it only compiles on socket-supporting platforms

@bdraco bdraco added this to the 2026.2.1 milestone Feb 19, 2026
@bdraco bdraco marked this pull request as ready for review February 19, 2026 15:15
@bdraco
Copy link
Member Author

bdraco commented Feb 19, 2026

thanks

@bdraco bdraco merged commit 0484b28 into dev Feb 19, 2026
42 checks passed
@bdraco bdraco deleted the fix-e131-esp8266-udp branch February 19, 2026 15:27
@swoboda1337 swoboda1337 mentioned this pull request Feb 20, 2026
swoboda1337 added a commit to swoboda1337/esphome that referenced this pull request Feb 20, 2026
PR esphome#14086 added #if/#elif/#endif guards for LWIP TCP support in loop().
PR esphome#14133 then unified the BSD sockets path using read_() and removed
the #if and #endif, but the merge left the #elif and its #endif from
esphome#14086 intact, causing a #endif without #if compile error.

Restore the opening #if guard to match setup() and the destructor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions bot locked and limited conversation to collaborators Feb 21, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

E1.31 not responding after update on esp8266 (socket lacks udp support)

4 participants