Powrót do strony głównej

Programowanie zdarzeniowe na Arduino: wzorce i przykłady

Artykuł demonstruje wdrożenie architektury zorientowanej na zdarzenia w projektach Arduino za pomocą biblioteki EVA Core. Omówiono zasady kompozycji komponentów, przykłady kodu dla czujników i peryferii, ograniczenia podejścia. Materiał skierowany do programistów middle/senior.

Model zdarzeniowy dla Arduino: od teorii do praktyki
Advertisement 728x90

Programowanie zdarzeniowe na Arduino: wzorce z enterprise w embedded

Arduino tradycyjnie kojarzy się z liniowymi szkicami i cykliczną pętlą loop(). Jednak przy komplikowaniu projektów taki podkhod prowadzi do spaghetti-kodu. Przedstawiamy metodę przenoszenia architektury zorientowanej na zdarzenia z aplikacji enterprise do programowania embedded za pomocą biblioteki EVA Core.

Problem struktury w mikrokontrolerach

W aplikacjach desktopowych i serwerowych kod jest zorganizowany wokół obsługiwaczy zdarzeń: kliknięcia, timery, pakiety sieciowe. Arduino natomiast zmusza do pisania wszystkiego wewnątrz jednej pętli loop(), gdzie trzeba ręcznie śledzić stany za pomocą millis() i flag. To tworzy dwa krytyczne ograniczenia:

  • Brak możliwości izolowania logiki komponentów — timingi „przeciągają się” przez całą hierarchię obiektów
  • Brak wyraźnych kontraktów między modułami w kwestii wymagań czasowych

Standardowe biblioteki dla Arduino rozwiązują problem fragmentarycznie: są obsługiwacze przycisków, osobno timery, ale brakuje jednolitej systemu zdarzeń. Próby zebrania „wszystkiego w jednym” napotykają sztywną zależność od konkretnej implementacji loop().

Google AdInline article slot

EVA Core: trzy komponenty w jednolitym systemie

Biblioteka EVA Core (Event-Driven Architecture) rozwiązuje problem poprzez trzy współdziałające warstwy:

  • Mechanizm callbacków dla metod klas — pozwala subskrybować zdarzenia bez globalnych funkcji
  • Interfejs Tickable — daje obiektom dostęp do cyklu czasu poprzez metodę tick()
  • Survival Kit — zestaw gotowych komponentów (przyciski, timery), zbudowanych na dwóch pierwszych zasadach

Kluczowa innowacja — oddzielenie logiki czasowej od reguł biznesowych. Czujnik sam zarządza swoimi timingami, a aplikacja reaguje tylko na zdarzenia. To pozwala:

  • Izolować algorytmy filtracji i eliminacji drgań w komponentach
  • Gwarantować rytm aktualizacji dla zagnieżdżonych obiektów
  • Wyeliminować konieczność „przeciągania” wywołań przez hierarchię

Rozważmy implementację na przykładzie czujnika temperatury:

Google AdInline article slot
#include <evaTickable.h>
#include <evaHandler.h>

using namespace eva;

class TempSensor : public Tickable {
private:
  IHandler* listener = nullptr;
  unsigned long lastRead = 0;

public:
  void subscribe(IHandler* handler) {
    listener = handler;
  }

private:
  void tick() override {
    if (millis() - lastRead > 1000) { // Interwał 1 s
      int value = readTemperature();
      if (listener && value != lastValue) {
        CallbackInfo info;
        info.eventType = TEMP_UPDATE;
        info.eventArg = value;
        listener->invoke(this, info);
      }
      lastRead = millis();
    }
  }
};

class ClimateControl : public IHandler {
private:
  TempSensor sensor;

  void onTempUpdate(int value) {
    if (value > 25) activateCooling();
  }

public:
  ClimateControl() {
    sensor.subscribe(this);
  }

  void invoke(void* sender, CallbackInfo info) override {
    if (info.eventType == TEMP_UPDATE) {
      onTempUpdate(info.eventArg);
    }
  }
};

void setup() {
  static ClimateControl system;
}

void loop() {
  eva::tac(); // Jedna punkt aktualizacji
}

Zalety podejścia:

  • TempSensor enkapsuluje logikę odczytu i filtracji
  • ClimateControl zna tylko zdarzenia, nie szczegóły czasowe
  • Dodanie nowych czujników nie wymaga zmian w loop()
  • Testowanie logiki biznesowej izolowanie od czasu

LEGO-architektura dla peryferii

Survival Kit realizuje zasadę kompozycji poprzez szablony kompilacji. Na przykład, obsługa wieloprzycisku na wejściu analogowym budowana jest łańcuchem transformatorów:

template <int PIN, int PIN_MODE, signed short... LEVELS>
using PinMultiButton = Button<QuantizeDecor<DebounceDecor<AnalogPinReader<PIN, PIN_MODE>>, LEVELS...>>;

Architektura przetwarzania sygnału:

Google AdInline article slot
  • Sygnał analogowy →
  • Debauns →
  • Kwantyzacja po poziomach →
  • Logika przycisku →
  • Zdarzenia

Ten wzorzec pozwala składać niestandardowe obsługiwacze z gotowych komponentów. Przykład — klawiatura z dyskretnych przycisków:

#include <evaSwitch.h>

class MyKeypad {
public:
  MyKeypad() {
    pinMode(2, INPUT_PULLUP);
    // ... inicjalizacja pozostałych pinów
  }

  signed short getValue() {
    if (digitalRead(2) == LOW) return 'u';
    // ... obsługa innych przycisków
    return 0;
  }
};

// Składanie łańcucha przetwarzania
Switch<DebounceDecor<MyKeypad>> keypad;

// Subskrypcja zdarzeń
keypad.setListener(new Handler<App>(this, &App::onKeyPress), ON_PRESS);

Każda warstwa odpowiada za swoją strefę odpowiedzialności. W razie potrzeby można zastąpić debauns bardziej złożonym algorytmem lub dodać filtrację według czasu naciśnięcia — bez zmian w głównej logice aplikacji.

Ograniczenia i scenariusze zastosowań

EVA Core pozycjonowana jest jako narzędzie metodyczne, a nie uniwersalne rozwiązanie. Warto ją stosować przy:

  • Tworzeniu projektów ze skomplikowaną hierarchią obiektów
  • Potrzebie wyraźnego rozdzielenia logiki czasowej i biznesowej
  • Nauczaniu zasad architektury event-driven na prostych urządzeniach

Krytyczne ograniczenia:

  • Zwiększone zużycie RAM z powodu tabel wirtualnych
  • Złożoność dla początkujących (wymagana znajomość szablonów i wskaźników na metody)
  • Nadmiarowość w prostych projektach (np. sterowanie jedną diodą LED)

Ważne jest zrozumienie: celem biblioteki jest demonstracja wzorców architektonicznych, a nie optymalizacja zasobów. W projektach produkcyjnych z sztywnymi ograniczeniami will be required analiza narzutu.

Co ważne

  • Model zdarzeniowy pozwala izolować zależności czasowe w komponentach
  • Interfejs Tickable gwarantuje regularną aktualizację zagnieżdżonych obiektów
  • Szablonowa kompozycja daje elastyczność bez narzutów w runtime
  • Rezygnacja z „przeciągania” wywołań upraszcza refaktoring i testowanie
  • Arduino staje się platformą do nauki wzorców enterprise

Podejście dowodzi, że nawet na ograniczonych mikrokontrolerach można stosować profesjonalne metody projektowania. Kluczowy zysk — kod staje się przewidywalny i skalowalny, a nie „byle działało”.

— Editorial Team

Advertisement 728x90

Czytaj dalej