Skip to content

Presence Engine Development Guide

This guide covers the complete development workflow for the Presence Detection Engine, from environment setup to testing and contributing.

  • 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

The repository includes a devcontainer configuration for one-click setup:

  1. Open the repository
  2. Click CodeCodespacesCreate codespace
  3. Wait for environment to build (~3 minutes)

The Codespace includes:

  • ESPHome with all dependencies
  • Python test environment
  • VS Code extensions for YAML and C++
Terminal window
# Clone repository
git clone https://github.com/r-mccarty/presence-dectection-engine.git
cd presence-dectection-engine
# Create Python virtual environment
python -m venv venv
source venv/bin/activate # or `venv\Scripts\activate` on Windows
# Install dependencies
pip install -r requirements.txt
pip install esphome platformio
# Install PlatformIO for C++ testing
pip install platformio
pio platform install espressif32
Terminal window
pip install esphome
# Verify installation
esphome version
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 dependencies

Create esphome/secrets.yaml:

wifi_ssid: "YourNetworkName"
wifi_password: "YourWiFiPassword"
# Optional: Home Assistant API
ha_api_password: "your-api-password"
# Optional: OTA updates
ota_password: "your-ota-password"

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 component
external_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.0m
Terminal window
cd esphome
esphome compile base.yaml
Terminal window
# Via USB
esphome run base.yaml
# Via OTA (after initial flash)
esphome run base.yaml --device presence-sensor.local
Terminal window
esphome logs base.yaml

The C++ components are tested with PlatformIO:

Terminal window
# Run all unit tests
pio test -e native
# Run specific test
pio test -e native -f test_state_machine
# Run with verbose output
pio test -e native -v

Test Structure:

tests/unit/
├── test_state_machine/
│ └── test_transitions.cpp
├── test_statistics/
│ └── test_rolling_stats.cpp
├── test_calibration/
│ └── test_mad.cpp
└── test_main.cpp

Example Test:

tests/unit/test_state_machine/test_transitions.cpp
#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());
}
Terminal window
# Install test dependencies
pip install pytest pytest-asyncio aiohttp
# Run E2E tests (requires Home Assistant)
pytest tests/e2e/ -v
# Run with specific Home Assistant URL
HA_URL=http://192.168.1.100:8123 pytest tests/e2e/ -v

Example E2E Test:

tests/e2e/test_presence_detection.py
import pytest
from homeassistant_api import Client
@pytest.fixture
async def ha_client():
client = Client(
os.environ.get('HA_URL', 'http://localhost:8123'),
os.environ.get('HA_TOKEN')
)
return client
@pytest.mark.asyncio
async 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'
Terminal window
git checkout main
git pull origin main
git checkout -b feature/your-feature
  • 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
Terminal window
# Run unit tests
pio test -e native
# Compile and flash
cd esphome && esphome run base.yaml
# Run E2E tests
pytest tests/e2e/
Terminal window
git add .
git commit -m "feat: add new feature"

Follow Conventional Commits:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation
  • test: Tests
  • refactor: Code refactoring

Push to GitHub and create a PR against main.

Terminal window
# View ESP32 serial output
esphome logs base.yaml --device /dev/ttyUSB0

Enable verbose logging in base.yaml:

logger:
level: VERBOSE
logs:
presence_engine: DEBUG
sensor: INFO

Check Developer Tools → States for entity values.

Enable debug logging in HA:

configuration.yaml
logger:
default: info
logs:
homeassistant.components.esphome: debug
  1. Check wiring (TX→RX, RX→TX)
  2. Verify baud rate (256000)
  3. Ensure 3.3V power (not 5V)
  1. Check enter_debounce isn’t too long
  2. Verify z-score is actually above threshold
  3. Check sensor readings in logs
  1. Ensure room is empty during calibration
  2. Collect at least 300 samples
  3. Check for interference sources
ResourceLocation
ESPHome Docshttps://esphome.io
LD2410 Datasheetdocs/hardware/ld2410.pdf
Component APIdocs/api/
Troubleshootingdocs/troubleshooting/
  • GitHub Issues: Bug reports and feature requests
  • Discord: OpticWorks community server
  • Home Assistant Forum: Tag with “opticworks”