// Includes:
#include <DMXSerial.h>
#include <LiquidCrystal.h>
#include <EEPROM.h>

// Variables:
int PumpState = LOW;  // State of the pump output pin
int PumpPWM = 0; // Output value of the pump, ranging from 0 to 25
int FanPWM = 0; // Output value of the fan, ranging from 0 to 255
int startChannel = 1; // DMX Address
int FanDefaultLevel = 26; // Default fan level when no DMX signal is received
int PumpDefaultLevel = 0; // Default pump level when no DMX signal is received
unsigned long previousMillis = 0;  // Stores the last time pin was updated
unsigned long previousMillisDmx = 0;
int DMXflash = 0;
int StateUp = 0; // State of the up button
int StateDown = 0; // State of the down button
int StateMenu = 0; // State of the menu button
int StateOK = 0; // State of the OK button
int MenuActive = 0; // Handler for configuration function (99 for debug, 0 for normal)
int MenuChange = 0; // Handler for display clearing
int Debug = 0; // Debug value handler
int FirstSave = 0; // Handler for first configuration of EEPROM
int SaveSettings = 0; // Handler for EEPROM update
byte AddByte0 = 0; // First address byte
byte AddByte1 = 0; // Second address byte
int DMXNotactive = 0; // Handler for active DMX signal
int ThermostatActive = 0; // Handler for thermostat input
int HeaterActive = 1; // Handler for heater control
int HeaterTimeout = 1; // Block pump
unsigned long ThermostatTime = 0;

// Constants:
const int FanPin = 3;  // PWM output pin for the fan.
const int PumpPin = 13;  // The number of the pump pin
const int UpPin = 6; // Pin for the up-button
const int DownPin = 5; // Pin for the down-button
const int MenuPin = 4; // Pin for the Menu button
const int OKPin = 2; // Pin for the OK button
const int maxoutput = 25; // Maximum value for the pump output variable
const long increment = 20; // Increment in on-time (Duty Cycle)
const int rs = 12, en = 11, d4 = 10, d5 = 9, d6 = 8, d7 = 7;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
const int Buttondelay = 100;
const int AddDMX = 0; // Takes up two bytes
const int AddFan = 2;
const int AddPump = 3;
const int AddFirst = 4;
const unsigned long HeaterTimeouttime = 120000; // 3 minutes timeout

void setup() {
  // Initialize LCD
  lcd.begin(16, 2); // Set up the LCD's number of columns and rows
  lcd.print("Initializing");
  lcd.setCursor(0, 1);
  lcd.print("SnoerFZ V1.0");

  // Initialize DMX
  DMXSerial.init(DMXReceiver);
 
  // Set pins
  pinMode(FanPin, OUTPUT); // Sets the fan pin as output
  pinMode(PumpPin, OUTPUT); // Sets the pump pin as output
  pinMode(A0, OUTPUT);
  pinMode(UpPin, INPUT_PULLUP);
  pinMode(DownPin, INPUT_PULLUP);
  pinMode(MenuPin, INPUT_PULLUP);
  pinMode(OKPin, INPUT_PULLUP);
  pinMode(A0, OUTPUT);
  pinMode(A1, INPUT_PULLUP);

  // Set defined state
  digitalWrite(PumpPin, 0);
  digitalWrite(A0, 1);
  analogWrite(FanPin, 0);

  // Read settings
  FirstSave = EEPROM.read(AddFirst);
  if (FirstSave < 255) {
    AddByte0 = EEPROM.read(AddDMX);
    AddByte1 = EEPROM.read(AddDMX + 1);
    FanDefaultLevel = EEPROM.read(AddFan);
    PumpDefaultLevel = EEPROM.read(AddPump);
    startChannel = AddByte0 * 256 + AddByte1;
  }

  // Finish setup
  delay(2000);
  lcd.clear();
}

void loop() {
  // Handle DMX
  unsigned long lastPacket = DMXSerial.noDataSince();
  if (lastPacket < 5000) {
    FanPWM = DMXSerial.read(startChannel);
    PumpPWM = (DMXSerial.read(startChannel + 1) / 10);
    DMXNotactive = 0;
  } else {
    FanPWM = FanDefaultLevel;
    PumpPWM = (PumpDefaultLevel / 10);
    DMXNotactive = 1;
  }

  // Handle PWM
  unsigned long Ontime = PumpPWM * increment;
  unsigned long Offtime = (maxoutput - PumpPWM) * increment;
  unsigned long currentMillis = millis();
  if (PumpPWM < 1) {
    PumpState = LOW;
  } else if (PumpPWM > 24) {
    PumpState = HIGH;
  } else {
    if (currentMillis - previousMillis <= Ontime) {
      PumpState = HIGH;
    } else if ((currentMillis - previousMillis >= Ontime)  && (currentMillis - previousMillis <= ( Offtime + Ontime ))) {
      PumpState = LOW;
    } else if (currentMillis - previousMillis >= (Ontime + Offtime)) {
      previousMillis = currentMillis;
    }
  }

  // Handle Heater
  ThermostatActive = !digitalRead(A1);
  if (ThermostatActive == 0){
    ThermostatTime = millis();
  }

  if ((millis() - ThermostatTime) > HeaterTimeouttime){
    HeaterTimeout = 1;
  } else {
    if (HeaterTimeout == 1) {
      MenuChange = 1;
    }
    HeaterTimeout = 0;
  }

  HeaterActive = !ThermostatActive;
  
  // Write outputs:
  if (HeaterTimeout == 0){
    digitalWrite(PumpPin, PumpState);
  }
  analogWrite(FanPin, FanPWM);
  digitalWrite(A0, HeaterActive);

  // Handle inputs:
  StateUp = !digitalRead(UpPin);
  StateDown = !digitalRead(DownPin);
  StateMenu = !digitalRead(MenuPin);
  StateOK = !digitalRead(OKPin);

  // Handle Menu Selection
  if ((StateMenu > 0) && (MenuActive == 0)) {
    delay(Buttondelay);
    MenuActive = 1;
    MenuChange = 1;
  } else if ((StateMenu > 0) && (MenuActive == 1)) {
    delay(Buttondelay);
    MenuActive = 2;
    MenuChange = 1;
  } else if ((StateMenu > 0) && (MenuActive == 2)) {
    delay(Buttondelay);
    MenuActive = 3;
    MenuChange = 1;
  } else if ((StateMenu > 0) && (MenuActive == 3)) {
    delay(Buttondelay);
    MenuActive = 0;
    MenuChange = 1;
  } else if ((MenuActive > 0) && (StateOK > 0)) {
    delay(Buttondelay);
    SaveSettings = 1;
    MenuActive = 4;
    MenuChange = 1;
  }

  // Handle Menu Function
  if (MenuActive == 1) { // DMX
    if ((StateUp > 0) && (StateDown < 1)) {
      if (startChannel < 511) {
        startChannel = startChannel + 1;
      } else {
        startChannel = 1;
      }
      MenuChange = 1;
      delay(Buttondelay);
    } else if ((StateUp < 1) && (StateDown > 0)) {
      if (startChannel > 1 ) {
        startChannel = startChannel - 1;
      } else {
        startChannel = 511;
      }
      MenuChange = 1;
      delay(Buttondelay);
    }
    AddByte0 = startChannel / 256;
    AddByte1 = startChannel % 256;
  } else if (MenuActive == 2) { // Fan
    if ((StateUp > 0) && (StateDown < 1)) {
      if (FanDefaultLevel < 255 ) {
        FanDefaultLevel = FanDefaultLevel + 1;
      } else {
        FanDefaultLevel = 0;
      }
      MenuChange = 1;
      delay(Buttondelay);
    } else if ((StateUp < 1) && (StateDown > 0)) {
      if (FanDefaultLevel > 0 ) {
        FanDefaultLevel = FanDefaultLevel - 1;
      } else {
        FanDefaultLevel = 255;
      }
      MenuChange = 1;
      delay(Buttondelay);
    }
  } else if (MenuActive == 3) { // Pump
    if ((StateUp > 0) && (StateDown < 1)) {
      if (PumpDefaultLevel < 255) {
        PumpDefaultLevel = PumpDefaultLevel + 1;
      } else {
        PumpDefaultLevel = 0;
      }
      MenuChange = 1;
      delay(Buttondelay);
    } else if ((StateUp < 1) && (StateDown > 0)) {
      if (PumpDefaultLevel > 0 ) {
        PumpDefaultLevel = PumpDefaultLevel - 1;
      } else {
        PumpDefaultLevel = 255;
      }
      MenuChange = 1;
      delay(Buttondelay);
    }
  }

  // Handle EEPROM update:
  if (SaveSettings == 1) {
    EEPROM.update(AddFirst, 0);
    EEPROM.update(AddDMX, AddByte0);
    EEPROM.update(AddDMX + 1, AddByte1);
    EEPROM.update(AddFan, FanDefaultLevel);
    EEPROM.update(AddPump, PumpDefaultLevel);
    SaveSettings = 0;
  }

  // Handle Display:
  // Display clear
  if (MenuChange == 1) {
    lcd.clear();
    MenuChange = 0;
  }
  // Menu handler
  if (MenuActive < 1) {
    lcd.setCursor(0, 0);
    lcd.print("SnoerFZ");
    if (DMXNotactive == 1) {
      unsigned long currenttimedmxflash = millis();
      if (currenttimedmxflash - previousMillisDmx >= 1000 ) {
        previousMillisDmx = currenttimedmxflash;
        if (DMXflash == 0 ) {
          lcd.setCursor(8, 0);
          lcd.print("No DMX");
          DMXflash = 1;
        } else {
          lcd.setCursor(8, 0);
          lcd.print("      ");
          DMXflash = 0;
        }
      }
    } else {
      lcd.setCursor(8, 0);
      lcd.print("DMX:");
      lcd.setCursor(13, 0);
      lcd.print(startChannel);
    }
    int FanPWMPrint = (FanPWM / 2.54);
    lcd.setCursor (0, 1);
    lcd.print("Fan:");
    lcd.setCursor(4, 1);
    lcd.print(FanPWMPrint);
    int PumpPWMPrint = (PumpPWM * 4);
    if (HeaterTimeout == 0){
      lcd.setCursor (8, 1);
      lcd.print("Pump:");
      lcd.setCursor(13, 1);
      lcd.print(PumpPWMPrint);
    } else if (HeaterTimeout == 1){
      lcd.setCursor (8, 1);
      lcd.print("LowTemp");
    }
  } else if (MenuActive == 1) {
    lcd.setCursor(0, 0);
    lcd.print("Set DMX:");
    lcd.setCursor (0, 1);
    lcd.print(startChannel);
  } else if (MenuActive == 2) {
    lcd.setCursor(0, 0);
    lcd.print("Set Fan Default:");
    lcd.setCursor (0, 1);
    lcd.print(FanDefaultLevel);
  } else if (MenuActive == 3) {
    lcd.setCursor(0, 0);
    lcd.print("Set Pump Default:");
    lcd.setCursor (0, 1);
    lcd.print(PumpDefaultLevel);
  } else if (MenuActive == 4) {
    lcd.setCursor(0, 0);
    lcd.print("Writing Values");
    delay(2000);
    MenuActive = 0;
    MenuChange = 1;
  } else if (MenuActive == 99) {
    lcd.setCursor(0, 0);
    lcd.print("Debug");
    lcd.setCursor (0, 1);
    lcd.print(AddByte0);
    lcd.setCursor (7, 1);
    lcd.print(AddByte1);
  }
}