Compare commits

..

2 Commits

Author SHA1 Message Date
43497376cb First OOP draft, Added readme 2026-03-16 23:05:03 +01:00
c4861d9793 First OOP parts 2026-03-16 21:40:29 +01:00
10 changed files with 944 additions and 0 deletions

202
Seilbahn/README.md Normal file
View File

@ -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)
```

View File

@ -12,3 +12,4 @@
platform = atmelavr platform = atmelavr
board = megaatmega2560 board = megaatmega2560
framework = arduino framework = arduino
lib_deps = mike-matera/ArduinoSTL@^1.3.3

83
Seilbahn/src/CButton.cpp Normal file
View File

@ -0,0 +1,83 @@
#include "CButton.h"
#include "Arduino.h"
// 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)
{
}
void CButton::setup()
{
pinMode(m_btnPin, INPUT_PULLUP);
pinMode(m_ledPin, OUTPUT);
}
void CButton::setLEDMode(LEDMode mode)
{
m_ledMode = mode;
}
LEDMode CButton::getLEDMode()
{
return m_ledMode;
}
void CButton::work()
{
// ===== 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;
// Flankenauswertung: nur bei steigender Flanke reagieren (nicht gedrückt → gedrückt)
if (m_currentButtonState && !m_buttonState)
{
// Entprellen
delay(20);
m_currentButtonState = digitalRead(m_btnPin) == LOW;
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;
}

36
Seilbahn/src/CButton.h Normal file
View File

@ -0,0 +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, 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;
short m_ledPin;
EVENT_TYPE m_eventType;
CEventQueue* m_queue;
bool m_currentButtonState;
bool m_buttonState;
LEDMode m_ledMode;
// 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_ */

View File

@ -0,0 +1,94 @@
#include "CEventQueue.h"
/*! \fn std::vector<SEvent> CEventQueue::popEvents(EVENT_TYPE eventType)
\brief Liefert einen chronologisch geordneten Vektor (FIFO) der aufgelaufenen
Events in dieser Queue zurueck. Diese werden daraufhin aus der Queue geloescht
\param eventType Falls nicht UNDEFINED, werden nur Events des angegebenen Typs
zurueckgegeben und aus der Queue geloescht, andernfalls alle
\return Chronologisch sortierter Vektor der Events (aelteste zuerst)
*/
std::vector<SEvent> CEventQueue::popEvents(EVENT_TYPE eventType)
{
std::vector<SEvent> ret;
switch(eventType)
{
case UNDEFINED:
ret = m_events;
m_events.clear();
break;
default:
{
std::vector<SEvent> without;
for(std::vector<SEvent>::iterator it=m_events.begin(); it!=m_events.end(); it++)
{
if(it->m_eventType==eventType)
{
ret.push_back(*it);
}
else
{
without.push_back(*it);
}
}
m_events = without;
}
break;
}
return ret;
}
/*! \fn SEvent CEventQueue::popEvent(bool* pValid)
\brief Liefert das chronologisch letzte (neueste) Event in der Queue zurueck
\param pValid true, falls ein Event aus der Queue zurueckgegeben wird,
false andernfalls (dann war die Queue naemlich leer)
\return Das chronologisch letzte (neueste) Event in der Queue
*/
SEvent CEventQueue::popEvent(bool* pValid)
{
return popEvent(m_events.size()-1, pValid);
}
/*! \fn SEvent CEventQueue::popEvent(unsigned short eventNumber,
* bool* pValid)
\brief Liefert das Event mit dem angegebenen Index in der Queue der gespeicherten
Events zurueck. Das Event wird daraufhin aus der Queue geloescht
\param eventNumber Index des zurueckzuliefernden Events in der Queue
\param pValid true, falls ein Event aus der Queue zurueckgegeben wird,
false andernfalls (dann existiert kein Event mit dem angegebenen Index)
\return Falls eventNumber nicht INVALID_EVENT_NUMBER, das Event mit dem
angegebenen Index in der Queue, das chronologisch erste (aelteste) Event
andernfalls
*/
SEvent CEventQueue::popEvent(unsigned short eventNumber, bool* pValid)
{
if(eventNumber<m_events.size())
{
*pValid=true;
std::vector<SEvent>::iterator it;
SEvent ret;
it=(m_events.begin() + eventNumber);
ret=*it;
m_events.erase(it);
return ret;
}
else
{
*pValid=false;
SEvent e;
e.m_eventType=UNDEFINED;
e.m_additionalInfo=0;
return e;
}
}
/*! \fn void CEventQueue::pushEvent(SEvent event)
\brief Fuegt der Queue ein Event hinzu. Sensoren und Aktoren
rufen diese Methode auf, um ueber ein Event zu informieren
\param event Hinzuzufuegendes Event
*/
void CEventQueue::pushEvent(SEvent event)
{
m_events.push_back(event);
}

View File

@ -0,0 +1,19 @@
#ifndef CEVENTQUEUE_H_
#define CEVENTQUEUE_H_
#include "SEvent.h"
#include "ArduinoSTL.h"
class CEventQueue
{
public:
std::vector<SEvent> popEvents(EVENT_TYPE eventtype=UNDEFINED);
SEvent popEvent(bool* pValid);
SEvent popEvent(unsigned short eventNumber, bool* pValid);
void pushEvent(SEvent event);
private:
std::vector<SEvent> m_events;
};
#endif /* CEVENTQUEUE_H_ */

View File

@ -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);
}

View File

@ -0,0 +1,94 @@
#ifndef CSEILMAINCONTROLLER_H_
#define CSEILMAINCONTROLLER_H_
#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;
// ===== 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_ */

24
Seilbahn/src/SEvent.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "SEvent.h"
const char* evToStr(SEvent ev)
{
switch(ev.m_eventType)
{
case BTN_ANLAGE_EIN_AUS: return "BTN_ANLAGE_EIN_AUS";
case BTN_STATION: return "BTN_STATION";
case BTN_SERVICE: return "BTN_SERVICE";
case BTN_FAHRGAST: return "BTN_FAHRGAST";
case BTN_QUIT: return "BTN_QUIT";
case BTN_START: return "BTN_START";
case BTN_HALT: return "BTN_HALT";
case BTN_NOTHALT: return "BTN_NOTHALT";
case BTN_VORWAERTS: return "BTN_VORWAERTS";
case BTN_RUECKWAERTS: return "BTN_RUECKWAERTS";
default: return "UNDEFINED";
}
}
void printEvent(SEvent event, void* who)
{
// TBD if needed
}

28
Seilbahn/src/SEvent.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef SEVENT_H_
#define SEVENT_H_
enum EVENT_TYPE
{
BTN_ANLAGE_EIN_AUS, // Button Anlage Ein/Aus wurde gedrückt
BTN_STATION, // Button Station Besetzt/Unbesetzt wurde gedrückt
BTN_SERVICE, // Button Servicebetrieb wurde gedrückt
BTN_FAHRGAST, // Button Fahrgastbetrieb wurde gedrückt
BTN_QUIT, // Button Quittieren wurde gedrückt
BTN_START, // Button Start wurde gedrückt
BTN_HALT, // Button Halt wurde gedrückt
BTN_NOTHALT, // Button Nothalt wurde gedrückt
BTN_VORWAERTS, // Button Vorwaerts wurde gedrückt
BTN_RUECKWAERTS, // Button Rueckwaerts wurde gedrückt
UNDEFINED=999,
};
struct SEvent
{
EVENT_TYPE m_eventType;
unsigned int m_additionalInfo;
};
const char* evToStr(SEvent ev);
void printEvent(SEvent event, void* who);
#endif /* SEVENT_H_ */