//
// Programm zum Emulieren von Tasterbetätigungen über einen Wechselschalter
//                    Schalterstellungen
//             1               0               1
//      Effekt schnell     Effekt aus    Effekt langsam
//       2 x Taster 1     1 X Taster 2    1 x Taster 1
//

#include <Streaming.h>
Print &cout {Serial};

class Timer {
public:
  void start() { timeStamp = millis(); }
  bool operator()(const uint32_t duration) const { return (millis() - timeStamp >= duration) ? true : false; }

private:
  uint32_t timeStamp {0};
};

enum SwitchEffects : uint8_t { on, ready, startWait, wait, waitEnd };
enum EffectSelection : uint8_t { effectA, effectB, effectsOff };

struct States {
  SwitchEffects pState;
  bool twoPulses;
  bool blockCheck;
};

//
// Konstanten und Variablendefinitionen
//
constexpr uint8_t deviceCheckPins[2] {4, 5};   // Pins am Umschalter
constexpr uint8_t devicePins[2] {6, 7};        // Pins für die Effektschaltung

// Bitmasken zum Schalten der Effektpins
constexpr uint8_t SW_MASK_ALL_OFF {0x00};
constexpr uint8_t SW_MASK_DEV_D {0x02};
constexpr uint8_t SW_MASK_DEV_E {0x01};

constexpr uint32_t PIN_ENABLE_DURATION_MS {200};   // Dauer des emulierten Tasterdrucks in Millisekunden
constexpr uint32_t PIN_WAIT_DURATION_MS {100};     // Dauer zwischen zwei Tasterdrücken in Millisekunden

Timer timer;   // Timer für Zeitmessungen erzeugen

//
// Funktionen
//
template <size_t N> EffectSelection checkPinState(const uint8_t (&pins)[N]) {
  for (size_t i = 0; i < N; ++i) {
    if (digitalRead(pins[i]) == LOW) { return static_cast<EffectSelection>(i); }
  }
  return EffectSelection::effectsOff;
}

template <size_t N> void switchPins(const uint8_t (&pins)[N], uint8_t mask) {
  uint8_t bitmask = 1 << (N - 1);
  for (size_t i = 0; i < N; ++i) { (mask << i & bitmask) ? digitalWrite(pins[i], HIGH) : digitalWrite(pins[i], LOW); }
}

template <size_t N> void fstm(const uint8_t (&pins)[N], States &st, Timer &tmr) {
  //
  // Notwendig für das Ausschalten des HIGH Signals an den entsprechenden Pins
  // (Entspricht dem Loslassen eines Tasters) und zum Auslösen des zweiten Tastenrucks (je nach Schalterstellung)
  //
  switch (st.pState) {
    case SwitchEffects::on:                        // Warten bis die Zeit für den emulierten Tastendruck abgelaufen ist
      if (true == tmr(PIN_ENABLE_DURATION_MS)) {   // Zeit abgelaufen, Pin wieder auf LOW bringen (= Taster loslassen)
        switchPins(devicePins, SW_MASK_ALL_OFF);
        // Wenn weiterer Tastendruck notwendig dann Status "startWait" für einen weiteren Durchlauf
        st.pState = (true == st.twoPulses) ? SwitchEffects::startWait : SwitchEffects::ready;
      }
      break;
    case SwitchEffects::startWait:   // Warten bis zur nächsten Tastersimulation
      tmr.start();
      st.pState = SwitchEffects::wait;
      break;
    case SwitchEffects::wait:                    // Testen ob die Wartezeit für den zweiten Tasterdruck abgelaufen ist
      if (true == tmr(PIN_WAIT_DURATION_MS)) {   // Wenn ja dann nächsten Tastendruck emulieren
        timer.start();
        st.pState = SwitchEffects::waitEnd;
      }
      break;
    case SwitchEffects::waitEnd:
      if (true == timer(PIN_ENABLE_DURATION_MS)) {   // Nächsten Tastendruck emulieren
        tmr.start();
        switchPins(devicePins, SW_MASK_DEV_D);
        st.twoPulses = false;
        st.pState = SwitchEffects::on;
      }
      break;
    case SwitchEffects::ready: st.blockCheck = false; break;
    default: break;
  }
}

void deviceSelect() {
  static EffectSelection prevCheck {EffectSelection::effectsOff};
  static States states {SwitchEffects::ready, false, false};

  if (false == states.blockCheck) {   // Die Abfrage wird blockiert, wenn gerade eine Schaltung erfolgt
    EffectSelection check = checkPinState(deviceCheckPins);
    delay(50);                  // Taster entprellen
    if (check != prevCheck) {   // Es hat eine Schaltebetätigung stattgefunden.
      timer.start();
      prevCheck = check;
      states.pState = SwitchEffects::on;
      states.blockCheck = true;   // Schalterabfrage solange blocken bis Tasteremulation beendet ist
      switch (check) {
        case EffectSelection::effectA:
          cout << F("Schalte Taster D - Effekt langsam\n");
          switchPins(devicePins, SW_MASK_DEV_D);
          break;
        case EffectSelection::effectB:
          cout << F("Schalte Taster D - Effekt schnell\n");
          switchPins(devicePins, SW_MASK_DEV_D);
          states.twoPulses = true;
          break;
        default:
          cout << F("Mittelstellung -> Schalte Taster E - Effekt aus\n");
          switchPins(devicePins, SW_MASK_DEV_E);
          break;
      }
    }
  }
  fstm(devicePins, states, timer);
}

//
// Hauptprogramm
//
void setup() {
  Serial.begin(115200);
  for (auto pin : deviceCheckPins) { pinMode(pin, INPUT_PULLUP); }
  switchPins(devicePins, SW_MASK_ALL_OFF);
  for (auto pin : devicePins) { pinMode(pin, OUTPUT); }
}

void loop() { deviceSelect(); }
Schalter anklicken