Skip to content

Developer Documentation

Practical guide for developers working on the lifx-emulator codebase

Welcome

This section provides developer-focused documentation for navigating and contributing to the lifx-emulator project. Whether you're fixing a bug, adding a feature, or just exploring the codebase, this guide will help you get oriented quickly.

Essential Reading

Quick Start

Understanding the Architecture

Before diving into development, understand the system design:

This Development section focuses on navigating and modifying the codebase, not architectural theory.


Code Navigation Guide

Purpose: Help you find your way around the codebase quickly

What's inside: - Package structure and file organization (13.6k LOC core library) - Entry points and main exports - Layer architecture (Network → Domain → Repository → Persistence) - Common code paths and module dependencies - Quick command reference for development

When to use: - First time exploring the codebase - Looking for where specific functionality lives - Understanding how modules relate to each other - Finding test files or documentation

Read the Code Navigation Guide →


Getting Started as a Developer

1. Environment Setup

# Clone repository
git clone https://github.com/Djelibeybi/lifx-emulator.git
cd lifx-emulator

# Install dependencies (uses uv package manager)
uv sync

# Activate virtual environment
source .venv/bin/activate

# Verify installation
pytest --version
pyright --version
ruff --version

2. Run Tests

# Run all tests (764 tests)
pytest

# Run with coverage report
pytest --cov=lifx_emulator --cov=lifx_emulator_app --cov-report=html

# Run specific module tests
pytest packages/lifx-emulator-core/tests/test_device.py -v

# Run specific test
pytest packages/lifx-emulator-core/tests/test_device.py::test_process_packet -v

3. Code Quality Checks

# Lint and auto-fix
ruff check --fix .

# Type checking
pyright

# Run all quality checks (mimics CI)
ruff check . && pyright && pytest

4. Run the Emulator

# As module
python -m lifx_emulator_app

# As installed command
lifx-emulator --api --verbose

# With custom devices
lifx-emulator --color 2 --multizone 1 --tile 1 --api

5. Build Documentation

# Serve locally (live reload)
uv run mkdocs serve

# Build static site
uv run mkdocs build

# View at http://localhost:8000

Development Workflow

Adding a New Feature

  1. Understand the architecture
  2. Read Architecture Decisions for context
  3. Review Architecture Overview for data flow
  4. Check Code Navigation for file locations

  5. Write tests first (TDD approach)

  6. Add test cases in appropriate tests/ directory
  7. Ensure 95% coverage (minimum 80%)
  8. Run pytest -v to verify tests fail

  9. Implement the feature

  10. Follow existing patterns and layer boundaries
  11. Keep functions under 10 complexity (McCabe)
  12. Add type hints for all public APIs
  13. Update docstrings

  14. Verify code quality

    ruff check --fix .        # Lint and format
    pyright                   # Type checking
    pytest --cov              # Test coverage
    

  15. Update documentation

  16. Add/update docstrings
  17. Update relevant markdown docs
  18. Add examples if public API changed

  19. Submit pull request

  20. Use conventional commits (feat:, fix:, docs:, etc.)
  21. Reference related issues
  22. Ensure CI passes

Fixing a Bug

  1. Reproduce the bug
  2. Write a failing test case
  3. Verify it fails: pytest -v

  4. Locate the issue

  5. Use Code Navigation to find relevant code
  6. Check Packet Flow for data flow
  7. Add debug logging if needed

  8. Fix the bug

  9. Make minimal changes
  10. Avoid scope creep
  11. Maintain backwards compatibility

  12. Verify the fix

    pytest -v                 # All tests pass
    pytest --cov              # Coverage maintained
    ruff check . && pyright   # Quality checks pass
    

  13. Submit pull request

  14. Use fix: prefix in commit message
  15. Explain root cause in PR description
  16. Reference issue number

Refactoring Code

  1. Establish test coverage
  2. Ensure affected code has >95% coverage
  3. Add tests if needed: pytest --cov=lifx_emulator.module

  4. Plan the refactoring

  5. Review Architecture Decisions
  6. Don't violate established patterns
  7. Consider backwards compatibility

  8. Refactor incrementally

  9. Small, focused changes
  10. Run tests after each change
  11. Keep commits atomic

  12. Verify no regressions

    pytest -v                 # All tests still pass
    pytest --cov              # Coverage maintained/improved
    ruff check .              # Complexity within limits
    

  13. Update documentation

  14. Revise docstrings
  15. Update architecture docs if needed

Code Quality Standards

Test Coverage

  • Target: 95% coverage
  • Minimum: 80% (enforced by CI)
  • Check: pytest --cov --cov-report=term-missing

Code Complexity

  • Max McCabe complexity: 10 per function
  • Max arguments: 5 per function
  • Max branches: 12 per function
  • Max statements: 50 per function
  • Check: ruff check . (enforced automatically)

Type Checking

  • Standard: Pyright standard mode
  • Target: Python 3.11+
  • Check: pyright

Formatting

  • Tool: Ruff formatter
  • Line length: 88 characters
  • Quote style: Double quotes
  • Indent: 4 spaces
  • Check: ruff format --check .
  • Fix: ruff format .

Import Organization

  • Order: stdlib → third-party → local
  • Tool: Ruff import sorter
  • Check: ruff check --select I .
  • Fix: ruff check --select I --fix .

Common Development Tasks

Regenerating Auto-Generated Code

Protocol packets (DO NOT EDIT protocol/packets.py):

python -m lifx_emulator.protocol.generator
# Downloads LIFX YAML spec and regenerates packets.py

Product registry (DO NOT EDIT products/registry.py):

python -m lifx_emulator.products.generator
# Downloads products.json from LIFX GitHub
# Regenerates registry.py with all 137+ products
# Updates specs.yml templates for new products

Adding a New Packet Type

  1. Regenerate protocol (if LIFX added new packet type):

    python -m lifx_emulator.protocol.generator
    

  2. Create handler function in appropriate handler module:

    # handlers/light_handlers.py
    def handle_new_packet(
        device: EmulatedLifxDevice,
        packet: NewPacket,
        header: LifxHeader,
    ) -> list[Any]:
        # Implementation
        return [ResponsePacket(...)]
    

  3. Register handler in handlers/registry.py:

    registry.register(NewPacket.PKT_TYPE, handle_new_packet)
    

  4. Add tests in tests/test_handlers.py:

    def test_handle_new_packet():
        device = create_color_light()
        packet = NewPacket(...)
        header = create_test_header()
        responses = handle_new_packet(device, packet, header)
        assert len(responses) == 1
        # ... assertions
    

Adding a New Device Type

  1. Add product to specs.yml (if needed):

    products:
      99:  # Product ID
        zones: 16
        tile_count: 5
        # ... other specs
    

  2. Create factory function in factories/factory.py:

    def create_new_device_type(
        serial: str | None = None,
        storage: IDeviceStorageBackend | None = None,
    ) -> EmulatedLifxDevice:
        builder = DeviceBuilder()
        # ... configure builder
        return builder.build()
    

  3. Add to __init__.py exports:

    __all__ = [
        # ...
        "create_new_device_type",
    ]
    

  4. Add CLI argument in __main__.py:

    @app.default
    def main(
        # ...
        new_device_type: int = 0,
    ):
        # ... device creation logic
    

  5. Add tests:

    def test_create_new_device_type():
        device = create_new_device_type()
        assert device.state.has_new_capability
        # ... assertions
    


Debugging Tips

Enable Verbose Logging

lifx-emulator --verbose
# Shows all packets sent/received with hex dumps

Inspect Device State

import asyncio
from lifx_emulator import create_color_light

device = create_color_light()
print(device.state)  # Pretty-prints all state

Test Single Packet

from lifx_emulator.protocol.packets import Light
from lifx_emulator.protocol.header import LifxHeader

packet = Light.Get()
header = LifxHeader(
    target=bytes.fromhex("d073d5000001") + b"\x00\x00",
    source=12345,
    sequence=1,
    pkt_type=Light.Get.PKT_TYPE,
)

responses = device.process_packet(packet, header, ("127.0.0.1", 56700))
print(responses)

Profile Performance

# Using pytest with profiling
pytest --profile

# Using cProfile
python -m cProfile -s cumtime -m lifx_emulator_app

Check Coverage Gaps

pytest --cov --cov-report=html
# Open htmlcov/index.html in browser
# Red lines = not covered

CI/CD Pipeline

Automated Checks (GitHub Actions)

Every pull request runs: 1. Ruff lint check - Code quality and formatting 2. Pyright type check - Type safety validation 3. Pytest with coverage - All 764 tests, 95% coverage target 4. Multi-version testing - Python 3.11, 3.12, 3.13, 3.14 5. Documentation build - Ensures docs compile

All checks must pass before merge.

Building Standalone Binaries

The project uses PyApp to create standalone executables that bundle Python and all dependencies into a single binary. Binaries are built automatically on release, but you can build them locally for testing.

Prerequisites:

# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Build locally:

# Clone PyApp
git clone --depth 1 --branch v0.26.0 https://github.com/ofek/pyapp pyapp

# Build with environment variables
cd pyapp
PYAPP_PROJECT_NAME=lifx-emulator \
PYAPP_PROJECT_VERSION=4.1.0 \
PYAPP_PYTHON_VERSION=3.12 \
PYAPP_EXEC_SPEC=lifx_emulator_app.__main__:main \
cargo build --release

# Binary is at target/release/pyapp
mv target/release/pyapp ../lifx-emulator
chmod +x ../lifx-emulator

Optional: Embed Python distribution (larger binary, no download on first run):

PYAPP_DISTRIBUTION_EMBED=1 cargo build --release

Cross-compile for other platforms:

# Add target (example: macOS ARM64)
rustup target add aarch64-apple-darwin

# Build for that target
CARGO_BUILD_TARGET=aarch64-apple-darwin cargo build --release

Supported platforms (built automatically on release):

Platform Target Binary Name
Linux x86_64 x86_64-unknown-linux-gnu lifx-emulator-linux-x86_64
macOS Intel x86_64-apple-darwin lifx-emulator-macos-x86_64
macOS ARM aarch64-apple-darwin lifx-emulator-macos-arm64
Windows x86_64 x86_64-pc-windows-msvc lifx-emulator-windows-x86_64.exe

See PyApp documentation for advanced configuration options.

Semantic Release

The project uses semantic-release with conventional commits:

  • feat: → Minor version bump (1.x.0)
  • fix: → Patch version bump (1.0.x)
  • BREAKING CHANGE: → Major version bump (x.0.0)
  • docs:, chore:, ci: → No version bump

Commit message format:

<type>(<scope>): <subject>

<body>

<footer>

Examples:

feat(core): add HEV device support

Implements HEV Clean device emulation with duration tracking.

Closes #123

fix(api): correct scenario merging precedence

Device-specific scenarios now correctly override type scenarios.

docs(guide): add scenario API examples


Project Structure Quick Reference

lifx-emulator/
├── packages/
│   ├── lifx-emulator-core/          # Core library (13.6k LOC)
│   │   ├── src/lifx_emulator/
│   │   │   ├── devices/             # Device lifecycle, state, persistence
│   │   │   ├── scenarios/           # Test scenario management
│   │   │   ├── protocol/            # LIFX binary protocol
│   │   │   ├── handlers/            # Packet type handlers
│   │   │   ├── products/            # Product registry
│   │   │   ├── factories/           # Device creation
│   │   │   ├── repositories/        # Storage abstraction
│   │   │   └── server.py            # UDP server
│   │   └── tests/                   # Core library tests
│   │
│   └── lifx-emulator/               # Standalone app (1.6k LOC)
│       ├── src/lifx_emulator_app/
│       │   ├── __main__.py          # CLI entry point
│       │   └── api/                 # HTTP API (FastAPI)
│       └── tests/                   # App/API tests
├── docs/                            # MkDocs documentation
│   ├── development/                 # This section
│   ├── architecture/                # System design
│   ├── guide/                       # User guides
│   ├── library/                     # Library API reference
│   ├── cli/                         # CLI/API documentation
│   └── tutorials/                   # Step-by-step tutorials
├── pyproject.toml                   # Workspace config (uv)
├── mkdocs.yml                       # Documentation config
└── README.md                        # Project overview

Resources

Internal Documentation

External Resources

Tools Documentation


Getting Help

Contribution Guidelines

For detailed contribution information: - Code of conduct: Follow respectful collaboration practices - How to submit issues: Use GitHub Issues with clear descriptions - Pull request process: Use conventional commits, ensure CI passes - Development setup: See Getting Started above

Questions and Discussion


Next Steps

  1. First time here? Start with Code Navigation
  2. Planning a feature? Read Architecture Decisions
  3. Need to understand data flow? Check Packet Flow
  4. Ready to code? Review the Getting Started section above

Happy coding! 🚀