Odczytywanie danych z portu szeregowego

Pobieranie danych z takich źródeł jak port szeregowy, gniazdo TCP/IP czy inne sprzęty sprawia problem – bajty przychodzą w różnych porcjach. Gdy coś zostało wysłane w jednej (dużej, czy niedużej – nieistotne) paczce, może się okazać że zostało odebrane na kilka razy. W drugą stronę sytuacja też jest możliwa. Jeśli wysłaliśmy w kilku paczkach (najczęściej szybko po sobie) to możemy dostać bajty na raz. Nie mamy nigdy pewności.

Przedstawię przykładowy sposób, w jaki ja sobie radzę z naszkicowanym problemem.

   using System;
   using System.Collections.Concurrent;
   using System.Threading;
   
   public class SerialPortReader
    {
        private readonly IRawSerialWrapper _serialWrapper;
        private readonly DataParser _dataParser = new DataParser();
        private readonly ConcurrentQueue<byte[]> _queue = new ConcurrentQueue<byte[]>();
        private Thread _mainLoopThread;
        private bool _isRunning;

        public SerialPortReader(IRawSerialWrapper serialWrapper)
        {
            _serialWrapper = serialWrapper;
        }

        public void StartListening(object options)
        {
            if (_isRunning == false)
            {
                var threadStart = new ThreadStart(ReadData);
                _mainLoopThread = new Thread(threadStart);
                _mainLoopThread.IsBackground = true;
                _mainLoopThread.Start();

                _serialWrapper.SetOptions(options);
                _serialWrapper.DataAvailable += () => _queue.Enqueue(_serialWrapper.GetData());

                _serialWrapper.Open();
                _isRunning = true;
            }
        }

        private void ReadData()
        {
            while (true)
            {
                byte[] bytes;
                if (_queue.TryDequeue(out bytes))
                    _dataParser.ReadData(bytes);

                Thread.Sleep(0);
            }
        }
    }

    public class DataParser
    {
        public void ReadData(byte[] bytes)
        {
        }
    }

    public interface IRawSerialWrapper : IDisposable
    {
        event Action DataAvailable;

        void SetOptions(object options);
        byte[] GetData();

        void Open();
        void Close();
    }

Praca została podzielona na dwa etapy. Pierwszy:

         _serialWrapper.DataAvailable += () => _queue.Enqueue(_serialWrapper.GetData());

czyli zaraz po pojawieniu sie danych wrzucamy je do kolejki. Nie dzieje się tutaj nic więcej, bardzo szybko obsługujemy event DataReveived().

Drugi etap:
EDITED:Zgodnie z komentarzem na dole strony, poniższa pętla jest źle napisana więc proszę zobaczyć w komentarzu jak to się robi dobrze.

            while (true)
            {
                byte[] bytes;
                if (_queue.TryDequeue(out bytes))
                    _dataParser.ReadData(bytes);

                Thread.Sleep(0);
            }

czyli osobny wątek zajmuje się tylko przetwarzaniem otrzymanych bajtów. Wysyła je do klasy odpowiedzialnej za połączenie ich w sensowne ramki i obsłużenie (DataParser). Dzięki takiemu połączeniu, kod, który wywołał IRawSerialWrapper.DataReveived nie musi czekać na przetworzenie tych danych. Będzie to wydajniejsze na wielordzeniowych maszynach.

Warta uwagi jest jeszcze linijka:

_mainLoopThread.IsBackground = true;

dzięki temu wątek ten nie zostanie jedynym działającym. Zatrzyma się, gdy wyłączymy główną aplikację.

Interfejs IRawSerialWrapper jest stworzony aby opakować i traktować w ten sam sposób różne sterowniki portu szeregowego. Interfejs ten implementuje też klasa która fack’uje port szeregowy. Dla testów Gui aplikacji wysyła zmieniające się dane, co uniezależnia development/testowanie od posiadania urządzenia wysyłającego dane na port szeregowy.

Reklamy
Ten wpis został opublikowany w kategorii Uncategorized i oznaczony tagami . Dodaj zakładkę do bezpośredniego odnośnika.

2 odpowiedzi na „Odczytywanie danych z portu szeregowego

  1. piotr.sowa pisze:

    Ta pętla ‚konsumująca’ jest kompletnie źle napisana… poniżej lepszy przykład:

    while (!disposed)
    {
      if (SpinWait.SpinUntil(() => !_queue.IsEmpty, 100))
      {
        while (!_queue.IsEmpty)
        {
          byte[] bytes;
          if (_queue.TryDequeue(out bytes))
          {
            _dataParser.ReadData(bytes);
          }
        }
      }
    }
    

    Źródło: http://piotrsowa.wordpress.com/2011/11/27/publisherconsumercollectiont/

    • Oczywiście masz rację. Dzięki Piotrek, stawiam piwo za pierwszy konstruktywny komentarz 🙂 Jeden z celów bloga spełniony – ktoś poprawił moje błędy.

Możliwość komentowania jest wyłączona.