/*
    POC Weiche, Fahrstraße, Zieltaste
    https://forum.arduino.cc/t/schattenbahnhofsteuerung/1208510/46
    Simulation: https://wokwi.com/projects/386439244339395585

    by noiasca
    2024-01-09 Grundgerüst
    2024-01-11 MCP23S17 eingebunden, Debug Meldungen, erstes Time Management
    2024-02-15 fixed class WeicheMCP  https://forum.arduino.cc/t/schattenbahnhofsteuerung/1208510/63
    2024-03-03 fixed class WeicheMCP, 4 turnouts

    Anmerkung: durch die fehlende SPI Kommunikation wird der Sketch im Wokwi Simualtor nicht funktionieren
*/

#include <Streaming.h>                // Streaming by Peter Polidoro (boardmanager) - macht das Serial.print einfacher.
#include <Adafruit_MCP23X17.h>

constexpr uint8_t csPinA {5};         // chip select für ersten MCP23S17
constexpr uint8_t buttonPinA {A0};    // nur mal ein Test Button
constexpr uint8_t buttonPinB {A1};    // noch ein Test Button
constexpr uint8_t buttonPinC {A2};    // nur mal ein Test Button
constexpr uint8_t buttonPinD {A3};    // noch ein Test Button

Adafruit_MCP23X17 mcpA;               // ein MCP Objekt muss angelegt werden - @todo: Umbauen auf Array

// Weichen an einem MCP23S17
class WeicheMCP {
    Adafruit_MCP23X17 &mcp;           // Referenz auf ein MCP Objekt - ein konkreter MCP23S17
    const uint8_t pinGerade;          // Ausgang für Gerade - hardcoded auf Port A
    const uint8_t pinAbbiegen;        // Ausgang für abbiegen - hardcoded auf Port A
    const uint8_t pinRueckGerade;     // Rückmeldung für Gerade - hardcoded auf Port B
    const uint8_t pinRueckAbbiegen;   // Rückmeldung für Abbiegen - hardcoded auf PortB
    uint32_t previousMillis = 0;      // Zeitmanagement
    const uint16_t interval = 2000;   // hard off der Magnetantriebe
    bool isActive = false;            // Magnetantrieb ist ein oder aus
    uint8_t direction = 0;            // 0 gerade, 1 abbiegen
  public:
    WeicheMCP(Adafruit_MCP23X17 &mcp, uint8_t pinGerade, uint8_t pinAbbiegen, uint8_t pinRueckGerade, uint8_t pinRueckAbbiegen /*, welcher MCP*/) : 
      mcp{mcp},
      pinGerade {pinGerade},
      pinAbbiegen {pinAbbiegen},
      pinRueckGerade {pinRueckGerade},
      pinRueckAbbiegen {pinRueckAbbiegen} {}

    // Einstellungen im setup()
    void begin() {
      mcp.pinMode(pinGerade, OUTPUT);
      mcp.pinMode(pinAbbiegen, OUTPUT);
      mcp.pinMode(pinRueckGerade, INPUT);   // tbc INPUT_PULLUP?
      mcp.pinMode(pinRueckAbbiegen, INPUT); // tbc INPUT_PULLUP?
    }

    // Aktion für Geradeausfahrt
    int gerade() {
      if (!isActive) {
        mcp.digitalWrite(pinGerade, HIGH);
        direction = 0;
        isActive = true;
        previousMillis = millis();
        Serial << "gerade " << pinGerade << '\n';
        return 0; // OK
      }
      return -1;  // befehl nicht angenommen
    }

    // Aktion für abbiegen
    // @todo - codeduplikate von gerade/abbieben in eine Funktion auslagern
    int abbiegen() {
      if (!isActive) {
        mcp.digitalWrite(pinAbbiegen, HIGH);
        direction = 1;
        isActive = true;
        previousMillis = millis();
        Serial << "abbiegen " << pinAbbiegen << '\n';
        return 0; // ok
      }
      return -1;  // befehl nicht angenommen
    }

    // Rückgabe des aktuellen Status
    int getStatus() {
      return direction;
    };

    // RUN: automatische Abschaltung der Magnetartikel
    void update(uint32_t currentMillis = millis()) {
      if (isActive && currentMillis - previousMillis > interval) {
        mcp.digitalWrite(pinGerade, LOW);
        mcp.digitalWrite(pinAbbiegen, LOW);
        isActive = false;
        Serial << "abschalten " << '\n';
      }
    }
};

WeicheMCP weiche[] {
  {mcpA, 0, 1, 8, 9},       // Anlage einer Weiche
  {mcpA, 2, 3, 10, 11},     // Anlage einer weiteren Weiche (am gleichen MCP)
  {mcpA, 4, 5, 12, 13},     // Anlage einer Weiche
  {mcpA, 6, 7, 14, 15},     // Anlage einer weiteren Weiche (am gleichen MCP)
};

// Eingänge der Buttons initialisieren
void buttonBegin() {
  pinMode(buttonPinA, INPUT_PULLUP);
  pinMode(buttonPinB, INPUT_PULLUP);
  pinMode(buttonPinC, INPUT_PULLUP);
  pinMode(buttonPinD, INPUT_PULLUP);
}

// Buttons abfragen und etwas ausführen
void buttonRead() {
  if (digitalRead(buttonPinA) == LOW) weiche[0].gerade();
  if (digitalRead(buttonPinB) == LOW) weiche[0].abbiegen();
  if (digitalRead(buttonPinC) == LOW) weiche[1].gerade();
  if (digitalRead(buttonPinD) == LOW) weiche[1].abbiegen();
}

// Befehle über die serielle Schnittstelle
void serialRead() {
  int c = Serial.read();
  switch (c) {
    case -1 : break;
    case '1': weiche[0].gerade(); break;
    case '2': weiche[0].abbiegen(); break;
    case '3': weiche[1].gerade(); break;
    case '4': weiche[1].abbiegen(); break;
    case '5': weiche[2].gerade(); break;
    case '6': weiche[2].abbiegen(); break;
    case '7': weiche[3].gerade(); break;
    case '8': weiche[3].abbiegen(); break;
  }
}

void setup() {
  Serial.begin(115200);
  Serial << "Weichensteuerung\n";
  delay(500);
  SPI.begin();

  if (!mcpA.begin_SPI(csPinA)) {
    Serial << "mcpA Error.\n";
  }

  Serial << "D120\n";
  for (auto &i : weiche)  {
    i.begin();  // jede Weiche initialisieren
  }
  buttonBegin();
  Serial << "ende setup\n";
}

void loop() {
  buttonRead(); // taster auslesen
  serialRead();

  // Hintergrund-Aktivitäten auslösen
  for (auto &i : weiche) i.update();  // jeder Weiche etwas Zeit für Hintergrundaktivitäten geben
}
//