Jak se hlídá elektrárna (2. část)

photovoltaic-iconDruhá část tohoto seriálu o tom, jak hlídat malou FVE a optimalizovat spotřebu, se už bude věnovat skriptovaní v BASHi. Jak jsem slíbil v minulém díle, 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:

  • řídit celý systém optimalizace spotřeby
  • stahovat (vyčítat) data ze střídače
  • spouštět ostatní skripty (pro generování HTML kodu a optimalizaci spotřeby)
  • nahrávat soubory na server

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

  • --stdout Pouze vypíše informace na stdout — stáhne data ze střídače a vypíše. Neprovádí optimalizaci ani aktualizaci HTML stránek
  • --daemon|-d Spustí skript s automatickým opakováním každých 60 vteřin.

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í. 

#!/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

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ů.

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

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.

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

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ý.

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
}

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.

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) )	
}

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čí:

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

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.

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

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:

  • FVEUSERNAME= — Uzivatelské jméno do střídače
  • FVEPWD= —heslo ke střídači
  • FTPUSERNAME= — uživatelské jméno pro komunikaci s FTP serverem
  • FTPPASSWD= — heslo pro komunikaci s FTP serverem
  • TMPDIR=/tmp — umístění dočasných souborů
  • FVEHTMLNAME=fve.html — jméno pro hlavní stránku
  • FVEHTMLOPTNAME=fveopts.html — jméno stránky se stavem řízení spotřeby
  • FVEHTMLPOWERNAME=fvepower.html — jméno stránky se stavem výroby
  • PATH=$PATH:~/bin — je nutné dopsat cestu k adresáři, ve kterém jsou ostatní skripty
  • FVEDATADIR=~/fve — adresář, ve kterém bude ukládána historie výroby
  • PRODUCTIONFILE="$FVEDATADIR"/production.`date +%Y%m` — soubor s historií celkové výroby
  • HISTORYFILE="$FVEDATADIR"/history.`date +%Y%m` — soubor s historií výroby
  • HTMLSERVER=SERVER.COM — jméno FTP serveru
  • Poslední tři proměnné pouze určují cesty, netřeba je měnit.
    • HTMLFILE="$TMPDIR"/"$FVEHTMLNAME"
    • HTMLOPTFILE="$TMPDIR"/"$FVEHTMLOPTNAME"
    • HTMLPOWERFILE="$TMPDIR"/"$FVEHTMLPOWERNAME"

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

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

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:

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

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.

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
}

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í.

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
 
}

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.

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *