Skriptujeme televizi a ChromeCast

Dnešní zápisek bude o skriptování velice běžných zařízení – jsou jimi televize Samsung a přehrávač Google Chromecast. Obojí samozřejmě v Pythonu jako součást chytře postaveného hloupého domu.

Skriptování televize

Televize Samsung, které disponují LAN portem, popř. WiFi připojením, lze vzdáleně ovládat pomocí jednoduchého protokolu stejným způsobem, jako kdybyste používali tlačítka z dálkového ovladače. Pro Android existují různé aplikace, já mám například nainstalovaný SamyGo Remote. My se však podíváme na to, jak se s televizí bavit z Pythonu. Použijeme pro to knihovnu samsungctl, která za nás udělá vše nutné.

Samotné použití je velice jednoduché, nejprve na televizi musíme povolit vzdálené ovládání (u mé televize Samsung UE37ES5500 v menu Síť / Nastavení AllShare) a sepsat dohromady konfiguraci:

REMOTE_CONFIG = {
    "host": "192.168.3.6",
    "port": 55000,
    "name": "Chromak MQTT",
    "description": "MQTT connector for Samsung TV",
    "id": "chromak",
    "timeout": 0.5,
    "method": "legacy",
}

V konfiguraci je důležitá položka host, kde je IP adresa vaší televize (je proto více než vhodné nastavit na DHCP serveru přidělování fixní IP). Položka port je defaultní port, na kterém televize poslouchá, položky id, name a description jsou libovolné řetězce a televize si klienta s těmito kombinacemi zapamatuje (při prvním pokusu o přístup zobrazí dialog, zda souhlasíte se vzdáleným ovládáním z takto identifikovaného zařízení). Položka timeout je nastavena na komunikačním socketu, pro lokální síť není třeba žádná vysoká hodnota. A konečně method (u mě "legacy", mám starší televizi) určuje komunikační metodu, dle samsungctl novější televize podporují i "websocket", bohužel televizi již nějaký pátek mám.

Po instalaci samsungctl (ideálně přes pip install samsungctl|) je použití až triviálně jednoduché:

from samsungctl import Remote

with Remote(REMOTE_CONFIG) as remote:
    remote.control("KEY_DTV")

Řetězec "KEY_DTV" je kód tlačítka vybraný z množiny podporovaný kódů. Za zmínku stojí ještě "KEY_POWEROFF", který televizi vypne, bohužel vypnutou televizi nejde takto zapnout. Jak toto omezení obejít si povíme později.

Nejprve jsem se snažil nějakým chytrým způsobem recyklovat a udržovat aktivní připojení, ale nakonec jsem rezignoval, samotné poslání kódu včetně nového připojení před každým kódem proběhne v LAN tak rychle, že nemá cenu znovupřipojování řešit.

Skriptování Google Chromecast

Google Chromecast je velice šikovná krabička, obzvlášť pokud na telefonu používáte Google služeb. Já kromě Chromecastu mám i JBL Playlist, což je elegantně jednoduchý, ale výborně hrající reproduktor s podporou Chromecastu. Takto vybaven jsem nalezl projekt pychromecast, který se umí na Chromecast připojit a ovládat jej. Příklady kódu níže jsem vykoukal ze zdrojového kódu a z příkladů v examples, dokumentace moc sdílná není.

Připojení k Chromecast

Nejprve je nutné Chromecast na síti najít a vytvořit objekt, pomocí kterého ho budeme ovládat, mělo by stačit použít:

import pychromecast

casts = pychromecast.get_chromecasts()

Bohužel, v mém případě se v seznamu casts neobjevil JBL Playlist, našel jsem ale alternativu, možná i vhodnější, protože nespoléhá na broadcast po lokální síti, ale můžeme v ní rovnou předdefinovat IP adresy, které Chromecasty mají (opět fixně nastavené na DHCP serveru):

KNOWN_CHROMECASTS = [
    ("192.168.1.8", 8009, None, "Chromecast", u"TV Obývák"),
    ("192.168.1.242", 8009, None, "Chromecast Audio", u"Ložnice"),
]

casts = []
for cast_args in KNOWN_CHROMECASTS:
    cast = pychromecast._get_chromecast_from_host(cast_args)
    casts.append(cast)
    lst = ChromeListener(cast)
    cast.media_controller.register_status_listener(lst)
    cast.register_status_listener(lst)

Koho by zajímalo, položky v KNOWN_CHROMECASTS mají následující význam: ip_address, port, uuid, model_name, friendly_name.

Nyní jednak máme seznam casts, kde jsou objekty sloužící k interakci s Chromecasty, ale zároveň jsme k Chromecastům přiřadili instanci ChromeListener, prostřednictvím které pozorujeme stav Chromecastu. Jak tato třída může vypadat si ukážeme v dalším odstavci.

Pozorujeme a ovlivňujeme stav Chromecastu

První ze záležitostí, které jsem v Pythonu naskriptoval, je automatické nastavení hlasitosti na Chromecastu při spuštění nové položky (ať už hudby nebo videa), neboť maximální hlasitost zvuku při přehrávání z Chromecastu je výrazně vyšší než při přehrávání z televize. Prostřednictvím třídy ChromeListener pozoruji změny aktuální hlasitosti a změnu stavu přehrávače a v případě potřeby reaguji:

VOLUME_THRESHOLD = 0.4

PLAYER_STATE_BUFFERING = "BUFFERING"
PLAYER_STATE_UNKNOWN = "UNKNOWN"
PLAYER_STATE_IDLE = "IDLE"
PLAYER_STATE_PAUSED = "PAUSED"
PLAYER_STATE_PLAYING = "PLAYING"

class ChromeListener(object):
    def __init__(self, cast):
        self.cast = cast

        self.last_player_state = None
        self.volume_level = None

    def new_media_status(self, status):
        player_state = status.player_state
        if player_state == PLAYER_STATE_BUFFERING:
            return

        if self.last_player_state in (PLAYER_STATE_UNKNOWN, PLAYER_STATE_IDLE) and (player_state in [PLAYER_STATE_PLAYING, PLAYER_STATE_PAUSED]):
            # Playback just started
            if self.volume_level > VOLUME_THRESHOLD:
                self.cast.set_volume(VOLUME_THRESHOLD*0.95)

        self.last_player_state = player_state

    def new_cast_status(self, status):
        self.volume_level = status.volume_level

Hlasitost upravuji následovně: pokud je v době spuštění přehrávání (pozná se přechodem do stavu PLAYING nebo PAUSED ze stavu UNKNOWN nebo IDLE) hlasitost vyšší než práh, nastavím novou hlasitost  pomocí metody set_volume lehce pod tento práh (VOLUME_THRESHOLD*0.95). Stav, kdy Chromecast bufferuje stream (BUFFERING) je ignorovaný.

Zapnutí televize pomocí Chromecastu

Jak jsem již říkal, Samsung televizi nejde pomocí jejich protokolu vzdáleně zapnout. Nicméně moje sestava TV + receiver + Chromecast umí televizi pomocí HDMI zapnout při spuštění nového media na Chromecastu. A tím pádem to lze i z Pythonu, stačí použít cosi jako:

cast.play_media("https://honzas.cz/wp-content/uploads/2014/06/cropped-header.jpg", "image/jpeg")
time.sleep(5)
cast.quit_app()

a naběhne vám televize s aktivním HDMI vstupem a Chromecast na výchozí obrazovce. Ukončení přehrávání pomocí quit_app() zajistí, že obrázek ze záhlaví mého blogu ani neuvidíte.

Další náměty na skriptování

Skriptování můžeme použít k řadě dalších vychytávek a drobností. Já používám hlavně tyto:

  • Při odchodu z domu (poznám přes MQTT ze stavu zabezpečovačky) vypnu televizi, JBL Playlist a aktuálně běžící aplikaci v Chromecastu.
  • Ráno, když děti vstávají a pouští si televizi, v 7:00 vypínám seriály a pustím rádio stream pomocí:
    cast.play_media("http://icecast7.play.cz/crojuniormini128.mp3", "audio/mpeg")

    Navíc v 7:25 vypnu i tento stream pomocí známého:

    cast.quit_app()

    Díky tomu děti hned ráno ví, kolik je hodin a jak moc mají přidat, abychom v 7:30 odcházeli z domu. Navíc, abych zabránil zapínání televize v případě, že jsou třeba prázdniny a nikdo se v 7 hodin na televizi nedívá, čtu si stav UniFi controlleru zda je televize on-line (jinými slovy zapnutá).

  • Vše, co jsem vám dnes popisoval, je součástí jednoho procesu, který navíc poslouchá na MQTT a umožňuje napojení na zbytek automatizace domu.

Závěrem

Jak vidíte, pokud se povede šikovně poskládat hardware (v mém připadě televize a Chromecast) se software, dají se obejít i omezené daného hardware (nelze po LAN zapnout televizi), navíc se jednoduchým skriptem nechá ovládat spoustu funkcí a vymyslet pár jednoduchých, ale pro život užitečných vychytávek.

Pokud máte Chromecast již naskriptovaný nebo máte v rukávu jiné vychytávky, podělte se o ně pod tweetem níže!