#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Servo.h>

// LCD nastaveni
LiquidCrystal_I2C lcd(0x27, 20, 4);

// DS18B20 nastaveni
#define ONE_WIRE_BUS 10
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// Servo nastaveni
Servo myservo;
#define SERVO_PIN 9

// Rotacni enkoder nastaveni
#define ENCODER_CLK 7
#define ENCODER_DT 6
#define ENCODER_SW 5

bool menuOption = 0; // prepinani menu na displeji
float temperature = 24; // promenna pro nacteni teploty na displeji
float lastTemperature = 0.0; // promenna pro prepsani displeje pri zmene teploty
int angle = 0; // promenna pro uhel serva
float minTemp = 50.0; // promenna pro minimalni rozsah teploty
float maxTemp = 70.0; // promenna pro minimalni rozsah teploty

// promenne pro otaceni rotacniho enkoderu
int currentStateCLK;
int lastStateCLK;
unsigned long lastButtonPress = 0; // promenna pro zmacknuti cudliku na enkoderu
unsigned long dsread = 0; // promenna pro cteni cidla co 3 sekundy

// symbol stupne
byte degreeSymbol[8] = {
  0b00111,
  0b00101,
  0b00111,
  0b00000,
  0b00000,
  0b00000,
  0b00000,
  0b00000
};

void setup() {
  // Nastaveni enkoderu jako input
  pinMode(ENCODER_CLK, INPUT);
  pinMode(ENCODER_DT, INPUT);
  pinMode(ENCODER_SW, INPUT_PULLUP);

  // Precteni aktualniho stavu enkoderu
  lastStateCLK = digitalRead(ENCODER_CLK);

  // Inicializace LCD
  lcd.init();
  lcd.backlight();

  // Vytvoreni znaku pro stupne v pameti
  lcd.createChar(0, degreeSymbol);

  // Inicializace teplotniho cidla
  sensors.begin();

  // Inicializace serva
  myservo.attach(SERVO_PIN);

  // Prvni cteni z cidla
  sensors.requestTemperatures();
  temperature = sensors.getTempCByIndex(0);
}

void loop() {
  // Cteni z cidla co 3 sekundy
  if (dsread + 3000 < millis()) {
    sensors.requestTemperatures();
    temperature = sensors.getTempCByIndex(0);
    dsread = millis();
  }

  // Premapovani rozsahu cidla na rozsah serva
  angle = map(temperature, minTemp, maxTemp, 0, 90);
  angle = constrain(angle, 0, 90);
  myservo.write(angle);

  // Pokud se hodnota teploty zmenila, aktualizuj displej
  if (temperature != lastTemperature)
    updateDisplay();

  // Precti enkoder a pokud se zmenil uprav promennou dle aktualniho menu
  currentStateCLK = digitalRead(ENCODER_CLK);

  if (currentStateCLK == 1 && currentStateCLK != lastStateCLK) {

    // na kterou stranu se enkoder pohnul:
    if (digitalRead(ENCODER_DT) != currentStateCLK) {
      if (!menuOption) minTemp = minTemp - 0.5;
      else maxTemp = maxTemp - 0.5;
    } else {
      // Encoder is rotating CW so increment
      if (!menuOption) minTemp = minTemp + 0.5;
      else maxTemp = maxTemp + 0.5;
    }

    // akutalizuj displej
    updateDisplay();
  }

  // Zapamatuj si aktualni stav enkoderu
  lastStateCLK = currentStateCLK;

  // Precti aktualni stav tlacitka
  int btnState = digitalRead(ENCODER_SW);

  // Pokud je zmacknuto a nebylo zmacknuti zaregistrovano v poslednich 50 ms, proved zmenu menu a aktualizuj displej
  if (btnState == LOW) {
    if (millis() - lastButtonPress > 50) {
      menuOption = !menuOption;
      updateDisplay();
    }

    // Zapamatuj si kdy bylo tlacitko zmacknuto
    lastButtonPress = millis();
  }

  // Delay pro debouncing enkoderu
  delay(1);
}


// Funkce pro napsani udaju na displej vcetne akutalniho menu
void updateDisplay() {
  lastTemperature = temperature;

  lcd.setCursor(0, 0);
  lcd.print("Teplota: ");
  lcd.print(temperature, 1);
  lcd.write(byte(0));
  lcd.print("C ");

  lcd.setCursor(0, 1);
  lcd.print("Uhel serva: ");
  lcd.print(angle, 1);
  lcd.write(byte(0));
  lcd.print("  ");

  lcd.setCursor(0, 2);
  if (menuOption == 0) lcd.print(">Min: ");
  else lcd.print(" Min: ");
  lcd.print(minTemp, 1);
  lcd.write(byte(0));
  lcd.print("C ");

  lcd.setCursor(0, 3);
  if (menuOption == 1) lcd.print(">Max: ");
  else lcd.print(" Max: ");
  lcd.print(maxTemp, 1);
  lcd.write(byte(0));
  lcd.print("C ");
}