fbpx

Gdy piłkarzyki zajęte są – kod urządzenia i GPIO

Opublikowane przez Jerzy Wickowski w dniu

Po krótkim omówieniu problemu i wstępnego pomysłu na rozwiązanie. Przeszedłem do wyboru urządzenia i mojego pierwszego zetknięcia z elektroniką. Następnie uderzyłem w architekturę rejestracji urządzenia. A teraz wreszcie przejdę, do czegoś ciekawszego, z programistycznego punktu widzenia, a minowicie do kodu, który oczywiście jest dostępny na githubie. W tej częście skupię, się na kodzie urządzenia.

Wybór technologii i dlaczego UWP

Przed rozpoczęciem implemetacji zastanawiałem, ze czy może przy okazji nowego własnego projektu, nie wykorzystać okazji, aby użyć Noda lub Pythona, a na Malince postawić Linuxa. Jednak zdałem sobie sprawę, że i tak nauczę się sporo nowych rzeczy. Ponadto przypomniał mi się obraz z przeszłości, gdy rozpoczynałem projekt i wybrałem same nowe, trendi i nieznane technologie. Wtedy ugrzązłem na starcie, bo ilość wiedzy koniecznej do wystartowania mnie przygniotła.

W związku z tym, postanowiłem, że będę wprowadzał nowe technologie, ale pojedyczno. Zatem zdecydowałem się na na Windows 10 IoT Core + UWP, czyli Uniwersal Windows Platform. Sam UWP posiada tę zaletę, że mogę pisać w C#. Ma natomiast sporo denerwujących ułomności, które przeszkadzają w pracy. Jednak im postaram się przyjrzeć kiedy indziej.

Struktura projektów

Do impelemtacji mojego rozwiązania stworzyłem cztery projekty:

Core

Jest to projekt, który zawiera logikę aplikacji oraz interfejsy, których impementacja jest konieczna do działania aplikacji.

Core.Tests

Tutaj sprawa jest dość oczywista. Testy do warstwy logiki. Ponadto masa fejkowych implementacji koniecznych interfejsów, bo niestety UWP trochę ssie na paszczyźnie mokowania, bo nie jest wspierane przez żaden framework. A przynajmniej wtedy nie potrafiłem żadnego znaleść.

RaspberyPi i Symulator

Te projekty zawierą, po pierwsze: implementacje interfejsów z logiki, aby można było ją uruchomić na RaspberyPi lub na Symulatorze. Po drugie kod je uruchamiajacy.

Architektura

Tworząc ten projekt ten projekt starałem się podchodzić do architektury bardzo powściągliwie, w związku z blędem, który już kiedyś popełniłem i opisałem w poście z patologii w patologię. Raczej starałem się stosować zdrowy rozsądek oraz ogólną koncepcję Onion Architecture.

W związku z tym, sercem aplikacji jest wszystko co znajduje się w projekcie Core. On właśnie posiada całą logikę i jaknajmnijeszą ilość zależności do zewnętrzych komponentów. Wiem, jest tam klasa ApiClientImp, która powinna być traktowana jako plugin, lecz na chwilę obecną komunikacja z API jest dostępna tylko po HTTP. Jeżeli, jednak pojawi sie jakakolwiek inna opcja to powinno to zostać wyrzucone na zewnątrz.

Kod

Aplikacja IsTableBusy nie jest bardzo rozbudowana. W związku z tym kod też nie jest w żaden sposób siermiężnie skomplikowany. Jednakże postaram się przedstawić najciekawsze fragmenty.

DeviceApp

Własnie tutaj ukrywa się cała logika aplikacji, czyli 140 lini kodu, a konstruktor wygląda tak:

public DeviceApp(IoTDevice device, ApiClient apiClient)
{
   this.device = device;
   this.apiClient = apiClient;
   this.lightManager = new LightManager(device);
   this.State = AppState.NotStarted;
}

Ładnie tutaj widać jakich zależności wymaga ta klasa. Może się zastanawiacie dlaczego tworzę, a nie wstrzykuję obiektu klasy LightManager. Robię to dlatego, że jest to nierozerwalna część logiki aplikacji. Można powiedzieć, że tam jest przeniesiona część logiki odpowiedzialnej za manipulacje światłem.

Led>

Czyli implementacja interfejsu Light dla RaspberyPi. Implemetacja ta polega na podłączeniu się do pina do którego będzie podpięta dioda oraz ustawienia jegp początkowego stanu na GpioPinValue.Low oraz zmiana tego trybu na GpioPinDriveMode.Output. Ma to na celu domyślnie wyłączenie diody i jej konfigurację, aby pin służył do wyświetlania, a nie pobierania danych.

public sealed class Led: Light
{
   private readonly GpioPin pin;
   private GpioPinValue value;

   public Led(int pinNumber)
   {
      var controller = GpioController.GetDefault();
      pin = controller.OpenPin(pinNumber);
      value = GpioPinValue.Low;
      pin.Write(value);
      pin.SetDriveMode(GpioPinDriveMode.Output);
   }

   public void On()
   {
      if (IsOn == false)
      {
         value = GpioPinValue.High;
         pin.Write(value);
      }
   }

   public void Off()
   {
      if (IsOn)
      {
         value = GpioPinValue.Low;
         pin.Write(value);
      }
   }

   public bool IsOn => value == GpioPinValue.High;
}

Button

Implementacja przycisku jest również bardzo prosta. Należało ustawić odpowiedzni tryb, częstotliwość odświeżania oraz podpiąć uchwyt na zdarzenie zmiany wartości. W tej sytuacji użyłem trybu GpioPinDriveMode.InputPullUp.

public sealed class ButtonImp : Button
{
   private readonly GpioPin pin;
   public ButtonImp(int pinNumber)
   {
      var controller = GpioController.GetDefault();
      pin = controller.OpenPin(pinNumber);
      pin.SetDriveMode(GpioPinDriveMode.InputPullUp);
      pin.DebounceTimeout = TimeSpan.FromMilliseconds(50);

      pin.ValueChanged += (s, e) =>
      {
         var value = pin.Read();
         if (value == GpioPinValue.Low)
         {
            Clicked?.Invoke(this, null);
         }
      };
   }
   
   public event TypedEventHandler<Button, object> Clicked;
}

Na koniec

Jak widzicie, napisanie kodu uruchamianego na Malince jest łatwiejsze niż się z początku zdaje. Przynajmniej niż mi się wydawało na starcie. A w następnym odcinku poszperam trochę, przy warstwie serwerowej.



Czy to był wartościowy artykuł? Zapisz się, a wyślę Ci dwa ebooki o czystym kodzie oraz będę informował Cię o nowych postach
Kategorie: IoT

0 Komentarzy

Dodaj komentarz

Twój adres email nie zostanie opublikowany.

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.

[contact-form-7 404 "Nie znaleziono"]

Zapisz się

Wyślę Ci dwa dokumenty mówiące o jakości kodu. Dodatkowo będę Cię informował o nowych postach i nowościach.