Projekt: Kontrola dostępu cz. 2

Czas na kolejną część poradnika z pomocą którego wykonamy prosty system kontroli dostępu oparty o Arduino. Dziś będzie nieco inaczej, gdyż Arduino i ogólnie elektronikę zostawimy trochę z boku, a w głównej mierze zajmiemy się stworzeniem od zera aplikacji w języku C#, za pomocą której będziemy mogli programować nasz system za pomocą komputera z wykorzystaniem aplikacji z przyjaznym GUI. Zaczynajmy więc!

Kod dla Arduino

Na początek dokończymy kod dla Arduino z poprzedniej części. Pominęliśmy wtedy funkcję odpowiedzialną za komunikację z komputerem przez UART.

Oto ona w całości:

void processSerialCmds() {
  String cmd = "";
  elapsedMillis timeout = 0;
  while(timeout <= 100) {
    if(Serial.available()) {
      cmd += (char) Serial.read();
      timeout = 0;
    }
  }
  cmd.trim();
  if(cmd.equals("")) return;

  if(cmd.equals("LOGOUT")) {
    if(adminAccess) {
      adminAccess = false;
      Serial.println("LOGGED_OUT");
      return;
    }
  }

  if(cmd.equals("LOGIN")) {
    actionTime = 0;
    currentMode = NEED_AUTH;
    Serial.println("AUTH_WAITING");
    return;
  }

  if(cmd.equals("ADD")) {
    currentMode = MASTER_ADD;
  } else if(cmd.equals("CLEAR")) {
    currentMode = MASTER_CLEAR;
  } else if(cmd.equals("OPEN")) {
    currentMode = ACCESS_GRANTED;
  }

  if(currentMode != READY && !adminAccess) {
    currentMode = NEED_AUTH;
    Serial.println("AUTH_WAITING");
  } else if(currentMode != READY) {
    loginTime = 0;
  }
  
  actionTime = 0;
}

Na początek zaimplementowałem tutaj tylko kilka podstawowych funkcji takich jak autoryzacja, dodanie karty oraz czyszczenie pamięci. Projekt będzie rozwijany dalej jako open-source na githubie i tam będą pojawiały się kolejne funkcje.

Myślę, że kod powinien być dla Was czytelny, podobne rozwiązanie zastosowałem już w jednym z poprzednich artykułów, mianowicie Prosty parser stringów na Arduino / Teensy.

Aplikacja w C#

Cały kod można pobrać stąd.

Aby zabrać się do napisania aplikacji w C# otwieramy Visual Studio (można pobrać stąd). Tworzymy nowy projekt z takimi ustawieniami:

Visual C# -> Classic Desktop -> Windows Forms Application

Teraz możemy zabrać się do tworzenia GUI. Wybieramy z lewej strony zakładkę Toolbox (możemy ją przypiąć, żeby się nie chowała) i przeciągamy potrzebne nam kontrolki na widok okna naszej aplikacji. Przyda nam się na pewno ListBox, który będzie reprezentował listę portów szeregowych dostępnych w komputerze, Button – przyciski oraz Label czyli etykiety. Dodatkowo możemy wrzucić GroupBox, który zgrupuje nam powiązane ze sobą elementy (ja zrobiłem grupy Połączenie oraz Ustawienia). Niezbędną kontrolką będzie też SerialPort. Odpowiada ona za komunikację przez port szeregowy.

W prawym dolnym rogu mamy okienko Properties, w którym możemy w prosty sposób ustawić właściwości zaznaczonego obiektu (np. nadać mu nazwę lub zmienić tekst). Jeżeli klikniemy tam ikonkę błyskawicy to zobaczymy listę zdarzeń, które można oprogramować dla danej kontrolki (np. Click).

Znając już podstawy podstaw obsługi środowiska możemy zaprojektować prosty interfejs. Musimy umieścić w nim listę portów szeregowych, przyciski do skanowania portów, połączenia z wybranym portem, autoryzację na urządzeniu, dodawanie karty, czyszczenie pamięci oraz symulację przyznania dostępu. Przydadzą się także etykiety – odpowiedź z urządzenia (np. AUTH_FAILED) oraz status czyli słowny opis tego. Ja rozmieściłem sobie te elementy w taki sposób:

Skoro mamy już interfejs to pora go oprogramować. Jeżeli dwukrotnie klikniemy na jakiś element to środowisko wygeneruje dla nas metodę obsługującą kliknięcie na daną kontrolkę. Inne eventy możemy wygenerować w zakładce Events (ikonka błyskawicy) w okienku Properties.

Kodu jest całkiem sporo, lecz głównie polega on na włączaniu / wyłączaniu odpowiednich elementów interfejsu oraz zmianie tekstu. Omówię więc tutaj tylko te fragmenty, które odpowiadają np. za komunikację z Arduino i mogą sprawiać problemy osobom, które nigdy nie pisały nic w C#. Całą resztę kodu można spokojnie ogarnąć, wystarczy tylko znać podstawy programowania w dowolnym języku oraz trochę angielskiego :)

private void btnScan_Click(object sender, EventArgs e)
        {
            string[] ports = SerialPort.GetPortNames();
            listPorts.Items.Clear();
            btnConnect.Enabled = false;
            foreach (string port in ports)
            {
                listPorts.Items.Add(port);
            }
        }

Na początku tworzymy tablicę stringów, do której przypisujemy nazwy portów pobrane z klasy SerialPort (jeżeli chcemy używać skróconej nazwy czyli SerialPort musimy na początku dokumentu dopisać using System.IO.Ports; ). Następnie czyścimy nasz listbox, wyłączamy przycisk umożliwiający połączenie się i dopisujemy po kolei elementy do listy za pomocą pętli foreach.

private void btnConnect_Click(object sender, EventArgs e)
        {
 
            try
            {
                if(serialPort.IsOpen)
                {
                    sendData("LOGOUT");
                    setLoggedState(false);
                    serialPort.Close();
                }
                else
                {
                    serialPort.PortName = listPorts.SelectedItem.ToString();
                    serialPort.Open();
                }
            }
            catch(Exception exception)
            {
                MessageBox.Show("ERROR: " + exception.Message);
            }
 
            if(serialPort.IsOpen)
            {
                btnConnect.Text = "Rozłącz";
                btnScan.Enabled = false;
                listPorts.Enabled = false;
 
                btnLogin.Enabled = true;
            }
            else
            {
                btnConnect.Text = "Połącz";
                btnScan.Enabled = true;
                listPorts.Enabled = true;
 
                btnLogin.Enabled = false;
                btnOpen.Enabled = false;
                btnAdd.Enabled = false;
                btnClear.Enabled = false;
            }
        }

Rzućmy teraz okiem na metodę, która wywoływana jest w momencie kliknięcia przycisku Połącz / Rozłącz:

Blok try {} catch() {} odpowiada za obsługę wyjątków. Jeżeli w bloku try wystąpi wyjątek (np. dzielenie przez 0) to sterowanie przejdzie do bloku catch, gdzie możemy zareagować na daną sytuację (dzięki czemu program nie wyłączy się z błędem).

Najpierw musimy sprawdzić, czy jesteśmy połączeni, czy nie. W tym celu sprawdzamy wartość logiczną jaką zwróci pole serialPort.IsOpen. Teraz w zależności od tej zmiennej możemy zerwać połączenie lub je nawiązać. Po wykonaniu tych opcji sprawdzamy, czy nawiązanie / zerwanie połączenia udało się i ustawiamy odpowiednie kontrolki w zależności od tego.

private void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            SerialPort sp = (SerialPort) sender;
            dataReceived = sp.ReadLine().Trim();
            Invoke(new EventHandler(handleReceivedData));
        }

Przejdźmy teraz do metody obsługującej zdarzenie, kiedy to otrzymujemy jakieś dane na port szeregowy. Odczytujemy te dane, a następnie robimy coś dziwnego. Używamy metody Invoke, która wykona dla nas metodę handleReceivedData. Chodzi o to, że event obsługujący odebranie danych przez port szeregowy pracuje w innym wątku niż główny i tym samym nie może modyfikować elementów interfejsu użytkownika. Z tego też powodu musimy to wszystko, co chcemy wykonać na interfejsie opakować w metodę i przekazać jako obiekt klasy EventHandler do metody Invoke.

Reszta kodu tak jak wspominałem wcześniej jest na tyle prosta, że nie wymaga omówienia i zachęcam do przejrzenia całości samemu :)

Kiedy mamy już cały kod możemy uruchomić naszą aplikację wciskając przycisk Start w górnym pasku.

Jeżeli wszystko działa jak należy to zmieniamy tryb kompilacji z Debug na Release, wciskamy F6 (Build Solution) i powinniśmy wtedy znaleźć skompilowanego .exe w folderze Visual Studio 2015\Projects\ArduinoAccessControlSetup\ArduinoAccessControlSetup\bin\Release.

Jak widać w Visual Studio można bardzo szybko i łatwo zrobić GUI do programowania naszych urządzeń opartych o Arduino :) Na dziś to tyle, w trzeciej części poradnika dowiemy się jak narysować schemat ideowy całego układu, poprowadzić ścieżki na płytce drukowanej oraz zmontować urządzenie.

Spis treści

  1. Program dla arduino (cz. 1)
  2. GUI do programowania w C# (cz. 2) (tutaj jesteś)
  3. Schemat oraz projektowanie PCB (cz. 3)