Strona używa cookies (ciasteczek). Dowiedz się więcej o celu ich używania i zmianach ustawień. Korzystając ze strony wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki.    X

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

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

windows sprzęt programowanie

Komentarze

0 nowych
Over   9 #1 05.03.2014 14:13

Dla mnie to magia.... te wiersze... linijki etc....konsola....

gowain   18 #2 05.03.2014 14:39

Ja kombinuję z czujnikiem DHT22 - temperatura + wilgotność. Na razie zapis do Google Docs działa fajnie. W przyszłości planuję zrobić widżet na Androida :)

cyryllo   16 #3 05.03.2014 15:26

@gowain DHT22 jest może i fajnym układem ale nie podłączysz więcej ich pod 1wire niż jeden.

gowain   18 #4 05.03.2014 16:28

@cyryllo ja dopiero zaczynam zabawę z RPi, więc może w moich pomysłach występują błędy logiczne :) Pod 1wire tylko jeden - ale pod inny pin można chyba podpiąć drugi, tak?

cyryllo   16 #5 05.03.2014 20:00

No masz tylko jeden 1wire w malinie ;) i jak użyjesz tego czujnika to inne nie będą działać. Domyślnie 1wire w malinie obsługuje do 8 czujników na raz.

gowain   18 #6 05.03.2014 20:07

Hmm :) Muszę sobie znaleźć na Ciebie namiar i dowiedzieć się kilku rzeczy, bo ja zielony jestem w takich sprawach i jak na razie bazuję na gotowych rozwiązaniach z neta, z jakimiś małymi modyfikacjami :)

revcorey   6 #7 05.03.2014 20:33

Żeby uniknąć problemów to trzeba optoizolację to raz. Dwa że niestety na taki projekt to za duży kaliber ;) na rpi lepiej 8-bitowce. BTW. Dołożyłbym inercję 1s na pomiarze temperatury żeby uniknąć skoków, możesz takie skoki bez inercji zobaczyć u mnie w wpisie o temperaturze i atmedze http://www.dobreprogramy.pl/Atmega-Prosty-termometr,Blog,49016.html . Generalnie wynika to z fizyki i w standardowych rozwiązaniach przemysłowych stosuje się inercję na przyjęciu pomiaru.

cyryllo   16 #8 05.03.2014 21:28

@gowain polecam znaleźć mnie na picoboard.pl (forum, strona) lub w grupie malinowe Pi na FB :P

cyryllo   16 #9 05.03.2014 21:30

@gowain no i mam nadzieje na HotZlocie :P

wobes   4 #10 07.03.2014 03:31

@cyryllo

Po co podpinać dht22 do 1wire, skoro dht22 nie obsługuje 1wire?

@revcorey

1) optoizolacja 1wire????????
2) inercja to właściwość, więc chyba nie to miałeś na myśli. Poza tym odczyt z DS18B20 w porównaniu z tym z przetwornika AD avra jest czysty jak woda źródlana
3) w swoim wpisie nazwaleś KTY 81-210 warystorem, co delikatnie mówiąc prawdą nie jest
4) śmieci na twoich wykresach w dużej mierze pochodzą od nieprawidłowo zaprojektowanego zasilania części analogowej procesora i zapewne uproszczonej obsłudze przetwornika AD (procesor w trakcie pomiaru powinien być uśpiony, peryferia powyłączane itp.)

cyryllo   16 #11 07.03.2014 09:12

@wobes Jak nie obsługuje? jak obsługuje :P Pokaż dowód że nie obsługuje bo wszędzie każdym opisie produktu w sklepach jest info, że komunikacja prze 1wire

revcorey   6 #12 07.03.2014 11:18

@wobes
1)Jest to możliwe(są jakieś konstrukcje na necie), czy jest sens to już dyskusyjne.
2)Człon inercyjny, normalnie w pracy używa się uproszczeń i w automatyce mówimy po prostu inercja. A przerobiłem już w pracy czujniki począwszy od pt100 po różnego rodzaju termopary, w każdym razie zwyczajowo dajemy człon inercyjny.
3)Bardziej termistor racja.
4)To układ demonstracyjny na jakimś tandetnym zasilaczu. Generalnie nigdy bym się nie zgodził na zrobienie czegoś w rodzaju odłączenia peryferiów itd. w czasie działania procesu. Pytanie co ci się stanie z układem? Widziałem robiony układ na atmegach dla jednego z największych polskich koncernów(pomijam celowość takiego działania ale to inna historia). Nie wiem w jakiej pozycji zostaje i/o po wyłączeniu ale to jest niebezpieczne. Ale jeśli napięcie spada w dół itd. to jesteś miliony w plecy. Tu już lepiej po prostu wykorzystać zewnętrzny przetwornik AC. Jak w przemysłowych rozwiązaniach.

revcorey   6 #13 07.03.2014 11:25

A btw. odnośnie członu inercyjnego to on jest nie tylko filtrem ale także pełni ważną rolę w układach regulacji.

wobes   4 #14 07.03.2014 20:24

@revcorey

Dokładnie tak jak piszesz: wykorzystać zewnętrzny przetwornik. Ten wbudowany bez wyłączenia na czas pomiaru liczników, obsługi magistral czy przerwań zewnętrznych, no i na koniec uśpienia rdzenia, ma bardzo kiepską dokładność, powtarzalność i generuje dużo śmieci - zresztą atmel wyraźnie o tym pisze. I/O się nie wyłącza - wystarczy nie ruszanie go w czasie pomiaru, wtedy nie ma też problemu z zachowaniem stanu. Oczywiście w zwykłych, amatorskich konstrukcjach zwykle wystarczy zastosowanie "filtrów cyfrowych".