úterý 11. února 2014

Automatické časové zapínání počítače pro nahrání pořadu s Tvheadend

V minulém článku jsem nakousl téma multimediálního centra s televizí v Ubuntu, ale správně jsem byl upozorněn na to, že bez automatického probuzení/zapnutí systému v čase, kdy se má nějaký ten pořad nahrát, není kompletní. A tak jsem se v tom pošťoural a výsledkem je pár nových poznatků a systém, který si před spaním přečte plán nahrávání Tvheadend, nastaví si budíček a po probuzení naopak zajistí, aby se po nahrání pořadu opět uspal, pokud je to vhodné...




RTC - hodiny reálného času

Základem fungování automatického probuzení/zapnutí systému jsou hodiny reálného času, které jsou přímo v HW systému. Tyto hodiny jsou napájeny baterií, která je udržuje v chodu, i když je systém vypnutý a odpojený od sítě. Stejná baterie zajišťuje udržení nastavení BIOSu. Probouzet se na budíček umí sytémy podporující APM, či novější ACPI, cca od roku 2000. ACPI standard rozšiřuje možnosti RTC o delší dobu plánování budíku a podporu probuzení z hibernace, tedy z uspání na disk. RTC může, krom alarmu v určitý čas, také generovat přerušení s určitou frekvencí (2 Hz až 8192 Hz, po násobcích 2), nebo generovat přerušení při každé aktualizaci hodin, tedy každou sekundu (1 Hz). Používá se přerušení IRQ 8 a výstup je dostupný přes zařízení /dev/rtc0, /dev/rtc na něj bývá linkováno (i v Ubuntu). Zmíněné tři generátory přerušení jsou nezávislé, samostatně nastavitelné. /dev/rtc je pouze pro čtení a otevřeno může být jen jednou - dokud ho předchozí klient nezavře, ostatní dostanou chybu, že zařízení je používáno.

RTC jsou pod Linuxem vždy nastaveny na UTC čas, nejsou posouvány podle lokálního času jako ve Windows. Nabootování do Windows tedy RTC rozhodí všem, kteří nejsou v pásmu UTC. Linux jinak RTC aktualizuje každých 11 minut, přičemž na chvíli vypne periodické přerušení, což může způsobit problém aplikaci, která na něm závisí. I aktualizace se ale dá vypnout.

U některých základních desek najdete v BIOSu v sekci Power Managementu možnost nastavit si alarm pro automatické probuzení systému přímo. Pro použití z operačního systému je v zásadě třeba nastavení budíku v BIOSu zrušit, jen nechat povoleno probouzení systému RTC alarmem.

K nastavení RTC modulu se dostanete přes procfs:

$ cat /proc/driver/rtc
rtc_time         : 11:21:55
rtc_date         : 2014-02-09
alrm_time        : 12:11:58
alrm_date        : 2014-02-09
alarm_IRQ        : yes
alrm_pending     : no
update IRQ enabled       : no
periodic IRQ enabled     : no
periodic IRQ frequency   : 1024
max user IRQ frequency   : 64
24hr             : yes
periodic_IRQ     : no
update_IRQ       : no
HPET_emulated    : yes
BCD              : yes
DST_enable       : no
periodic_freq    : 1024
batt_status      : okay


Je tu aktuální čas a datum, nastavení a stav budíku, i ostatních generátorů přerušní. Na mém příkladu můžete vidět, že je budík aktivní (alarm_IRQ : yes).

Nastavení RTC budíku

Pro nastavení budíku se používá čas v sekundách od začátku roku 1970. Protože jsme si řekli, že je to čas UTC, od kterého jsme u nás jednu až dvě hodiny napřed, podle toho, jestli máme zimní, nebo letní čas, je potřeba čas vždy správně přepočítat. K tomu naštěstí máme chytré nástroje přímo v systému.

Nastavení času budíku se dělá zápisem do souboru /sys/class/rtc/rtc0/wakealarm, k němuž má přístup pouze root a i ten tam může zapsat nový čas pouze tehdy, když ještě budík není nastaven. Pokud nastaven je a chcete ho změnit, musíte tam nejprve poslat znak 0 (nula), čímž předchozí nastavení zrušíte.

Pro konverzi času je nejjednodušší použít příkaz date. Můžete si alarm nastavit například na určitou hodinu následujícího dne (jak bylo řečeno - pod rootem a hodit před to jednoduše sudo nestačí, to by se nevztahovalo na použité přesměrování - když už, tak třeba sudo su -c 'příkaz'):

echo 0 > /sys/class/rtc/rtc0/wakealarm
date +%s -d "next day 7:00" > /sys/class/rtc/rtc0/wakealarm

Naopak přečíst a zkonvertovat aktuální nastavení budíku můžete třeba takto:

cat /sys/class/rtc/rtc0/wakealarm | xargs -I {} date +"%d/%m/%Y %H:%M" -d "@{}"
10/02/2014 17:00

Pokud dostanete prázdný řetězec, budík nastaven není.

Podívat se můžete i na příkaz rtcwake, který umí systém okamžitě uspat, či vypnout, a současně nastavit čas, kdy se má systém probudit.

Odkazy:
https://www.kernel.org/doc/Documentation/rtc.txt
http://man7.org/linux/man-pages/man4/rtc.4.html


Probuzení systému podle rozvrhu nahrávání Tvheadend

Mám tedy TV server, který mi může nějaký ten pořad nahrát, kliknutím v prohlížeči, nebo XBMC si ho můžu objednat, ale vzhledem k tomu, že mi počítač neběží 24 hodin denně, bude potřeba vyřešit nějakou automatiku, která si systém sama zapne, když je potřeba a případně zase po dokonání díla zpátky uspí. Mé řešení by mělo umět zhruba toto:

  • při uspávání systému si přečíst plán nahrávání a nastavit RTC alarm na nejbližší pořad
  • při probuzení by mělo být schopno určit, zda proběhlo podle nastavené automatiky, nebo jinak
  • pokud automaticky, měl by se systém po skončení nahrávání opět uspat
  • ovšem pouze tehdy, pokud mezitím uživatel systém nezačal používat, aby mu neusnul pod rukama

Takže jsem se tím začal prokousávat, začal jsem wiki Tvheadend. Máte-li při uspávání, nebo probouzení s některými moduly, nebo aplikacemi problém, můžete se tam podívat, jak je při suspendu odebrat a při probuzení opět zavést. Další skript, který řeší vlastní programování RTC alarmu má podle mého k dokonalosti hodně daleko, ale vlastní zpracování dat z logů funkční je, i když je v tom mrak zbytečností. Tak jsem to trochu přepsal a doplnil.

Teoreticky by se dalo dostat k datům, které drží přímo Tvheadend v xml formátu, jenže to vyžaduje přihlášení a navíc by tu byly trochu větší latence, takže se probereme utrousenými logy, ve kterých ta data najdeme také. Každý timer má vlastní log v adresáři

~hts/.hts/tvheadend/dvr/log/

Pokud by vám připadalo zvláštní to ~hts, shell to přeloží do celé cesty $HOME uživatele hts, ve kterém Tvheadend operuje (pokud jste to neměnili). K hrabání se v těchto datech musíte mít oprávnění, takže můžete použít roota. Suspend/resume skripty běží stejně pod rootem, takže to není potřeba řešit. Nakonec jsem uplácal následující skript, který mám v souboru

/etc/pm/sleep.d/05_tv-timer.sh

#!/bin/bash
#
# set RTC Wakeup alarm
# script does not check if recording is in progress
#

suspend_fn()
{
    # pokud systém uspává post-process skript, musí se poklidit
    pkill -f waitxinput
    rm -f /run/shm/tvsuspend

    # časová rezerva pro probuzení systému před začátkem nahrávání
    safe_margin=60
    # cesta k logům rekordéru
    log_path=~hts/.hts/tvheadend/dvr/log
 
    ######################
 
    start_date=0
    current_date=`date +%s`

    for i in $( find "$log_path" -type f ); do
        tmp_start=`sed -nr '/"start":/s/[^0-9]*([0-9]+).*/\1/p' $i`

        # kontrola prošlých timerů
        if [ $tmp_start -gt $current_date ]; then

            # bližší čas si zapamatujem
            if [ $start_date -eq 0 -o $tmp_start -lt $start_date ]; then
                start_date=$tmp_start
            fi
        fi
    done
 
    wake_date=$((start_date-safe_margin))
 
    # nastavení budíčku, pokud byl nalezen vyhovující timer
    if [ $start_date -ne 0 ]; then
        echo 0 > /sys/class/rtc/rtc0/wakealarm
        echo $wake_date > /sys/class/rtc/rtc0/wakealarm

        # nastavovaný čas bude uložen pro použití v resume skriptu
        echo $wake_date > /run/shm/rtc_alarm_time

    # pokud není k budíku důvod, pro jistotu poklidíme
    else rm -f /run/shm/rtc_alarm_time
    fi
}

resume_fn()
{
    # pokud se najde záznam o času budíku, porovná se s aktuálním časem
    if [ -e /run/shm/rtc_alarm_time ]; then
        rat=`cat /run/shm/rtc_alarm_time`
        ct=`date +%s`
        d=$((ct-rat))
        d=${d/-/} # absolutní hodnota sprostým odebráním případného mínusu
        echo $d
        if [ $d -gt 60 ]; then
            rm -f /run/shm/rtc_alarm_time
            # rozdíl mezi časem posledně nastaveného budíku a časem při tomto probuzení
            # je větší, než minuta, takže se systém pravděpodobně neprobudil automaticky
        else
            killall xbmc.bin & # XBMC systém automaticky uspává při nečinnosti
            su gdh -c 'touch /run/shm/tvsuspend; export DISPLAY=:0; ~/bin/waitxinput >/dev/null; rm /run/shm/tvsuspend' &
            # systém se pravděpodobně probudil automaticky, takže byl vytvořen kontrolní soubor
            # a spuštěn pythonní skript "waitxinput", který v případě, že uživatel pohne myší,
            # nebo stiskne klávesu klávesnice, skončí, čímž dojde ke smazání kontrolního souboru
            # a post-processor skript spuštěný Tvheadend po skončení nahrávání pak systém neuspí
        fi
    fi
}

case $1 in
    suspend )
        suspend_fn
        ;;
    resume )
        resume_fn
        ;;
esac
exit 0

waitxinput

Součástí předchozího skriptu je volání skriptu waitxinput, který mám v adresáři ~/bin/. Jak je zmíněno v komentáři, je to jednoduchý trigger, který je aktivován jakýmkoli vstupem z klávesnice, nebo myši. Zkrátka čeká, dokud nějaký vstup nezaznamená a pak skončí, takže se mohou provést příkazy za ním.
Pro jeho zfunkčnění je potřeba doinstalovat modul Xlib:

sudo apt-get install python-xlib


#!/usr/bin/python
# skript čeká na jakýkoliv vstup z klávesnice, či myši, aby se ukončil
from Xlib import X
from Xlib.display import Display

display = Display(':0')
root = display.screen().root
root.grab_pointer(True,
        X.ButtonPressMask | X.ButtonReleaseMask | X.PointerMotionMask,
        X.GrabModeAsync, X.GrabModeAsync, 0, 0, X.CurrentTime)
root.grab_keyboard(True,
        X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime)
display.next_event()

Automatické vypnutí po skončení nahrávání

Tvheadend takovou funkci přímo nenabízí, ale umí po skončení nahrávání spustit příkaz. Nastavení je v záložce Configuration > Recording > Digital Video Recorder položka Post-processor command:, sem si můžete napsat cestu k vlastnímu skriptu, který může jen systém zase uspat, nebo vypnout. Skript si hodíte například do souboru
~hts/suspend.sh
Já tam mám následující:

#!/bin/bash

# pokud resume skript vytvořil a nesmazal soubor tvsuspend
# systém je vhodné po skončení nahrávání uspat

if [ -e /run/shm/tvsuspend ]; then
    sudo /usr/sbin/pm-suspend
fi

A protože je příkaz pm-suspend pouze pro roota, musí se spouštět se sudo a udělit uživateli hts právo ho spouštět bez hesla editací sudoers. Takže spustíte příkaz:

sudo visudo

a na konec souboru si doplníte:

hts ALL=NOPASSWD: /usr/sbin/pm-suspend # TVHeadend

Pár slov na konec

Jen pár upozornění. Zatím jsem ještě neřešil možnost, že by bylo třeba nahrát více pořadů za sebou, nekontroluju čas další nahrávky, abych případně suspend odložil, musím tam přidat vypnutí monitoru, pokud se systém zapnul automaticky. Místa pro další vylepšení je tam ještě hodně. Nejsem žádný nahrávací maniak, takže uvidím, jakou budu mít motivaci, kdyby mě arrange v minulém článku tímto směrem nenakopnul (za což každopádně děkuju), asi bych to neřešil vůbec...

Žádné komentáře:

Okomentovat

Zkuste prosím při komentováni používat místo volby Anonymní volbu Název/adresa URL, kde vyplníte nějakou přezdívku, adresu zadávat nemusíte. Vědět, které příspěvky jsou od jednoho člověka, je fajn. Díky.

Pokud by se vám náhodou odeslaný komentář na stránce nezobrazil, vytáhnu ho z koše hned jak si toho všimnu. I Google spam filter se občas sekne.