# Referenční dokumentace HBUS API pro HELLOS-UNI

[English version](hbus-api.md)

Tato dokumentace se vztahuje k **verzi protokolu HBUS `01`** a
**verzi firmwaru HELLOS-UNI `4.0`**.
Popis chování portů se vztahuje k **verzi hardwaru HELLOS-UNI `2.1`**.

Tento dokument popisuje přenosový protokol a aplikační data implementovaná
ve firmwaru HELLOS-UNI. Ukázky JSON pod označením **Data požadavku** a
**Data odpovědi** obsahují pouze hodnotu pole HBUS `p`.

## Autor a cíle návrhu

Autorem HBUS je **HELLOS smart tech s.r.o.** Protokol má **otevřenou
specifikaci**, která umožňuje vývojářům implementovat jej ve vlastních projektech.

Protokol byl navržen pro:

- Master-master (multi-master) komunikaci na RS-485, aby zařízení mohla odesílat
  okamžité události bez čekání na dotaz řídicí jednotky.
- Zjednodušení implementace a integrace do vlastních projektů díky čitelnému
  textovému formátu. Pakety lze kontrolovat pouhým okem, bez speciálního
  dekodéru binárního protokolu.
- Nahrazení téměř 50 let starého protokolu Modbus RTU v aplikacích, pro které
  je jeho model master/slave s periodickým dotazováním nevhodný, zejména
  při komunikaci řízené událostmi.

HBUS používá rámce JSON založené na ASCII pro soužití se slave zařízeními
Modbus RTU na RS-485. Bajty funkcí běžného čtení a zápisu Modbus, například
`0x01`-`0x06`, `0x0f` a `0x10`, jsou řídicí znaky a v kompaktních JSON paketech
HBUS se jako nezakódované bajty nevyskytují.

## Software a samostatný provoz

K HBUS je k dispozici:

- **hbusd**, server a MQTT gateway distribuovaný jako Debian balík.
- **Desktopová aplikace pro Linux a Windows**.
- **Mobilní aplikace**, zatím pouze pro Android.

Čidla s HBUS mohou fungovat také samostatně, bez hbusd, MQTT brokeru nebo
kterékoli z těchto aplikací. Jejich čitelný JSON výstup lze přijímat a prohlížet
přes libovolný RS-485 převodník s odpovídajícím nastavením sériové komunikace.
To usnadňuje integraci do vlastních projektů. Vlastní řídicí jednotka může
pomocí níže popsaného protokolu posílat požadavky a potvrzovat pakety,
které vyžadují spolehlivé doručení.

HELLOS-UNI lze také nastavit tak, aby svůj úplný stav posílal automaticky
v pravidelných intervalech, bez dotazování a potvrzování. Po nastavení tedy
pro příjem těchto hlášení není potřeba programovat vlastní logiku požadavků
ani ACK. Čitelný textový výstup lze přímo sledovat v sériovém terminálu nebo
zpracovávat běžnými textovými nástroji a jednoduchými skripty.
Viz [Nastavení intervalu automatického odesílání stavu](#nastavení-intervalu-automatického-odesílání-stavu).

## Přenos

HBUS přenáší kompaktní JSON po poloduplexní lince RS-485 s nastavením 8N1.
Výchozí rychlost 19200 Bd je naprosto dostačující pro běžné pollované čtení
vstupů a čidel. Pro aplikace využívající okamžité události je doporučeno
115200 Bd a více, aby se zkrátila doba přenosu paketů a latence událostí.
Úplný paket má následující strukturu:

```json
{"H":"01","l":"0073","c":"84449af2","d":"00000000","s":"cf38542d","r":1,"p":{"button_press":{"A":{"1":[1082810]}}}}
```

| Pole | Typ | Význam |
| --- | --- | --- |
| `H` | string | Verze protokolu. Tento firmware přijímá `01`. |
| `l` | string | Celková délka paketu v bajtech jako čtyři hexadecimální číslice s malými písmeny. |
| `c` | string | CRC-32 jako osm hexadecimálních číslic s malými písmeny. |
| `d` | string | Osmiznaková cílová adresa. |
| `s` | string | Osmiznaková zdrojová adresa. |
| `r` | integer | `1` vyžaduje ACK; `0` nikoli. |
| `p` | object | Aplikační data popsaná níže. |

CRC se počítá z celého JSON paketu po nahrazení osmi znaků v poli `c`
osmi podtržítky:

```json
"c":"________"
```

Implementace musí vypočítat `l` před výpočtem CRC. Paket nemá ukončovací znak
řádku. Přijímač hledá v proudu bajtů hlavičku `{"H":"01"` a podle `l` určí
hranici paketu. Nesouvisející bajty před paketem tedy ignoruje.

Časové limity příjmu, získání přístupu ke sběrnici a ACK závislé na délce
přenosu se počítají z rychlosti použité při inicializaci UART. Spolehlivý
přenos proto používá stejný stavový automat od 1200 do 460800 Bd.
ACK přijaté během čekání před opakováním přenosu stále dokončí původní
doručení a zabrání zbytečnému odeslání duplicitního paketu.

### Adresy

| Adresa | Význam |
| --- | --- |
| `00000000` | Výchozí adresa centrální řídicí jednotky používaná v `hbusd` |
| `ffffffff` | Všesměrová cílová adresa (broadcast) |
| Jiná osmimístná hexadecimální hodnota | Adresa jednotlivého modulu |

Výchozí adresa modulu odpovídá posledním čtyřem bajtům unikátního ID RP2040,
zapsaným jako osm hexadecimálních znaků s malými písmeny. Adresy `00000000`
a `ffffffff` nelze modulu přiřadit.

### ACK

Platný adresovaný paket s `r:1` je potvrzen ještě před zpracováním
aplikačního příkazu:

```json
{"a":"84449af2"}
```

ACK obsahuje CRC přijatého paketu. Potvrzuje pouze správné doručení na
transportní úrovni, nikoli úspěšné provedení požadované operace. Úspěch nebo
neúspěch příkazu je nutné určit ze samostatného paketu odpovědi.
Broadcast pakety se nepotvrzují.

Pakety tlačítek, enkodéru a měření odesílané modulem používají spolehlivé
doručení. Nepotvrzený paket modul opakuje až osmkrát s postupně rostoucí
náhodnou prodlevou mezi pokusy.

## Použití API přes hbusd

Při výchozím pojmenování MQTT témat v `hbusd` odešlete celá aplikační data
jako JSON do:

```text
hbusd/<device-address>/send
```

Příklad:

```bash
mosquitto_pub -t hbusd/cf38542d/send -m '{"g":"a"}'
```

Přijaté koncové hodnoty se publikují pod:

```text
hbusd/<device-address>/read/<payload-path>
```

Například odpověď na identifikaci vytvoří `read/hw`, `read/sw` a
`read/sw_source`. Pole rozkládá `hbusd` na samostatné MQTT zprávy ve stejném
koncovém tématu, přičemž zachovává pořadí událostí pro příjemce, například
Node-RED. MQTT prefix a části `send`/`read` lze v `hbusd` nastavit.

## Identifikace

Identifikaci lze vyžádat od všech modulů broadcastem nebo od jednoho modulu
adresovaným požadavkem (unicast).

**Cílová adresa požadavku:** `ffffffff`

**Data požadavku:**

```json
{"identify":true}
```

Modul naplánuje odpověď po náhodné prodlevě od nuly do deseti sekund,
aby omezil kolize více odpovídajících zařízení. Odpočet je neblokující,
takže zpracování aplikace a HBUS mezitím normálně pokračuje.

Pro okamžitou unicast odpověď odešlete na adresu modulu:

```json
{"device":"identify"}
```

**Data odpovědi:**

```json
{
  "hw": "UNI-2.1.0",
  "sw": "3.2-2026-09-06-1780922bd",
  "sw_source": "frozen",
  "alias": "Kitchen switches"
}
```

| Pole | Význam |
| --- | --- |
| `hw` | Řetězec s modelem/verzí hardwaru |
| `sw` | Verze aplikace ze sestavení nebo manifestu nainstalovaného HBU |
| `sw_source` | `frozen` nebo `update` |
| `alias` | Uživatelem přiřazený název modulu, případně prázdný řetězec |

Odpověď se odesílá na adresu centrální řídicí jednotky `00000000`. Modul
odesílá tato data také automaticky po nastaveném intervalu nečinnosti.
Režim debug je odešle jednou ihned po spuštění.

## Čtení všech měření

**Data požadavku:**

```json
{"g":"a"}
```

Odpověď obsahuje poslední naměřené hodnoty po aplikaci kompenzací.
Měření standardně probíhá každých deset sekund.

**Příklad dat odpovědi:**

```json
{
  "u": 234,
  "ds": {
    "100000a3": {"tmp": 24.1}
  },
  "sht3x": {
    "0": {"tmp": 23.8, "hum": 47.2}
  },
  "scd4x": {
    "0": {"co2": 612, "tmp": 24.0, "hum": 46.9}
  },
  "input": {
    "A": {"0": true, "1": false, "2": false, "3": false}
  },
  "impulse_counter": {
    "B": {"0": 12345, "1": 81, "2": 0, "3": 907}
  },
  "pwm": {
    "B": {"0": 50, "1": 0, "2": 75, "3": 100}
  },
  "output": {
    "C": {"0": 1, "1": 0, "2": 0, "3": 1}
  },
  "adc": {
    "A": {"0": 0.0, "1": 25.0, "2": 50.1, "3": 100.0}
  }
}
```

| Cesta | Jednotka/význam |
| --- | --- |
| `u` | Doba běhu modulu od spuštění v sekundách |
| `ds/<id>/tmp` | Teplota DS18x20 ve stupních Celsia |
| `sht3x/<id>/tmp` | Teplota SHT3x ve stupních Celsia |
| `sht3x/<id>/hum` | Relativní vlhkost SHT3x v procentech |
| `scd4x/<id>/co2` | Koncentrace oxidu uhličitého SCD4x v ppm |
| `scd4x/<id>/tmp` | Teplota SCD4x ve stupních Celsia |
| `scd4x/<id>/hum` | Relativní vlhkost SCD4x v procentech |
| `input/<port>/<index>` | Poslední stav stavového vstupu po filtraci zákmitů; `true` znamená sepnutý vstup aktivní v LOW |
| `impulse_counter/<port>/<index>` | Absolutní počet přijatých impulzů aktivních v LOW od spuštění |
| `pwm/<port>/<index>` | Poslední nastavená střída PWM v procentech |
| `output/<port>/<index>` | Poslední nastavený logický stav výstupu, `0` nebo `1` |
| `adc/A/<index>` | Filtrovaná hodnota ADC vstupu od 0,0 do 100,0 procent |

ID čidel DS18x20 tvoří posledních osm hexadecimálních znaků jejich ROM.
ID čidel SHT3x a SCD4x jsou pořadové indexy od nuly přiřazené při spuštění.

Odpověď používá spolehlivé doručení a je adresována odesílateli požadavku.

## Vstupní události

Vstupní události jsou nevyžádané pakety se spolehlivým doručením odesílané
na `00000000`.

### Stisk tlačítka

```json
{"button_press":{"A":{"1":[1082810]}}}
```

### Uvolnění tlačítka

Události uvolnění se odesílají pouze pro port nastavený jako `button_double`.

```json
{"button_release":{"A":{"1":[1083152]}}}
```

Pole umožňuje přenést v jednom paketu více událostí nashromážděných během
jednoho průchodu hlavní smyčkou. Každá hodnota je časová značka
`time.ticks_ms()`, nikoli kalendářní čas.

### Stavový vstup

```json
{"input":{"A":{"1":true}}}
```

Každá změna stavu po filtraci zákmitů vytvoří událost s aktuální logickou
hodnotou. Pokud se pro stejný vstup před sestavením dalšího paketu nahromadí
více změn, odešle se poslední stav. Modul také uchovává úplný stav všech
portů nastavených jako `input` nebo `input_safe` a zahrnuje jej do každé
odpovědi na `{"g":"a"}`.

Režim `input` čte přímo signálové GPIO a používá pull-up rezistor 10 kOhm
na desce. Režim `input_safe` ponechá signálové GPIO jako vstup bez pull-upu
a čte párové GPIO pro řízení pull-upu přes rezistor 10 kOhm s využitím
interního pull-upu RP2040. Komunikace a filtrace zákmitů po dobu 30 ms
jsou v obou režimech stejné.

Slabší interní pull-up činí `input_safe` citlivějším na svodové proudy a
rušení na dlouhých kabelech. Sériový rezistor omezuje proud do vzorkovaného
GPIO, ale nechrání proti vnějšímu přepětí; signálové GPIO zůstává fyzicky
připojené k portu.

### Čítač impulzů

Porty nastavené jako `impulse_counter` nebo `impulse_counter_safe` počítají
platné impulzy aktivní v LOW od nuly po každém spuštění. Hodnoty se uchovávají
pouze v RAM a nezapisují se do flash. Každý impulz LOW a předcházející úroveň
HIGH musí trvat alespoň 1 ms. Platný impulz se započítá při náběžné hraně.

Změny čítačů nikdy nevytvářejí nevyžádané pakety. Absolutní počty jsou zahrnuty
pouze do úplného stavu vraceného na `{"g":"a"}` a do volitelného automatického
odesílání úplného stavu. Čítače lze vynulovat pouze restartem modulu nebo
změnou konfigurace jeho portů.

Běžný režim čte signálové GPIO a používá pull-up rezistor 10 kOhm na desce.
Bezpečný režim čte párové GPIO pro řízení pull-upu přes rezistor 10 kOhm
a používá slabší interní pull-up RP2040. Pro rychlé impulzy a dlouhé nebo
rušené vedení je vhodnější běžný režim.

### ADC vstup

Port A lze nastavit jako čtyři ADC vstupy. Pasivní režimy publikují hodnoty
pouze jako součást odpovědi na `{"g":"a"}`. Aktivní režimy navíc posílají
nevyžádané pakety se spolehlivým doručením obsahující pouze vstupy, jejichž
filtrovaná hodnota se dostatečně změnila:

```json
{"adc":{"A":{"1":37.6}}}
```

Hodnoty ADC používají procentní rozsah od `0.0` do `100.0`. Vzorky se filtrují
v nativním 12bitovém rozlišení a před převodem na procenta se kvantují na
9 bitů. To poskytuje 512 různých vstupních úrovní; zobrazované desetinné
místo tedy neznamená fyzické rozlišení 0,1 procenta.

ADC v RP2040 má přibližně 8,7 efektivního bitu a trpí dokumentovanou chybou
diferenciální nelinearity RP2040-E11. Tyto vstupy jsou určeny například
pro ovládání potenciometry, nikoli pro přesné měření napětí.

Hodnoty do 0,4 procenta se hlásí jako `0.0`, hodnoty od 99,6 procenta jako
`100.0`. Aktivní hlášení používá pásmo necitlivosti 1,0 procenta a je omezeno
na jeden paket za 200 ms. Změny nashromážděné během tohoto intervalu se
sloučí a pro každý pin se uchová pouze poslední hodnota. Při neúspěšném
doručení zůstávají poslední hodnoty připravené k odeslání a další pokus
se odloží o jednu sekundu.

Režimy `adc_raw_passive` a `adc_raw_active` vypínají pull-up rezistory desky.
Režimy `adc_pullup_passive` a `adc_pullup_active` je zapínají.
Všechny ADC režimy jsou dostupné pouze na portu A.

## PWM výstupy

PWM je dostupné pouze na portech B a C. Celý port se nastaví na jednu
z pevných hardwarových frekvencí režimem `pwm_50hz`, `pwm_1khz` nebo
`pwm_25khz`. Všechny piny jsou výstupy push-pull a po každém restartu
začínají se střídou nula procent.

Jeden příkaz může změnit jeden nebo více pinů, a to i na obou nastavených portech:

```json
{"pwm":{"B":{"0":50,"2":75}}}
```

Názvy portů a indexy pinů se ověřují před změnou kteréhokoli výstupu.
Střídy musí být celá čísla od 0 do 100. Po provedení celého příkazu modul
okamžitě vrátí úplný stav každého portu uvedeného v požadavku:

```json
{"pwm":{"B":{"0":50,"1":0,"2":75,"3":0}}}
```

Aktuální stav všech nastavených PWM portů lze také vyžádat přímo:

```json
{"pwm":"get"}
```

Stav PWM je součástí každé odpovědi na `{"g":"a"}`. Střídy jsou provozním
stavem a nezapisují se do flash.

## Digitální výstupy

Režimy `output` a `output_safe` poskytují čtyři logické výstupy na libovolném
univerzálním portu. Každý pin po restartu začíná v logické nule. Jediný příkaz
může změnit jeden nebo více pinů na jednom nebo více nastavených portech:

```json
{"output":{"B":{"0":1,"2":0}}}
```

Názvy portů, indexy pinů a všechny hodnoty se ověřují před změnou kteréhokoli
výstupu. Hodnoty musí být celá čísla `0` nebo `1`. Odpověď obsahuje úplný
stav každého portu uvedeného v požadavku:

```json
{"output":{"B":{"0":1,"1":0,"2":0,"3":0}}}
```

Aktuální stav všech nastavených výstupních portů lze také vyžádat:

```json
{"output":"get"}
```

Stav výstupů je součástí každé odpovědi na `{"g":"a"}` a neukládá se do flash.

V režimu `output` je signálové GPIO přímým výstupem push-pull. V režimu
`output_safe` zůstává signálové GPIO vstupem a párové GPIO pro řízení
pull-upu budí signál pouze přes rezistor 10 kOhm na desce. Oba režimy
používají stejné příkazy a odpovědi HBUS.

Rezistor omezuje proud při konfliktu výstupů nebo zkratu. Nejde o ochranu
proti přepětí, protože signálové GPIO zůstává fyzicky připojené k portu.
Bezpečný režim je určen pro statické logické vstupy a hradla MOSFETů;
nemůže přímo dodávat významný proud do zátěže. Vnější obvody musí zajistit
bezpečný stav během spouštění nebo resetu modulu.

## Rotační enkodér

```json
{"encoder_delta":{"B":{"2":-1}}}
```

Kanál enkodéru se hlásí na indexu `2`. Více pohybů nashromážděných během
jednoho intervalu se sečte, takže rozdíl může být menší než `-1` nebo větší
než `1`. Stisk tlačítka enkodéru se hlásí jako `button_press` na indexu `3`.

## Ovládání zařízení

### Čtení nastavení zařízení

**Data požadavku:**

```json
{"device":"get_settings"}
```

**Data odpovědi:**

```json
{
  "device_settings": {
    "address": "cf38542d",
    "alias": "Kitchen switches",
    "hbus_baudrate": 19200,
    "identify_interval": 5,
    "state_interval": "DISABLED",
    "bus_mode": "hbus",
    "active_bus_mode": "hbus",
    "modbus_address": 17
  }
}
```

`identify_interval` je vyjádřen v minutách, `state_interval` v sekundách.
Obě nastavení mohou obsahovat `DISABLED`. Nastavení zařízení je záměrně
odděleno od identifikačních dat a vrací se pouze na explicitní unicast požadavek.

`bus_mode` je protokol zvolený pro příští spuštění. `active_bus_mode` je
protokol používaný při aktuálním běhu a do restartu se může lišit. Přepínač
DIP pro debug vynutí `active_bus_mode` na `hbus`, aniž by změnil uložené
`bus_mode`. Hodnota `modbus_address` se uchovává i v režimu HBUS.

### Restart

**Data požadavku:**

```json
{"device":"restart"}
```

Modul provede okamžitý hardwarový reset po potvrzení ACK na transportní
úrovni. Aplikační odpověď se neposílá.

### Obnovení továrního nastavení

**Data požadavku:**

```json
{"device":"factory_reset"}
```

Modul odstraní trvalou konfiguraci, data o pádech a veškerý firmware
v souborovém systému a poté restartuje do aplikace vestavěné ve firmwaru
(frozen). Před resetem se neposílá aplikační odpověď.

### Nastavení adresy zařízení

**Data požadavku:**

```json
{"device":{"set_device_address":"12abcdef"}}
```

Odpověď při úspěchu:

```json
{"device":"DEVICE ADDRESS UPDATED"}
```

Odpověď při chybě:

```json
{"device":"DEVICE ADDRESS UPDATE FAILED"}
```

Klienti musí posílat přesně osm hexadecimálních znaků a nesmějí posílat
`00000000` ani `ffffffff`. Aktuální implementace vyhodnocuje prvních osm
znaků, takže delší řetězce zkrátí místo odmítnutí. Adresa se převede na malá
písmena a okamžitě trvale uloží. Úspěšná odpověď již používá novou zdrojovou adresu.

### Nastavení rychlosti HBUS

**Data požadavku:**

```json
{"device":{"set_hbus_baudrate":115200}}
```

Úspěšná odpověď vrací úplné trvale uložené nastavení:

```json
{"device_settings":{"address":"cf38542d","alias":"Kitchen switches","hbus_baudrate":115200,"identify_interval":5,"state_interval":"DISABLED"}}
```

Odpověď při chybě:

```json
{"device":"HBUS BAUDRATE UPDATE FAILED"}
```

Povolené rychlosti jsou `1200`, `2400`, `4800`, `9600`, `19200`, `38400`,
`57600`, `115200`, `230400` a `460800`. Odpověď se odešle aktuální rychlostí.
Nová rychlost se načte z flash při příštím restartu; řídicí jednotka musí
svou rychlost odpovídajícím způsobem změnit.

### Nastavení režimu sběrnice

**Data požadavku:**

```json
{"device":{"set_bus_mode":{"mode":"modbus","modbus_address":17}}}
```

`mode` musí být `hbus` nebo `modbus`. `modbus_address` musí být od 1 do 247.
Příkaz uloží obě hodnoty a vrátí úplná data `device_settings`. Aktivní
transport se změní až po restartu. Nastavená rychlost UART je společná
pro HBUS a Modbus RTU.

Odpověď při chybě:

```json
{"device":"BUS MODE UPDATE FAILED"}
```

Pro obnovení přístupu lze spustit modul se zapnutým debug DIP přepínačem,
který vynutí HBUS při uložené rychlosti. Tovární reset obnoví HBUS na 19200 Bd.

### Nastavení aliasu modulu

**Data požadavku:**

```json
{"device":{"set_alias":"Kitchen switches"}}
```

Z aliasu se odstraní krajní bílé znaky, ihned se trvale uloží a zahrne do
každé identifikační odpovědi. Může obsahovat text UTF-8, nesmí obsahovat
řídicí znaky a je omezen na 64 bajtů po zakódování. Prázdný řetězec jej
vymaže. Úspěšná změna vrátí úplná identifikační data unicastem.
Odpověď při chybě:

```json
{"device":"ALIAS UPDATE FAILED"}
```

### Nastavení intervalu automatické identifikace

**Data požadavku:**

```json
{"device":{"set_identify_interval":30}}
```

Povolené intervaly jsou `1`, `5`, `10`, `30` a `60` minut. Automatickou
identifikaci při nečinnosti lze vypnout pomocí:

```json
{"device":{"set_identify_interval":"DISABLED"}}
```

Hodnota se okamžitě trvale uloží. Úspěšná změna vrátí unicastem úplná data
`device_settings` včetně nového `identify_interval`. Neplatné hodnoty vrátí:

```json
{"device":"IDENTIFY INTERVAL UPDATE FAILED"}
```

Vypnutí automatické identifikace nepotlačí explicitní unicast ani broadcast
požadavky na identifikaci. Nepotlačí ani jednu identifikaci naplánovanou
s náhodnou prodlevou až deset sekund po spuštění v režimu debug.

### Nastavení intervalu automatického odesílání stavu

**Data požadavku:**

```json
{"device":{"set_state_interval":30}}
```

Povolené intervaly jsou `5`, `30`, `60` a `300` sekund. Automatické odesílání
stavu je ve výchozím nastavení vypnuté a lze jej explicitně vypnout pomocí:

```json
{"device":{"set_state_interval":"DISABLED"}}
```

Hodnota se okamžitě trvale uloží. Úspěšná změna vrátí unicastem úplná data
`device_settings` včetně nového `state_interval`. Neplatné hodnoty vrátí:

```json
{"device":"STATE INTERVAL UPDATE FAILED"}
```

V každém povoleném intervalu modul sestaví stejná úplná stavová data jako
pro `{"g":"a"}` a odešle je na `00000000`. Paket má `r` nastavené na `0`:
nevyžaduje ACK a neopakuje se. Neúspěšný pokus nebo pokus při obsazené
sběrnici se přeskočí až do dalšího nastaveného intervalu.

Po spuštění nebo změně intervalu se první odeslání odloží o jeden interval
plus náhodný posun až do délky celého intervalu. Každý další interval má
náhodnou odchylku plus nebo minus deset procent. Moduly spuštěné současně
proto nezůstávají synchronizované.

## Konfigurace portů

### Čtení přiřazení

**Data požadavku:**

```json
{"port":"get"}
```

**Příklad odpovědi:**

```json
{"port":{"A":"button_simple","B":"encoder_reverse"}}
```

Prázdná tabulka se hlásí jako:

```json
{"port":"PORT TABLE EMPTY"}
```

### Změna přiřazení

**Data požadavku:**

```json
{"port":{"A":"button_double","B":"encoder","C":"input_safe"}}
```

Zadané klíče se sloučí se stávajícím přiřazením a vrátí se celá tabulka.
Podporované režimy jsou `button_simple`, `button_double`, `input`,
`input_safe`, `impulse_counter`, `impulse_counter_safe`, `output`,
`output_safe`, `encoder`, `encoder_reverse`, `pwm_50hz`, `pwm_1khz`,
`pwm_25khz`, `adc_raw_passive`, `adc_raw_active`, `adc_pullup_passive`
a `adc_pullup_active`. PWM režimy jsou povoleny pouze na portech B a C;
ADC režimy pouze na portu A. Přiřazení se ihned trvale uloží. Restartujte
modul, aby se podle nového přiřazení znovu vytvořila obsluha vstupů,
výstupy a detekce čidel.

### Vymazání přiřazení

**Data požadavku:**

```json
{"port":"reset"}
```

**Odpověď:**

```json
{"port":"PORT TABLE TRUNCATED"}
```

Aktivní provozní konfigurace zůstává platná až do restartu.

## Kompenzace čidel

Kompenzační hodnoty jsou přičítané korekce aplikované na odpovídající pole
v odpovědi na `{"g":"a"}`. Nepřepisují uložené nezpracované hodnoty měření.

### Čtení kompenzací

```json
{"compensation":"get"}
```

Příklad odpovědi:

```json
{"compensation":{"ds":{"100000a3":{"tmp":-0.4}},"scd4x":{"0":{"co2":25}}}}
```

Prázdná tabulka se hlásí jako:

```json
{"compensation":"COMPENSATION TABLE EMPTY"}
```

### Změna kompenzací

```json
{"compensation":{"ds":{"100000a3":{"tmp":-0.4}}}}
```

Zadané skupiny čidel na nejvyšší úrovni se sloučí se stávající tabulkou,
ihned se trvale uloží a vrátí v odpovědi. Aktualizace skupiny, například
`ds`, nahradí celou tuto skupinu; slučování není rekurzivní.

### Vymazání kompenzací

```json
{"compensation":"reset"}
```

Odpověď:

```json
{"compensation":"COMPENSATION TABLE TRUNCATED"}
```

## Konfigurace SCD4x

Čidla SCD4x se adresují indexem od nuly používaným v naměřených datech.
Jeden požadavek může obsahovat příkazy pro více čidel, ale záznam každého
čidla obsahuje jeden příkaz.

**Struktura požadavku:**

```json
{
  "scd4x": {
    "0": {"cmd":"set_sensor_altitude","value":250}
  }
}
```

**Struktura odpovědi:**

```json
{
  "scd4x": {
    "0": {"set_sensor_altitude":250}
  }
}
```

Před každým příkazem se periodické měření zastaví a poté znovu spustí.

| Příkaz | Hodnota požadavku | Hodnota odpovědi |
| --- | --- | --- |
| `persist_settings` | žádná | `true` |
| `get_temperature_offset` | žádná | Stupně Celsia |
| `set_temperature_offset` | Stupně Celsia | Zadaná číselná hodnota |
| `get_sensor_altitude` | žádná | Metry |
| `set_sensor_altitude` | Metry | Zadané celé číslo |
| `get_ambient_pressure` | žádná | Pascaly |
| `set_ambient_pressure` | Pascaly | Zadané celé číslo |
| `get_serial_number` | žádná | Desítkový řetězec |
| `get_sensor_variant` | žádná | `SCD40`, `SCD41`, `SCD43` nebo řetězec s neznámým kódem |

Okolní tlak se interně omezí na 70000-120000 Pa. Neznámé indexy čidel
a příkazy se ignorují a nevytvářejí aplikační odpověď.

## Přenos firmwaru

Příkazy pro firmware používají objekt `fw`. Odesílatel by měl do každého
požadavku vložit nové `request_id` tvořené osmi hexadecimálními znaky
s malými písmeny. Modul je zkopíruje do odpovídající odpovědi, takže
`hbusd` může odmítnout opožděné odpovědi z předchozího pokusu.

Odpovědi pro firmware mají tuto strukturu:

```json
{
  "fw": {
    "request_id":"12ab34cd",
    "state":"receiving",
    "id":"4386db09768e458e",
    "offset":512,
    "size":43548
  }
}
```

ID přenosu tvoří prvních 16 znaků SHA-256 celého archivu.
Maximální velikost dekódovaného bloku je 512 bajtů.

### Zahájení nebo navázání přenosu

```json
{
  "fw": {
    "command":"begin",
    "request_id":"12ab34cd",
    "size":43548,
    "sha256":"4386db09768e458e5f76cb9036dbbdf4f39d62d4906b76cfcf20fc8589e131fe"
  }
}
```

Stav odpovědi je `receiving`, `ready` nebo `installed`. U existujícího
odpovídajícího přenosu určuje `offset`, odkud má odesílatel pokračovat.
Zahájení přenosu jiného archivu zahodí neúplný nebo připravený archiv,
ale nikdy neodstraní sloty nainstalovaného firmwaru.

### Odeslání bloku

```json
{
  "fw": {
    "command":"chunk",
    "request_id":"23bc45de",
    "id":"4386db09768e458e",
    "offset":0,
    "data":"<Base64 data>"
  }
}
```

Bloky musí navazovat. Opakování již kompletně uloženého bloku se přijme
pouze tehdy, když se jeho bajty shodují. Překrývající se, vynechané,
změněné nebo příliš velké bloky se odmítají.

### Dokončení nahrávání

```json
{"fw":{"command":"finish","request_id":"34cd56ef","id":"4386db09768e458e"}}
```

`finish` ověří celkovou velikost a SHA-256 před změnou stavu na `ready`.
Archiv neinstaluje ani neaktivuje.

### Dotaz na stav

```json
{"fw":{"command":"status","request_id":"45de67f0"}}
```

Možné stavy:

| Stav | Význam |
| --- | --- |
| `idle` | Neexistují metadata přenosu |
| `receiving` | Je přítomen neúplný archiv |
| `ready` | Úplný a ověřený přenosový archiv čeká na instalaci |
| `installed` | Archiv byl nainstalován a vybrán pro příští spuštění |
| `rolled_back` | Spuštění nainstalovaného aktivního firmwaru selhalo |
| `error` | Požadavek selhal; viz `error` |

Odpovědi `receiving`, `ready`, `installed` a `rolled_back` obsahují `id`,
`offset` a `size`. Stavy po instalaci obsahují také `version`.
`rolled_back` přidává `reason`.

### Instalace

```json
{"fw":{"command":"install","request_id":"56ef7801","id":"4386db09768e458e"}}
```

Instalace ověří chráněný obal HBE1, ID klíče AES, kontrolní otisk
rozšifrovaného vnitřního archivu, manifest a každý rozbalený soubor.
Poté přepne kompletní sloty v souborovém systému a vrátí stav `installed`
s verzí z manifestu. Modul se automaticky nerestartuje.

### Zrušení přenosu

```json
{"fw":{"command":"abort","request_id":"67f08912","id":"4386db09768e458e"}}
```

Zrušení odstraní archiv ve stavu `receiving` nebo `ready` a vrátí `idle`.
Firmware ve stavu `installed` nebo `rolled_back` nelze takto zrušit,
protože již patří do sady aktivního a předchozího slotu.

### Chybová odpověď

Chyby protokolu, ověření a stavu se vracejí jako:

```json
{
  "fw": {
    "request_id":"67f08912",
    "state":"error",
    "error":"firmware chunk offset mismatch"
  }
}
```

Text chyby je určen pro diagnostiku a v budoucí verzi firmwaru může být
upřesněn. Klienti by se měli rozhodovat především podle `state`,
nikoli podle rozboru chybového řetězce.

## Chování starších příkazů při chybách

Příkazy přenosu firmwaru vždy vracejí strukturovaný stav úspěchu nebo chyby.
Starší příkazy pro zařízení, porty, kompenzace a SCD4x nemají společný formát
chybové odpovědi. Podle příkazu vracejí stavový řetězec velkými písmeny,
jsou tiše ignorovány nebo pouze zapíší výjimku do ladicího logu. Proto:

- Považujte transportní ACK pouze za potvrzení přijetí paketu.
- Čekejte na dokumentovanou odpověď, pokud existuje.
- Na řídicí jednotce používejte časový limit.
- Po změně trvalé konfigurace načtěte nastavení zpět.
