diff --git a/Seilbahn/README.md b/Seilbahn/README.md new file mode 100644 index 0000000..aec4e96 --- /dev/null +++ b/Seilbahn/README.md @@ -0,0 +1,202 @@ +# Doppelmayr Modell-Seilbahn + +Arduino-basierte Steuerung einer Doppelmayr Modell-Seilbahn. Die Steuerung bildet das Bedienpanel einer echten Anlage nach — mit Taster-LED-Kombinationen, Betriebsmodi und einer vollständigen Fahrtrichtungssteuerung. + +## Hardware + +**Mikrocontroller:** Arduino Mega 2560 + +**Bedienpanel:** + +| Taster / LED | Funktion | +|----------------------------|-------------------------------------------------------| +| Anlage Ein/Aus | Anlage ein- und ausschalten | +| Station Besetzt/Unbesetzt | Stationsstatus anzeigen | +| Servicebetrieb | In den Servicemodus wechseln | +| Fahrgastbetrieb | In den Fahrgastmodus wechseln | +| Quittieren | Zustandswechsel bestätigen | +| Start | Fahrt starten | +| Halt | Anlage anhalten | +| Nothalt | Anlage sofort stoppen | +| Vorwärts | Fahrtrichtung Vorwärts wählen | +| Rückwärts | Fahrtrichtung Rückwärts wählen | + +**Ereignisanzeige (3-farbig, ohne Taster):** + +| LED | Bedeutung | +|-------|----------------------------------| +| Rot | Servicebetrieb aktiv | +| Gelb | Zustandswechsel wartet auf Quittierung | +| Grün | Fahrgastbetrieb aktiv | + +## Pinbelegung + +| Signal | Pin | +|-----------------------------|-----| +| LED Anlage Ein/Aus | 2 | +| Taster Anlage Ein/Aus | 3 | +| LED Station | 4 | +| Taster Station | 5 | +| LED Servicebetrieb | 6 | +| Taster Servicebetrieb | 7 | +| LED Quittieren | 9 | +| Taster Quittieren | 10 | +| LED Grün Ereignisanzeige | 11 | +| LED Gelb Ereignisanzeige | 12 | +| LED Rot Ereignisanzeige | 13 | +| LED Fahrgastbetrieb | 22 | +| Taster Fahrgastbetrieb | 23 | +| LED Vorwärts | 30 | +| Taster Vorwärts | 31 | +| LED Rückwärts | 32 | +| Taster Rückwärts | 33 | +| LED Start | 36 | +| Taster Start | 37 | +| LED Halt | 38 | +| Taster Halt | 39 | +| LED Nothalt | 40 | +| Taster Nothalt | 41 | + +## Software + +### Architektur + +``` +main.cpp + └── CMainController + ├── CEventQueue (zentrale Event-Queue) + ├── CButton × 10 (je ein Objekt pro Taster/LED-Paar) + └── Zustandsmaschinen (AnlagenZustand + FahrtRichtung) +``` + +Die Steuerung basiert auf einer **Event-Queue**: Jeder `CButton` erkennt Tastendrücke per Flankenauswertung mit Entprellung und schreibt einen `SEvent` in die zentrale Queue. Der `CMainController` verarbeitet diese Events jeden Loop-Durchlauf und aktualisiert die Zustände sowie alle LEDs. + +**Loop-Ablauf in `CMainController::work()`:** +1. `pollButtons()` — alle relevanten Buttons abfragen, Events in Queue schreiben +2. `processEvents()` — Events aus Queue lesen, Zustand ändern +3. `updateLEDs()` — alle LEDs anhand des aktuellen Zustands setzen + +### LED-Modi + +LEDs können drei Zustände haben, die in `CButton` verwaltet werden: + +| Modus | Verhalten | +|----------------|------------------------------------| +| `LEDMode::OFF` | Aus | +| `LEDMode::ON` | Dauerhaft an | +| `LEDMode::BLINK` | Blinkt (500 ms Intervall) | + +Alle LEDs im `BLINK`-Modus blinken synchron, da der Blink-Timer als `static` Member in `CButton` für alle Instanzen geteilt wird. + +### Zustandsmaschinen + +#### AnlagenZustand + +```mermaid +stateDiagram-v2 + [*] --> AUS + + AUS --> SERVICE : BTN_ANLAGE_EIN_AUS + + SERVICE --> AUS : BTN_ANLAGE_EIN_AUS + SERVICE --> FAHRGAST_QUITTIEREN : BTN_FAHRGAST + + FAHRGAST_QUITTIEREN --> AUS : BTN_ANLAGE_EIN_AUS + FAHRGAST_QUITTIEREN --> FAHRGAST : BTN_QUIT + + FAHRGAST --> AUS : BTN_ANLAGE_EIN_AUS + FAHRGAST --> SERVICE_QUITTIEREN : BTN_SERVICE + + SERVICE_QUITTIEREN --> AUS : BTN_ANLAGE_EIN_AUS + SERVICE_QUITTIEREN --> SERVICE : BTN_QUIT +``` + +#### FahrtRichtung + +Aktiv wenn `AnlagenZustand == SERVICE` oder `AnlagenZustand == FAHRGAST`. + +```mermaid +stateDiagram-v2 + [*] --> HALT + + HALT --> VORWAERTS_QUITTIEREN : BTN_VORWAERTS + HALT --> RUECKWAERTS_QUITTIEREN : BTN_RUECKWAERTS + HALT --> WARTE_START_VORWAERTS : BTN_QUIT\n[vorherigeRichtung != RUECKWAERTS] + HALT --> WARTE_START_RUECKWAERTS : BTN_QUIT\n[vorherigeRichtung == RUECKWAERTS] + + NOTHALT --> VORWAERTS_QUITTIEREN : BTN_VORWAERTS + NOTHALT --> RUECKWAERTS_QUITTIEREN : BTN_RUECKWAERTS + NOTHALT --> WARTE_START_VORWAERTS : BTN_QUIT\n[vorherigeRichtung != RUECKWAERTS] + NOTHALT --> WARTE_START_RUECKWAERTS : BTN_QUIT\n[vorherigeRichtung == RUECKWAERTS] + + VORWAERTS_QUITTIEREN --> WARTE_START_VORWAERTS : BTN_QUIT + VORWAERTS_QUITTIEREN --> RUECKWAERTS_QUITTIEREN : BTN_RUECKWAERTS + VORWAERTS_QUITTIEREN --> HALT : BTN_HALT + VORWAERTS_QUITTIEREN --> NOTHALT : BTN_NOTHALT + + WARTE_START_VORWAERTS --> VORWAERTS : BTN_START + + VORWAERTS --> HALT : BTN_HALT + VORWAERTS --> NOTHALT : BTN_NOTHALT + + RUECKWAERTS_QUITTIEREN --> WARTE_START_RUECKWAERTS : BTN_QUIT + RUECKWAERTS_QUITTIEREN --> VORWAERTS_QUITTIEREN : BTN_VORWAERTS + RUECKWAERTS_QUITTIEREN --> HALT : BTN_HALT + RUECKWAERTS_QUITTIEREN --> NOTHALT : BTN_NOTHALT + + WARTE_START_RUECKWAERTS --> RUECKWAERTS : BTN_START + + RUECKWAERTS --> HALT : BTN_HALT + RUECKWAERTS --> NOTHALT : BTN_NOTHALT +``` + +## PlatformIO + +Das Projekt verwendet [PlatformIO](https://platformio.org) als Build-System — eine Alternative zur Arduino IDE mit deutlich besserer Projekt- und Bibliotheksverwaltung. + +### Voraussetzungen + +- [VS Code](https://code.visualstudio.com/) mit der [PlatformIO-Extension](https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide) +- oder PlatformIO Core (CLI) via `pip install platformio` + +### Projekt bauen und flashen + +```bash +# Projekt kompilieren +pio run + +# Auf den Arduino flashen (USB) +pio run --target upload + +# Seriellen Monitor öffnen (9600 Baud) +pio device monitor +``` + +### Konfiguration (`platformio.ini`) + +```ini +[env:megaatmega2560] +platform = atmelavr +board = megaatmega2560 +framework = arduino +lib_deps = mike-matera/ArduinoSTL@^1.3.3 +``` + +- **board:** Arduino Mega 2560 +- **lib_deps:** ArduinoSTL — stellt `std::vector` für die EventQueue bereit, da die Arduino-Standardbibliothek keine STL enthält + +### Projektstruktur + +``` +Seilbahn/ +├── platformio.ini # Board- und Bibliothekskonfiguration +├── src/ +│ ├── main.cpp # setup() und loop() +│ ├── CMainController # Hauptsteuerung, besitzt alle Objekte +│ ├── CButton # Taster-LED-Abstraktion mit Entprellung +│ ├── CEventQueue # FIFO-Queue für Button-Events +│ └── SEvent # Event-Typ und -Struktur +├── include/ # Globale Header (leer) +├── lib/ # Projektlokale Bibliotheken (leer) +└── test/ # Unit-Tests (PlatformIO-Testframework) +``` diff --git a/Seilbahn/platformio.ini b/Seilbahn/platformio.ini index 67e8cb2..9a2cb2e 100644 --- a/Seilbahn/platformio.ini +++ b/Seilbahn/platformio.ini @@ -12,3 +12,4 @@ platform = atmelavr board = megaatmega2560 framework = arduino +lib_deps = mike-matera/ArduinoSTL@^1.3.3 diff --git a/Seilbahn/src/CButton.cpp b/Seilbahn/src/CButton.cpp index 709d5e1..4c2ebea 100644 --- a/Seilbahn/src/CButton.cpp +++ b/Seilbahn/src/CButton.cpp @@ -1,47 +1,83 @@ #include "CButton.h" - #include "Arduino.h" -CButton::CButton(short btnPin, short ledPin) +// Einmalige Definition der static-Member (Speicher wird hier reserviert) +unsigned long CButton::s_lastBlinkTime = 0; +bool CButton::s_blinkState = false; + +CButton::CButton(short btnPin, short ledPin, EVENT_TYPE eventType, CEventQueue* queue) + : m_btnPin(btnPin) + , m_ledPin(ledPin) + , m_eventType(eventType) + , m_queue(queue) + , m_currentButtonState(false) + , m_buttonState(false) + , m_ledMode(LEDMode::OFF) { - m_btnPin = btnPin; - m_ledPin = ledPin; - m_currentButtonState = false; - m_buttonState = false; } -bool CButton::getLED() +void CButton::setup() { - return digitalRead(m_ledPin); + pinMode(m_btnPin, INPUT_PULLUP); + pinMode(m_ledPin, OUTPUT); } -void CButton::setLED(bool status) +void CButton::setLEDMode(LEDMode mode) { - digitalWrite(m_ledPin, status); + m_ledMode = mode; +} + +LEDMode CButton::getLEDMode() +{ + return m_ledMode; } void CButton::work() { - // Taster abfragen und aktuellen Zustand zwischenspeichern + // ===== LED aktualisieren ===== + switch (m_ledMode) + { + case LEDMode::ON: + digitalWrite(m_ledPin, HIGH); + break; + + case LEDMode::OFF: + digitalWrite(m_ledPin, LOW); + break; + + case LEDMode::BLINK: + // s_lastBlinkTime und s_blinkState sind static → werden von allen + // Instanzen geteilt. Der erste Button der in diesem Loop work() aufruft + // aktualisiert den Timer — alle anderen lesen denselben Zustand. + // So blinken alle LEDs im BLINK-Modus exakt synchron. + if (millis() - s_lastBlinkTime >= BLINK_INTERVAL_MS) + { + s_lastBlinkTime = millis(); + s_blinkState = !s_blinkState; + } + digitalWrite(m_ledPin, s_blinkState ? HIGH : LOW); + break; + } + + // ===== Button abfragen ===== m_currentButtonState = digitalRead(m_btnPin) == LOW; - // Aktuellen Zustand mit vorherigem vergleichen + + // Flankenauswertung: nur bei steigender Flanke reagieren (nicht gedrückt → gedrückt) if (m_currentButtonState && !m_buttonState) { - // Warten zum entprellen + // Entprellen delay(20); - // Taster abfragen und aktuellen Zustand zwischenspeichern m_currentButtonState = digitalRead(m_btnPin) == LOW; - // Prüfen ob immernoch gedrückt - if (m_currentButtonState == LOW) - { - // Knopf gedrückt => Code ausführen - - // ============================================================ - fahrtrichtung = HALT; - // ============================================================ - // Speichern des ButtonStates als Vergleichswert für den nächsten Durchlauf - m_buttonState = m_currentButtonState; - } + if (m_currentButtonState) + { + SEvent event; + event.m_eventType = m_eventType; + event.m_additionalInfo = 0; + m_queue->pushEvent(event); + } } + + // Immer aktualisieren — so wird der Button nach dem Loslassen wieder erkannt + m_buttonState = m_currentButtonState; } \ No newline at end of file diff --git a/Seilbahn/src/CButton.h b/Seilbahn/src/CButton.h index 3b8325d..0ffc9ea 100644 --- a/Seilbahn/src/CButton.h +++ b/Seilbahn/src/CButton.h @@ -1,24 +1,36 @@ #ifndef CBUTTON_H_ #define CBUTTON_H_ +#include "SEvent.h" +#include "CEventQueue.h" + +enum class LEDMode { OFF, ON, BLINK }; + class CButton { public: - CButton(short btnPin, short ledPin); - void connect(); // Button mit Controller "verbinden" - void work(); // Button periodisch abfragen - - bool getLED(); // LED status abfragen - void setLED(bool status); // LED an/ausschalten - + CButton(short btnPin, short ledPin, EVENT_TYPE eventType, CEventQueue* queue); + void setup(); // pinMode setzen (im setup() aufrufen) + void work(); // Button abfragen + LED-Zustand aktualisieren + + void setLEDMode(LEDMode mode); + LEDMode getLEDMode(); private: - short m_btnPin; // Pinnummer Button am Arduino - short m_ledPin; // Pinnummer LED am Arduino + short m_btnPin; + short m_ledPin; + EVENT_TYPE m_eventType; + CEventQueue* m_queue; - bool m_currentButtonState; // Hilfsvariable für aktuellen Status - bool m_buttonState; // Hilfsvariable für letzten Status + bool m_currentButtonState; + bool m_buttonState; -} + LEDMode m_ledMode; -#endif /* CBUTTON_H_ */ \ No newline at end of file + // static: einmal für alle CButton-Instanzen — sorgt für synchrones Blinken + static unsigned long s_lastBlinkTime; + static bool s_blinkState; + static const int BLINK_INTERVAL_MS = 500; +}; + +#endif /* CBUTTON_H_ */ diff --git a/Seilbahn/src/CMainController.cpp b/Seilbahn/src/CMainController.cpp index e69de29..bfde98e 100644 --- a/Seilbahn/src/CMainController.cpp +++ b/Seilbahn/src/CMainController.cpp @@ -0,0 +1,363 @@ +#include "CMainController.h" +#include "Arduino.h" + +// Initialisierungsliste: m_queue wird zuerst gebaut (steht zuerst im Header), +// danach die Buttons — jeder bekommt &m_queue, was zu diesem Zeitpunkt bereits +// gültig ist. +CMainController::CMainController() + : m_queue() + , m_btnAnlageEinAus (PIN_KNOPF_ANLAGE_EIN_AUS, PIN_LED_ANLAGE_EIN_AUS, BTN_ANLAGE_EIN_AUS, &m_queue) + , m_btnStation (PIN_KNOPF_STATION, PIN_LED_STATION, BTN_STATION, &m_queue) + , m_btnServicebetrieb(PIN_KNOPF_SERVICEBETRIEB, PIN_LED_SERVICEBETRIEB, BTN_SERVICE, &m_queue) + , m_btnFahrgastbetrieb(PIN_KNOPF_FAHRGASTBETRIEB, PIN_LED_FAHRGASTBETRIEB, BTN_FAHRGAST, &m_queue) + , m_btnQuitBetrieb (PIN_KNOPF_QUIT_BETRIEB, PIN_LED_QUIT_BETRIEB, BTN_QUIT, &m_queue) + , m_btnStart (PIN_KNOPF_START_BETRIEB, PIN_LED_START_BETRIEB, BTN_START, &m_queue) + , m_btnHalt (PIN_KNOPF_HALT, PIN_LED_HALT, BTN_HALT, &m_queue) + , m_btnNothalt (PIN_KNOPF_NOTHALT, PIN_LED_NOTHALT, BTN_NOTHALT, &m_queue) + , m_btnVorwaerts (PIN_KNOPF_VORWAERTS, PIN_LED_VORWAERTS, BTN_VORWAERTS, &m_queue) + , m_btnRueckwaerts (PIN_KNOPF_RUECKWAERTS, PIN_LED_RUECKWAERTS, BTN_RUECKWAERTS, &m_queue) + , m_anlagenzustand(AnlagenZustand::AUS) + , m_fahrtrichtung(FahrtRichtung::HALT) + , m_vorherigeRichtung(FahrtRichtung::HALT) +{ +} + +// ============================================================ +// setup(): einmalig beim Start aufrufen +// ============================================================ +void CMainController::setup() +{ + // Jeden Button mit setup() initialisieren (setzt pinMode für Taster + LED) + m_btnAnlageEinAus.setup(); + m_btnStation.setup(); + m_btnServicebetrieb.setup(); + m_btnFahrgastbetrieb.setup(); + m_btnQuitBetrieb.setup(); + m_btnStart.setup(); + m_btnHalt.setup(); + m_btnNothalt.setup(); + m_btnVorwaerts.setup(); + m_btnRueckwaerts.setup(); + + // Die drei Ereignis-LEDs haben keinen Button → direkt hier initialisieren + pinMode(PIN_LED_ROT_EREIGNISANZEIGE, OUTPUT); + pinMode(PIN_LED_GELB_EREIGNISANZEIGE, OUTPUT); + pinMode(PIN_LED_GRUEN_EREIGNISANZEIGE, OUTPUT); +} + +// ============================================================ +// work(): jeden Loop-Durchlauf aufrufen +// ============================================================ +void CMainController::work() +{ + pollButtons(); // 1. Buttons abfragen → Events landen in der Queue + processEvents(); // 2. Events verarbeiten → Zustand ändern + updateLEDs(); // 3. LEDs anhand des neuen Zustands setzen +} + +// ============================================================ +// pollButtons(): work() aller Buttons aufrufen +// ============================================================ +void CMainController::pollButtons() +{ + // Welche Buttons abgefragt werden, hängt vom aktuellen Anlagenzustand ab. + // So können z.B. im AUS-Zustand nur relevante Buttons Events erzeugen. + switch (m_anlagenzustand) + { + case AnlagenZustand::AUS: + m_btnAnlageEinAus.work(); + break; + + case AnlagenZustand::SERVICE_QUITTIEREN: + case AnlagenZustand::FAHRGAST_QUITTIEREN: + m_btnAnlageEinAus.work(); + m_btnQuitBetrieb.work(); + break; + + case AnlagenZustand::SERVICE: + m_btnAnlageEinAus.work(); + m_btnFahrgastbetrieb.work(); + m_btnQuitBetrieb.work(); + m_btnStart.work(); + m_btnHalt.work(); + m_btnNothalt.work(); + m_btnVorwaerts.work(); + m_btnRueckwaerts.work(); + break; + + case AnlagenZustand::FAHRGAST: + m_btnAnlageEinAus.work(); + m_btnServicebetrieb.work(); + m_btnQuitBetrieb.work(); + m_btnStart.work(); + m_btnHalt.work(); + m_btnNothalt.work(); + m_btnVorwaerts.work(); + m_btnRueckwaerts.work(); + break; + + default: + break; + } +} + +// ============================================================ +// processEvents(): alle Events in der Queue verarbeiten +// ============================================================ +void CMainController::processEvents() +{ + bool valid; + SEvent ev = m_queue.popEvent(&valid); + + while (valid) + { + switch (m_anlagenzustand) + { + // ==================== AUS ==================== + case AnlagenZustand::AUS: + if (ev.m_eventType == BTN_ANLAGE_EIN_AUS) + m_anlagenzustand = AnlagenZustand::SERVICE; + break; + + // ==================== SERVICE_QUITTIEREN ==================== + // Zustandswechsel nach SERVICE oder FAHRGAST wurde ausgelöst, + // Nutzer muss erst quittieren bevor es weitergeht. + case AnlagenZustand::SERVICE_QUITTIEREN: + if (ev.m_eventType == BTN_ANLAGE_EIN_AUS) + m_anlagenzustand = AnlagenZustand::AUS; + else if (ev.m_eventType == BTN_QUIT) + m_anlagenzustand = AnlagenZustand::SERVICE; + break; + + // ==================== SERVICE ==================== + case AnlagenZustand::SERVICE: + if (ev.m_eventType == BTN_ANLAGE_EIN_AUS) + m_anlagenzustand = AnlagenZustand::AUS; + else if (ev.m_eventType == BTN_FAHRGAST) + m_anlagenzustand = AnlagenZustand::FAHRGAST_QUITTIEREN; + else + processFahrtrichtungEvent(ev); // Fahrtrichtungs-Events weiterleiten + break; + + // ==================== FAHRGAST_QUITTIEREN ==================== + case AnlagenZustand::FAHRGAST_QUITTIEREN: + if (ev.m_eventType == BTN_ANLAGE_EIN_AUS) + m_anlagenzustand = AnlagenZustand::AUS; + else if (ev.m_eventType == BTN_QUIT) + m_anlagenzustand = AnlagenZustand::FAHRGAST; + break; + + // ==================== FAHRGAST ==================== + case AnlagenZustand::FAHRGAST: + if (ev.m_eventType == BTN_ANLAGE_EIN_AUS) + m_anlagenzustand = AnlagenZustand::AUS; + else if (ev.m_eventType == BTN_SERVICE) + m_anlagenzustand = AnlagenZustand::SERVICE_QUITTIEREN; + else + processFahrtrichtungEvent(ev); + break; + + default: + break; + } + + ev = m_queue.popEvent(&valid); // nächstes Event holen + } +} + +// ============================================================ +// processFahrtrichtungEvent(): Fahrtrichtungs-Statemachine +// Wird von processEvents() aufgerufen wenn der Anlagenzustand +// SERVICE oder FAHRGAST ist und kein Anlagen-Event vorliegt. +// ============================================================ +void CMainController::processFahrtrichtungEvent(SEvent ev) +{ + switch (m_fahrtrichtung) + { + // ===== Anlage steht (normal oder Nothalt) ===== + case FahrtRichtung::HALT: + case FahrtRichtung::NOTHALT: + if (ev.m_eventType == BTN_QUIT) + { + // Richtung von vor dem Halt wiederherstellen + if (m_vorherigeRichtung == FahrtRichtung::RUECKWAERTS) + m_fahrtrichtung = FahrtRichtung::WARTE_START_RUECKWAERTS; + else + m_fahrtrichtung = FahrtRichtung::WARTE_START_VORWAERTS; + } + else if (ev.m_eventType == BTN_VORWAERTS) + m_fahrtrichtung = FahrtRichtung::VORWAERTS_QUITTIEREN; + else if (ev.m_eventType == BTN_RUECKWAERTS) + m_fahrtrichtung = FahrtRichtung::RUECKWAERTS_QUITTIEREN; + break; + + // ===== Vorwärts gewählt, wartet auf Quittieren ===== + case FahrtRichtung::VORWAERTS_QUITTIEREN: + if (ev.m_eventType == BTN_QUIT) + m_fahrtrichtung = FahrtRichtung::WARTE_START_VORWAERTS; + else if (ev.m_eventType == BTN_HALT) + m_fahrtrichtung = FahrtRichtung::HALT; + else if (ev.m_eventType == BTN_NOTHALT) + m_fahrtrichtung = FahrtRichtung::NOTHALT; + else if (ev.m_eventType == BTN_RUECKWAERTS) + m_fahrtrichtung = FahrtRichtung::RUECKWAERTS_QUITTIEREN; + break; + + // ===== Quittiert, wartet auf Start ===== + case FahrtRichtung::WARTE_START_VORWAERTS: + if (ev.m_eventType == BTN_START) + { + m_fahrtrichtung = FahrtRichtung::VORWAERTS; + m_vorherigeRichtung = FahrtRichtung::VORWAERTS; + } + break; + + // ===== Anlage fährt vorwärts ===== + case FahrtRichtung::VORWAERTS: + if (ev.m_eventType == BTN_HALT) + m_fahrtrichtung = FahrtRichtung::HALT; + else if (ev.m_eventType == BTN_NOTHALT) + m_fahrtrichtung = FahrtRichtung::NOTHALT; + break; + + // ===== Rückwärts gewählt, wartet auf Quittieren ===== + case FahrtRichtung::RUECKWAERTS_QUITTIEREN: + if (ev.m_eventType == BTN_QUIT) + m_fahrtrichtung = FahrtRichtung::WARTE_START_RUECKWAERTS; + else if (ev.m_eventType == BTN_HALT) + m_fahrtrichtung = FahrtRichtung::HALT; + else if (ev.m_eventType == BTN_NOTHALT) + m_fahrtrichtung = FahrtRichtung::NOTHALT; + else if (ev.m_eventType == BTN_VORWAERTS) + m_fahrtrichtung = FahrtRichtung::VORWAERTS_QUITTIEREN; + break; + + // ===== Quittiert, wartet auf Start ===== + case FahrtRichtung::WARTE_START_RUECKWAERTS: + if (ev.m_eventType == BTN_START) + { + m_fahrtrichtung = FahrtRichtung::RUECKWAERTS; + m_vorherigeRichtung = FahrtRichtung::RUECKWAERTS; + } + break; + + // ===== Anlage fährt rückwärts ===== + case FahrtRichtung::RUECKWAERTS: + if (ev.m_eventType == BTN_HALT) + m_fahrtrichtung = FahrtRichtung::HALT; + else if (ev.m_eventType == BTN_NOTHALT) + m_fahrtrichtung = FahrtRichtung::NOTHALT; + break; + + default: + break; + } +} + +// ============================================================ +// updateLEDs(): alle LEDs anhand des aktuellen Zustands setzen +// Wird jedes Loop aufgerufen → LEDs sind immer konsistent. +// ============================================================ +void CMainController::updateLEDs() +{ + // ===== Anlage Ein/Aus ===== + // LED leuchtet solange die Anlage eingeschaltet ist + if (m_anlagenzustand != AnlagenZustand::AUS) + m_btnAnlageEinAus.setLEDMode(LEDMode::ON); + else + m_btnAnlageEinAus.setLEDMode(LEDMode::OFF); + + // ===== Station Besetzt/Unbesetzt ===== + // LED leuchtet solange die Anlage eingeschaltet ist + if (m_anlagenzustand != AnlagenZustand::AUS) + m_btnStation.setLEDMode(LEDMode::ON); + else + m_btnStation.setLEDMode(LEDMode::OFF); + + // ===== Servicebetrieb ===== + // LED leuchtet im Servicezustand (auch während Quittieren) + if (m_anlagenzustand == AnlagenZustand::SERVICE + || m_anlagenzustand == AnlagenZustand::SERVICE_QUITTIEREN) + m_btnServicebetrieb.setLEDMode(LEDMode::ON); + else + m_btnServicebetrieb.setLEDMode(LEDMode::OFF); + + // ===== Fahrgastbetrieb ===== + // LED leuchtet im Fahrgastzustand (auch während Quittieren) + if (m_anlagenzustand == AnlagenZustand::FAHRGAST + || m_anlagenzustand == AnlagenZustand::FAHRGAST_QUITTIEREN) + m_btnFahrgastbetrieb.setLEDMode(LEDMode::ON); + else + m_btnFahrgastbetrieb.setLEDMode(LEDMode::OFF); + + // ===== Quittieren ===== + // LED blinkt wenn ein Zustandswechsel auf Quittierung wartet + if (m_anlagenzustand == AnlagenZustand::SERVICE_QUITTIEREN + || m_anlagenzustand == AnlagenZustand::FAHRGAST_QUITTIEREN) + m_btnQuitBetrieb.setLEDMode(LEDMode::BLINK); + else + m_btnQuitBetrieb.setLEDMode(LEDMode::OFF); + + // ===== Ereignisanzeige (3-farbig) ===== + // Keine Buttons → direkt per digitalWrite gesetzt + // Rot: Servicebetrieb aktiv + if (m_anlagenzustand == AnlagenZustand::SERVICE) + digitalWrite(PIN_LED_ROT_EREIGNISANZEIGE, HIGH); + else + digitalWrite(PIN_LED_ROT_EREIGNISANZEIGE, LOW); + + // Gelb: Zustandswechsel wartet auf Quittierung + if (m_anlagenzustand == AnlagenZustand::SERVICE_QUITTIEREN + || m_anlagenzustand == AnlagenZustand::FAHRGAST_QUITTIEREN) + digitalWrite(PIN_LED_GELB_EREIGNISANZEIGE, HIGH); + else + digitalWrite(PIN_LED_GELB_EREIGNISANZEIGE, LOW); + + // Grün: Fahrgastbetrieb aktiv + if (m_anlagenzustand == AnlagenZustand::FAHRGAST) + digitalWrite(PIN_LED_GRUEN_EREIGNISANZEIGE, HIGH); + else + digitalWrite(PIN_LED_GRUEN_EREIGNISANZEIGE, LOW); + + // ===== Fahrtrichtungs-LEDs ===== + // Nur relevant wenn die Anlage eingeschaltet ist + + // Halt: leuchtet wenn Anlage steht (normaler Halt) + if (m_anlagenzustand != AnlagenZustand::AUS && m_fahrtrichtung == FahrtRichtung::HALT) + m_btnHalt.setLEDMode(LEDMode::ON); + else + m_btnHalt.setLEDMode(LEDMode::OFF); + + // Nothalt: leuchtet wenn Nothalt aktiv + if (m_anlagenzustand != AnlagenZustand::AUS && m_fahrtrichtung == FahrtRichtung::NOTHALT) + m_btnNothalt.setLEDMode(LEDMode::ON); + else + m_btnNothalt.setLEDMode(LEDMode::OFF); + + // Start: blinkt wenn quittiert wurde und auf Start gewartet wird + if (m_anlagenzustand != AnlagenZustand::AUS && + (m_fahrtrichtung == FahrtRichtung::WARTE_START_VORWAERTS || m_fahrtrichtung == FahrtRichtung::WARTE_START_RUECKWAERTS) ) + m_btnStart.setLEDMode(LEDMode::BLINK); + else + m_btnStart.setLEDMode(LEDMode::OFF); + + // Vorwärts: leuchtet dauerhaft nur wenn Anlage tatsächlich vorwärts fährt, + // blinkt während Quittieren und Warten auf Start + if (m_anlagenzustand != AnlagenZustand::AUS && m_fahrtrichtung == FahrtRichtung::VORWAERTS) + m_btnVorwaerts.setLEDMode(LEDMode::ON); + else if (m_anlagenzustand != AnlagenZustand::AUS && + (m_fahrtrichtung == FahrtRichtung::VORWAERTS_QUITTIEREN || m_fahrtrichtung == FahrtRichtung::WARTE_START_VORWAERTS)) + m_btnVorwaerts.setLEDMode(LEDMode::BLINK); + else + m_btnVorwaerts.setLEDMode(LEDMode::OFF); + + // Rückwärts: leuchtet dauerhaft nur wenn Anlage tatsächlich rückwärts fährt, + // blinkt während Quittieren und Warten auf Start + if (m_anlagenzustand != AnlagenZustand::AUS && m_fahrtrichtung == FahrtRichtung::RUECKWAERTS) + m_btnRueckwaerts.setLEDMode(LEDMode::ON); + else if (m_anlagenzustand != AnlagenZustand::AUS && + (m_fahrtrichtung == FahrtRichtung::RUECKWAERTS_QUITTIEREN || m_fahrtrichtung == FahrtRichtung::WARTE_START_RUECKWAERTS)) + m_btnRueckwaerts.setLEDMode(LEDMode::BLINK); + else + m_btnRueckwaerts.setLEDMode(LEDMode::OFF); +} diff --git a/Seilbahn/src/CMainController.h b/Seilbahn/src/CMainController.h index d494d27..9430621 100644 --- a/Seilbahn/src/CMainController.h +++ b/Seilbahn/src/CMainController.h @@ -1,13 +1,94 @@ #ifndef CSEILMAINCONTROLLER_H_ #define CSEILMAINCONTROLLER_H_ -#include +#include "CButton.h" +#include "CEventQueue.h" +#include "SEvent.h" + +// enum class = scoped enum → Werte sind nicht global sichtbar, sondern nur als +// AnlagenZustand::AUS, AnlagenZustand::SERVICE etc. +// Das verhindert Namenskonflikte mit den gleichnamigen Werten in main.cpp. +enum class AnlagenZustand +{ + AUS, + SERVICE_QUITTIEREN, + SERVICE, + FAHRGAST_QUITTIEREN, + FAHRGAST, +}; + +enum class FahrtRichtung +{ + HALT, + NOTHALT, + VORWAERTS_QUITTIEREN, + WARTE_START_VORWAERTS, + VORWAERTS, + RUECKWAERTS_QUITTIEREN, + WARTE_START_RUECKWAERTS, + RUECKWAERTS, +}; class CMainController { public: + CMainController(); + void setup(); // pinMode aller Buttons und LEDs setzen + void work(); // Buttons abfragen, Events verarbeiten, LEDs aktualisieren private: -} + // ===== Pin-Definitionen ===== + static const short PIN_LED_ANLAGE_EIN_AUS = 2; + static const short PIN_KNOPF_ANLAGE_EIN_AUS = 3; + static const short PIN_LED_STATION = 4; + static const short PIN_KNOPF_STATION = 5; + static const short PIN_LED_SERVICEBETRIEB = 6; + static const short PIN_KNOPF_SERVICEBETRIEB = 7; + static const short PIN_LED_FAHRGASTBETRIEB = 22; + static const short PIN_KNOPF_FAHRGASTBETRIEB = 23; + static const short PIN_LED_ROT_EREIGNISANZEIGE = 13; + static const short PIN_LED_GELB_EREIGNISANZEIGE = 12; + static const short PIN_LED_GRUEN_EREIGNISANZEIGE = 11; + static const short PIN_LED_QUIT_BETRIEB = 9; + static const short PIN_KNOPF_QUIT_BETRIEB = 10; + static const short PIN_LED_START_BETRIEB = 36; + static const short PIN_KNOPF_START_BETRIEB = 37; + static const short PIN_LED_HALT = 38; + static const short PIN_KNOPF_HALT = 39; + static const short PIN_LED_NOTHALT = 40; + static const short PIN_KNOPF_NOTHALT = 41; + static const short PIN_LED_VORWAERTS = 30; + static const short PIN_KNOPF_VORWAERTS = 31; + static const short PIN_LED_RUECKWAERTS = 32; + static const short PIN_KNOPF_RUECKWAERTS = 33; -#endif /* CSEILMAINCONTROLLER_H_ */ \ No newline at end of file + // ===== EventQueue ===== + // Muss VOR den Buttons deklariert sein! Member werden in Deklarationsreihenfolge + // initialisiert — die Buttons bekommen einen Zeiger auf diese Queue. + CEventQueue m_queue; + + // ===== Buttons ===== + CButton m_btnAnlageEinAus; + CButton m_btnStation; + CButton m_btnServicebetrieb; + CButton m_btnFahrgastbetrieb; + CButton m_btnQuitBetrieb; + CButton m_btnStart; + CButton m_btnHalt; + CButton m_btnNothalt; + CButton m_btnVorwaerts; + CButton m_btnRueckwaerts; + + // ===== Zustandsvariablen ===== + AnlagenZustand m_anlagenzustand; + FahrtRichtung m_fahrtrichtung; + FahrtRichtung m_vorherigeRichtung; // Merkt sich die letzte Fahrtrichtung für Quittieren nach HALT/NOTHALT + + // ===== Private Hilfsmethoden ===== + void pollButtons(); // Alle work()-Methoden der Buttons aufrufen + void processEvents(); // Events aus der Queue lesen und Zustand ändern + void processFahrtrichtungEvent(SEvent ev); // Fahrtrichtungs-Statemachine + void updateLEDs(); // Alle LEDs anhand des aktuellen Zustands setzen +}; + +#endif /* CMAINCONTROLLER_H_ */