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.
Quick Links¶
Essential Reading¶
- Code Navigation Guide - Navigate the codebase efficiently
- Architecture Overview - Understand system design
- Architecture Decisions - Design rationale and ADRs
- Testing Guide - Writing and running tests
Quick Start¶
- Installation - Set up your development environment
- Best Practices - Code quality standards
Understanding the Architecture¶
Before diving into development, understand the system design:
- Architecture Overview - System layers and component interaction
- Architecture Decisions - 15 ADRs explaining design choices
- Packet Flow - How packets are processed
- Protocol Details - Binary protocol implementation
- Device State - State management internals
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
Third-party client examples¶
The tutorials include examples that drive the emulator with lifx-async, a third-party LIFX client. It lives in the optional third-party dependency group, so a plain uv sync does not install it. To run those examples, sync with the group enabled:
uv sync keeps the environment in step with the lock file, so running a plain uv sync afterwards removes the group again. To use it for a single command without changing the environment, pass the group to uv run instead:
Client-driven integration suite¶
packages/lifx-emulator-core/tests/test_client_lifx_async.py starts an emulated fleet on loopback and drives it end to end with lifx-async: discovery, classification of every product family, identity reads, colour, power and label round trips, multizone and matrix transfers, and capability gating on the switch. The suite carries the client marker and skips itself with a clear reason when lifx-async is not installed, so a plain uv sync followed by pytest is unaffected.
To reproduce the CI run locally, install the group and select the marker:
Deselect it with -m "not client". CI runs the suite on the latest-Python Linux leg only; the other legs skip it. Renovate keeps lifx-async current in uv.lock, so a client release that changes behaviour fails that leg.
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 zensical serve
# Build static site
uv run zensical build
uv run llmstxt-standalone build
# View at http://localhost:8000
Development Workflow¶
Adding a New Feature¶
- Understand the architecture
- Read Architecture Decisions for context
- Review Architecture Overview for data flow
-
Check Code Navigation for file locations
-
Write tests first (TDD approach)
- Add test cases in appropriate
tests/directory - Ensure 95% coverage (minimum 80%)
-
Run
pytest -vto verify tests fail -
Implement the feature
- Follow existing patterns and layer boundaries
- Keep functions under 10 complexity (McCabe)
- Add type hints for all public APIs
-
Update docstrings
-
Verify code quality
-
Update documentation
- Add/update docstrings
- Update relevant markdown docs
-
Add examples if public API changed
-
Submit pull request
- Use conventional commits (feat:, fix:, docs:, etc.)
- Reference related issues
- Ensure CI passes
Fixing a Bug¶
- Reproduce the bug
- Write a failing test case
-
Verify it fails:
pytest -v -
Locate the issue
- Use Code Navigation to find relevant code
- Check Packet Flow for data flow
-
Add debug logging if needed
-
Fix the bug
- Make minimal changes
- Avoid scope creep
-
Maintain backwards compatibility
-
Verify the fix
-
Submit pull request
- Use
fix:prefix in commit message - Explain root cause in PR description
- Reference issue number
Refactoring Code¶
- Establish test coverage
- Ensure affected code has >95% coverage
-
Add tests if needed:
pytest --cov=lifx_emulator.module -
Plan the refactoring
- Review Architecture Decisions
- Don't violate established patterns
-
Consider backwards compatibility
-
Refactor incrementally
- Small, focused changes
- Run tests after each change
-
Keep commits atomic
-
Verify no regressions
-
Update documentation
- Revise docstrings
- 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.10+
- 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):
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¶
-
Regenerate protocol (if LIFX added new packet type):
-
Create handler class in appropriate handler module:
# handlers/light_handlers.py class NewPacketHandler(PacketHandler): """Handle LightNewPacket (NNN) -> LightStateNewPacket (NNN).""" PKT_TYPE = Light.NewPacket.PKT_TYPE def handle( self, device_state: DeviceState, packet: Light.NewPacket | None, res_required: bool, ) -> list[Any]: # Implementation return [Light.StateNewPacket(...)] -
Register handler by adding an instance to the module's handler list (
create_default_registry()inhandlers/__init__.pyregisters every list): -
Add tests in
packages/lifx-emulator-core/tests/test_light_handlers_extended.py:def test_handle_new_packet(color_device): header = LifxHeader( source=12345, target=color_device.state.get_target_bytes(), sequence=1, pkt_type=Light.NewPacket.PKT_TYPE, res_required=True, ) packet = Light.NewPacket(...) responses = color_device.process_packet(header, packet) assert len(responses) == 1 # ... assertions
Adding a New Device Type¶
-
Add product to specs.yml (if needed):
-
Create factory function in
factories/factory.py: -
Add to
__init__.pyexports: -
Add CLI argument in
__main__.py: -
Add tests:
Debugging Tips¶
Enable Verbose Logging¶
Inspect Device State¶
from lifx_emulator import create_color_light
device = create_color_light()
print(device.state) # Pretty-prints all state
Test Single Packet¶
from lifx_emulator import create_color_light
from lifx_emulator.protocol.header import LifxHeader
from lifx_emulator.protocol.packets import Light
device = create_color_light("d073d5000001")
packet = Light.GetColor()
header = LifxHeader(
target=bytes.fromhex("d073d5000001") + b"\x00\x00",
source=12345,
sequence=1,
pkt_type=Light.GetColor.PKT_TYPE,
res_required=True,
)
responses = device.process_packet(header, packet)
print(responses) # list of (header, packet) tuples
Profile Performance¶
# Using pytest with profiling
pytest --profile
# Using cProfile
python -m cProfile -s cumtime -m lifx_emulator_app
Check Coverage Gaps¶
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.10, 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):
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:
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/ # Zensical 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 # Zensical-compatible documentation config
└── README.md # Project overview
Resources¶
Internal Documentation¶
External Resources¶
- LIFX LAN Protocol Docs
- LIFX Products JSON
- Python Type Hints
- asyncio Documentation
- Pytest Documentation
- FastAPI Documentation
Tools Documentation¶
- uv Package Manager
- Ruff Linter
- Pyright Type Checker
- PyApp Binary Builder
- Zensical
- Conventional Commits
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¶
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Next Steps¶
- First time here? Start with Code Navigation
- Planning a feature? Read Architecture Decisions
- Need to understand data flow? Check Packet Flow
- Ready to code? Review the Getting Started section above
Happy coding! 🚀