Skip to content

Troubleshooting Guide

Common issues and solutions when working with lifx.

Table of Contents

Discovery Issues

No Devices Found

Symptom: discover() returns an empty group

Common Causes:

  1. Devices not on same network

    # Check network connectivity
    import asyncio
    from lifx.network.discovery import discover_devices
    
    devices = []
    async for device in discover_devices(timeout=5.0):
        devices.append(device)
    print(f"Found {len(devices)} devices")
    

  2. Firewall blocking UDP port 56700

    # Linux: Check if port is open
    sudo netstat -an | grep 56700
    
    # Allow UDP on port 56700
    sudo ufw allow 56700/udp
    

  3. Broadcast address incorrect

Try different broadcast addresses:

from lifx import discover, DeviceGroup

# Default (255.255.255.255)
devices = []
async for device in discover():
    devices.append(device)
group = DeviceGroup(devices)

# Network-specific (e.g., 192.168.1.255)
devices = []
async for device in discover(broadcast_address="192.168.1.255"):
    devices.append(device)
group = DeviceGroup(devices)

Solution:

import asyncio
from lifx.network.discovery import discover_devices

async def diagnose_discovery():
    print("Attempting discovery...")

    # Try with extended timeout
    devices = []
    async for device in discover_devices(
        timeout=10.0,
        broadcast_address="255.255.255.255"
    ):
        devices.append(device)

    if not devices:
        print("No devices found. Check:")
        print("1. Devices are powered on")
        print("2. Devices are on the same network")
        print("3. Firewall allows UDP port 56700")
        print("4. Try a network-specific broadcast address")
    else:
        print(f"Found {len(devices)} devices:")
        for device in devices:
            print(f"  - {device.serial} at {device.ip}")

asyncio.run(diagnose_discovery())

Partial Device Discovery

Symptom: Only some devices discovered

Causes:

  • Devices on different subnets
  • Network congestion
  • Devices slow to respond

Solution:

A single discover_devices() call already re-broadcasts GetService on an escalating schedule within the discovery window, so devices that miss the first broadcast get several more chances to respond. There is no need to call discovery multiple times.

If some devices are still missed on a slow or congested network, increase the timeout to widen the discovery window:

devices = []
async for device in discover_devices(timeout=30.0):
    devices.append(device)

Connection Problems

Connection Refused

Symptom: LifxConnectionError: Connection refused

Causes:

  • Incorrect IP address
  • Device powered off
  • Network unreachable

Solution:

from lifx import Device, LifxConnectionError
import asyncio

async def test_connection(ip: str):
    try:
        async with await Device.connect(ip) as light:
            label = await light.get_label()
            print(f"Connected to: {label}")
            return True

    except LifxConnectionError as e:
        print(f"Connection failed: {e}")
        print("Check:")
        print("1. Device IP is correct")
        print("2. Device is powered on")
        print("3. Device is reachable (try ping)")
        return False

# Test connectivity
asyncio.run(test_connection("192.168.1.100"))

Connection Drops

Symptom: Intermittent LifxConnectionError or LifxNetworkError

Causes:

  • WiFi signal weak
  • Network congestion
  • Device overloaded

Solution:

The library itself retransmits within each request's timeout, so transient packet loss is handled for you. An application-level wrapper like the one below is for retrying whole operations that failed — not for per-packet reliability.

import asyncio
from lifx import Device, LifxError

async def resilient_operation(ip: str, max_retries: int = 3):
    """Retry operations with exponential backoff"""
    async with await Device.connect(ip) as light:
        for attempt in range(max_retries):
            try:
                await light.set_power(True)
                print("Success!")
                return
            except LifxError as e:
                wait_time = 2 ** attempt  # 1s, 2s, 4s
                print(f"Attempt {attempt + 1} failed: {e}")

                if attempt < max_retries - 1:
                    print(f"Retrying in {wait_time}s...")
                    await asyncio.sleep(wait_time)

    print("All retries exhausted")

Timeout Errors

Request Timeouts

Symptom: LifxTimeoutError: Request timed out after X seconds

Causes:

  • Device slow to respond
  • Network latency high
  • Device busy processing other requests

Solution:

from lifx import Device

# Increase timeout for slow devices
async with await Device.connect(ip, timeout=5.0) as light:
    # get_color() returns (color, power, label)
    color, power, label = await light.get_color()

Discovery Timeout Too Short

Symptom: Some devices not found

Solution:

from lifx import discover

# Increase the discovery timeout (the default is 15.0 seconds)
devices = []
async for device in discover(timeout=30.0):
    devices.append(device)

print(f"Found {len(devices)} devices")

Performance Issues

Slow Operations

Symptom: Operations take longer than expected

Diagnosis:

import asyncio
import time
from lifx import Device

async def measure_latency():
    async with await Device.connect("192.168.1.100") as light:
        # Measure single request
        start = time.time()
        await light.get_label()
        elapsed = time.time() - start
        print(f"Single request: {elapsed*1000:.2f}ms")

        # Measure sequential requests
        start = time.time()
        for _ in range(10):
            await light.get_label()
        elapsed = time.time() - start
        print(f"10 sequential: {elapsed*1000:.2f}ms ({elapsed*100:.2f}ms avg)")

        # Measure concurrent requests
        start = time.time()
        await asyncio.gather(*[light.get_label() for _ in range(10)])
        elapsed = time.time() - start
        print(f"10 concurrent: {elapsed*1000:.2f}ms")

Common Causes:

  1. Sequential instead of concurrent operations

Slow approach (sequential):

for device in devices:
    await device.set_color(Colors.BLUE)

Fast approach (concurrent):

await asyncio.gather(
    *[device.set_color(Colors.BLUE) for device in devices]
)

  1. Not reusing connections

Inefficient (creates new connection each time):

for i in range(10):
    async with await Device.connect(ip) as light:
        await light.set_color(HSBK(hue=(360 / 10) * i, saturation=1.0, brightness=1.0, kelvin=3500))

Efficient (reuses connection):

async with await Device.connect(ip) as light:
    for i in range(10):
        await light.set_color(HSBK(hue=(360 / 10) * i, saturation=1.0, brightness=1.0, kelvin=3500))

  1. Need fresh data?

Use get_*() methods to always fetch from the device:

# Always fetch fresh data
# get_color() returns all three values in one call
color, power, label = await light.get_color()

# Or fetch other device info
version = await light.get_version()

Gen4 Power-Save Wake Tail

Symptom: The first command after a device has been idle for roughly a minute or more is slower than usual — up to ~250 ms instead of the single-digit milliseconds a busy device answers in. Subsequent commands respond at full speed.

Causes:

  • Gen4 devices use WiFi power-save while idle, so the radio takes a moment to wake for the first packet
  • Affects gen4 devices only — gen2 and gen3 devices show no wake tail

This is a latency effect, not a reliability problem: on healthy networks, an idle device loses zero packets. Every command still succeeds; the first one after idle just takes a little longer.

Identifying gen4 devices:

Gen4 devices report a host firmware major version of 4 or later:

firmware = await device.get_host_firmware()
if firmware.version_major >= 4:
    print("Gen4 device: expect a sub-250 ms wake tail after idle")

Inside async with, the cached device.host_firmware property is already populated, so you can check device.host_firmware.version_major directly. Do not try to identify gen4 devices by product ID — the products registry has no generation field.

Solution:

Most applications can ignore the wake tail entirely. If your application is latency-sensitive and cannot tolerate a slower first command, an optional periodic poll keeps the device's radio awake:

import asyncio
from lifx import Light

async def keep_awake(light: Light) -> None:
    """Optional: poll periodically so a gen4 device's radio stays awake."""
    while True:
        # Any request works; get_color() returns colour, power and label
        # in a single request/response pair.
        await light.get_color()
        await asyncio.sleep(15)  # 10-15 s keeps the wake tail away

Run it alongside your application with asyncio.create_task() or asyncio.TaskGroup — no extra coordination is needed, because the library serialises requests per connection. The poll is read-only, so it is safe to run continuously, and one request every 15 seconds stays far below the ~20 msg/sec a device can handle.

lifx-async deliberately ships no keepalive daemon

Measured on real hardware, idle devices lose zero packets on healthy networks — the wake tail is a small, bounded latency cost, not a reliability problem. Whether to spend a packet every 10–15 seconds to avoid it is the application's choice, so the library does not make it for you.

Streaming frames to a device? See the Animation Guide for how sustained streaming interacts with gen4 power-save.

Docker / Container Networking

Symptom: Discovery doesn't work in Docker container

Cause: Container network isolation

Solution:

# docker-compose.yml
services:
  app:
    network_mode: "host"  # Use host network for UDP broadcast

Or use manual device specification:

# Don't rely on discovery
from lifx import Colors, Device

async with await Device.connect("192.168.1.100") as light:
    await light.set_color(Colors.BLUE)

Debugging Tips

Enable Debug Logging

import logging

# Enable DEBUG logging for lifx
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# Or for specific modules
logging.getLogger('lifx.network').setLevel(logging.DEBUG)
logging.getLogger('lifx.devices').setLevel(logging.DEBUG)

Check Product Registry

from lifx.products import get_product, get_registry

# Check how many products the registry knows
registry = get_registry()
print(f"Registry contains {len(registry)} products")

# Check specific product
product = get_product(27)  # LIFX A19
if product:
    print(f"Name: {product.name}")
    print(f"Capabilities: {product.capabilities}")

Verify Device Reachability

# Ping device
ping 192.168.1.100

# Check UDP port (requires nmap)
sudo nmap -sU -p 56700 192.168.1.100

# Test with netcat
echo -n "test" | nc -u 192.168.1.100 56700

Getting Help

If you're still experiencing issues:

  1. Check GitHub Issues: github.com/Djelibeybi/lifx-async/issues
  2. Enable debug logging: Capture logs with logging.DEBUG
  3. Provide details:
  4. Python version
  5. lifx version
  6. Device model and firmware version
  7. Network configuration
  8. Minimal reproduction code
  9. Full error traceback

Common Error Messages

Error Meaning Solution
LifxTimeoutError Device didn't respond Increase timeout, check network
LifxConnectionError Can't connect to device Check IP, firewall, device power
LifxDeviceNotFoundError Device not discovered Check network, increase timeout
LifxProtocolError Invalid response Update firmware, check device type
LifxUnsupportedCommandError Device doesn't support command Check device capabilities
AttributeError: 'Light' has no attribute 'set_color_zones' Wrong device class Use MultiZoneLight

Next Steps