Blog (34)
Komentarze (536)
Recenzje (0)

"Mądrzejszy" termometr, część 3

@mktos"Mądrzejszy" termometr, część 305.03.2014 14:31

Trochę upłynęło czasu. Można więc zadać pytanie: jak tam mój bardzo skomplikowany termometr? Działa?

Działa.

Przypominając: mam czujnik temperatury na 1-wire, DS18B20, a nawet kilka. Podpięte są one do Raspberry Pi. RPi wraz z modułem w1-gpio oraz w1-therm tworzy pewne pliki, które zawierają dane z urządzenia. Te dane są parsowane przez stado skryptów PHP tworząc usługę webową, która może udostępniać temperaturę w postaci różnych formatów danych.

Jednym z tych formatów jest czysty tekst, którego intensywnie używam, jest wszechobecny JSON, a także coś specyficznego dla Windows - format WNS, będący w gruncie rzeczy odpowiednim XML-em.

Całe założenie tego co robiłem od początku było takie, że chciałem mieć temperaturę za oknem prezentowaną w postaci wartości na przypiętym "kafelku" w systemie Windows 8. Skoro mamy już sprzęt, mamy już udostępnione dane, co będzie następnym krokiem? Oczywiście sama aplikacja, dla Windows 8. Wygląda ona mniej więcej tak:

Głównie składa się z jednej prostej rzeczy - listy wielkich kwadratów prezentujących aktualne odczyty, oraz - nieco mniejsze - ich nazwy. Jest to graficzne przedstawienie pewnego modelu danych, który zawiera takie pola jak nazwa czujnika, adres serwera, wartość odczytu i parę innych. Zaktualizowanie wartości temperatury odbywa się "ręcznie", poprzez wywołanie metody UpdateReading(). Metoda ta ma następującą postać:


public async Task UpdateReading()
{
	if (this.Server == null && this.SensorName == null)
		this.Reading = float.NaN;

	var filter = new HttpBaseProtocolFilter();
	filter.CacheControl.ReadBehavior = Windows.Web.Http.Filters.HttpCacheReadBehavior.MostRecent; filter.CacheControl.WriteBehavior = Windows.Web.Http.Filters.HttpCacheWriteBehavior.NoCache; 
	HttpClient h = new HttpClient(filter);            
	try
	{
		Uri uri = new Uri(this.Server + this.SensorName + ".txt");
		string r = await h.GetStringAsync(uri);
		this.Reading = float.Parse(r);
		this.ReadingError = false;
	}
	catch (Exception)
	{
		this.ReadingError = true;
		this.Reading = float.NaN;
	}
}

Wykorzystany tutaj jest HttpClient... jednak należy uważać, ponieważ jest to Windows.Web.Http.HttpClient, nie System.Net.HttpClient. Posiada od trochę dodatkowych możliwości w stosunku do swojego kolegi z System.Net, przede wszystkim kontrolę nad cache oraz fakt, że wszystko realizowane jest poprzez async/await. Aktualizacja odczytu po prostu łączy się z serwerem i odczytuje dane z pliku http://<serwer>/<nazwa czujnika>.txt, na co usługa webowa automatycznie odpowiada zwykłym, czystym tekstem. Ten tekst parsowany jest do postaci liczby, która wrzucana jest do właściwości Reading.

Po dowiązaniu modelu danych do kontrolki GridView wraz z ustawieniem szablonu danych, dostajemy taką aplikację, jaką mniej więcej pokazałem. Do tego niezbędne były jeszcze dwie rzeczy - muszę mieć możliwość dodawania i usuwania czujników na bieżąco. W dodatku fajnie by było, gdyby taka lista czujników podróżowała wraz ze mną po różnych urządzeniach. No i chcę mieć możliwość "przypięcia" dowolnego wielkiego kwadratu do ekranu startowego, aby te odczyty oglądać bez uruchamiania aplikacji.

Dodawanie i usuwanie czujników z listy okazało się nietrudne - model danych zawarty jest w typowej kolekcji typu ObservableCollection, która jest bezproblemowo dowiązywana do kontrolki GridView. Większym problemem okazało się po pierwsze to, że w przeciwieństwie do Windows Phone, w XAML dla Windows 8 nie ma przy wiązaniu danych parametru StringFormat i aby liczbę powiązać z wyświetlaniem w taki sposób, aby tam jeszcze pokazywać napis "stopni C", trzeba było pokombinować z własnym konwerterem.

Dodatkowo, zgodnie z oficjalnymi wymogami, dodawanie nowych czujników (oraz przypinanie) powinno być realizowane w postaci tzw. flyoutów, a nie przechodzenia do innej strony aplikacji. Dodałem zatem pasek komend (AppBar) na dole aplikacji, zawierający takie rzeczy jak pokazywanie małego okienka do dodawania elementu, usuwanie zaznaczonych elementów (zaznaczanie elementów już realizuje GridView sam z siebie), aktualizację odczytów oraz przypinanie/odpinanie od ekranu startowego.

To, co dla mnie było naistotniejsze, przypinanie do ekranu głównego, realizowane jest przez "secondary tiles". Aplikacja może mieć wiele drugorzędnych kafelków - u mnie tworzony jest jeden kafelek na każdy czujnik. Kafelki identyfikowane są przez ich unikatowe identyfikatory, u mnie odpowiada za to metoda ToSecondaryTileName() w moim modelu. Posiadając nazwę, pewne dodatkowe właściwości, obrazek i ustawiając rozmiar drugorzędnego kafelka można dodać go do ekranu startowego za pomocą takiego kodu:


Uri logo = new Uri("ms-appx:///Assets/squareTile.png");
	string tileActivationArguments = appbarTileId + " was pinned at " + DateTime.Now.ToLocalTime().ToString();

	SecondaryTile secondaryTile = new SecondaryTile(appbarTileId,
													t.SensorName,                                                                    
													tileActivationArguments,
													logo,
													TileSize.Default);

	secondaryTile.VisualElements.ForegroundText = ForegroundText.Light;

	bool isPinned = await secondaryTile.RequestCreateForSelectionAsync(GetElementRect((FrameworkElement)sender), Windows.UI.Popups.Placement.Above);

Warto tutaj zwrócić uwagę, że nie powoduje to od razu przypięcia kafelka do ekranu głównego, a wywołanie systemowego okienka, gdzie użytkownik ma podgląd oraz może nadać własną nazwę.

Jeżeli przypięcie się powiodło, to mamy kafelek, który uruchamia aplikację, przekazując pewne dane (mało istotne dla mnie - tileActivationArguments - tutaj powinno być coś sensownego, a nie data przypięcia kafelka). Trzeba teraz jakoś wymusić jego aktualizację automatycznie co pewien czas na podstawie danych z usługi sieciowej. Do tego służy TileUpdateManager.


	if (isPinned)
		Windows.UI.Notifications.TileUpdateManager.CreateTileUpdaterForSecondaryTile(appbarTileId).StartPeriodicUpdate(t.ToWnsUri(), Windows.UI.Notifications.PeriodicUpdateRecurrence.Hour);

TileUpdateManager zaczyna aktualizować kafelek korzystając z pewnego adresu internetowego zwracanego przez metodę ToWnsUri(), z częstotliwością jednego razu na godzinę. Minimalny czas aktualizacji kafelka w taki sposób to raz na 30 minut. Wyszedłem z założenia, że temperatura aktualizowana raz na godzinę mi wystarczy.

Po przypięciu kafelków do głównego ekranu oraz odczekaniu aż system je zaktualizuje dostajemy mniej więcej taki efekt:

Potem system aktualizuje je zgodnie z tymi zadeklarowanymi harmonogramami co jakiś czas, metodą "pull", znaczy łączy się z odpowiednią usługą i pobiera XML, którym zmienia dane na kafelku. Moja aplikacja przewiduje tylko jeden rozmiar i tylko temperaturę, ale można to oczywiście uatrakcyjnić na różne sposoby. Przede wszystkim dodając logo aplikacji, którego wciąż nie dodałem i mam zamiast tego kwadrat z przekątnymi w środku.

Przy aplikacji pogrzebałem nieco dalej - bo przecież fajnie by było, gdyby przycisk "przypnij" zmieniał się na "odepnij", wskazane by było, aby użytkownik wiedział, że tam jest AppBar na dole, a nie żeby był schowany (wzorem aplikacji Poczta na przykład), czego nie da się zrobić w standardzie (ale jest gotowa klasa do tego) i tak dalej. Samą aplikację zacząłem sklejać z kilku przykładów Microsoftu, więc nie jest najpiękniejsza, niestety, ale póki co - działa.

Tutaj chciałbym powiedzieć o jeszcze jednej rzeczy - czujniki DS18B20 łączy się szeregowo. Ja sobie zrobiłem malutką płytkę, która łączy odpowiednie gniazdka, natomiast każdy czujnik ma zrobioną końcówkę do gniazdek pasujących. Czujniki mam obecnie trzy, jeden na długim kabelku wyrzucony za okno, drugi mierzący temperaturę w pokoju i trzeci - wewnątrz obudowy RPi. Zrobione jest to za pomocą płytki uniwersalnej, która leży w środku obudowy, w której jest moje Raspberry... ale z góry przepraszam wszystkich, którzy mają pojęcie o lutowaniu za zrobienie takiego potworka :-)

(swoją drogą wnętrze mojej obudowy dla RPi zawierające te wszystkie komponenty wpychane na siłę też nie wygląda dobrze).

Samą aplikację, w wersji jakiej mam to aktualnie, czyli dość rozgrzebanej, udostępniam tutaj: http://trash.ktos.info/TemperatureApp.zip (do własnoręcznej kompilacji). Działa, ale mogłaby działać lepiej, ma kilka błędów. W obecnym stanie nie nadaje się do publikacji w Sklepie za nic.

Ale sama aplikacja bez danych jest niewiele warta, dlatego też mam wystawioną usługę "na świat" - pod adresem http://ktostemperature.azurewebsites.net/external.txt są aktualizowane co jakiś czas dane z mojego faktycznego czujnika zewnętrznego, i taki adres można dodać do tej aplikacji (adres serwera: "http://ktostemperature.azurewebsites.net/", nazwa czujnika "external") i powinno to działać. Oprócz .txt dostępne jest oczywiście również .json i .wns.

Niestety, w trakcie używania pierwszej wersji mojego wielce skomplikowanego rozwiązania (które da się zastąpić wystawioną za okno rurką z kolorowym alkoholem) okazało się, że czujnik był źle zaizolowany i zdarzały się zwarcia - zwarcie powodowało, że moduł 1-wire na RPi przestawał działać poprawnie aż do restartu systemu, kiedy działał już bardziej poprawnie (nie wykrywał czujnika ze zwarciem). Poprawiłem, ale błędy w hardware są gorsze do rozwiązania niż błędy w oprogramowaniu.

Podsumowując:

  • mam czujniki DS18B20 do pomiaru temperatury podłączone do Raspberry Pi,
  • moduł jądra pozwala dane z czujnika przedstawiać jako pliki tekstowe o określonej zawartości,
  • dane temperatury z tych plików są prezentowane przez napisaną w PHP usługę sieciową (mniej więcej zgodną z podejściem REST),
  • jest napisana w C# aplikacja dla Windows 8, która odczytuje dane z czujników i prezentuje je jako wielkie kwadraty,
  • lub pozwala "przypiąć" czujnik do ekranu Start,
  • a dodatkowo mam napisany w PHP skrypt uruchomiony na Windows Azure, który dane z czujnika z mojej sieci lokalnej wyprowadza również na świat.

I to wszystko, aby tylko zobaczyć czy jest ciepło za oknem :-)

Szanowna Użytkowniczko! Szanowny Użytkowniku!
×
Aby dalej móc dostarczać coraz lepsze materiały redakcyjne i udostępniać coraz lepsze usługi, potrzebujemy zgody na dopasowanie treści marketingowych do Twojego zachowania. Twoje dane są u nas bezpieczne, a zgodę możesz wycofać w każdej chwili na podstronie polityka prywatności.

Kliknij "PRZECHODZĘ DO SERWISU" lub na symbol "X" w górnym rogu tej planszy, jeżeli zgadzasz się na przetwarzanie przez Wirtualną Polskę i naszych Zaufanych Partnerów Twoich danych osobowych, zbieranych w ramach korzystania przez Ciebie z usług, portali i serwisów internetowych Wirtualnej Polski (w tym danych zapisywanych w plikach cookies) w celach marketingowych realizowanych na zlecenie naszych Zaufanych Partnerów. Jeśli nie zgadzasz się na przetwarzanie Twoich danych osobowych skorzystaj z ustawień w polityce prywatności. Zgoda jest dobrowolna i możesz ją w dowolnym momencie wycofać zmieniając ustawienia w polityce prywatności (w której znajdziesz odpowiedzi na wszystkie pytania związane z przetwarzaniem Twoich danych osobowych).

Od 25 maja 2018 roku obowiązuje Rozporządzenie Parlamentu Europejskiego i Rady (UE) 2016/679 (określane jako "RODO"). W związku z tym chcielibyśmy poinformować o przetwarzaniu Twoich danych oraz zasadach, na jakich odbywa się to po dniu 25 maja 2018 roku.

Kto będzie administratorem Twoich danych?

Administratorami Twoich danych będzie Wirtualna Polska Media Spółka Akcyjna z siedzibą w Warszawie, oraz pozostałe spółki z grupy Wirtualna Polska, jak również nasi Zaufani Partnerzy, z którymi stale współpracujemy. Szczegółowe informacje dotyczące administratorów znajdują się w polityce prywatności.

O jakich danych mówimy?

Chodzi o dane osobowe, które są zbierane w ramach korzystania przez Ciebie z naszych usług, portali i serwisów internetowych udostępnianych przez Wirtualną Polskę, w tym zapisywanych w plikach cookies, które są instalowane na naszych stronach przez Wirtualną Polskę oraz naszych Zaufanych Partnerów.

Dlaczego chcemy przetwarzać Twoje dane?

Przetwarzamy je dostarczać coraz lepsze materiały redakcyjne, dopasować ich tematykę do Twoich zainteresowań, tworzyć portale i serwisy internetowe, z których będziesz korzystać z przyjemnością, zapewniać większe bezpieczeństwo usług, udoskonalać nasze usługi i maksymalnie dopasować je do Twoich zainteresowań, pokazywać reklamy dopasowane do Twoich potrzeb. Szczegółowe informacje dotyczące celów przetwarzania Twoich danych znajdują się w polityce prywatności.

Komu możemy przekazać dane?

Twoje dane możemy przekazywać podmiotom przetwarzającym je na nasze zlecenie oraz podmiotom uprawnionym do uzyskania danych na podstawie obowiązującego prawa – oczywiście tylko, gdy wystąpią z żądaniem w oparciu o stosowną podstawę prawną.

Jakie masz prawa w stosunku do Twoich danych?

Masz prawo żądania dostępu, sprostowania, usunięcia lub ograniczenia przetwarzania danych. Możesz wycofać zgodę na przetwarzanie, zgłosić sprzeciw oraz skorzystać z innych praw wymienionych szczegółowo w polityce prywatności.

Jakie są podstawy prawne przetwarzania Twoich danych?

Podstawą prawną przetwarzania Twoich danych w celu świadczenia usług jest niezbędność do wykonania umów o ich świadczenie (tymi umowami są zazwyczaj regulaminy). Podstawą prawną przetwarzania danych w celu pomiarów statystycznych i marketingu własnego administratorów jest tzw. uzasadniony interes administratora. Przetwarzanie Twoich danych w celach marketingowych realizowanych przez Wirtualną Polskę na zlecenie Zaufanych Partnerów i bezpośrednio przez Zaufanych Partnerów będzie odbywać się na podstawie Twojej dobrowolnej zgody.