Skip to content

About

Home Assistant custom integration: Virtual presence sensors with sensor fusion and optional LLM support

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Repository files navigation

HA Soft Presence

hacs_badge GitHub release GitHub commit activity License

Virtual presence sensors for Home Assistant — inspired by the Aqara Presence Soft Sensor concept, but fully transparent, fully local, and fully configurable.

Instead of relying on a single sensor, HA Soft Presence combines multiple signals (mmWave, PIR, door contacts, media players, workstations, lights, …) using a weighted score engine and a 6-state machine to produce a stable, automation-ready occupancy decision per room.


✨ Features

  • 🏠 8 entities per room — binary occupancy, score, confidence, reason, plus 4 optional AI advisory entities
  • 🔀 Sensor fusion — mmWave, PIR, BLE/ESPresense, media player, workstation, lights, door contacts, locks
  • 🔄 6-state machine — clear → possible_entry → occupied → clear_pending → clear
  • 📊 Hysteresis — separate occupied/clear thresholds, no flickering
  • 🌙 Sleep mode — configurable entities raise the clear threshold at night
  • ⏱️ Configurable timeout per room before going clear
  • 💬 Reason string — human-readable explanation, e.g. "mmWave active + Media playing"
  • 📡 HA Events — fires ha_soft_presence_state_changed on every transition
  • 🛠️ Service calls — force_occupied, force_clear, reset_override per room
  • 🤖 Optional AI advisory — via Gemini, Ollama, OpenAI — off by default, fully local
  • 📍 Area auto-fill — entity selectors pre-filled from matching HA area on setup
  • 🖥️ Config UI — full multi-step setup, no YAML required
  • 🌍 11 languages — EN, DE, FR, ES, IT, NL, PL, PT, SV, RU, BG

🚀 Installation

Via HACS (recommended)

HA Soft Presence is available in the HACS default store.

Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.

  1. Open HACS → search for HA Soft Presence (or click the button above)
  2. Install HA Soft Presence
  3. Restart Home Assistant

Manual

  1. Copy custom_components/ha_soft_presence/ to your HA config directory
  2. Restart Home Assistant

⚙️ Setup

Open your Home Assistant instance and start setting up a new integration.

  1. Click the button above or go to Settings → Devices & Services → Add Integration
  2. Search for HA Soft Presence
  3. Follow the 5-step wizard:
    • Step 1 — Room name, has door, transit room, disable door-entry check
    • Step 2 — mmWave & PIR sensors (pre-filled from matching HA area)
    • Step 3 — Context sensors: doors, windows, locks, media, lights, switches, workstation (pre-filled)
    • Step 4 — Thresholds, timeout, sleep mode entities
    • Step 5 — Optional AI advisory

Repeat to add more rooms.

Tip — an entity missing from a picker? Several steps filter by device_class (PIR → motion/occupancy, doors → door, windows → window). Many contact and motion sensors ship with a generic or wrong class, so they won't show up. Open the entity's settings and set Show as to the matching type (Door, Window, …) — it then appears in the right step.

Home Assistant entity settings dialog — the 'Show as' field (German: 'Anzeigen als') set to Door
Entity settings → Show as (here: Anzeigen als → Tür)


🏷️ Entities created per room

Rule engine (always)

Entity Example Description
binary_sensor.{room}_presence_soft binary_sensor.wohnzimmer_presence_soft on = occupied, off = clear
sensor.{room}_presence_score sensor.wohnzimmer_presence_score Score 0–100
sensor.{room}_presence_confidence sensor.wohnzimmer_presence_confidence high / medium / low
sensor.{room}_presence_reason sensor.wohnzimmer_presence_reason "mmWave active + Media playing"

AI advisory (optional)

Entity Example Description
binary_sensor.{room}_presence_llm binary_sensor.wohnzimmer_presence_llm AI occupancy estimate
sensor.{room}_presence_llm_score sensor.wohnzimmer_presence_llm_score AI score 0–100
sensor.{room}_presence_llm_confidence sensor.wohnzimmer_presence_llm_confidence AI confidence
sensor.{room}_presence_llm_reason sensor.wohnzimmer_presence_llm_reason AI explanation

Attributes on binary_sensor.{room}_presence_soft

presence_score: 78
confidence: high
reason: "mmWave active + Media playing"
active_sources:
  - mmwave
  - media_playing
state_machine: occupied
last_positive_signal: "2026-04-24T22:33:00+00:00"
last_positive_reason: "mmWave active + Media playing"  # what last triggered OCCUPIED
last_positive_sources:
  - mmwave
  - media_playing
timeout_remaining: 210
room_name: Wohnzimmer
manual_override: null        # "occupied" | "clear" | null
sleep_mode_active: false
sensors:                     # diagnostic — all configured sensors with current HA state
  mmwave:
    binary_sensor.bedroom_mmwave: "on"
  pir:
    binary_sensor.bedroom_pir: "off"

⚖️ Score weights

Signal Weight Notes
mmWave active +80 Strongest signal — detects still persons
Person-count sensor +80 Camera/people-counter; value > 0 counts as presence
PIR active +35 Real-time motion
PIR recent (decay) up to +15 Fades over 5 min after PIR goes off
Workstation active +35 Binary sensor or power > 10 W
Media playing +30 Media player in "playing" state
Media paused +15
Light manually on +20 Any configured light/switch on
Lock recently unlocked up to +15 Decays over 10 min
Door recently opened up to +10 Decays over 5 min

Score ≥ occupied_threshold (default 50) → OCCUPIED Score ≤ clear_threshold (default 20) → start no-presence timeout Between thresholds → hold current state (hysteresis) Sleep mode active → clear_threshold replaced by sleep_clear_threshold (default 5)


🔁 State machine

CLEAR
  ↓  (score enters hysteresis zone)
POSSIBLE_ENTRY
  ↓  (score ≥ occupied_threshold)
OCCUPIED
  ↓  (score ≤ clear_threshold, after min_hold_time)
CLEAR_PENDING  ←→  OCCUPIED (if score recovers)
  ↓  (timeout expires, no contradicting signal)
CLEAR

Note

A door closing alone does not trigger CLEAR — it is a context signal, not proof of absence.


🔌 Supported sensor types

Category What to select
mmWave DFRobot SEN0395, FP2, LD2410, EP1, Everything Presence One
PIR Any binary_sensor with device class motion or occupancy
Person-count Numeric sensor from a camera / people-counter — value > 0 triggers presence
Door contacts binary_sensor with device class door
Window contacts binary_sensor with device class window
Locks Any lock entity
Media players Any media_player entity
Lights Any light entity
Switches Any switch entity
Workstation binary_sensor (on/off) or sensor with device class power (W, active when > 10 W)

🌙 Sleep mode

Configure one or more entities (e.g. input_boolean.sleepmode_bedroom, group.sleepmode_all) as sleep mode indicators. When any of them is on, the clear threshold is replaced by the much lower sleep mode clear threshold (default: 5).

This means the room stays occupied as long as bed sensors, PIR, or any other signal is above 5 — preventing false "clear" states while sleeping.

The sleep_mode_active attribute on the binary sensor reflects the current state.


📡 HA Events

On every occupied ↔ clear transition, HA Soft Presence fires:

event_type: ha_soft_presence_state_changed
event_data:
  room_name: Wohnzimmer
  room_slug: wohnzimmer
  entry_id: abc123
  occupied: true
  score: 78
  confidence: high
  reason: "mmWave active"
  state_machine: occupied
  manual_override: null

Use in automations:

triggers:
  - trigger: event
    event_type: ha_soft_presence_state_changed
    event_data:
      room_slug: wohnzimmer
      occupied: true

🛠️ Service calls

Service Description
ha_soft_presence.force_occupied Override room to occupied, ignore sensor signals
ha_soft_presence.force_clear Override room to clear, ignore sensor signals
ha_soft_presence.reset_override Remove override, return to automatic detection
ha_soft_presence.reload_all Reload all HA Soft Presence config entries at once

force_occupied, force_clear, and reset_override take the entity_id of the room's binary_sensor.*_presence_soft entity.

actions:
  - action: ha_soft_presence.force_occupied
    data:
      entity_id: binary_sensor.arbeitszimmer_presence_soft

🤖 AI Advisory (optional, off by default)

When enabled, an AI analysis runs once on startup and then whenever new sensor events are recorded (respecting the configured minimum interval, default 5 min).

Note

The AI receives only anonymised data: event type + age in seconds. No names, no images, no raw sensor values are sent externally.

Supported providers: any HA conversation agent (Gemini, Ollama, OpenAI, …)

AI entities show "Waiting for evaluation" until the first response arrives.


💡 Design principles

  1. 🚪 Door closing ≠ vacant — door is context only, not proof of absence
  2. 🚶 Door-validated fast clear — if the door didn't open since the room became occupied, nobody could have left → 30 s timeout instead of the full no-presence timeout
  3. 📡 mmWave is highest-weight and overrides PIR
  4. ⚡ occupied is set fast; clear is set slowly (timeout + hysteresis)
  5. 🛏️ Bed sensors → use the PIR sensor slots (binary on/off)
  6. 🌙 Sleep mode keeps the room occupied longer with minimal configuration
  7. 🔒 All processing is local — nothing leaves HA unless AI is explicitly enabled

🗺️ Roadmap

Feature Status Notes
ESPresense / BLE ✅ done One sensor per tracked device; state = room name string, matched against room slug
Person-count sensors ✅ done Numeric camera / people-counter sensors; value > 0 → +80 score
Camera / Frigate support planned Frigate person-detection binary sensors; own score weight distinct from PIR
Camera snapshot + Vision AI planned Send camera snapshot to vision-capable AI; opt-in, privacy-first
Batch AI evaluation ✅ done Single LLM call per agent covering all rooms — minimises API requests
Direct HTTP AI provider ✅ done Call MiniMax, Groq, or any OpenAI-compatible /chat/completions API directly without an HA conversation agent. Per-room provider toggle + base URL / key / model; rooms sharing a backend still batch into one call. Handles reasoning-model output (<think> / code fences)
Room-level aggregation planned "Anyone home on floor 1?" aggregating multiple rooms
HA Quality Scale — iot_class ✅ done local_push is correct — coordinator is primarily event-driven via async_track_state_change_event
HA Quality Scale — diagnostics.py ✅ done Download Diagnostics button on integration device page; dumps config, entity state, and internal coordinator state (lock-in, clear-pending, event log)
HA Quality Scale — Tests planned pytest-homeassistant-custom-component test suite
HA Quality Scale — Repairs / Issues ✅ done Three actionable repair issues: no presence sensors, has_door without contact, missing entities
HA Quality Scale — Entity unique_id ✅ done Config entry unique_id set to room slug; prevents duplicate room entries
Sleep mode ✅ done Configurable entities raise clear threshold when active
HA Events ✅ done ha_soft_presence_state_changed on every transition
Service calls ✅ done force_occupied, force_clear, reset_override
Area auto-fill ✅ done Entity selectors pre-filled from matching HA area
AI initial evaluation ✅ done AI runs once on startup
Multi-language (11 languages) ✅ done EN, DE, FR, ES, IT, NL, PL, PT, SV, RU, BG
Options flow ✅ done Full reconfiguration without deleting the integration

📄 License

MIT — free to use, no warranty.

About

Home Assistant custom integration: Virtual presence sensors with sensor fusion and optional LLM support

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages