Presence Engine Development Guide
This guide covers the complete development workflow for the Presence Detection Engine, from environment setup to testing and contributing.
Prerequisites
Section titled “Prerequisites”- Operating System: Windows, macOS, or Linux
- Python: 3.10 or higher
- Node.js: 18+ (for ESPHome dashboard)
- Hardware: ESP32 + LD2410 for testing
- Home Assistant: Local instance for integration testing
Development Environment Options
Section titled “Development Environment Options”Option 1: GitHub Codespaces (Recommended)
Section titled “Option 1: GitHub Codespaces (Recommended)”The repository includes a devcontainer configuration for one-click setup:
- Open the repository
- Click Code → Codespaces → Create codespace
- Wait for environment to build (~3 minutes)
The Codespace includes:
- ESPHome with all dependencies
- Python test environment
- VS Code extensions for YAML and C++
Option 2: Local Development
Section titled “Option 2: Local Development”Install Dependencies
Section titled “Install Dependencies”# Clone repositorygit clone https://github.com/r-mccarty/presence-dectection-engine.gitcd presence-dectection-engine
# Create Python virtual environmentpython -m venv venvsource venv/bin/activate # or `venv\Scripts\activate` on Windows
# Install dependenciespip install -r requirements.txtpip install esphome platformio
# Install PlatformIO for C++ testingpip install platformiopio platform install espressif32Install ESPHome
Section titled “Install ESPHome”pip install esphome
# Verify installationesphome versionProject Structure
Section titled “Project Structure”presence-detection-engine/├── .devcontainer/ # GitHub Codespaces config├── docs/│ ├── architecture/ # System design documentation│ ├── workflows/ # Development guides│ └── troubleshooting/ # Common issues and solutions├── esphome/│ ├── base.yaml # Base ESPHome configuration│ ├── secrets.yaml.example│ └── components/│ └── presence_engine/│ ├── __init__.py│ ├── presence_engine.h│ ├── presence_engine.cpp│ ├── state_machine.h│ ├── statistics.h│ └── calibration.h├── homeassistant/│ ├── dashboards/ # Lovelace YAML│ ├── blueprints/ # Automation blueprints│ └── helpers/ # Input number/boolean configs├── tests/│ ├── unit/ # PlatformIO C++ tests│ └── e2e/ # Python integration tests├── hardware/ # 3D printable mounts├── platformio.ini # PlatformIO configuration└── requirements.txt # Python dependenciesConfiguration
Section titled “Configuration”ESPHome Secrets
Section titled “ESPHome Secrets”Create esphome/secrets.yaml:
wifi_ssid: "YourNetworkName"wifi_password: "YourWiFiPassword"
# Optional: Home Assistant APIha_api_password: "your-api-password"
# Optional: OTA updatesota_password: "your-ota-password"Base Configuration
Section titled “Base Configuration”The esphome/base.yaml provides the foundation:
esphome: name: presence-sensor platform: ESP32 board: esp32dev
wifi: ssid: !secret wifi_ssid password: !secret wifi_password
api: password: !secret ha_api_password
ota: password: !secret ota_password
logger: level: DEBUG
# Include the presence engine componentexternal_components: - source: type: local path: components
presence_engine: # LD2410 UART configuration uart_id: uart_ld2410 tx_pin: GPIO17 rx_pin: GPIO16
# Detection parameters enter_threshold: 2.0 exit_threshold: 1.0 enter_debounce: 500ms exit_debounce: 2000ms
# Distance window (optional) min_distance: 0.2m max_distance: 2.0mBuilding and Flashing
Section titled “Building and Flashing”Compile Firmware
Section titled “Compile Firmware”cd esphomeesphome compile base.yamlFlash to Device
Section titled “Flash to Device”# Via USBesphome run base.yaml
# Via OTA (after initial flash)esphome run base.yaml --device presence-sensor.localView Logs
Section titled “View Logs”esphome logs base.yamlTesting
Section titled “Testing”Unit Tests (C++)
Section titled “Unit Tests (C++)”The C++ components are tested with PlatformIO:
# Run all unit testspio test -e native
# Run specific testpio test -e native -f test_state_machine
# Run with verbose outputpio test -e native -vTest Structure:
tests/unit/├── test_state_machine/│ └── test_transitions.cpp├── test_statistics/│ └── test_rolling_stats.cpp├── test_calibration/│ └── test_mad.cpp└── test_main.cppExample Test:
#include <unity.h>#include "state_machine.h"
void test_clear_to_entering() { StateMachine sm; sm.set_thresholds(2.0, 1.0);
TEST_ASSERT_EQUAL(STATE_CLEAR, sm.current_state());
sm.update(2.5); // Above enter threshold TEST_ASSERT_EQUAL(STATE_ENTERING, sm.current_state());}
void test_entering_requires_debounce() { StateMachine sm; sm.set_thresholds(2.0, 1.0); sm.set_debounce(500, 2000);
sm.update(2.5); // Enter ENTERING state TEST_ASSERT_EQUAL(STATE_ENTERING, sm.current_state());
// Not enough time passed sm.advance_time(400); sm.update(2.5); TEST_ASSERT_EQUAL(STATE_ENTERING, sm.current_state());
// Now enough time sm.advance_time(200); sm.update(2.5); TEST_ASSERT_EQUAL(STATE_PRESENT, sm.current_state());}Integration Tests (Python)
Section titled “Integration Tests (Python)”# Install test dependenciespip install pytest pytest-asyncio aiohttp
# Run E2E tests (requires Home Assistant)pytest tests/e2e/ -v
# Run with specific Home Assistant URLHA_URL=http://192.168.1.100:8123 pytest tests/e2e/ -vExample E2E Test:
import pytestfrom homeassistant_api import Client
@pytest.fixtureasync def ha_client(): client = Client( os.environ.get('HA_URL', 'http://localhost:8123'), os.environ.get('HA_TOKEN') ) return client
@pytest.mark.asyncioasync def test_presence_detected(ha_client): # Simulate presence by adjusting threshold await ha_client.set_state( 'number.presence_sensor_enter_threshold', state='0.5' )
# Wait for state change await asyncio.sleep(2)
state = await ha_client.get_state('binary_sensor.presence_sensor') assert state.state == 'on'Development Workflow
Section titled “Development Workflow”1. Create Feature Branch
Section titled “1. Create Feature Branch”git checkout maingit pull origin maingit checkout -b feature/your-feature2. Make Changes
Section titled “2. Make Changes”- C++ code: Follow the existing style (4-space indent, snake_case)
- Python code: Use Black formatter and isort
- YAML: 2-space indent, follow ESPHome conventions
3. Test Locally
Section titled “3. Test Locally”# Run unit testspio test -e native
# Compile and flashcd esphome && esphome run base.yaml
# Run E2E testspytest tests/e2e/4. Commit
Section titled “4. Commit”git add .git commit -m "feat: add new feature"Follow Conventional Commits:
feat:New featurefix:Bug fixdocs:Documentationtest:Testsrefactor:Code refactoring
5. Create Pull Request
Section titled “5. Create Pull Request”Push to GitHub and create a PR against main.
Debugging
Section titled “Debugging”Serial Monitor
Section titled “Serial Monitor”# View ESP32 serial outputesphome logs base.yaml --device /dev/ttyUSB0Debug Logging
Section titled “Debug Logging”Enable verbose logging in base.yaml:
logger: level: VERBOSE logs: presence_engine: DEBUG sensor: INFOHome Assistant Debugging
Section titled “Home Assistant Debugging”Check Developer Tools → States for entity values.
Enable debug logging in HA:
logger: default: info logs: homeassistant.components.esphome: debugCommon Issues
Section titled “Common Issues””LD2410 not responding”
Section titled “”LD2410 not responding””- Check wiring (TX→RX, RX→TX)
- Verify baud rate (256000)
- Ensure 3.3V power (not 5V)
“State stuck in ENTERING”
Section titled ““State stuck in ENTERING””- Check
enter_debounceisn’t too long - Verify z-score is actually above threshold
- Check sensor readings in logs
”Calibration fails”
Section titled “”Calibration fails””- Ensure room is empty during calibration
- Collect at least 300 samples
- Check for interference sources
Resources
Section titled “Resources”| Resource | Location |
|---|---|
| ESPHome Docs | https://esphome.io |
| LD2410 Datasheet | docs/hardware/ld2410.pdf |
| Component API | docs/api/ |
| Troubleshooting | docs/troubleshooting/ |
Getting Help
Section titled “Getting Help”- GitHub Issues: Bug reports and feature requests
- Discord: OpticWorks community server
- Home Assistant Forum: Tag with “opticworks”