strona główna
artykuły i recenzje
poradniki
galeria zdjęć
fotoblog

Obsługa GPIO w Raspberry Pi przez stronę www - podejście alternatywne

Dodano: Niedziela, 2 grudnia 2012, godzina 03:27:04
Kategoria: Artykuły i recenzje

Wstęp

W sieci bez problemu można znaleźć poradniki dotyczące obsługi portów GPIORaspberry Pi z poziomu strony internetowej. Duża część z nich opiera się na wykorzystaniu do tego celu całego pakietu oprogramowania: serwera web, najczęściej Apache, interpretera języka PHP i oczywiście jakiejś biblioteki do obsługi GPIO (przykładowy artykuł opublikowany na łamach portalu mikrokontroler.pl). W wielu przypadkach sugeruje się także instalację serwera baz danych MySQL oraz dodatkowych rozszerzeń dla PHP. To wszystko w rezultacie powoduje, że dla tak prostej operacji, jaką jest ustawienie lub odczytanie stanu na konkretnym GPIO, instalujemy masę niepotrzebnego i pożerającego zasoby oprogramowania. Oczywiście, jeżeli ktoś planuje rozbudowaną aplikację, wykorzystującą bazę danych, jakiś gotowy framework dla PHP, to takie podejście jest prawdopodobnie odpowiednie.

Trzeba jednak mieć na uwadze, że PHP jest językiem interpretowanym i napisany w nim kod nie jest kompilowany*. Z jednej strony jest to zaleta, ponieważ aplikacje w PHP możemy uruchamiać niezależnie od środowiska, w którym działa interpreter. Z drugiej, dla przedstawionego w tytule zadania jest to pewien istotny problem, bo w jaki sposób z poziomu aplikacji napisanej w PHP wykonywać operacje odwołujące się bezpośrednio do zasobów sprzętowych? Najczęściej spotykane rozwiązanie jest trochę nieeleganckie - wykorzystuje jedną z funkcji umożliwiającą wykonanie określonej komendy w powłoce systemowej lub uruchomienie zewnętrznego programu (shell_exec(), exec(), system() itd.). Przy jej pomocy wykonywany jest zewnętrzny program lub skrypt, który uzyskuje dostęp, najczęściej przy pomocy odpowiedniej biblioteki, do zasobów sprzętowych i na przykład ustawia stan wysoki na wybranym kanale GPIO. Nie bez powodu jednak większość firm hostingowych blokuje możliwość wykonywania tych funkcji w PHP. W przypadku włamania na nasz serwer, intruz przy ich pomocy może szybko uzyskać kontrolę nad maszyną i nieźle nabroić, szczególnie w przypadku gdy proces serwera posiada uprawnienia root.

*wykonałem tutaj w pewnym sensie skrót myślowy, uproszczenie, ponieważ obecnie językowi PHP jest trochę bliżej do Javy - jest kompilowany do tak zwanego kodu bajtowego, jednak na potrzeby tego artykułu takie stwierdzenie jest wystarczające i nie będę się zagłębiać w szczegóły dotyczące interpretera PHP

Przegląd istniejących rozwiązań

Poniżej zamieszczam listę gotowych rozwiązań dotyczących postawionego w tytule problemu, które udało mi się znaleźć w sieci (stan na grudzień 2012).

WebIOPi
Bardzo ciekawe i profesjonalne rozwiązanie, wciąż rozwijane. Posiada API bazujące na podejściu RESTful (format danych w JSON), jako serwer wykorzystuje aplikację napisaną w języku Python. Bazuje na zmodyfikowanej bibliotece raspberry-gpio-python napisanej w języku C (bezpośredni dostęp do /dev/mem). Na chwilę obecną nie umożliwia dostępu do SPI/I2C/UART, ale trwają nad tym prace.

Web Control of Raspberry Pi GPIO
Poradnik krok po kroku z serwisu instructables. Rozwiązanie wykorzystuje PHP, serwery Apache i MySQL, ale komunikacja odbywa się z pośrednictwem MySQL - interfejs web tylko zapisuje dane do bazy, a operacje na sprzęcie wykonywane są przez skrypt powłoki systemowej, na podstawie danych pobieranych z bazy.

Web Pi Monitor & GPIO Controller (piStatus)
Bardzo rozbudowane narzędzie umożliwiające nie tylko obsługę GPIO, ale posiadające także konsolę i menadżer plików. Bazuje na skryptach CGI, bibliotece WiringPi i wymaga do działania serwera obsługującego CGI (np. Lighttpd).

RPi GPIO Web Control Panel

Przy projektowaniu aplikacji, którą ostatecznie ochrzciłem trochę przydługą nazwą RPi GPIO Web Control Panel, przyjąłem kilka założeń. Przede wszystkim, powinna ona umożliwiać zmianę i odczyt aktualnego stanu dla wybranych i wcześniej zadeklarowanych portów GPIO, nie będąc jednocześnie zbyt skomplikowaną. Ten problem, jak wynika z poprzedniego akapitu, można rozwiązać na wiele sposobów. Ja przedstawię natomiast taki, który jest dosyć nietypowy ze względu na wykorzystane narzędzia. Zaznaczam przy tym, że nie ma doskonałego, jedynie słusznego rozwiązania tego problemu i każde podejście ma swoje wady i zalety, również moje.

W przedstawionej aplikacji wykorzystałem zdecydowanie mało znany serwer web KoanLogic Srl KLone wraz z jedną z gotowych bibliotek do obsługi GIPIO w Raspberry Pi, napisanej w języku C. Rezultatem mojej pracy jest strona internetowa, którą możecie zobaczyć poniżej:

Jeżeli nie chcecie brnąć przez tekst, w którym opisuję jak powstała powyższa aplikacja, to na samym dole artykułu zamieściłem krótką instrukcję jej instalacji, konfiguracji i uruchomienia. Projekt RPi GPIO Web Control Panel jest również dostępny w GitHub. Zachęcam jednak do zapoznania się przynajmniej z akapitami dotyczącymi instalacji KLone i biblioteki bcm2835.

Starałem się przygotować ten tekst na zasadzie “krok po kroku”, ale nie jest to artykuł dla bardzo początkujących i zakładam, że Czytelnik:

  • zna język C i/lub C++ oraz HTML
  • posiada Raspberry Pi model B w rewizji 1 lub 2
  • zainstalował i uruchomił na nim jeden z gotowych systemów z rodziny Linux
  • potrafi obsługiwać dowolny edytor tekstowy w trybie tekstowym (nie dotyczy osób, które pracują na Raspberry Pi w środowisku graficznym)
  • zdaje sobie sprawę z tego, że sygnały GPIO w Raspberry Pi mają logikę 3,3 V, są wyprowadzone bezpośrednio z procesora i nieumiejętne podłączenie do nich zewnętrznych elementów może skończyć się w najlepszym wypadku uszkodzeniem GPIO, a w najgorszym całego Raspberry Pi
  • podłączył Raspberry Pi do sieci z dostępem do Internetu
  • ma ochotę poznać nowe narzędzie - serwer KLone

Poniższy tekst i pierwszą wersję mojej aplikacji opracowałem wykorzystując:

Serwer KoanLogic Srl KLone

Pierwszy raz z serwerem KLone zetknąłem się przy okazji mojej dyplomowej pracy magisterskiej. Poszukiwałem wtedy rozwiązania, które dałoby się uruchomić w systemie QNX Neutrino 6.5 i umożliwiało wykorzystanie bibliotek systemowych. W dodatku, udostępniony w systemie QNX, gotowy serwer o nazwie Slinger okazał się zbyt ograniczony (nie obsługiwał na przykład plików typu .css i .js) i posiadał sporo błędów. Na moje ówczesne, niewielkie potrzeby mógłbym w gruncie rzeczy samemu napisać prosty serwer www, ale ostatecznie trafiłem na KLone i wykorzystałem gotowe rozwiązanie.

Każdy serwer to, w dużym uproszczeniu, zwykła aplikacja nasłuchująca na odpowiednim porcie TCP, odbierająca zapytania przesyłane z przeglądarki, a na koniec generująca i odsyłająca odpowiedź. Jeżeli chcielibyście zgłębić temat gniazd sieciowych i pisania aplikacji sieciowych w systemach Unix/Linux, to szczerze polecam doskonałą książkę pt. “UNIX programowanie usług sieciowych” (W. Richard Stevens, WNT, Warszawa 2008, 2 wydanie).

Wracając do tematu, KLone to oprogramowaniem wieloplatformowe, od wersji 3.1.0 udostępnione na zasadach licencji BSD, działające między innymi w systemach operacyjnych: QNX, OpenWRT, VxWorks, FreeBSD, NetBSD, Linux, Windows i innych, w tym na wielu platformach sprzętowych: x86/x64, ARM, MIPS, PowerPC, SH, Cris. Jest to w pełni samodzielne rozwiązanie, zawierające w sobie serwer www i SDK, umożliwiające tworzenie statycznych oraz dynamicznych stron www, głównie w językach C/C++. KLone, ze względu na swoje duże możliwości, przy zachowaniu niewielkiego rozmiaru i zapotrzebowania na zasoby, dedykowany jest rozwiązaniom typu embedded (systemy wbudowane), ale nic nie stoi na przeszkodzie, żeby wykorzystać go także w pełnowymiarowych systemach. Do zalet KLone można zaliczyć między innymi:

  • obsługę sesji i cookies, wsparcie szyfrowania SSL
  • możliwość pracy w protokołach IPv4 i IPv6
  • zestaw funkcji API operujących na danych przesyłanych z i do przeglądarki (formularze, przesyłanie plików)
  • różne tryby pracy (iteracyjny, współbieżny)
  • wysoką wydajność i niskie zapotrzebowanie na zasoby (pamięć RAM, dysk)

Podejście do tworzenia aplikacji dla tego serwera jest trochę inne niż w przypadku PHP. Przede wszystkim, stworzone przez nas strony dynamiczne są wraz z aplikacją serwera kompilowane i łączone w jeden, wykonywalny plik - włącznie ze wszystkimi plikami grafik, stylów itd. Pociąga to za sobą jedną, istotną wadę - każda modyfikacja naszej aplikacji www wymaga ponownego skompilowania całego środowiska KLone.

Ponadto, trzeba trochę przestawić się w myśleniu i podejściu do budowania www, ponieważ strony dynamiczne tworzy się tutaj w języku C i/lub C++ (istnieje możliwość wykorzystania PHP oraz CGI, ale jeszcze tego nie praktykowałem), ewentualnie wykorzystując przy tym dostępne API lub inne biblioteki zewnętrzne i systemowe. Kod stron dynamicznych umieszczamy bezpośrednio w pliku z kodem HTML, wykorzystując do tego dwa rodzaje specjalnych znaczników.

Na pierwszy rzut oka może się to wszystko wydać bardzo skomplikowane, ale wystarczy przejrzeć krótki poradnik dla początkujących dostępny na stronie internetowej serwera KLone, żeby szybko przekonać się jak to ugryźć.

KLone wykorzystuje autorską bibliotekę LibU oraz framework MaKL do budowania projektów C/C++, ale ich omówienie znacznie przekracza zakres tego artykułu, dlatego nie będę zbytnio wnikać w szczegóły. Zainteresowani znajdą o tym informacje na stronie KoanLogic Srl.

Instalacja i uruchomienie serwera KoanLogic Srl KLone w Raspbian “wheezy”

Na początek przygotujemy sobie w katalogu domowym /home/pi miejsce na pliki serwera, pobierzemy przygotowany przeze mnie plik Makefile i skompilujemy KLone z domyślną, testową stroną internetową.

cd ~
mkdir klone
cd klone
wget http://www.tech-blog.pl/wordpress/wp-content/uploads/2012/11/klone_makefile_310 -O Makefile
make

Po kilku minutach i kilku ekranach z informacjami dotyczącymi kompilacji powinniście otrzymać komunikat oznaczający sukces:

KLone daemon (kloned) is ready to be started.
 
You can modify web pages or configuration (kloned.conf) editing files
under /home/pi/klone/webapp directory.
 
Run 'make' afterwards to rebuild the daemon.

Żeby potwierdzić działanie serwera uruchamiamy jego daemona w trybie foreground:

./kloned -F

I wywołujemy w przeglądarce adres IP na porcie 8080 naszego Raspberry Pi. Rezultatem będzie prosta strona demonstracyjna, informująca o tym, że serwer KLone działa poprawnie:

Kombinacją CTRL+C przerywamy działanie serwera i przechodzimy do instalacji biblioteki umożliwiającą obsługę GPIO z programów w języku C/C++.

Instalacja i uruchomienie biblioteki bcm2835 - obsługa GPIO

Na chwilę obecną dostępne są dwie biblioteki w języku C do obsługi GPIO w Raspberry Pibcm2835 oraz WiringPi. Ja wykorzystałem akurat tę pierwszą, ale nic nie stoi na przeszkodzie żeby wybrać drugą lub napisać swoją własną (tutaj znajdziecie dodatkowe informacje).

W celu pobrania i zainstalowania biblioteki bcm2835 należy wykonać w katalogu domowym kolejno poniższe komendy:

wget http://www.open.com.au/mikem/bcm2835/bcm2835-1.13.tar.gz
tar -zxvf bcm2835-1.13.tar.gz
cd bcm2835-1.13
./configure
make
sudo make check
sudo make install

W celu przetestowania działania biblioteki możecie skompilować i uruchomić jeden z przykładowych programów, np. blink.c, który w 0,5-sekundowych odstępach zmienia stan na wybranej nóżce GPIO. W tym celu, z poziomu katalogu z biblioteką, wykonujemy:

cd examples/blink/
gcc -o blink -lrt blink.c -lbcm2835
sudo ./blink

Dwa dodatkowe parametry (”-lrt” i “-lbcm2835″) informują linker, żeby użył biblioteki librt oraz libbcm2835 przy linkowaniu. Przed kompilacją powinniście sprawdzić w kodzie jaki numer GPIO jest ustawiony (w wersji 1.13 jest to RPI_GPIO_P1_11 czyli pin numer 11 w gnieździe P1) i podłączyć do niego np. diodę LED z rezystorem, żeby zaobserwować działanie programu. Wynik kompilacji należy uruchomić z uprawnieniami root, w przeciwnym wypadku nie uda się zainicjować biblioteki, ze względu na brak uprawnień dostępu do /dev/mem.

Przed dalszymi krokami warto również zapoznać się z nazewnictwem numeracji pinów w tej bibliotece, ponieważ funkcje przyjmują jako parametr numer GPIO zgodny z dokumentacją układu, a nie numeracją zastosowaną w gniazdach Raspberry Pi! Na szczęście autor przygotował w plikach nagłówkowych nazwy dla wyprowadzeń, które łatwo rozszyfrować, np. RPI_V2_GPIO_P1_03 oznacza pin numer 3 w listwie P1, w 2 rewizji Raspberry Pi.

Dokładne informacje o numeracji znajdziecie na tej stronie (sekcja RPiGPIOPin).

Przykładowa strona dynamiczna na serwerze KLone

Wyznaję zasadę, że żeby nauczyć się czegoś nowego, to najlepiej uczynić to na podstawie praktycznego przykładu. Dlatego zanim przejdę do docelowej aplikacji do sterowania GPIO w Raspberry Pi z poziomu www, pokażę Wam jak przygotować prostą stronę dynamiczną. Wyświetlane będą na niej takie informacje jak adres IP odwiedzającego, serwera, aktualna data i czas na serwerze oraz parametr przekazany w GET. Czytelnicy, którzy zapoznali się ze wspomnianym kilka akapitów wcześniej poradnikiem dla początkujących ze strony KLone mogą pominąć ten punkt i przejść dalej.

Jeżeli przyjrzeliście się plikowi Makefile, to na pewno znaleźliście tam deklarację WEBAPP_DIR wskazującą na katalog webapp. To właśnie w nim domyślnie znajdują się pliki wszystkich witryn oraz plik konfiguracyjny serwera, o nazwie kloned.conf, który znajdziecie w podkatalogu webapp/etc. Domyślnie zawiera on deklarację jednej strony, umieszczonej w katalogu webapp/www, którą serwer powinien dostarczać przy pomocy protokołu http, na porcie 8080 dowolnego adresu IPv4. Na nasze potrzeby zmienimy tylko numer portu na standardowy dla usługi http, czyli 80 i ustawimy logowanie żądań i informacji z serwera w katalogu /tmp. Po tym zabiegu plik konfiguracyjny powinien wyglądać tak jak poniżej, a daemona kloned należy od teraz uruchamiać z uprawnieniami root (ze względu na dowiązanie do portu 80).

Po więcej informacji dotyczących konfiguracji serwera KLone odsyłam np. na stronę Ubuntu manuals.

server_list app_http
allow_root yes
 
log
{
	type file
	file.basename /tmp/kloned_log
}
 
app_http
{
	type http
 
	addr tcp4://*:80
	dir_root /www
 
	access_log
	{
		type file
		file.basename /tmp/kloned_access_log
	}
}

Pliki domyślnej witryny znajdują się w katalogu webapp/www. Znajdziecie tam jeden plik ze stylami CSS oraz jeden z tajemniczym rozszerzeniem - index.kl1. W serwerze KLone do oznaczania plików zawierających treść dynamiczną służą różne rozszerzenia: klone/kl1 (pliki z kodem w języku C) oraz klx (kod w języku C++). Usuwamy oba pliki i tworzymy nowy, o nazwie index.kl1, z zawartością jak poniżej (łączenie kodu HTML z C jest nietypowe i niestety automatyczne kolorowanie składni nie radzi sobie z tym najlepiej):

<!DOCTYPE html>
<%!
#include <time.h>
#include <string.h>
%>
<html>
<head>
<title>Testowa strona dynamiczna na serwerze KLone</title>
<meta charset="UTF-8" />
<style type="text/css" media="all">body{font:14px/1.618 Arial,sans-serif;}#main{width:800px;margin:0 auto;}</style>
</head>
<body>
<%
time_t unix_seconds;
char buffer[32];
const char *get_arg;
 
// czas i data
time(&unix_seconds);
strftime(buffer, sizeof(buffer), "%X %d-%m-%Y", localtime(&unix_seconds));
 
// parametr z GET
get_arg = request_get_getarg(request, "test");
%>
<div id="main">
<p>Aktualny czas na serwerze: <strong><% io_printf(out, "%s", buffer); %></strong></p>
<p>Twoje połączenie z serwerem wykonane zostało z adresu IP/portu: <strong><% io_printf(out, "%s", request_get_peer_addr(request)); %></strong>
<br />Adres IP/port lokalny serwera: <strong><% io_printf(out, "%s", request_get_addr(request)); %></strong></p>
<% if(get_arg != NULL && strlen(get_arg) > 0){ %>
<p>Przekazany w GET parametr ("test"): <strong><% io_printf(out, "%s", get_arg); %></strong></p>
<% } else { %>
<p>Nie przekazano parametru lub przekazany parametr jest pusty</p>
<% } %>
</div>
</body>
</html>

Najważniejszy element, który powinniście poznać, to dwa rodzaje znaczników służących do osadzania kodu C/C++: <%!%> oraz <%%>. Pierwszy zestaw służy do wstawiania dyrektyw preprocesora, deklaracji zmiennych globalnych i funkcji. Z kolei znaczniki <%%> przeznaczone są do umieszczania pozostałego kodu C/C++. W pewnym sensie każdą stronę dynamiczną można traktować tak, jakby cały kod umieszczony pomiędzy wszystkimi znacznikami <% … %> znajdował się w ciele jakiejś dużej funkcji generującej wynikowy kod HTML, natomiast wszystko umieszczone pomiędzy <%! … %> znajdowało się poza nią. Ponadto, wszystko co znajdzie się w pliku na zewnątrz tych znaczników zostanie przekazane w niezmienionej formie do przeglądarki, podobnie jak ma to miejsce przy umieszczaniu kodu PHP wewnątrz kodu HTML (przy pomocy <?php?>).

W powyższym przykładzie wykorzystałem tylko dwa pliki nagłówkowe (time.h oraz string.h), które dołączyłem przy pomocy dyrektyw #include umieszczonych wewnątrz znaczników <%! … %> (tuż pod nagłówkiem <!DOCTYPE html>, który nie bez powodu znalazł się na samym początku pliku).

Istotne są również trzy funkcje dostępne w KLone API, z których często będziecie korzystać:

  • request_get_getarg
  • request_get_postarg
  • io_printf

Przy pomocy pierwszych dwóch z nich możemy odczytać wartość parametru przekazanego z przeglądarki metodą, odpowiednio GET lub POST. Ostatnia służy natomiast do przekazywania łańcucha znakowego do bufora, który na koniec zostanie zwrócony w odpowiedzi do przeglądarki. Funkcja io_printf() ma podobną składnię jak printf(), ale posiada dodatkowy parametr wskazujący bufor, do którego przekazujemy dane (w powyższym przykładzie jest to out). Niektóre z funkcji dostępnych w API operują na dostępnych globalnie zmiennych, takich jak np. request, response oraz wspomniany out. Po więcej informacji na ten temat odsyłam do KLone API.

Po uruchomieniu daemona KLone i wywołaniu IP “maliny” z przykładowym parametrem GET, w przeglądarce powinniście ujrzeć taką stronę:

Jeżeli udało Wam się bez problemów uruchomić stronę testową i ewentualnie dokonać w niej jakichś zmian (przypominam o potrzebie kompilacji KLone po każdej zmianie!), to możemy przejść do właściwego projektu.

RPi GPIO Web Control Panel od kuchni

Opisywany projekt panelu www działa na bardzo często stosowanej, choć nieefektywnej na przykład pod względem ilości przesyłanych danych, zasadzie okresowego odpytywania serwera o aktualne dane (ang. polling). Pracująca w przeglądarce aplikacja, a dokładniej ta jej część, która została napisana w języku JavaScript, pobiera okresowo, przy pomocy asynchronicznego żądania, plik generujący dane w formacie JSON z aktualnymi stanami na zdefiniowanych portach GPIO. Na ich podstawie zmieniony zostaje wygląd strony prezentującej aktualny stan systemu. Drugi plik, na podstawie przekazanych parametrów w GET, zmienia stan na wybranym porcie GPIO i w odpowiedzi przesyła aktualny stan tego portu, również w formacie JSON.

Wspomniane 3 pliki to:

  • index.kl1 - główny plik aplikacji, odpowiadający za wygenerowanie listy zdefiniowanych portów GPIO (wraz z ich opisem i aktualnym stanem) i kodu HTML całej strony.
  • read_all.kl1 - generuje dane w formacie JSON z listą zdefiniowanych portów GPIO i ich aktualny stan (wysoki/niski).
  • set_out.kl1 - na podstawie parametrów z GET ustawia żądany stan na wybranym porcie GPIO i w odpowiedzi zwraca jego aktualny stan (format JSON).

Poza dynamicznymi plikami serwera KLone, w skład aplikacji wchodzą również pliki js/jquery-1.8.3.min.js oraz js/engine.js, zawierające odpowiednio bibliotekę jQuery i kod w JavaScript odpowiedzialny za manipulację obiektami na stronie oraz wykonywaniem asynchronicznych żądań. Dodatkowo, w katalogu css znalazły się jeszcze dwa pliki (reset.css i main.css), zawierające deklarację stylów dla strony.

Przyjęte założenia dotyczące działania aplikacji wymagały rozwiązania dwóch problemów. Pierwszy dotyczył sposobu deklaracji wykorzystywanych portów GPIO - wszystkie trzy pliki dynamiczne w praktyce działają niezależnie, ponieważ każde ich wywołanie w przeglądarce to nowy wątek procesu serwera, dlatego powinny operować na tych samych danych. Drugim problemem okazało się przygotowanie systemu do pracy, czyli ustawienie dla wszystkich portów GPIO zadeklarowanych funkcji, tj. trybu pracy, rezystora podciągającego w trybie pracy jako wejście i domyślnego stanu w przypadku trybu pracy jako wyjście.

Pierwszy problem można by było rozwiązać przy pomocy dodatkowych plików, które przechowywałyby zapisane ustawienia. Jednak takie rozwiązanie wymagałoby napisania dodatkowych funkcji wczytujących, zapisujących i weryfikujących poprawność ustawień, co dodatkowo skomplikowałoby całość. Biorąc pod uwagę chyba słuszne założenie o tym, że liczba i tryb pracy GPIO nie będą zmieniane zbyt często, zdecydowałem się na utworzenie prostej, statycznej biblioteki. Zadeklarowałem w niej dwie różne struktury opisujące pojedynczy port GPIO w trybie wejścia (RpiGpioWebCpIn) i wyjścia (RpiGpioWebCpOut) oraz dostępne globalnie tablice, które zawierają listę wykorzystywanych przez aplikację portów (odpowiednio gpio_insgpio_outs).

Kwestia dotycząca inicjalizacji systemu do pracy okazała się bardziej skomplikowana. Początkowo myślałem nad tym, żeby za konfigurację portów GPIO na starcie odpowiadał dodatkowy program, uruchamiany przed serwerem KLone i wykorzystujący do tego wspomnianą bibliotekę zawierającą tablice deklaracji portów. Drugim pomysłem było wykorzystanie sesji i ciasteczek do sprawdzania czy system został już skonfigurowany, ale to z kolei wymagałoby wywołania w przeglądarce którejś ze stron aplikacji przynajmniej raz po starcie serwera.

Ostateczne rozwiązanie okazało się najprostsze i wyjątkowo zgrabne - inicjalizację portów GPIO umieściłem w bibliotece, w funkcji server_init(), którą dołączyłem do zdarzenia uruchomienia serwera KLone przy pomocy mechanizmu hook dostępnego w API (zobacz przykład User provided hooks). Kod opisanej biblioteki zamieszczam poniżej.

Plik nagłówkowy rpigpiowebcp.h

// rpigpiowebcp.h
// Author: Piotr Dymacz (pepe2k@gmail.com)
 
#ifndef RPIGPIOWEBCP_H
#define RPIGPIOWEBCP_H
 
#include <stdio.h>
#include <klone/klone.h>
#include <bcm2835.h>
 
#define RPIGPIOWEBCP_VERSION "ver. 1.0"
 
/* struct definition for single GPIO in output mode */
typedef struct {
	RPiGPIOPin	number;
	const char*	name;
	uint8_t		default_val;
} RpiGpioWebCpOut;
 
/* struct definition for single GPIO in input mode */
typedef struct {
	RPiGPIOPin		number;
	const char*		name;
	bcm2835PUDControl	mode;
} RpiGpioWebCpIn;
 
/* GPIO definition arrays */
extern RpiGpioWebCpOut	gpio_outs[];
extern RpiGpioWebCpIn	gpio_ins[];
 
/* number of defined GPIOs */
extern int gpio_outs_num;
extern int gpio_ins_num;
 
int server_init( void );
 
#endif /* RPIGPIOWEBCP_H */

Plik źródłowy rpigpiowebcp.c

// rpigpiowebcp.c
// Author: Piotr Dymacz (pepe2k@gmail.com)
 
#include "rpigpiowebcp.h"
 
/*
called when KLone server starts
prepares all defined GPIOs (mode and pull up/down in input mode or default value in output mode)
*/
int server_init( void ){
	int i = 0;
 
	if( bcm2835_init() ){
 
		if( gpio_outs_num > 0 || gpio_ins_num > 0 ){
 
			// GPIOs in output mode
			for( i = 0; i < gpio_outs_num; i++ ){
 
				bcm2835_gpio_fsel( gpio_outs[i].number, BCM2835_GPIO_FSEL_OUTP );
 
				bcm2835_gpio_write( gpio_outs[i].number, gpio_outs[i].default_val );
 
			}
 
			// GPIOs in input mode
			for( i = 0; i < gpio_ins_num; i++ ){
 
				bcm2835_gpio_fsel( gpio_ins[i].number, BCM2835_GPIO_FSEL_INPT );
 
				bcm2835_gpio_set_pud( gpio_ins[i].number, gpio_ins[i].mode );
 
			}
 
		} else {
			printf( "Nie zdefiniowano żadnych portów GPIO.\n" );
			exit( EXIT_FAILURE );
		}
 
	} else {
		printf( "Wystąpił błąd przy inicjalizacji GPIO.\nSerwer musi być uruchomiony z uprawnieniami root.\n" );
		exit( EXIT_FAILURE );
	}
 
	return 0;
}
 
/* user provided hooks setup */
void hooks_setup( void ){
	hook_server_init( server_init );
}
 
// GPIOs in output mode definition
extern RpiGpioWebCpOut gpio_outs[] = {
	RPI_V2_GPIO_P1_11,	"Wyjście P1.11",		LOW,
	RPI_V2_GPIO_P1_12,	"Dioda LED (zielona)",		HIGH,
	RPI_V2_GPIO_P1_24,	"Dioda LED (biała)",		LOW,
	RPI_V2_GPIO_P1_26,	"Dioda LED (niebieska)",	LOW,
	RPI_V2_GPIO_P1_19,	"Wyjście P1.19",		LOW,
	RPI_V2_GPIO_P1_21,	"Wyjście P1.21",		LOW,
	RPI_V2_GPIO_P1_23,	"Wyjście P1.23",		LOW
};
 
// GPIOs in input mode definition
extern RpiGpioWebCpIn gpio_ins[] = {
	RPI_V2_GPIO_P1_13,	"Wejście P1.13",	BCM2835_GPIO_PUD_UP,
	RPI_V2_GPIO_P1_15,	"Wejście P1.15",	BCM2835_GPIO_PUD_DOWN
};
 
// count defined GPIOs
extern int gpio_outs_num = sizeof(gpio_outs) / sizeof(RpiGpioWebCpOut);
extern int gpio_ins_num	 = sizeof(gpio_ins)  / sizeof(RpiGpioWebCpIn);

Struktury opisujące pojedynczy port GPIO posiadają po trzy pola, w tym dwa takie same - number oraz name, do ustawienia numeru portu i własnej, czytelnej nazwy (przypominam o nietypowej numeracji w bibliotece bcm2835!). Dla portów zdefiniowanych jako wyjście struktura zawiera wartość domyślną (default_val), która zostanie ustawiona po starcie serwera, a w przypadku wejść pole mode do wyboru rezystora podciągającego. W powyższym kodzie znajduje się już kilka zadeklarowanych przeze mnie portów w trybie wyjścia i dwa w trybie wejścia.

Funkcja server_init() wywoływana jest na starcie daemona KLone i ma za zadanie ustawić wszystkie parametry zadeklarowanych portów GPIO, a w razie braku takiej deklaracji lub problemów z inicjalizacją biblioteki bcm2835, zakończyć pracę serwera. Dzięki takiemu rozwiązaniu, serwer można uruchamiać od razu na starcie systemu i mieć pewność, że zdefiniowane porty będą od uruchomienia Raspberry Pi pracowały w wybranym przez nas trybie.

Plik dynamiczny read_all.kl1 generuje listę aktualnych stanów na wszystkich zadeklarowanych portach GPIO, niezależnie od tego czy zostały ustawione w trybie wejścia, czy wyjścia - dla funkcji bcm2835_gpio_lev() z biblioteki bcm2835 nie ma to znaczenia.

Poniżej kod źródłowy pliku read_all.kl1 oraz przykładowa odpowiedź dla zdefiniowanych powyżej portów (do formatowania danych w formacie JSON w przeglądarce Firefox używam dodatku o nazwie JSONView).

<%!
	#include <bcm2835.h>
	#include <rpigpiowebcp.h>
%><%
	int i = 0;
 
	// resposne type: application/json
	response_set_content_type( response, "application/json; charset=utf-8" );
 
	if ( bcm2835_init() ){
 
		io_printf( out, "{\"error\": 0, \"gpio\": [" );
 
		// get status for all defined GPIOs
		// bcm2835_gpio_lev() works in both modes (input/output)
 
		// GPIOs in output mode
		for( i = 0; i < gpio_outs_num; i++ ){
 
			io_printf( out, "{" );
 
			io_printf( out, "\"num\": %d,", gpio_outs[i].number );
			io_printf( out, "\"val\": %d", bcm2835_gpio_lev(gpio_outs[i].number) );
 
			io_printf( out, "}" );
 
			if( i < gpio_outs_num - 1 ){
				io_printf( out, "," );
			}
		}
 
		if( gpio_outs_num > 0 && gpio_ins_num > 0 ){
			io_printf( out, "," );
		}
 
		// GPIOs in input mode
		for( i = 0; i < gpio_ins_num; i++ ){
 
			io_printf( out, "{" );
 
			io_printf( out, "\"num\": %d,", gpio_ins[i].number );
			io_printf( out, "\"val\": %d", bcm2835_gpio_lev(gpio_ins[i].number) );
 
			io_printf( out, "}" );
 
			if( i < gpio_ins_num - 1 ){
				io_printf( out, "," );
			}
		}
 
		io_printf( out, "]}" );
 
		bcm2835_close();
 
	} else {
		io_printf( out, "{\"error\": 1, \"error_desc\": \"Inicjalizacja biblioteki bcm2835 nie powiodła się.\"}");
	}
%>

W odróżnieniu od read_all.kl1, plik set_out.kl1 oczekuje dwóch parametrów w GET - numeru portu (pin) i żądanej wartości stanu (val). Z tego względu, kod zawiera kilka etapów weryfikacji przekazanych danych, w myśl zasady filtrowania danych wejściowych. W odpowiedzi przekazuje aktualny odczyt stanu portu, którego zmiany zażądano.

Poniżej kod źródłowy set_out.kl1.

<%!
	#include <stdlib.h>
	#include <string.h>
	#include <bcm2835.h>
	#include <rpigpiowebcp.h>
%><%
	int i = 0;
	int found = 0;
 
	int pin_num;
	int pin_val;
 
	// get GET params
	const char* get_pin_num = request_get_getarg( request, "pin" );
	const char* get_pin_val = request_get_getarg( request, "val" );
 
	// resposne type: application/json
	response_set_content_type( response, "application/json; charset=utf-8" );
 
	// check GET params
	if( get_pin_num != NULL && get_pin_val != NULL ){
 
		// convert them to int
		pin_num = atoi( get_pin_num );
		pin_val = atoi( get_pin_val );
 
		if( strlen( get_pin_val ) == 1 && ( get_pin_val[0] == '0' || get_pin_val[0] == '1' ) ){
 
			if ( bcm2835_init() ){
 
				if( gpio_outs_num > 0 ){
 
					for( i = 0; i < gpio_outs_num; i++ ){
 
						if( gpio_outs[i].number == pin_num ){
							found = 1;
							break;
						}
 
					}
 
					if( found ){
 
						// set...
						bcm2835_gpio_write( pin_num, pin_val );
 
						// ...and return status
						io_printf( out, "{\"error\": 0, \"gpio\": [{\"num\": %d, \"val\": %d}]}", pin_num, bcm2835_gpio_lev(pin_num) );
 
					} else {
						io_printf( out, "{\"error\": 1, \"error_desc\": \"Wybrany GPIO nie znajduje się w tablicy portów zdefiniowanych jako wyjście.\"}" );
					}
 
				} else {
					io_printf( out, "{\"error\": 1, \"error_desc\": \"Nie zdefiniowano żadnych portów GPIO w trybie wyjścia.\"}" );
				}
 
				bcm2835_close();
 
			} else {
				io_printf( out, "{\"error\": 1, \"error_desc\": \"Inicjalizacja biblioteki bcm2835 nie powiodła się.\"}");
			}
 
		} else {
			io_printf( out, "{\"error\": 1, \"error_desc\": \"Nieprawidłowa wartość parametru val.\"}");
		}
 
	} else {
		io_printf( out, "{\"error\": 1, \"error_desc\": \"Nieprawidłowe lub brak parametrów w GET.\"}");
	}
%>

W głównym pliku aplikacji, tj. index.kl1, generowany jest kod HTML strony www panelu z kilkoma dodatkowymi informacjami, takimi jak lokalny adres IP serwera i adres IP przeglądającego. W celu uniknięcia po załadowaniu strony niepotrzebnego oczekiwania na pobranie informacji o aktualnym stanie GPIO, w pętlach generujących listę portów wczytywany jest również ich stan. Kod źródłowy pliku index.kl1 poniżej.

<!DOCTYPE html>
<%!
	#include <stdlib.h>
	#include <string.h>
	#include <bcm2835.h>
	#include <rpigpiowebcp.h>
%><%
	int i = 0;
%><html lang="pl">
<head>
	<title>RPi GPIO Web Control Panel <% io_printf( out, "%s", RPIGPIOWEBCP_VERSION ); %> - <% io_printf( out, "%s", request_get_addr(request) ); %></title>
	<meta charset="UTF-8" />
	<meta name="author" content="Piotr Dymacz, www.tech-blog.pl" />
	<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Open+Sans&subset=latin,latin-ext" />
	<link rel="stylesheet" href="css/reset.css" />
	<link rel="stylesheet" href="css/main.css" />
</head>
<body>
	<section id="wrapper">
		<header>
			<h1 class="floatl">RPi GPIO Web Control Panel <% io_printf( out, "%s", RPIGPIOWEBCP_VERSION ); %></h1>
			<h1 class="floatr"><% io_printf( out, "%s", request_get_addr(request) ); %></h1>
			<hr />
		</header>
<% if ( bcm2835_init() ){ %>
<% if( gpio_ins_num > 0 ) { %>
		<section id="inputs">
			<div class="list">
				<h1>Wejścia (<% io_printf( out, "%d", gpio_ins_num ); %>)</h1>
<% for( i = 0; i < gpio_ins_num; i++ ){ %>
				<div class="gpio <% if( bcm2835_gpio_lev(gpio_ins[i].number) == HIGH ){ %>true<% } else { %>false<% } %>" id="gpio_<% io_printf( out, "%d", gpio_ins[i].number ); %>">
					<div class="slider">
						<div class="inset">
							<div class="label">ON</div>
							<div class="label">OFF</div>
							<div class="control"></div>
							<div class="clear"></div>
						</div>
					</div>
					<div class="name"><% if( strlen(gpio_ins[i].name) ){ io_printf( out, "%s", gpio_ins[i].name ); } else { %>GPIO numer <% io_printf( out, "%d", gpio_ins[i].number ); } %></div>
					<div class="clear"></div>
				</div>
<% } %>
			</div>
		</section>
<% } %>
<% if( gpio_outs_num > 0 ) { %>
		<section id="outputs">
			<div class="list">
				<h1>Wyjścia (<% io_printf( out, "%d", gpio_outs_num ); %>)</h1>
<% for( i = 0; i < gpio_outs_num; i++ ){ %>
				<div class="gpio <% if( bcm2835_gpio_lev(gpio_outs[i].number) == HIGH ){ %>true<% } else { %>false<% } %>" id="gpio_<% io_printf( out, "%d", gpio_outs[i].number ); %>">
					<div class="slider">
						<div class="inset">
							<div class="label">ON</div>
							<div class="label">OFF</div>
							<div class="control"></div>
							<div class="clear"></div>
						</div>
					</div>
					<div class="name"><% if( strlen(gpio_outs[i].name) ){ io_printf( out, "%s", gpio_outs[i].name ); } else { %>GPIO numer <% io_printf( out, "%d", gpio_outs[i].number ); } %></div>
					<div class="clear"></div>
				</div>
<% } %>
			</div>
		</section>
<% } %>
		<hr />
<% bcm2835_close(); %>
<% } %>
		<section id="notifications">
			<h2>Ostatnie zdarzenia</h2>
			<ul></ul>
		</section>
	</section>
 
	<footer>
		<div class="floatl">Lokalny IP: <strong><% io_printf( out, "%s", request_get_addr(request) ); %></strong> / połączenie z IP: <strong><% io_printf(out, "%s", request_get_peer_addr(request)); %></strong><br />Sprawdź: <a href="http://www.tech-blog.pl" title="Strona internetowa autora: www.tech-blog.pl" target="_blank">tech-blog</a> / <a href="http://www.raspberrypi.org/" title="Strona internetowa fundacji Raspberry Pi" target="_blank">Raspberry Pi</a> / <a href="http://www.koanlogic.com/klone/" title="Serwer KLone" target="_blank">serwer KLone</a></div>
		<div class="floatr"><div class="indicator"><span></span></div></div>
		<div class="clear"></div>
	</footer>
 
	<script src="js/jquery-1.8.3.min.js" type="text/javascript"></script>
	<script src="js/engine.js" type="text/javascript"></script>
</body>
</html>

Ostatnim, ale bardzo istotnym plikiem aplikacji jest js/engine.js, w którym zawarta jest cała funkcjonalność spajająca stronę główną i pliki generujące dane. Znajduje się w nim kilka funkcji, które odpowiadają między innymi za pobranie aktualnych stanów na wszystkich zdefiniowanych portach (GetData()), wygenerowanie żądania zmiany stanu na wybranym porcie (SetData()) oraz zmianę wyglądu panelu po odebraniu danych (UpdateValues() i UpdateGpio()).

Dodatkową funkcjonalnością jest prosty system powiadomień, który generuje listę ostatnich pięciu zdarzeń - zmian stanów na portach wejściowych i wyjściowych oraz w przypadku wystąpienia błędu (na przykład braku połączenia z serwerem). Funkcjonalność dotycząca powiadomień znajduje się w funkcjach AddNotification()RemoveLastNotification().

Kod źródłowy pliku engine.js poniżej.

var Interval = 1000;
var MaxNotifications = 5;
var WaitingForResponse = false;
 
Date.prototype.CustomDateTimeString = function(){
	var ho = this.getHours();
	if(ho < 10) ho = "0" + ho;
 
	var mi = this.getMinutes();
	if(mi < 10) mi = "0" + mi;
 
	var se = this.getSeconds();
	if(se < 10) se = "0" + se;
 
	var da = this.getDate();
	if(da < 10) da = "0" + da;
 
	var mo = this.getMonth()+1;
	if(mo < 10) mo = "0" + mo;
 
	var ms = this.getMilliseconds();
	if(ms < 10) {
		ms = "000" + ms;
	} else {
		if(ms < 100) {
			ms = "00" + ms;
		} else {
			if (ms != 1000) ms = "0" + ms;
		}
	}
 
	return da + "-" + mo + "-" + this.getFullYear() + " " + ho + ":" + mi + ":" + se + ":" + ms;
}
 
function RemoveLastNotification(){
	if($("#notifications li").length > MaxNotifications){
		$("#notifications li:gt(" + (MaxNotifications-1) + ")").fadeOut(250, function(){
			$(this).remove();
		});
	}
}
 
function AddNotification(text, success){
	var now = new Date();
 
	if(success == true){
		$("#notifications ul").prepend($('<li class="success"><span><strong>' + now.CustomDateTimeString() + '</strong></span>' + text + '</li>').fadeIn(250, function(){
			RemoveLastNotification();
		}));
	} else {
		$("#notifications ul").prepend($('<li class="error"><span><strong>' + now.CustomDateTimeString() + '</strong></span>' + text + '</li>').fadeIn(250, function(){
			RemoveLastNotification();
		}));
	}
}
 
function UpdateGpio(num, val){
	var gpio_id = "#gpio_" + num;
 
	if(val == 1){
		if($(gpio_id).hasClass("false")){
 
			$(gpio_id + " div.control").animate({
				width: "75%"
			}, 100, function(){
				$(gpio_id).removeClass("false");
				$(gpio_id).addClass("true");
				$(gpio_id + " div.control").animate({width: "50%"}, 75, function(){
					AddNotification("Zmiana stanu portu <strong>" + $(gpio_id + " div.name").text() + "</strong> na <strong>HIGH</strong>.", true);
				});
			});
		}
	} else {
		if($(gpio_id).hasClass("true")){
 
			$(gpio_id + " div.control").animate({
				width: "75%"
			}, 100, function(){
				$(gpio_id).removeClass("true");
				$(gpio_id).addClass("false");
				$(gpio_id + " div.control").animate({width: "50%"}, 75, function(){
					AddNotification("Zmiana stanu portu <strong>" + $(gpio_id + " div.name").text() + "</strong> na <strong>LOW</strong>.", true);
				});
			});
		}
	}
}
 
function UpdateValues(gpios, on_finish){
	for(i = 0; i < gpios.length; i++){
		UpdateGpio(gpios[i].num, gpios[i].val);
	}
 
	if(on_finish && typeof(on_finish) === "function"){
		on_finish();
	}
}
 
function GetData(){
	if(!WaitingForResponse){
		$.getJSON("read_all.kl1",{},
			function(data){
				if(data.error == 0){
					UpdateValues(data.gpio);
					PollServerAfterInterval();
				} else {
					AddNotification(data.error_desc, false);
				}
			});
	} else {
		PollServerAfterInterval();
	}
}
 
function SetData(n, v){
	$.getJSON("set_out.kl1",{pin: n, val: v},
		function(data){
			if(data.error == 0){
				UpdateValues(data.gpio, function(){
					WaitingForResponse = false;
				});
			} else {
				AddNotification(data.error_desc, false);
			}
		});
}
 
function PollServerAfterInterval(){
	var animate_to = "90%";
 
	if($(".indicator > span").css("marginLeft") != "0px"){
		animate_to = "0%";
	}
 
	$(".indicator > span").animate({
		marginLeft: animate_to
	}, Interval, function(){
		GetData();
	});
}
 
$(document).ready(function(){
 
	// AJAX error handler
	$.ajaxSetup({"error":function(XMLHttpRequest,textStatus,errorThrown){
		AddNotification("Wystąpił błąd w połączeniu z serwerem - spróbuj oświeżyć stronę.", false);
	}});
 
	// start polling only if any of GPIOs is defined
	if($("div.gpio").length > 0){
		AddNotification("Rozpoczęcie pracy.", true);
		PollServerAfterInterval();
	} else {
		AddNotification("Nie zdefiniowano żadnych GPIO w pliku nagłówkowym!", false);
	}
 
	// output value change
	$("#outputs div.gpio").click(function(){
		var temp = $(this).attr("id").split("_");
 
		if(!WaitingForResponse){
			WaitingForResponse = true;
			if($(this).hasClass("false")){
				SetData(temp[1], 1);
			} else {
				SetData(temp[1], 0);
			}
		}
	});
 
});

Podsumowanie

Jeżeli przebrnęliście przez cały powyższy tekst, to na pewno zadajecie sobie pytanie o to, co tak naprawdę lepszego, w porównaniu do innych, oferuje taka propozycja rozwiązania postawionego w tytule problemu? W kilku zdaniach postaram się na nie odpowiedzieć.

Przede wszystkim, zaproponowane rozwiązanie jest kompaktowe i w pewnym sensie uniwersalne. Serwer KLone sam w sobie oferuje całą funkcjonalność niezbędną do realizacji zadania. W gruncie rzeczy, wykorzystanie biblioteki bcm2835 było podyktowane wyłącznie moim lenistwem, bo przecież mając możliwość napisania dowolnego kodu w języku C/C++ wewnątrz aplikacji web, nic innego nie stało na przeszkodzie żeby funkcjonalność dotyczącą operacji na GPIO stworzyć samemu.

Ponadto, w praktyce, poza ograniczeniem czasowym o którym poniżej, nic więcej nie ogranicza funkcjonalności, które chcielibyśmy zawrzeć w naszej aplikacji wykorzystującej serwer KLone. Wszystko to, co jest możliwe do zrealizowania w języku C/C++ na wybranej platformie sprzętowej, możemy umieścić wewnątrz strony internetowej. Może to być program wysyłający i pobierający dane z portu szeregowego, manipulujący plikami na dysku, interfejsami SPI lub I2C lub w końcu, operujący na rejestrach procesora, nawet wykorzystując wstawki w języku Asembler. Oczywiście, to tylko kilka z brzegu przykładowych możliwości, bo biorąc pod uwagę mnogość dostępnych bibliotek w języku C/C++, zakres tego, co jesteśmy w stanie zrealizować w oparciu o KLone jest ogromny.

W tym wszystkim jest jednak istotna kwestia, której trzeba być świadomym. Aplikacja napisana dla serwera KLone jest w pewnym sensie obudowana z zewnątrz przez to środowisko i nasz kod jest wykonywany w ramach kodu serwera. Stąd właśnie ograniczenie czasowe, o którym wspomniałem. W ramach eksperymentu możecie napisać i wywołać prostą stronę dynamiczną, w której jedyną instrukcją w języku C będzie na przykład sleep(10). Rezultat będzie zgodny z oczekiwaniem - Wasza przeglądarka będzie musiała poczekać aż 10 sekund na odpowiedź z serwera:

Napisałem na początku artykułu, że nie ma rozwiązań idealnych i to które przedstawiłem na pewno też takie nie jest. Pomijając funkcjonalności, których sam nie dodałem, takich jak autoryzacja dostępu do panelu, możliwość zmiany konfiguracji w trakcie pracy itd., to wady posiada również wybrane narzędzie - serwer KLone. Dla części z Was największą z nich, możliwe, że nie do przeskoczenia, będzie sposób projektowania aplikacji dla tego serwera - po prostu trzeba się tego od nowa nauczyć.

Ponadto, muszę przyznać, że ten projekt nie jest niestety zbyt dobrze udokumentowany i szczególnie początkujący będą mieli na starcie utrudnione zadanie. Tak naprawdę, pomijając trochę nieaktualny opis API i równie nieaktualne, dostępne w GitHub Wiki, najwięcej informacji i odpowiedzi znaleźć można w archiwum listy dyskusyjnej użytkowników KLone, a w przypadku najnowszej wersji na liście dyskusyjnej KLone w portalu SourceForge.

Co więcej, KLone nie jest młodym projektem, bo jego początki sięgają roku 2005. Na szczęście, od niedawna serwer udostępniany jest w ramach otwartej licencji, nawet dla zastosowań komercyjnych, co moim zdaniem może być realną szansą na jego dalszy rozwój, może już nie przez pierwotnych autorów.

Plany na przyszłość

Początkowo w ogóle nie zakładałem, że upublicznię ten projekt i napiszę o nim tak obszerny artykuł. Tak naprawdę potrzebowałem bardzo prostego panelu web do sterowania portami GPIO w Raspberry Pi, ale żadne z rozwiązań znalezionych w sieci nie przypadło mi do gustu - były albo zbyt rozbudowane, albo wykorzystywały narzędzia, których nie znałem. Mniej więcej tak rozpoczęły się prace nad tym, o czym teraz czytacie. Z biegiem czasu projekt nabierał lepszych kształtów i za na mową kilku osób, które miały okazję oglądać go w jeszcze bardzo początkowej fazie, zdecydowałem się na doprowadzenie go do funkcjonalnej wersji, która nie tylko działa, ale i prezentuje się znośnie. I wbrew pozorom, nie trwało to jakoś nadzwyczajnie długo - na wersję oznaczoną numerem 1.0 poświęciłem z górką kilka wieczorów, z czego najwięcej czasu zabrało mi przekopywanie się przez dokumentację i kod źródłowy KLone.

Jestem świadomy wielu braków w obecnej wersji, ale traktuję ją jako pewnego rodzaju punkt wyjścia, z którego każdy projekt musi zacząć. Jeżeli RPi GPIO Web Control Panel spotka się z zainteresowaniem, to na pewno będą go rozwijał. Nie widzę też przeszkód, żeby rozwijały go i inne osoby, w takiej lub innej formie. Podejrzewam, że wielu z moich Czytelników dotąd nie słyszało o KLone, a po przeczytaniu tego artykułu może się nim zainteresują i opracują własne rozwiązania wykorzystujące ten serwer.

Poniżej zamieszam listę funkcjonalności, które moim zdaniem powinny znaleźć się w ewentualnych, kolejnych wersjach panelu.

  • Obsługa wielu języków.
  • Autoryzowany dostęp z panelem logowania (sesje).
  • Zmiana trybu pracy z okresowego odpytywania serwera (ang. polling) na tryb “wypychania” danych (ang. push).
  • Możliwość zmiany wszystkich ustawień GPIO w trakcie pracy aplikacji.
  • Obsługa innych bibliotek do manipulowania GPIO w Raspberry Pi lub, najlepiej, opracowanie własnej.

Instrukcja instalacji i uruchomienia RPi GPIO Web Control Panel dla niecierpliwych

Jeżeli chcecie wyłącznie pobrać i uruchomić przedstawiony przeze mnie panel, bez zagłębiania się w szczegóły dotyczące implementacji, to wystarczy że wykonacie kilka opisanych poniżej kroków.

  1. Raspberry Pi, na którym ma zostać uruchomiony panel musi posiadać dostęp do Internetu, przynajmniej na czas pobierania plików.
  2. Pobierz, zainstaluj i sprawdź działanie biblioteki bcm2835.
  3. Przeczytaj i zapoznaj się z numeracją portów GPIO w bibliotece bcm2835.
  4. Pobierz archiwum z RPi GPIO Web Control Panel z GitHub, rozpakuj je.
  5. Zmień deklarację wykorzystywanych portów GPIO w pliku lib/rpigpiowebcp.c według swoich potrzeb.
  6. Zapoznaj się i ewentualnie zmień konfigurację serwera KLone w pliku webapp/etc/kloned.conf.
  7. W katalogu z panelem uruchom kompilację całego środowiska - komenda “make“.
  8. Jeżeli kompilacja się powiodła, uruchom daemona serwera KLone w trybie foreground przy pomocy komendy “sudo ./kloned -F“.
  9. Otwórz w przeglądarce adres IP swojego Raspberry Pi z uruchomionym serwerem KLone.
  10. Po zapoznaniu się z funkcjonalnością panelu, przerwij proces serwera (kombinacja klawiszy CTRL+C).
  11. Jeżeli chcesz uruchomić serwer na stałe, w normalnym trybie, wydaj komendę “sudo ./kloned“.
  12. W przypadku problemów lub pytań - umieść poniżej komentarz, postaram się pomóc.

» 1 komentarz «

tech-blog | Nowy artykuł: Obsługa GPIO w Raspberry Pi przez stronę www - podejście alternatywne
Dodany: Niedziela, 2 grudnia 2012 o godzinie: 03:33:06

[...] publikacji obszerniejszego tekstu. Dzisiaj nadrabiam zaległości i publikuję artykuł pt. Obsługa GPIO w Raspberry Pi przez stronę www - podejście alternatywne. Znajdziecie w nim opis implementacji stworzonego przeze mnie panelu web do sterowania portami GPIO [...]

» dodaj komentarz «





» Komentarze dodane przez niezarejestrowanych użytkowników muszą być zatwierdzone przez moderatora


» kategorie wpisów

Co nowego na stronie (wpisów: 43)
Dzikie.NET (wpisów: 16)
Humor (wpisów: 46)
Inne (wpisów: 34)
Kapsle Tymbark (wpisów: 29)
Moje projekty i pomysły (wpisów: 43)
Narzekam na… (wpisów: 16)
Nowości w galerii (wpisów: 12)
Nowości, ciekawostki (wpisów: 595)
O mnie (wpisów: 36)
Wordpress (wpisów: 1)
Zabawki (wpisów: 9)
Zdjęcie bez komentarza (wpisów: 91)
Zdjęcie z komentarzem (wpisów: 23)
Strony w domenie tech-blog.pl wykorzystują pliki cookies w celach statystycznych, analizy oglądalności oraz na potrzeby wyświetlania reklam. Jeżeli nie wyrażasz na to zgody, zmień ustawienia wykorzystywanej przeglądarki internetowej. Więcej informacji na stronie Polityka prywatności i cookies (ciasteczka).

» najnowsze zdjęcia w galerii

Project Turris #02Project Turris #17

» archiwum wpisów

styczeń 2014 (wpisów: 2)
grudzień 2013 (wpisów: 6)
listopad 2013 (wpisów: 27)
październik 2013 (wpisów: 21)
wrzesień 2013 (wpisów: 27)
sierpień 2013 (wpisów: 8)
lipiec 2013 (wpisów: 8)
czerwiec 2013 (wpisów: 6)
maj 2013 (wpisów: 16)
kwiecień 2013 (wpisów: 17)
marzec 2013 (wpisów: 18)
luty 2013 (wpisów: 22)
styczeń 2013 (wpisów: 19)
grudzień 2012 (wpisów: 20)
listopad 2012 (wpisów: 33)
październik 2012 (wpisów: 25)
wrzesień 2012 (wpisów: 15)
maj 2012 (wpisów: 1)
kwiecień 2012 (wpisów: 1)
marzec 2012 (wpisów: 1)
styczeń 2012 (wpisów: 2)
grudzień 2011 (wpisów: 1)
listopad 2011 (wpisów: 7)
październik 2011 (wpisów: 4)
wrzesień 2011 (wpisów: 1)
sierpień 2011 (wpisów: 3)
lipiec 2011 (wpisów: 2)
czerwiec 2011 (wpisów: 3)
maj 2011 (wpisów: 1)
marzec 2011 (wpisów: 3)
luty 2011 (wpisów: 5)
styczeń 2011 (wpisów: 5)
grudzień 2010 (wpisów: 6)
listopad 2010 (wpisów: 2)
październik 2010 (wpisów: 1)
wrzesień 2010 (wpisów: 4)
sierpień 2010 (wpisów: 2)
lipiec 2010 (wpisów: 4)
kwiecień 2010 (wpisów: 4)
marzec 2010 (wpisów: 12)
luty 2010 (wpisów: 4)
styczeń 2010 (wpisów: 1)
listopad 2009 (wpisów: 1)
październik 2009 (wpisów: 1)
wrzesień 2009 (wpisów: 1)
sierpień 2009 (wpisów: 3)
lipiec 2009 (wpisów: 2)
czerwiec 2009 (wpisów: 6)
kwiecień 2009 (wpisów: 1)
marzec 2009 (wpisów: 25)
luty 2009 (wpisów: 9)
styczeń 2009 (wpisów: 13)
grudzień 2008 (wpisów: 7)
listopad 2008 (wpisów: 11)
październik 2008 (wpisów: 1)
wrzesień 2008 (wpisów: 9)
sierpień 2008 (wpisów: 49)
lipiec 2008 (wpisów: 2)
czerwiec 2008 (wpisów: 39)
maj 2008 (wpisów: 52)
kwiecień 2008 (wpisów: 49)
marzec 2008 (wpisów: 58)
luty 2008 (wpisów: 38)
styczeń 2008 (wpisów: 12)
grudzień 2007 (wpisów: 16)
listopad 2007 (wpisów: 2)
październik 2007 (wpisów: 8)
wrzesień 2007 (wpisów: 16)
sierpień 2007 (wpisów: 7)
lipiec 2007 (wpisów: 3)
czerwiec 2007 (wpisów: 3)
maj 2007 (wpisów: 11)
kwiecień 2007 (wpisów: 11)
marzec 2007 (wpisów: 16)
luty 2007 (wpisów: 9)
styczeń 2007 (wpisów: 13)
grudzień 2006 (wpisów: 5)
listopad 2006 (wpisów: 4)
październik 2006 (wpisów: 13)
wrzesień 2006 (wpisów: 15)
sierpień 2006 (wpisów: 8)
lipiec 2006 (wpisów: 5)
czerwiec 2006 (wpisów: 39)
maj 2006 (wpisów: 38)
All rights reserved Copyright 2006-2012 Piotr Dymacz