# Modul: Odchozí signály (Loxone / HTTP)

## Účel

- EMS šle **logické signály** (např. `EXPORT_BAN_ACTIVE`) na externí cíle přes **DB konfigurované routy** (`ems.signal_route`).
- Každý pokus o odeslání se zapisuje do **`ems.signal_outbound_journal`** (retry, HTTP metadata, stav `queued` → `sent` → `verified`).
- **`ems.signal_state`** drží poslední požadovanou / odeslanou / ověřenou hodnotu pro **idempotenci** a diagnostiku readbacku.

Kritické řízení výkonu (**Deye, EV, TČ**) zůstává ve **Modbus exporteru** a tabulce **`ems.modbus_command`**. Tento modul **nenahrazuje** plánované zápisy výkonu do invertoru ani paralelní zápis na stejné Modbus výstupy.

## Hranice: signál vs. Modbus

| Typ akce | Kde |
|----------|-----|
| Plán, ekonomika, limity výkonu, zápis registrů Deye / nabíječky / TČ | `control_exporter` + `ems.modbus_command` |
| Indikace (LED), informativní stav do Loxone, jednoduchý HTTP přepínač (Shelly) | `ems.signal_*` + `services/signal_service.py` |

Duplicitní ovládání **stejného fyzického výstupu** přes Modbus i přes signálový HTTP kanál se vyhýbe — jedna autoritativní cesta na výstup.

## Destinace

| `destination_type` | Chování |
|--------------------|---------|
| `loxone_vi` | `GET {base}/dev/sps/io/{destination_key}/{value_text}` (stejná konvence jako legacy `send_loxone_setpoints`). |
| `http_rest` | Zápis z `route_config_json` (`method`, `path_template` s `{value}` / `{v}`). Verify přes `verify_config_json` (`read_path`, volitelně `json_path` pro JSON odpověď). |

## Worker běh

- Zařazení (`enqueue_site_signals`) probíhá po **každém** dokončeném pokusu o `export_setpoints` (včetně předčasných returnů u AUTO bez plánu), v `finally` bloku — aby LED mohla klesnout i při chybějícím plánu.
- Odeslání a verify běží v **APScheduler** jobech v `backend/app/lifespan.py` (interval řádů sekund).

## Konfigurace (příklad Loxone + readback)

Viz [Loxone integrace – VI/VO](../loxone-integration.md) (`EMS_ExportBan_Active`, `EMS_ExportBan_Active_FB`).

SQL příklad routy (po doplnění `endpoint_id` z `ems.site_endpoint` pro daný miniserver):

```sql
INSERT INTO ems.signal_route (
  site_id, destination_type, endpoint_id, signal_code, destination_key,
  verify_readback, verify_config_json
) VALUES (
  3,
  'loxone_vi',
  (SELECT id FROM ems.site_endpoint WHERE site_id = 3 AND endpoint_type = 'loxone_http' LIMIT 1),
  'EXPORT_BAN_ACTIVE',
  'EMS_ExportBan_Active',
  true,
  '{"loxone_io_name": "EMS_ExportBan_Active_FB"}'::jsonb
);
```
