Síla myšlenky je neviditelná jako semeno, ze kterého vyroste obrovský strom. Je však příčinou viditelných změn v životě člověka. (L.N.Tolstoj)

Jak se hlídá elektrárna

Zveřejněno: 28. 02. 2014 | Autor:
Kategorie: Linux

Majitelé malých fotovoltaických elektráren (FVE, do 5kWp výkonu) si tuto elektrárnu pořizují z jednoho důvodu — ušetřit na nákladech za elektriku. Jsa jedním z nich (mám doma na střeše FVE 4.4kWp) jsem se rozhodl, že budu v tomto trochu aktivnější a dle aktuální výroby budu zapínat či vypínat naše domácí elektrospotřebiče (boilery). Za tímto účelem jsem vytvořil sadu skriptů — FVEread. V tomto krátkém seriálu popíšu, co všechno skripty umí a jak to dělají.

Jak funguje FVE

Nebojte, nemíním zde popisovat fyzikální pozadí foto-voltaického jevu, jen krátce shrnu zapojení a terminologii, abychom se v následující části neztráceli.

Panely malé fotovoltaické elektrárny

Panely malé fotovoltaické elektrárny

První v řetězci jsou fotovoltaické panely. Tyto jsou nejviditelnější částí celé FVE – jsou vidět na střechách domů a bohužel i na polích. Tyto panely se zapojují do série, přičemž po osvícení generují stejnosměrné napětí (a následně i proud). Dráty z panelů se poté svedou do jediného místa – střídače, což je zařízení, které ze stejnosměrného proudu dělá proud střídavý, 220V, tedy to, co je v běžné síti. Také hlídá, aby „tzv. seděla fáze“ a mnohé další. Ze střídače se, nyní již střídavý, proud svede do pojistkové skříně tak, že vyrobená energie je k dispozici pro spotřebu v domácnosti a nebo se „posílá“ do rozvodné sítě.

Vzhledem k tomu, že jsem si FVE pořizoval s úmyslem maximálně ušetřit, chci maximum z vyrobené energie rovnou spotřebovat doma — v místě výroby. Většina domácích FVE se „smíří“ s tím, že mají např. mrazák či boiler naprogramovaný na hodiny, kdy obvykle svítí sluníčko, jinak se nespíná (ala časově spínané zásuvku). Ovšem v takovém případě nahříváte vodu v poledne i v případě, že venku prší, či je mlha a vaše FVE neprodukuje nic (případně jen desítky wattů, což je tak akorát na provoz počítače, který bude spotřebu a vše okolo hlídat), což není nejekonomičtější.

A nebo budeme aktivnější: budeme aktivně hlídat výrobu a na základě rozvahy a energetického plánu si budeme vybírat, které zařízení zapneme. A o tom bude pojednávat tento krátký seriál.

Co všechno musí skript hlídat

Tedy cíl je dán: hlídat mnozství vyrobené energie a na jeho základě aktivně řídit spotřebu. Co k tomu budeme potřebovat:

Nyní již k vlastnímu skriptu a tomu, co musí umět (uvažujeme, že chceme vygenerovat i HTML stránku).

  1. V prvním neoddiskutovatelném případě je to hlídání výroby. To znamená:
    • Připojit se ke střídači
    • Stáhnout si potřebná data
    • Zpracovat data do proměnných tak, aby tomu systém rozuměl
  2. Dle výroby se rozhodnout, které spotřebiče mají být zaplé a které nikoli
    • Porovnat posledních x minut výroby a určit, jak moc kolísavý je výkon, aby se co minutu neměnil stav v případě, že výroba je na spínané hraně
    • Přihlédnout k současnému stavu zařízení
    • Zapnout či naopak vypnout spotřebič
    • Případně aktualizovat stránku se stavem zařízení
  3. Aktualizovat stránku na internetu
    • Vygenerovat stránku s aktuální výrobou
    • Aktualizovat stránku na internetu

Jak vidíte, je toho poměrně dost, co musí náš skript umět. Dá se rozdělit na celkem tři logické celky a několik podfunkcí.

První, hlavní, se jmenuje FVEread — vyčítá informace ze střídače a pouští další skripty. FVEoptRun se stará o optimalizaci spotřeby – rozhoduje se, které zařízení má být zaplé, které vyplé a podobně. FVEgenGraphs nedělá nic jiného, než že generuje grafy. A konečně FVEgenPage generuje a aktualizuje HTML stránky (o jejichž nahrávání na server se stará FVEread z čistě praktických duvodu).

V tomto krátkém seriálu projdu všechny skripty a části tohoto systému, přičemž vysvětlím, proč je to psáno tak, jak to je napsáno. Podotýkám, že skript je zatím ve vývoji, především co se týče logiky části FVEoptRun, která je prozatím velmi hloupá, ale než se k tomu v seriálu dostaneme, zajisté již bude přepsaná a chytřejší.

Nyní se podíváme na to, jak hlídat malou FVE a optimalizovat spotřebu a v této části se již budeme věnovat skriptovaní v BASHi. Jak jsem slíbil v předchozí části, zde se budeme věnovat hlavnímu skriptu jménem FVEread, který je hlavní činnou jednotkou celého systému.

Před tím, než začnete číst tento seriál, doporučuji si pročíst můj nedávný krátký článek o GITu. Zvláště, pokud uvažujete nad tím, že byste tento skript (sadu) použili, byste měli používat výhradně kód z GitHubu, nikoli to, co je uváděno zde.

FVEread je hlavní výkonnou jednotkou celého systému. Jeho funkce jsou:

FVEread přijímá krom parametru -h|--help už jen dva parametry.

Aby byl skript efektivní a fungoval tak, jak má, je vnodné jej pouštět v pravidelných intervalech (minuta či dvě — tak aby vše bylo dynamické a mohlo se reagovat na skutečnou situaci. Já sám to mám naprogramované pomocí služby cron:

* * * * * /cesta/k/FVEread 2> /dev/null

kdy každou minutu se spustí celý systém. Druhá možnost je právě parametr --daemon, kdy se děje totéž, ale skript se spustí jen jednou a co minutu se provádí hlavní interní routina. Tedy je jen na vás, čemu dáte přednost — cronu nebo spouštění na pozadí, např. přes init.

Dobrá ale, je nutné začít popořadě tak, jak se jednotlivé funkce načítají.

[codesyntax lang=“bash“ lines=“no“]

#!/bin/bash
source ~/.FVErc

function printHelp() {
	echo -e "usage: `basename $0` [--stdout]
 Opts:
  --help|-h   Print this help message
  --stdout    Will not update webpage, but print just output to stdout
  --daemon|-d Enable auto-refresh, otherwise the script will issue only 
              one iteration."
exit 0
}

if [ "$#" = 1 ]; then
	if [ "$1" = '-h' -o "$1" = "--help" ]; then
		printHelp
		exit
	elif [ "$1" = "--stdout" ]; then
		export STDOUT="YES"
	elif [ "$1" = "--daemon" -o "$1" = "-d" ]; then
		export DAEMON=1
	else
		echo "Non supported parametr $1"
		exit 1
	fi
elif [ "$#" -gt 1 ]; then
	echo "Too much options"
	printHelp
	exit
fi

if [ $DAEMON ]; then
	while true; do
		mainLoop
		sleep 60
	done
else 
	mainLoop
fi

[/codesyntax]

Jak vidno, pokud je použit přepínač --daemon (a nebo -d), script skončí ve smyčce while-do-done, která neskončí. Pokud tento parametr předán není, skočí se rovnou to funkce mainLoop.

Nepovažuji za nutné popisovat funkci print_help() a část s processingem parametrů – s tím si užijeme v některém z dalších dílů, kde parametry zpracováváme spolu s funkcí getopts.

Jediná zajímavost zde je druhý řádek, kde se načítají některé proměnné, jako uživatelské jméno a heslo do střídače, k FTP serveru, cesty a názvy souborů.

[codesyntax lang=“bash“ lines=“no“]

function mainLoop() {
        getData
        if [ "$STDOUT" ]; then
                printDataStdout 
        else
                genFiles
                FVEoptRun "$CURRENT"
                uploadDataFTP
        fi
        cleanFile       
}

[/codesyntax]

Funkce mainLoop spouští již jednotlivé úkoly. getData stáhne data ze střídače a naplní proměnné, se kterými poté operují další funkce. Pokud je předán parametr --stdout, tak v tuto chvíli veškerá práce končí, načtené proměnné se jen vypíší na terminál.

Parametr --stdout používám na ostatních počítačích, kde mám udělaný

alias FVE='ssh fvechecker "FVEread --stdout"'

kde fvechecker je počítač určený pro hlídání FVE. Takto mohu jednoduše zjistit aktuální hodnoty, aniž bych se musel připojovat na web.

Pokud ovšem požadujete všechny další funkcionality skriptu, je potřeba vygenerovat HTML soubory a grafy (funkce genFiles), upravit a optimalizovat spotřebu (FVEoptRun) a konečně nahrát soubory na server (uploadDataFTP). FVEoptRun je již jiný skript s vlastní logikou, ale nebojte se, popíšu jej v jednom z dalších dílů. Ale ještě před tím, než začneme vypisovat data na terminál či nahrávat soubory, je nutné vyčíst data ze střídače — čas na funkci getData().

Vyčítáme data ze střídače

Funkce getData() je jednohuchá routina, která prvně stáhne stránku ze střídače (vizte poznámku níže) — getFileFromFVE a poté spustí 2 velmi podobné funkce pro získání proměnné $CURRENT (aktuální výroba) a $TODAY (množství energie vyrobené za dnešek).

Střídač, co mám, je KOSTAL Piko 4.2. Je možné k němu přistupovat přes http stránku s autentifikací uživatel/heslo. Proto funkce getData je customizována a stahuje onu HTML stránku, rozparsuje ji a vrátí hodnoty. Pokud byste měli jiný střídač, úpravám této funkce se nevyhnete.

V případě, že není možné se připojit ke střídači, plní tyto proměnné nulami.

[codesyntax lang=“bash“ lines=“no“]

function getData() {
	getFileFromFVE 
	if [ $? = 1 ]; then
		CURRENT="-0"
		TODAY="-0"
		FVESTATUS="Down/stopped"
	else
		CURRENT="`getCurrent`"
		TODAY="`getToday`"
		FVESTATUS="Running"
	fi
        getAvrg
}

[/codesyntax]
Stahování dat ze střídače dělám běžným cURL příkazem, kterému předávám i username a heslo a ukládám do $TMPDIR/FVE.$$.html. Proměnná $TMPDIR je načítána hned na začátku celého skriptu. Pokud neznáte trik s $$, poté vězte, že konstrukce $$ je PID daného procesu. Výborně se to hodí na generování dočasných souborů s unikátním názvem. Je ovšem nutné pamatovat na to, abyste tyto soubory odstraňovali — tento skript vytvoří jeden takovýto soubor každou minutu, což je 1440 souborů za den! V případě, že stažení stránky ze střídače nefunguje, navrací funkce návratovou hodnotu 1 a předpokládá se chyba v komunikaci, či že je střídač vypnutý.

[codesyntax lang=“bash“ lines=“no“]

function getFileFromFVE() {
	curl -u $FVEUSERNAME:$FVEPWD http://stridac > "$TMPDIR"/FVE.$$.html 2>/dev/null 
	if [ $? != 0 ]; then
		echo "Stridac neni dostupny"
		return 1
	else
		return 0
	fi
}

[/codesyntax]

Existují dvě funkce, které jsou skoro stejné, jen se liší drobnostmi, které vyhledávají. Obě funkce následně sdílí společný konec, tj. že na získanou hodnotu volají krátkou routinu cleanLine, která vrátí pouze čísla. Jedná se o implementační kroky poměrně pevně vázané na můj střídač. Funkce getAvrg zprůměruje výrobu za posledních 10 minut.

[codesyntax lang=“bash“ lines=“no“]

function getCurrent() {
	iCURRENT=`cat "$TMPDIR"/FVE.$$.html | grep -A2 'aktu[.]*'|grep ln -A2 | tail -1 `
	echo `cleanLine "$iCURRENT"`
}
function getToday() {
	iTODAY=`cat "$TMPDIR"/FVE.$$.html | grep -A2 'denn[.]*'|grep energie -A2 | tail -1`
	echo `cleanLine "$iTODAY"`
}
function cleanLine() {
	LINE=$1
	echo $LINE | sed 's/<\/td>//g' | sed 's/&nbsp//g' | tr -d ' ' | \
	sed 's/xxx/0/g' | tr -dc '[:print:]'
}
function getAvrg() {
	AVRG=$(expr $(expr $(tail -10 $HISTORYFILE | awk '{print $2}' | sed '/^$/d' | sed ':a;N;$!ba;s/\n/ + /g')) / $(tail -10 $HISTORYFILE | awk '{print $2}' | sed '/^$/d' | wc -l) )	
}

[/codesyntax]

Nyní již je v proměnných $CURRENT a $TODAY uložen aktuální výkon a množství vyrobené energie pro daný den. Ve funkci getData tedy pokračujeme k tomu, má-li se vše vytisknout na terminál (standardni output), a nebo vytvořit HTML soubory a nahrát na server a optimalizovat spotřebuju. Jakmile se zavolá funkce printDataStdout, vypíší se hodnoty a funkce končí:

[codesyntax lang=“bash“ lines=“no“]

function printDataStdout() {
	#usage: #printDataStdout 
	echo "My FV: `date`:"
	echo "Current production [kW]  = $CURRENT"
	echo "Today was produced [kWh] = $TODAY"
	return 0
}

[/codesyntax]

Generujeme soubory

Podtitul k této části by mohlo být „ale jen náznakem, více někdy příště“. O generování HTML souborů se totiž stará skript FVEgenPage a o generování grafů FVEgenGraphs.

Poté, co se z funkce mainLoop() zavolá genFiles(), do souboru history se zapíší aktuální hodnoty ze střídače spolu s datem a časem. Poté se vygeneruje HTML stránka s aktuální produkcí, a nakonec vygenerují grafy.
[codesyntax lang=“bash“ lines=“no“]

function genFiles() {
  echo `date +%Y%m%d%H%M` $CURRENT $TODAY $AVRG >> "$HISTORYFILE"
  FVEgenPage  --powerpage --current $CURRENT --today $TODAY --fvestatus $FVESTATUS
  updateProductionFile
  FVEgenGraphs --all
}

[/codesyntax]

Když už mluvíme o proměnných a souborech, trochu je představím. Začnu s proměnnými, uloženými v souboru ~/.FVErc:

Není jich málo, pravda. Na druhou stranu, některé není třeba měnit (libovolné dočasné cesty a soubory), jiné jsou vázány na mou konkrétní implementaci. O tom, jak vypadajií HTML soubory, se rozepíšu v některém z příštích dílů, kdy se budu věnovat FVEgenPage.

Ovšem nejpodstatnější soubory jsou soubory history a production, které jsou umístěny v $FVEDATADIR.

Soubor history obsahuje 4 sloupce. První sloupec je čas, kdy byla data získána. Ve druhém sloupci je zaznamenán aktuální výkon v daném čase. V předposledním sloupci je hodnota, kolik FVE vyrobila energie do té chvíle. Poslední sloupec uvádí průměrnou výrobu za posledních 10 minut. Kompenzují se tím drobné výkyvy a např. minutové zatmění mraky a podobně.

Soubor production má sloupce jen dva. Časová známka obsahuje pouze datum — jeden den tak je uveden pouze na jednom řádku.

Ze souboru history je tak možné vyčíst průběh výroby v průběhu doby. Naopak production obsahuje vlastně jednotnou historii produkce v průběhu dnů.

Obsah souboru history vzniká na jediném řádku ve funkci genFiles
[codesyntax lang=“bash“ lines=“no“]

echo `date +%Y%m%d%H%M` $CURRENT $TODAY $AVRG >> "$HISTORYFILE"

[/codesyntax]

Obsah souboru production vzniká na více řádcích. V prvním se smaže předešlý záznam pro daný den a poté se na konec souboru vloží data. Nakonec jsem k tomu napsal zvláštní funkci:

[codesyntax lang=“bash“ lines=“no“]

function updateProductionFile() {
	DATE=`date +%Y%m%d`
	sed -i /"$DATE"/d $PRODUCTIONFILE
	echo "$DATE $TODAY" >> $PRODUCTIONFILE
	return 0
}

[/codesyntax]

Na prvním řádku se vytvoří proměnná data, poté se pomocí sed-u odstraní řádek, který obsahuje záznam toho dne. A nakonec se do souboru production vloží nový záznam ve formátu datum-hodnota.

Toto je možné samozřejmě řešit více přístupy, případně vše generovat jen ze souboru history. Ovšem prvně je tato implementace jednodušší co do kódu, tak i do pochopení.

Nahráváme a uklízíme

V předešlé části jsme vygenerovali data a soubory. Nyní je před námi poslední výkonová část skriptu. Funkce uploadDataFTP. Tato funkce na FTP server nahraje potřebná data — grafy a html soubory.

[codesyntax lang=“bash“ lines=“no“]

function checkRunningStatus() {
	if [ -e "$TMPDIR"/.fveread ]; then
		echo "Already running, quiting."
		echo $$ >> "$TMPDIR"/.fveDebug
		exit 0
	fi
	echo $$ > "$TMPDIR"/.fveread
}
function uploadDataFTP() {
	checkRunningStatus

	lftp -u "$FTPUSERNAME",$FTPPASSWD -e "put -O "$HTMLDESTDIR" $TMPDIR/fvem.png $TMPDIR/fved.png /$TMPDIR/fvep.png $HTMLPOWERFILE $HTMLOPTFILE $HTMLFILE && bye" $HTMLSERVER
	mv "$TMPDIR"/fved.png "$TMPDIR"/d`date +%Y%m%d`.png
	mv "$TMPDIR"/fvem.png "$TMPDIR"/m`date +%Y%m`.png
	mv "$TMPDIR"/fvep.png "$TMPDIR"/p`date +%Y%m`.png
	lftp -u "$FTPUSERNAME",$FTPPASSWD -e "mput -O "$HTMLDESTDIR"/fve.hist/ $TMPDIR/m*.png $TMPDIR/p*.png $TMPDIR/d*.png && bye" $HTMLSERVER

	rm -f "$TMPDIR"/.fveread
	return 0
}

[/codesyntax]
V první řadě se překontroluje, jestli se již nenahrávají data na FTP server. Může se stát, že se nahrávání z nejakého důvodu zasekne (např. spadne spojení) a pak by se množilo množství FTP příkazů. Funkce checkRunningStatus tak překontroluje, jestli již existuje tzv. „zámek“, tj. soubor "$TMPDIR"/.fveread a pokud neexistuje, vytvoří jej. Pokud tento soubor ovšem najde, přestane se provádět a skript se ukončí. Poté se na server uloží všechna data. Jakmile jsou FTP přenosy hotové, smaže se zámek.

V průběhu doby vzniká poměrně velké množství souborů (grafy, html stránky…), které nejsou po přenesení na server potřeba. Proto existuje funkce cleanFile, která nakonec uklidí.
[codesyntax lang=“bash“ lines=“no“]

function cleanFile() {
	rm -f "$TMPDIR"/FVE.$$.html
	rm -f "$TMPDIR"/fved.png
	rm -f "$TMPDIR"/fvem.png
	rm -f "$TMPDIR"/fvep.png
	rm -f "$TMPDIR"/d`date +%Y%m%d`.png
	rm -f "$TMPDIR"/m`date +%Y%m`.png
	rm -f "$TMPDIR"/p`date +%Y%m`.png

}

[/codesyntax]
A tímto hlavní skript končí. I když práce vlastně teprve začíná — nezapomeňte, že zatím se ze střídače pouze stáhla data, uložila do pár souborů, poté se zavolala magie (FVEoptRun, FVEgenPage, FVEgenGraphs) a pak se vše nahrálo na FTP server a smazalo… Tedy, zatím žádná užitečná práce, řeklo by se. Nebojte, všechno bude.

Vzhledem k úzce specifickému tématu další pokračování tohoto tématu naleznete na blogu autora tohoto seriálu.