Shelly Plug S Gen3, chytrá zásuvka pro bastlíře

Odpovědět
Pablo74
Příspěvky: 178
Registrován: 03 lis 2019, 17:00

Shelly Plug S Gen3, chytrá zásuvka pro bastlíře

Příspěvek od Pablo74 » 06 zář 2026, 11:36

Rád se podělím o zkušenosti se zásuvkou Shelly Plug S Gen3 a to z "lokálního" pohledu: žádný Shelly Cloud, žádná oficiální Shelly aplikace.
Celé ovládání běží čistě lokálně – přes BLE a přes lokální HTTP RPC v domácí Wi-Fi síti. Tenhle první příspěvek bude spíš přehledový, další budou navazovat konkrétními detaily a problémy, které jsme spolu s AI řešili a (některé) vyřešili.

Bez cloudu?
Shelly Cloud je fajn, pokud vám nevadí účet u výrobce a závislost na jeho serverech. Jakmile ale chcete zásuvku sdílet mezi víc lidmi bez cloudového účtu, nebo prostě chcete mít jistotu, že vám zásuvka půjde ovládat i bez internetu, dává smysl jít lokální cestou. Shelly to naštěstí umožňuje – zařízení má plnohodnotné lokální RPC rozhraní, které nepotřebuje jejich aplikaci ani cloud.

HW v kostce
- řídicí čip ESP32
- relé pro spínání zátěže
- RGB LED indikace, samostatně nastavitelná barva/jas pro stav zapnuto/vypnuto
- jedno fyzické tlačítko s více funkcemi – krátký stisk přepíná výstup, podržení 5 s znovu otevře BLE párovací okno, podržení 10 s = tovární reset

SW v kostce – lokální RPC
Celé zařízení se ovládá přes JSON-RPC rozhraní, dostupné dvěma cestami:
- lokálně po HTTP (v rámci domácí Wi-Fi, žádný cloud)
- přes BLE (Bluetooth Low Energy), bez nutnosti použít Wi-Fi

Metody jsou rozdělené do komponent – namátkou Switch (ovládání výstupu, Switch.Set, Switch.GetStatus), Sys (systémové info a config), WiFi, Cloud (dá se explicitně vypnout), BLE, Script (uživatelské skripty) a PLUGS_UI (nastavení LED). Užitečný detail: Switch.Set má rovnou parametr toggle_after (v sekundách) – zásuvka se sama vrátí do opačného stavu bez nutnosti posílat druhý příkaz.

skriptování (mJS)
Shelly umí interně uložit a spustit vlastní skripty přímo na zařízení (jazyk mJS, odlehčený JavaScript). Skript se nahraje buď přes její rozhraní v prohlížeči nebo vzdáleně přes Script.Create a Script.PutCode (po částech), a pomocí Script.SetConfig se dá nastavit, aby se spouštěl automaticky při startu zařízení. Výhoda je zásadní – takový skript běží přímo na ESP32 v zásuvce; funguje i v situaci, kdy telefon není k zásuvce vůbec připojen Typické využití je třeba časované zapnutí/vypnutí bez závislosti na klientovi.

nastavení LED
Barvy i jas LED se dají nastavit zvlášť pro režim zásuvky ON/OFF.

komunikace přes BLE
Zásuvka po zapnutí (nebo po podržení tlačítka 5 s) otevře 15minutové párovací okno, ve kterém se dá spárovat s mobilem či tabletem (přes BLE). Podporováno je až 20 současně spárovaných klientů. Přes BLE se posílá stejné JSON-RPC jako po HTTP, jen zabalené do BLE charakteristik.

Už toho mám zprovozněno víc než jen zapnutí/vypnutí, které ovládám jak z mobilu, tak i z notebooku.

Pablo74
Příspěvky: 178
Registrován: 03 lis 2019, 17:00

Re: Shelly Plug S Gen3, chytrá zásuvka pro bastlíře

Příspěvek od Pablo74 » 16 zář 2026, 04:26

Shelly Plug S Gen3 – BLE protokol do detailu (a proč vás firmware 2.0.0 může zaskočit)

Navazuji na minulý příspěvek. Tady je konkrétní BLE protokol, jak jsme ho s AI (Anthropic Claude Sonnet 5) reverse-engineerovali a ověřili na reálném hardwaru, a hlavně bezpečnostní past, do které jsme spadli při přechodu z firmwaru 1.7.3 na 2.0.0.

BLE RPC – service a charakteristiky

Pro Shelly Gen2+ zařízení (platí i pro Plug S Gen3) je RPC service UUID:

Kód: Vybrat vše

5f6d4f53-5f52-5043-5f53-56435f49445f
(tohle je z oficiální Shelly knowledge base, ne z odhadu komunity – starší UUID, které kolovalo po fórech, je jiné a nefunguje spolehlivě).
Pod ním jsou tři charakteristiky:

- charData (...5f64-6174615f5f5f) – vlastní datový přenos
- charTxCtl (...5f74-785f63746c5f) – řízení odesílání
- charRxCtl (...5f72-785f63746c5f) – řízení příjmu

formát přenosu
Protokol je jednoduchý: do TX/RX control charakteristik se posílá 4bajtová délka payloadu (big-endian), samotný JSON-RPC payload se pak posílá po částech (chunky ~180 B) do datové charakteristiky. RxCtl a datová odpověď se čtou explicitním pollingem, ne přes notify/subscribe – to je důležitý detail, pokud si píšete vlastního BLE klienta.

párování a bonding
Párovací okno je otevřené 15 minut po každém zapnutí zásuvky (ne jen při prvním spuštění) a dá se kdykoliv znovu otevřít podržením fyzického tlačítka na 5 s (10 s = tovární reset). Zásuvka umí držet bondy až s 20 klienty současně, takže sdílení mezi víc zařízeními/lidmi není problém.

past č. 1 – Secure provisioning (firmware 1.7.5+)
Tohle nás pěkně překvapilo. Shelly kvůli kybernetické bezpečnosti zavedlo od firmwaru 1.7.5 funkci "Secure provisioning": jakmile zařízení poprvé dostane IP adresu na Wi-Fi, automaticky se do 5 minut vypne BLE RPC (a zavře se i otevřené AP), pokud předtím nebylo nastaveno AP heslo. A jakmile se AP heslo jednou nastaví, nejde ho vrátit zpět na otevřené/bez hesla. Pro architekturu založenou čistě na BLE bez hesla (typicky pokud chcete zůstat mimo cloud, ale zásuvku přesto připojit k domácí Wi-Fi kvůli vypnutí AP režimu) je to zásadní problém – jeden konkrétní kus zásuvky nám takhle nechtěně "zamrzl" a BLE RPC na ní přestalo fungovat pár minut po připojení k Wi-Fi.

past č. 2 – šifrované GATT ve firmwaru 2.0.0
Ještě zajímavější past přišla s firmwarem 2.0.0. Podle oficiální dokumentace zařízení od této verze vyžaduje pro veškerou BLE RPC komunikaci šifrované a autentizované GATT spojení – starší firmware (1.7.3, se kterým byla moje aplikace původně stavěná a testovaná) používal obyčejné nešifrované GATT čtení/zápis.

Reálný projev: zásuvka po upgradu na 2.0.0 se v telefonu i mé aplikaci tvářila jako spárovaná (viditelná v systémovém Bluetooth i ve vlastním device pickeru), ale komunikace s ní nešla vůbec – moje aplikace ji hlásila jako nedostupnou.

Přes logy bluetoothd jsme to dohledali přesně: moje aplikace zapíše do `txCtl` charakteristiky → zařízení odpoví LE_ATT_ERROR_INSUFFICIENT_AUTHORIZATION → OS (na notebooku) korektně spustí SMP párování → Shelly zásuvka sama párování odmítne s chybou "Peer sent Pairing Failed with reason=5" (SMP reason 5 = "Pairing Not Supported"). Zápis pak trvale selže. Vyloučili jsme Matter (bylo vypnuté) i chybu firmwaru (stejné chování na 2.0.1-beta1).

řešení problému

Podle oficiální dokumentace BLE komponenty je potřeba před novým bondem explicitně zavolat RPC BLE.StartPairing po lokální síti (HTTP), např.:

Kód: Vybrat vše

http://<ip zásuvky>/rpc/BLE.StartPairing?timeout=60
Bez toho zařízení nové párování legitimně odmítne (odtud ten reason=5). Jakmile jednou existuje bond, další připojení se autentizuje automaticky, žádné další volání BLE.StartPairing už není potřeba. Bondy přežívají restart zařízení, pořád platí limit 20 klientů.

zapeklitý detail navíc
Jakmile jednou párování selže, operační systém (potvrzeno na notebooku a mobilu) si interně "zapamatuje", že zařízení párování odmítá, a při dalších pokusech se o SMP handshake ani nesnaží – a to i po správném zavolání BLE.StartPairing. Co nakonec zafungovalo:

- na notebooku: kompletní restart systému (žádný jiný workaround jsme nenašli)
- na mobilu: zapomenutí zařízení v systémovém Bluetooth + smazání starého bondu přímo na zásuvce přes BLE.DeletePairedDevice?addr="<mac>" (adresu zjistíte přes BLE.ListPairedDevices) + několik opakovaných pokusů přímo z mé aplikace (ne přes systémové párovací okno)

praktický dopad pro vaše projekty
Pokud stavíte cokoliv, co má zásuvku ovládat čistě přes BLE bez cloudu:

- na firmware 1.7.x počítejte s tím, že jakmile zásuvku připojíte k Wi-Fi bez nastaveného AP hesla, BLE RPC vám do 5 minut spadne
- na firmware 2.0.0+ musíte před prvním párováním nového klienta zavolat BLE.StartPairing po lokální síti – tzn. telefon či notebook musí být v momentě prvního párování na stejné Wi-Fi jako zásuvka (přes BLE samotné to obejít nejde, zařízení párování vyžaduje i ve stavu complete)
- tahle podmínka platí jen pro první spárování nového klienta – běžné ovládání po spárování jede čistě přes BLE, bez potřeby Wi-Fi/internetu

---

Napsáno s využitím AI.

Pablo74
Příspěvky: 178
Registrován: 03 lis 2019, 17:00

Re: Shelly Plug S Gen3, chytrá zásuvka pro bastlíře

Příspěvek od Pablo74 » 11 říj 2026, 17:14

Shelly Plug S Gen3 – co jsme si postavili a co jsme se cestou naučili

Poslední díl seriálu o lokálním ovládání Shelly Plug S Gen3 bez cloudu a bez oficiální aplikace. Tady je shrnutí praktických věcí, které jsme reálně postavili a odzkoušeli na hardwaru.

problém: modrá LED bliká pořád

Když zásuvku nikdy nepřipojíte k Wi-Fi (čistě BLE architektura), zůstává natrvalo v access point (AP) režimu – a to se projevuje trvalým blikáním modré LED. Tohle blikání je systémový indikátor AP režimu a **přebíjí** vaše vlastní nastavení barev přes `PLUGS_UI.SetConfig` – s nastavením LED barev pro zapnuto/vypnuto to nemá nic společného, je to nezávislá vrstva.

řešení: Wi-Fi ano, cloud ne

Řešení, ke kterému jsme došli: zásuvku necháte připojit k domácí Wi-Fi (přes `WiFi.SetConfig`, poslané po BLE), ale zároveň explicitně vypnete cloud (`Cloud.SetConfig(enable:false)`). Zásuvka tak opustí AP režim (LED přestane blikat), ale ovládání zůstává čistě lokální – žádná oficiální aplikace, žádný účet, žádný cloud. Wi-Fi heslo se posílá jednorázově a nikde se neukládá (výjimka je popsaná níže u klonování) – webová aplikace nemá ani žádný způsob, jak by mohla přečíst uložená Wi-Fi hesla z telefonu, takže se musí zadat ručně.

Užitečné detaily k Wi-Fi:

- zásuvka umí uložit 2 sítě (`sta` hlavní + `sta1` záložní) a mezi nimi sama přepíná
- pokud zásuvku přenesete tam, kam nedosáhne ani jedna nakonfigurovaná síť, na stock firmwaru se AP režim spolehlivě znovu nezapne automaticky (viz feature request v komunitě Shelly, který přesně tohle žádá) – zásuvka pravděpodobně jen dál zkouší připojení s červenou LED. Aplikaci to ale nezablokuje, protože BLE je na Wi-Fi nezávislé a novou konfiguraci přes `WiFi.SetConfig` lze poslat vždycky
- funguje i `WiFi.Scan` a nabídka seznamu viditelných sítí seřazený podle síly signálu

nastavení LED

Barvy i jas LED (zvlášť pro zapnuto/vypnuto) jdou nastavit přes `PLUGS_UI.SetConfig`. V aplikaci jsme přidali i posuvníky jasu (0–100 % po 20% krocích) pro oba stavy. Klíčové je při zápisu vždy sloučit nová data se stávajícím configem ze zařízení (`PLUGS_UI.GetConfig`) – jinak přepíšete i hodnoty, které jste neměnili.

klonování konfigurace mezi zásuvkami

Pokud kupujete víc kusů najednou, je otravné konfigurovat každou zvlášť. Přidali jsme při párování nové zásuvky zaškrtávací pole pro klonování z jiné, existující: místnost/budova, barva a jas LED, i Wi-Fi SSID/heslo – vše se rovnou pošle přes BLE na nové zařízení. Tohle je jediný důvod, proč moje aplikace nakonec Wi-Fi heslo v IndexedDB ukládá (dřív záměrně neukládala vůbec) – bez uloženého hesla by klonování Wi-Fi nedávalo smysl.

drobnosti, na které jsme naráželi

- `Sys.SetConfig` s `device.name` mění jen interní přátelské jméno čtené přes `Shelly.GetDeviceInfo` – **nemění** skutečné vysílané BLE jméno (`ShellyPlugSG3-XXXXXX`), to zůstává pevné. Pokud si chcete zařízení znovu najít podle jména, musíte si pamatovat obě jména zvlášť
- pole `gen` v `Shelly.GetDeviceInfo` odráží generaci RPC/API protokolu, ne marketingové označení Gen3/Gen4 – pro rozlišení HW generace se musí použít pole model/app
- Web Bluetooth spolehlivě funguje jen v Chrome na Androidu; na iOS Safari nefunguje vůbec (Apple vyžaduje WebKit pro všechny iOS prohlížeče a ten Web Bluetooth neimplementuje) – je to tvrdá platformní bariéra, ne chyba appky

alternativní ovladač: M5StickS3, bez telefonu

Vedle webové aplikace jsme rozjeli i samostatný lokální ovladač na M5StickS3 (ESP32, dvě tlačítka) jako BLE central bez závislosti na telefonu, přímo přes NimBLE-Arduino. Pár postřehů z portování protokolu:

- s NimBLE-Arduino v2.x se změnilo dost názvů oproti starším verzím/starším návodům: `NimBLEAdvertisedDeviceCallbacks` → `NimBLEScanCallbacks`, `setAdvertisedDeviceCallbacks` → `setScanCallbacks`, `NimBLEAddress` teď potřebuje explicitní byte s typem adresy, `NimBLEScan::start()` bere délku v milisekundách (ne v sekundách) a samotné `start()` se může vrátit dřív, než skenování doopravdy skončí – řeší se čekáním na `onScanEnd()` s timeoutem
- stejný BLE RPC protokol (service/charakteristiky, chunkování) jako ve webové aplikaci, jen s blokujícím `call()` API místo async/await
- funguje spolehlivě end-to-end i po restartu M5: najde zásuvku podle jména, spáruje se (občas první pokus spadne na "gatt connect failed", druhý pokus už projde), zapamatuje si zásuvku a přepíná ji přes BLE
- na displeji se čte i barva LED ze zásuvky (`PLUGS_UI.GetConfig`, škálováno na plnou intenzitu, jas se záměrně ignoruje) – zajímavý detail: `M5GFX`ové `drawString()` netraktuje `\n` jako zalomení řádku (vykreslí ho jako cizí znak a slova se slijí dohromady), takže víceřádkový text potřebuje samostatné volání `drawString()` na každý řádek

---

tím uzavíráme tenhle seriál – pokud vás to zajímá klidně se ptejte v komentářích, rád doplním detaily.

Odpovědět

Kdo je online

Uživatelé prohlížející si toto fórum: Žádní registrovaní uživatelé a 2 hosti