//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Day timer
// 8 channels
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// **************************************************** BASIC DEFINITIONS

#include <limits.h>
#include <EEPROM.h> // max: 4096
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

#define LED_PIN 13 // MEGA: 13 / RP2040: 25
#define RND_PIN A0 // MEGA: A0 / RP2040: 26 (A0)
#define PWRCTRL_PIN A4 // MEGA: A4 / RP2040: 27 (A1)
#define BUTTON_A_PIN A1 // any digital
#define BUTTON_B_PIN A3 // any digital
#define BUTTON_C_PIN 12 // any digital
#define BUTTON_D_PIN A2 // any digital
#define OUT_0_PIN 2 // any digital
#define OUT_1_PIN 3 // any digital
#define OUT_2_PIN 4 // any digital
#define OUT_3_PIN 5 // any digital
#define OUT_4_PIN 6 // any digital
#define OUT_5_PIN 7 // any digital
#define OUT_6_PIN 8 // any digital
#define OUT_7_PIN 9 // any digital

// **************************************************** AUX

// ----------------------------------------------------
void printLine() {
  Serial.println();
}

// ----------------------------------------------------
template <typename T, typename... Types>
void printLine(T first, Types... other) {
  Serial.print(first);
  printLine(other...) ;
}

// **************************************************** CLASSES

// ----------------------------------------------------
class Button {
private:
  byte pin; // defined button (pin)
  Button* button[4]; // simultaneous button(s) (max: 4)
  byte buttons; // number of simultaneous button(s)
  bool lastState;
  bool wasOnFlag;
  bool wasOn3sFlag;
  unsigned long lastDebounceTime;
  const unsigned long debounceDelay = 50;
  bool currentState;
  unsigned long pressedTime;
  bool ignoreButtonRelease;
public:
  Button(byte buttonPin) : pin(buttonPin) { // define button
    pinMode(pin, INPUT_PULLUP);
    buttons = 0; // no objects stored
    lastState = HIGH;
    currentState = HIGH;
    lastDebounceTime = 0;
    wasOnFlag = false;
    wasOn3sFlag = false;
    pressedTime = 0;
    ignoreButtonRelease = false;
  }
  Button(Button* buttonObjects[], byte length) : buttons(length) { // define simultaneous already defined button(s)
    for (byte i = 0; i < buttons; i++) button[i] = buttonObjects[i];
    lastState = HIGH;
    currentState = HIGH;
    lastDebounceTime = 0;
    wasOnFlag = false;
    wasOn3sFlag = false;
    pressedTime = 0;
    ignoreButtonRelease = false;
  }
  void reset() {
    ignoreButtonRelease = true;
    if(buttons > 0) { // reset all simultaneous button(s)
      for(byte i = 0; i < buttons; i++) button[i]->reset();
    }
  }
  void update() {
    bool reading;
    if(buttons == 0) { // defined button
      reading = digitalRead(pin);
    } else { // simultaneous button(s)
      reading = LOW;
      for(byte i = 0; i < buttons; i++) { // reading LOW only if all pins are LOW
        if(digitalRead(button[i]->pin) == HIGH) reading = HIGH;
      }
    }
    if(reading != lastState) {
      lastDebounceTime = millis();
    }
    if((millis() - lastDebounceTime) > debounceDelay) {
      if(currentState != reading) {
        currentState = reading;
        if(currentState == LOW) { // button(s) pressed
          ignoreButtonRelease = false; // remove reset
          pressedTime = millis();
        } else { // button(s) released
          if(buttons > 0) { // reset all simultaneous button(s)
            for(byte i = 0; i < buttons; i++) button[i]->reset();
          }
          if(!ignoreButtonRelease) { // if reset, ignore button(s) released, otherwise, signal
            unsigned long heldTime = millis() - pressedTime;
            wasOnFlag = true;
            if(heldTime >= 3000) {
              wasOnFlag = false;
              wasOn3sFlag = true;
            }
          }
        }
      }
    }
    lastState = reading;
  }
  bool wasOn() {
    if(wasOnFlag) {
      wasOnFlag = false;
      return true;
    }
    return false;
  }
  bool wasOn3s() {
    if(wasOn3sFlag) {
      wasOn3sFlag = false;
      return true;
    }
    return false;
  }
};

// ----------------------------------------------------
class ConfigDigit {
private:
  byte *digitMemory;
  byte screenX; // left pixel
  byte screenY; // top pixel
  byte mode; // type of config
public:
  ConfigDigit() {}
  ConfigDigit(byte *_digitMemory, byte _screenX, byte _screenY, byte _mode) {
    digitMemory = _digitMemory;
    screenX = _screenX;
    screenY = _screenY;
    mode = _mode;
  }
  byte value() {
    return *digitMemory;
  }
  void show() {
    if(mode == 0) display.fillTriangle(screenX + 2, screenY + 8, screenX, screenY + 10, screenX + 4, screenY + 10, WHITE);
    if(mode == 1) display.fillTriangle(screenX + 2, screenY - 2, screenX, screenY - 4, screenX + 4, screenY - 4, WHITE);
    display.display();
  }
  void hide() {
    if(mode == 0) display.fillRect(screenX, screenY + 8, 6, 8, BLACK);
    if(mode == 1) display.fillRect(screenX, screenY - 8, 6, 8, BLACK);
    display.display();
  }
  void increment(byte min, byte max) {
    if(*digitMemory >= max) *digitMemory = min;
    else (*digitMemory)++;
    display.setTextColor(WHITE, BLACK);
    display.setCursor(screenX, screenY);
    display.print(*digitMemory);
    display.display();
  }
};

// ----------------------------------------------------
class OnOffSettings {
public:
  byte channel;
  byte timeHoursStart0;
  byte timeHoursStart1;
  byte timeMinutesStart0;
  byte timeMinutesStart1;
  byte timeHoursEnd0;
  byte timeHoursEnd1;
  byte timeMinutesEnd0;
  byte timeMinutesEnd1;
};
// **************************************************** MAIN DEFINITIONS

// ----------------------------------------------------
byte outSwitchWidth;
byte outSwitchHeight;
byte outSwitchHBorder;
byte outSwitchRowStartY;
byte timeRowStartY;

byte relays[8]; //relay outputs

int powerLossCtrlThreshold = 700; // RPi 2040: <=3.3V, <=4095 / arduino NANO: <=5V, <=1023

Button bA = Button(BUTTON_A_PIN);
Button bB = Button(BUTTON_B_PIN);
Button bC = Button(BUTTON_C_PIN);
Button bD = Button(BUTTON_D_PIN);
Button* _bAC[] = {&bA, &bC};
Button* _bBD[] = {&bB, &bD};
Button* _bAB[] = {&bA, &bB};
Button* _bCD[] = {&bC, &bD};
Button bAC = Button(_bAC, 2);
Button bBD = Button(_bBD, 2);
Button bAB = Button(_bAB, 2);
Button bCD = Button(_bCD, 2);

byte status; // 0:pause - 1:run - 2:configClock - 3:configChannels
bool timeChanged;
byte timeHours[2]; // current hours
byte timeMinutes[2]; // current minutes
byte speed[2]; // current speed
byte configPosition;
ConfigDigit timeHours0;
ConfigDigit timeHours1;
ConfigDigit timeMinutes0;
ConfigDigit timeMinutes1;
ConfigDigit speed0;
ConfigDigit speed1;
ConfigDigit configClockDigit[6];
byte configTimeHours[2]; // config hours
byte configTimeMinutes[2]; // config minutes
byte configSpeed[2]; // config speed
OnOffSettings configOnOffSettings; // channel, timeHoursStart[2], timeMinutesStart[2], timeHoursEnd[2], timeMinutesEnd[2]
int onOffSettingsSize = 10;
OnOffSettings onOffSettings[10];
unsigned long previousMS;
unsigned long powerLossCtrlMS;

// **************************************************** FUNCTIONS

// ----------------------------------------------------
void setup() {
  Serial.begin(115200);

  randomSeed(analogRead(RND_PIN)); // live this pin not connected

/*
  // RP2040 I2C0: SDA (GP0, GP4, GP8, GP12, GP16, GP20), SCL (GP1, GP5, GP9, GP13, GP17, GP21)
  // not needed for default
  Wire.setSDA(8);  // GP8 for SDA (I2C0) ... use the default: 4
  Wire.setSCL(9);  // GP9 for SCL (I2C0) ... use the default: 5
  Wire.begin();    // Start I2C
*/

  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  delay(2000);

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);  // Off
  // set the 8 output relays as output
  relays[0] = OUT_0_PIN;
  relays[1] = OUT_1_PIN;
  relays[2] = OUT_2_PIN;
  relays[3] = OUT_3_PIN;
  relays[4] = OUT_4_PIN;
  relays[5] = OUT_5_PIN;
  relays[6] = OUT_6_PIN;
  relays[7] = OUT_7_PIN;
  for(byte ch = 0; ch < 8; ch++) {
    pinMode(relays[ch], OUTPUT);
    digitalWrite(relays[ch], LOW);  // Off
  }

  unsigned long powerLossCtrlMS = millis();

//  EEPROM.begin(2048); // rpi 2040 - normal flash sector size is 4K

  outSwitchWidth = display.width() / 8; // 8 outputs
  outSwitchHBorder = outSwitchWidth / 4;
  outSwitchHeight = outSwitchWidth - 2 * outSwitchHBorder; // square
  outSwitchRowStartY = 0; // top of the screen

  timeRowStartY = display.height() - 7; // bottom of the screen

  getStoredTime();
  getStoredSpeed();
  getStoredOnOffSettings();

  timeHours0 = ConfigDigit(&configTimeHours[0], 31, timeRowStartY, 1);
  timeHours1 = ConfigDigit(&configTimeHours[1], 37, timeRowStartY, 1);
  timeMinutes0 = ConfigDigit(&configTimeMinutes[0], 49, timeRowStartY, 1);
  timeMinutes1 = ConfigDigit(&configTimeMinutes[1], 55, timeRowStartY, 1);
  speed0 = ConfigDigit(&configSpeed[0], 109, timeRowStartY, 1);
  speed1 = ConfigDigit(&configSpeed[1], 115, timeRowStartY, 1);
  configClockDigit[0] = timeHours0;
  configClockDigit[1] = timeHours1;
  configClockDigit[2] = timeMinutes0;
  configClockDigit[3] = timeMinutes1;
  configClockDigit[4] = speed0;
  configClockDigit[5] = speed1;

  display.clearDisplay();
  display.setTextSize(1);

  printLine("PAUSE");
  status = 0; // 0:pause - 1:run - 2:configClock - 3:configChannels
  showPauseText();

  timeChanged = true;

  printLine("AFTER SETUP");
}

// ----------------------------------------------------
void loop() {
  int power = analogRead(PWRCTRL_PIN);
  if(power < powerLossCtrlThreshold) { // eventual power loss
    unsigned long currentMS = millis();
    if((currentMS - powerLossCtrlMS) >= 5000) { // minimum interval: 5s
      storeTime();
      printLine("Power loss (<", powerLossCtrlThreshold, "): ", power);
      digitalWrite(LED_PIN, HIGH);  // On
      delay(500);
      digitalWrite(LED_PIN, LOW);  // Off
      powerLossCtrlMS = currentMS;
    }
  }
  if(timeChanged) {
    showTime();
    timeChanged = false;
  }
  if(status == 1) tryIncrementTime(); // run
  buttons();
}

// ----------------------------------------------------
void tryIncrementTime() {
  unsigned long currentMS = millis();
  byte _speed = speed[0] * 10 + speed[1];
  if(_speed * (currentMS - previousMS) / 60000 >= 1) { // 'minute' change
    if(timeMinutes[1] < 9) timeMinutes[1]++;
    else {
      timeMinutes[1] = 0;
      if(timeMinutes[0] < 5) timeMinutes[0]++;
      else { // 'hour' change
        timeMinutes[0] = 0;
        if((timeHours[0] < 2 && timeHours[1] < 9) || (timeHours[0] == 2 && timeHours[1] < 3)) timeHours[1]++;
        else {
          timeHours[1] = 0;
          if(timeHours[0] < 2) timeHours[0]++;
          else timeHours[0] = 0;
        }
      }
    }
    previousMS = currentMS;
    timeChanged = true;
  }
}

// ----------------------------------------------------
void buttons() {
 // Buttons: A(A1) B(A3) C(D12) D(A2)
// status ... 0:pause - 1:run - 2:configClock - 3:configChannels

  // arrays of buttons always first
  bAC.update();
  bBD.update();
  bAB.update();
  bCD.update();
  // single buttons always last
  bA.update();
  bB.update();
  bC.update();
  bD.update();

  // arrays of buttons always first
  bool bAC_wasOn = bAC.wasOn();
  bool bAC_wasOn3s = bAC.wasOn3s();
  bool bBD_wasOn = bBD.wasOn();
  bool bBD_wasOn3s = bBD.wasOn3s();
  bool bAB_wasOn = bAB.wasOn();
  bool bAB_wasOn3s = bAB.wasOn3s();
  bool bCD_wasOn = bCD.wasOn();
  bool bCD_wasOn3s = bCD.wasOn3s();
  // single buttons always last
  bool bA_wasOn = bA.wasOn();
  bool bA_wasOn3s = bA.wasOn3s();
  bool bB_wasOn = bB.wasOn();
  bool bB_wasOn3s = bB.wasOn3s();
  bool bC_wasOn = bC.wasOn();
  bool bC_wasOn3s = bC.wasOn3s();
  bool bD_wasOn = bD.wasOn();
  bool bD_wasOn3s = bD.wasOn3s();
  if(bAC_wasOn || bAC_wasOn3s) {
    bA_wasOn = false;
    bA_wasOn3s = false;
    bC_wasOn = false;
    bC_wasOn3s = false;
  }
  if(bBD_wasOn || bBD_wasOn3s) {
    bB_wasOn = false;
    bB_wasOn3s = false;
    bD_wasOn = false;
    bD_wasOn3s = false;
  }
  if(bAB_wasOn || bAB_wasOn3s) {
    bA_wasOn = false;
    bA_wasOn3s = false;
    bB_wasOn = false;
    bB_wasOn3s = false;
  }
  if(bCD_wasOn || bCD_wasOn3s) {
    bC_wasOn = false;
    bC_wasOn3s = false;
    bD_wasOn = false;
    bD_wasOn3s = false;
  }
/*
  if(bA_wasOn) printLine("bA ON");
  if(bA_wasOn3s) printLine("bA3s ON");
  if(bB_wasOn) printLine("bB ON");
  if(bB_wasOn3s) printLine("bB3s ON");
  if(bC_wasOn) printLine("bC ON");
  if(bC_wasOn3s) printLine("bC3s ON");
  if(bD_wasOn) printLine("bD ON");
  if(bD_wasOn3s) printLine("bD3s ON");
  if(bAC_wasOn) printLine("bAC ON");
  if(bAC_wasOn3s) printLine("bAC3s ON");
  if(bBD_wasOn) printLine("bBD ON");
  if(bBD_wasOn3s) printLine("bBD3s ON");
  if(bAB_wasOn) printLine("bAB ON");
  if(bAB_wasOn3s) printLine("bAB3s ON");
  if(bCD_wasOn) printLine("bCD ON");
  if(bCD_wasOn3s) printLine("bCD3s ON");
*/
  switch(status) {
    case 0: // pause
      if(bBD_wasOn) { // set to run
        printLine("RUN");
        status = 1;
        hidePauseText();
        previousMS = millis(); // this function will restart counting after approximately 50 days
        break;
      }
      if(bCD_wasOn3s) { // set to configClock
        printLine("CONFIG CLOCK");
        status = 2;
        hidePauseText();
        showConfigText(true);
        configPosition = 0;
        memcpy(configTimeHours, timeHours, sizeof(timeHours));
        memcpy(configTimeMinutes, timeMinutes, sizeof(timeMinutes));
        memcpy(configSpeed, speed, sizeof(speed));
        configClockDigit[configPosition].show();
        break;
      }
      if(bAB_wasOn3s) { // set to configChannels
        printLine("CONFIG CHANNELS");
        status = 3;
        hidePauseText();
        showConfigText(false);
// show row
// show cursor on channel of selected row
for(byte ch = 0; ch < 8; ch++) selectedOutSwitch(ch, true);
display.display(); 
        break;
      }
      break;
    case 1: // run
      if(bAC_wasOn) { // set to pause
        printLine("PAUSE");
        status = 0;
        showPauseText();
        break;
      }
      break;
    case 2: // configClock
      if(bD_wasOn) { // move right
        printLine("CLOCK: MOVE RIGHT");
        configClockDigit[configPosition].hide();
        configPosition++;
        if(configPosition > 5) configPosition = 0;
        configClockDigit[configPosition].show();
        break;
      }
      if(bB_wasOn) { // increment
        printLine("CLOCK: INCREMENT");
        if(configPosition == 0) configClockDigit[0].increment(0, 2); // timeHours0
        if(configPosition == 1) configClockDigit[1].increment(0, configClockDigit[0].value() == 2 ? 3 : 9); // timeHours1
        if(configPosition == 2) configClockDigit[2].increment(0, 5); // timeMinutes0
        if(configPosition == 3) configClockDigit[3].increment(0, 9); // timeMinutes1
        if(configPosition == 4) configClockDigit[4].increment(configClockDigit[5].value() == 0 ? 1 : 0, 6); // speed0
        if(configPosition == 5) configClockDigit[5].increment(configClockDigit[4].value() == 0 ? 1 : 0, 9); // speed1
        break;
      }
      if(bAC_wasOn3s) { // cancel and set to pause
        printLine("CANCEL CONFIG CLOCK");
        status = 0;
        hideConfigText(true);
        configClockDigit[configPosition].hide();
        showPauseText();
        showTime();
        break;
      }
      if(bBD_wasOn3s) { // confirm and set to pause
        printLine("CONFIRM CONFIG CLOCK");
        status = 0;
        hideConfigText(true);
        configClockDigit[configPosition].hide();
        showPauseText();
        memcpy(timeHours, configTimeHours, sizeof(configTimeHours));
        memcpy(timeMinutes, configTimeMinutes, sizeof(configTimeMinutes));
        memcpy(speed, configSpeed, sizeof(configSpeed));
        storeTime();
        storeSpeed();
        showTime();
        break;
      }
      break;
    case 3: // configChannels
      if(bA_wasOn) { // move up
        printLine("CHANNELS: MOVE UP");
// hide cursor on channel of previous selected row
// increment and show another row
// show cursor on channel of selected row
        break;
      }
      if(bC_wasOn) { // move down
        printLine("CHANNELS: MOVE DOWN");
// hide cursor on channel of previous selected row
// decrement and show another row
// show cursor on channel of selected row
        break;
      }
      if(bD_wasOn) { // move right
        printLine("CHANNELS: MOVE RIGHT");

        break;
      }
      if(bB_wasOn) { // increment
        printLine("CHANNELS: INCREMENT");

        break;
      }
      if(bAC_wasOn3s) { // cancel and set to pause
        printLine("CANCEL CONFIG CHANNELS");
        status = 0;
        hideConfigText(false);
// hide row
// hide cursor on channel of selected row
        showPauseText();
        showTime();
        break;
      }
      if(bBD_wasOn3s) { // confirm and set to pause
        printLine("CONFIRM CONFIG CHANNELS");
        status = 0;
        hideConfigText(false);
// hide row
// hide cursor on channel of selected row
        showPauseText();
// insert configOnOffSettings in selected onOffSettings[s]
        storeOnOffSettings();
        showTime();
        break;
      }
      break;
  }
}

// ----------------------------------------------------
void showTime() {
  display.setTextColor(WHITE, BLACK);
  display.setCursor(1, timeRowStartY);
  display.print("Time:");
  display.print(timeHours[0]);
  display.print(timeHours[1]);
  display.print("h");
  display.print(timeMinutes[0]);
  display.print(timeMinutes[1]);
  display.print("m Speed:");
  display.print(speed[0]);
  display.print(speed[1]);
  display.print("x");
  bool on[8];
  for(byte ch = 0; ch < 8; ch++) { // reset channels display row
    byte x = ch * outSwitchWidth;
    display.fillRect(x + outSwitchHBorder, outSwitchRowStartY, outSwitchWidth - 2 * outSwitchHBorder, outSwitchHeight, BLACK);
    display.drawRect(x + outSwitchHBorder, outSwitchRowStartY, outSwitchWidth - 2 * outSwitchHBorder, outSwitchHeight, WHITE);
    on[ch] = false;
   }
  int currentTime = (timeHours[0] * 10 + timeHours[1]) * 60 + timeMinutes[0] * 10 + timeMinutes[1];
  for(byte i = 0; i < onOffSettingsSize; i++) {
    if(onOffSettings[i].channel > 7) break;
    int timeStart = (onOffSettings[i].timeHoursStart0 * 10 + onOffSettings[i].timeHoursStart1) * 60 + onOffSettings[i].timeMinutesStart0 * 10 + onOffSettings[i].timeMinutesStart1;
    int timeEnd = (onOffSettings[i].timeHoursEnd0 * 10 + onOffSettings[i].timeHoursEnd1) * 60 + onOffSettings[i].timeMinutesEnd0 * 10 + onOffSettings[i].timeMinutesEnd1;
    if(!on[onOffSettings[i].channel]) on[onOffSettings[i].channel] = currentTime >= timeStart && currentTime <= timeEnd;
    outSwitch(onOffSettings[i].channel, on[onOffSettings[i].channel]);
  }
  display.display(); 
}

// ----------------------------------------------------
void showPauseText() {
  display.fillRoundRect(44, 27, 39, 13, 2, WHITE);
  display.setTextColor(BLACK, WHITE);
  display.setCursor(49, 30);
  display.print("PAUSE");
  display.display(); 
}

// ----------------------------------------------------
void hidePauseText() {
  display.fillRoundRect(44, 27, 39, 13, 2, BLACK);
  display.display(); 
}

// ----------------------------------------------------
void showConfigText(bool clock) {
  if(clock) {
    display.fillRoundRect(23, 35, 81, 13, 2, WHITE);
    display.setTextColor(BLACK, WHITE);
    display.setCursor(28, 38);
    display.print("CONFIG CLOCK");
  } else {
    display.fillRoundRect(14, 40, 99, 13, 2, WHITE);
    display.setTextColor(BLACK, WHITE);
    display.setCursor(19, 43);
    display.print("CONFIG CHANNELS");
  }
}

// ----------------------------------------------------
void hideConfigText(bool clock) {
  if(clock) display.fillRoundRect(23, 35, 81, 13, 2, BLACK);
  else display.fillRoundRect(14, 40, 99, 13, 2, BLACK);
}

// ----------------------------------------------------
void outSwitch(byte ch, bool on) { // 0-7 (A-H), true/false ... show on sceen and set output
  byte x = ch * outSwitchWidth;
  if(on) {
    display.fillRect(x + outSwitchHBorder, outSwitchRowStartY, outSwitchWidth - 2 * outSwitchHBorder, outSwitchHeight, WHITE);
    digitalWrite(relays[ch], HIGH);  // On
  } else {
    display.drawRect(x + outSwitchHBorder, outSwitchRowStartY, outSwitchWidth - 2 * outSwitchHBorder, outSwitchHeight, WHITE);
    digitalWrite(relays[ch], LOW);  // Off
  }
}

// ----------------------------------------------------
void selectedOutSwitch(byte ch, bool on) { // 0-7 (A-H), true/false
  byte x = ch * outSwitchWidth;
  if(on) display.fillRoundRect(x + outSwitchWidth / 2 - outSwitchHBorder / 2, outSwitchRowStartY + outSwitchHeight + outSwitchHBorder / 2, outSwitchHBorder, outSwitchHBorder, outSwitchHBorder / 2, WHITE);
  else display.fillRoundRect(x + outSwitchWidth / 2 - outSwitchHBorder / 2, outSwitchRowStartY + outSwitchHeight + outSwitchHBorder / 2, outSwitchHBorder, outSwitchHBorder, outSwitchHBorder / 2, BLACK);
}

// ----------------------------------------------------
void storeTime() {
  printLine("time to store: ", timeHours[0], timeHours[1], "h", timeMinutes[0], timeMinutes[1], "m");
  byte offset = 0;
  EEPROM.update(offset++, timeHours[0]);
  EEPROM.update(offset++, timeHours[1]);
  EEPROM.update(offset++, timeMinutes[0]);
  EEPROM.update(offset++, timeMinutes[1]);
//  EEPROM.commit(); // rpi 2040
}

// ----------------------------------------------------
void getStoredTime() {
  byte offset = 0;
  timeHours[0] = EEPROM.read(offset++);
  timeHours[1] = EEPROM.read(offset++);
  timeMinutes[0] = EEPROM.read(offset++);
  timeMinutes[1] = EEPROM.read(offset++);
  printLine("time read (raw): ", timeHours[0], timeHours[1], "h", timeMinutes[0], timeMinutes[1], "m");
  // check validity
  bool ok = true;
  if(timeHours[0] > 2) ok = false;
  if(timeHours[0] == 2 && timeHours[1] > 3) ok = false;
  if(timeHours[1] > 9) ok = false;
  if(timeMinutes[0] > 5) ok = false;
  if(timeMinutes[1] > 9) ok = false;
  if(!ok) { // not valid, reset
    timeHours[0] = 0;
    timeHours[1] = 0;
    timeMinutes[0] = 0;
    timeMinutes[1] = 0;
  }
  printLine("time read (fixed): ", timeHours[0], timeHours[1], "h", timeMinutes[0], timeMinutes[1], "m");
}

// ----------------------------------------------------
void storeSpeed() {
  printLine("speed to store: ", speed[0], speed[1], "x");
  byte offset = 4;
  EEPROM.update(offset++, speed[0]);
  EEPROM.update(offset++, speed[1]);
//  EEPROM.commit(); // rpi 2040
}

// ----------------------------------------------------
void getStoredSpeed() {
  byte offset = 4;
  speed[0] = EEPROM.read(offset++);
  speed[1] = EEPROM.read(offset++);
  printLine("speed read (raw): ", speed[0], speed[1], "x");
  // check validity
  bool ok = true;
  if(speed[0] > 6) ok = false;
  if(speed[0] == 6 && speed[1] != 0) ok = false;
  if(speed[0] == 0 && speed[1] < 1) ok = false;
  if(speed[1] > 9) ok = false;
  if(!ok) { // not valid, reset
    speed[0] = 6;
    speed[1] = 0;
  }
  printLine("speed read (fixed): ", speed[0], speed[1], "x");
}

// ----------------------------------------------------
void storeOnOffSettings() {
  checkOnOffSettings();
  sortOnOffSettings();
  printLine("store OnOffSettings!");
  byte offset = 6;
  for(byte i = 0; i < onOffSettingsSize; i++) {
    EEPROM.update(offset++, onOffSettings[i].channel);
    EEPROM.update(offset++, onOffSettings[i].timeHoursStart0);
    EEPROM.update(offset++, onOffSettings[i].timeHoursStart1);
    EEPROM.update(offset++, onOffSettings[i].timeMinutesStart0);
    EEPROM.update(offset++, onOffSettings[i].timeMinutesStart1);
    EEPROM.update(offset++, onOffSettings[i].timeHoursEnd0);
    EEPROM.update(offset++, onOffSettings[i].timeHoursEnd1);
    EEPROM.update(offset++, onOffSettings[i].timeMinutesEnd0);
    EEPROM.update(offset++, onOffSettings[i].timeMinutesEnd1);
  }
//  EEPROM.commit(); // rpi 2040
}

// ----------------------------------------------------
void getStoredOnOffSettings() {
  byte offset = 6;
  for(byte i = 0; i < onOffSettingsSize; i++) {
    onOffSettings[i].channel = EEPROM.read(offset++);
    onOffSettings[i].timeHoursStart0 = EEPROM.read(offset++);
    onOffSettings[i].timeHoursStart1 = EEPROM.read(offset++);
    onOffSettings[i].timeMinutesStart0 = EEPROM.read(offset++);
    onOffSettings[i].timeMinutesStart1 = EEPROM.read(offset++);
    onOffSettings[i].timeHoursEnd0 = EEPROM.read(offset++);
    onOffSettings[i].timeHoursEnd1 = EEPROM.read(offset++);
    onOffSettings[i].timeMinutesEnd0 = EEPROM.read(offset++);
    onOffSettings[i].timeMinutesEnd1 = EEPROM.read(offset++);
// test - temp
onOffSettings[i].channel = random(15);
onOffSettings[i].timeHoursStart0= random(2);
onOffSettings[i].timeHoursStart1= random(10);
onOffSettings[i].timeMinutesStart0= random(6);
onOffSettings[i].timeMinutesStart1 = random(10);
onOffSettings[i].timeHoursEnd0 = random(2);
onOffSettings[i].timeHoursEnd1 = random(10);
onOffSettings[i].timeMinutesEnd0 = random(6);
onOffSettings[i].timeMinutesEnd1 = random(10);
  }
  printLine("OnOffSettings read (raw)!");
  checkOnOffSettings();
  sortOnOffSettings();
}

// ----------------------------------------------------
void sortOnOffSettings() {
  printLine("Before sort ...");
  for(byte i = 0; i < onOffSettingsSize; i++) printLine("\t", i, ") [", onOffSettings[i].channel, "] ", onOffSettings[i].timeHoursStart0, onOffSettings[i].timeHoursStart1, "h", onOffSettings[i].timeMinutesStart0, onOffSettings[i].timeMinutesStart1, "m ", onOffSettings[i].timeHoursEnd0, onOffSettings[i].timeHoursEnd1, "h", onOffSettings[i].timeMinutesEnd0, onOffSettings[i].timeMinutesEnd1, "m");
  // sort by channel, by timeStart and by timeEnd
  for(byte i = 0; i < onOffSettingsSize - 1; i++) {
    for(byte j = 0; j < onOffSettingsSize - i - 1; j++) {
      unsigned long sortA = onOffSettings[j].channel * 100000000 + onOffSettings[j].timeHoursStart0 * 10000000 + onOffSettings[j].timeHoursStart1 * 1000000 + onOffSettings[j].timeMinutesStart0 * 100000 + onOffSettings[j].timeMinutesStart1 * 10000 + onOffSettings[j].timeHoursEnd0 * 1000 + onOffSettings[j].timeHoursEnd1 * 100 + onOffSettings[j].timeMinutesEnd0 * 10 + onOffSettings[j].timeMinutesEnd1;
      unsigned long sortB = onOffSettings[j + 1].channel * 100000000 + onOffSettings[j + 1].timeHoursStart0 * 10000000 + onOffSettings[j + 1].timeHoursStart1 * 1000000 + onOffSettings[j + 1].timeMinutesStart0 * 100000 + onOffSettings[j + 1].timeMinutesStart1 * 10000 + onOffSettings[j + 1].timeHoursEnd0 * 1000 + onOffSettings[j + 1].timeHoursEnd1 * 100 + onOffSettings[j + 1].timeMinutesEnd0 * 10 + onOffSettings[j + 1].timeMinutesEnd1;
      if(sortA > sortB) {
        OnOffSettings temp = onOffSettings[j];
        onOffSettings[j] = onOffSettings[j + 1];
        onOffSettings[j + 1] = temp;
      }
    }
  }
  printLine("After sort ...");
  for(byte i = 0; i < onOffSettingsSize; i++) printLine("\t", i, ") [", onOffSettings[i].channel, "] ", onOffSettings[i].timeHoursStart0, onOffSettings[i].timeHoursStart1, "h", onOffSettings[i].timeMinutesStart0, onOffSettings[i].timeMinutesStart1, "m ", onOffSettings[i].timeHoursEnd0, onOffSettings[i].timeHoursEnd1, "h", onOffSettings[i].timeMinutesEnd0, onOffSettings[i].timeMinutesEnd1, "m");
}

// ----------------------------------------------------
void checkOnOffSettings() {
  // check validity
  for(byte i = 0; i < onOffSettingsSize; i++) {
    bool ok = true;
    if(onOffSettings[i].channel > 7)  ok = false;
    if(onOffSettings[i].timeHoursStart0 > 2) ok = false;
    if(onOffSettings[i].timeHoursStart0 == 2 && onOffSettings[i].timeHoursStart1 > 3) ok = false;
    if(onOffSettings[i].timeHoursStart1 > 9) ok = false;
    if(onOffSettings[i].timeMinutesStart0 > 5) ok = false;
    if(onOffSettings[i].timeMinutesStart1 > 9) ok = false;
    if(onOffSettings[i].timeHoursEnd0 > 2) ok = false;
    if(onOffSettings[i].timeHoursEnd0 == 2 && onOffSettings[i].timeHoursEnd1 > 3) ok = false;
    if(onOffSettings[i].timeHoursEnd1 > 9) ok = false;
    if(onOffSettings[i].timeMinutesEnd0 > 5) ok = false;
    if(onOffSettings[i].timeMinutesEnd1 > 9) ok = false;
    if(ok) {
      int timeStart = (onOffSettings[i].timeHoursStart0 * 10 + onOffSettings[i].timeHoursStart1) * 60 + onOffSettings[i].timeMinutesStart0 * 10 + onOffSettings[i].timeMinutesStart1;
      int timeEnd = (onOffSettings[i].timeHoursEnd0 * 10 + onOffSettings[i].timeHoursEnd1) * 60 + onOffSettings[i].timeMinutesEnd0 * 10 + onOffSettings[i].timeMinutesEnd1;
      ok = timeStart <= timeEnd;
    }
    if(!ok) { // not valid, reset
      onOffSettings[i].channel = 8; // invalid
      onOffSettings[i].timeHoursStart0 = 0;
      onOffSettings[i].timeHoursStart1 = 0;
      onOffSettings[i].timeMinutesStart0 = 0;
      onOffSettings[i].timeMinutesStart1 = 0;
      onOffSettings[i].timeHoursEnd0 = 0;
      onOffSettings[i].timeHoursEnd1 = 0;
      onOffSettings[i].timeMinutesEnd0 = 0;
      onOffSettings[i].timeMinutesEnd1 = 0;
    }
  }
}

// ----------------------------------------------------
void processAtPowerDown() { // when power down, save current status
  storeTime();
}