#include <stdio.h>
#include <avr/io.h>
#include <Wire.h>

#define I2C_ADDR    0x27
#define LCD_COLUMNS 16
#define LCD_LINES   2

#define T1ICR1 15624  // 1HZ timer1 (FCPU/1024 -1)
#define PERIOD 10     // Period for LONG PWM in heating output
#define OFFSET 200    // Interval with PWM control
#define BAND 10  			// No temprature update band

// ***********************************************
// LCD Definitions
// ***********************************************

// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

// flags for backlight control
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00

#define LCD_ENABLE_BIT 0b00000100  // Enable bit
#define LCD_READ_WRITE_BIT 0b00000010  // Read/Write bit
#define LCD_REGISTER_SELECT_BIT 0b00000001  // Register select bit

typedef struct LiquidCrystalDevice_t {
	uint8_t Address;
	uint8_t Columns;
	uint8_t Rows;
	uint8_t Backlight;
	uint8_t DisplayFunction;
	uint8_t DisplayControl;
	uint8_t DisplayMode;
} LiquidCrystalDevice_t;



// ***********************************************
// LCD Routines
// ***********************************************

LiquidCrystalDevice_t lq_init(uint8_t address, uint8_t columns, uint8_t rows, uint8_t dotSize)
{
	LiquidCrystalDevice_t device;

	device.Address = address;
	device.Columns = columns;
	device.Rows = rows;
	device.Backlight = LCD_NOBACKLIGHT;
	device.DisplayFunction = LCD_4BITMODE | LCD_1LINE | dotSize;
	device.DisplayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;

	// Initialize to default text direction (for roman languages)
	device.DisplayMode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;

	if (rows > 1) {
		device.DisplayFunction |= LCD_2LINE;
	}

	// for some 1 line displays you can select a 10 pixel high font
	if ((dotSize != 0) && (rows == 1)) {
		device.DisplayFunction |= LCD_5x10DOTS;
	}

	// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
	// according to datasheet, we need at least 40ms after power rises above 2.7V
	// before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
	_delay_ms(100);
	
	// Now we pull both RS and R/W low to begin commands
	lq_transmitI2C(&device, LCD_NOBACKLIGHT);	// reset expanderand turn backlight off (Bit 8 =1)
	_delay_ms(1000);

	//put the LCD into 4 bit mode
	// this is according to the hitachi HD44780 datasheet
	// figure 24, pg 46
	
	// we start in 8bit mode, try to set 4 bit mode
	lq_transmitI2C(&device, 0x03 << 4);
	_delay_us(4500); // wait min 4.1ms
	
	// second try
	lq_writeDevice4Bits(&device, 0x03 << 4);
	_delay_us(4500); // wait min 4.1ms
	
	// third go!
	lq_writeDevice4Bits(&device, 0x03 << 4);
	_delay_us(150); // wait min 150 mics
	
	// finally, set to 4-bit interface
	lq_writeDevice4Bits(&device, 0x02 << 4);

	// set # lines, font size, etc.
	lq_sendCommand(&device, LCD_FUNCTIONSET | device.DisplayFunction);
	
	// turn the display on with no cursor or blinking default
	lq_turnOnDisplay(&device);
	
	// clear it off
	lq_clear(&device);
	
	// set the entry mode
	lq_sendCommand(&device, LCD_ENTRYMODESET | device.DisplayMode);
	
	lq_returnHome(&device);

	return device;
};

void lq_print(struct LiquidCrystalDevice_t* device, char * value)
{
	char letter = *value;

	while(letter != 0x00)
	{
		lq_writeDeviceByte(device, letter, LCD_REGISTER_SELECT_BIT);
		letter = *(++value);
	}
};

void lq_turnOnBacklight(struct LiquidCrystalDevice_t* device)
{
	device->Backlight = LCD_BACKLIGHT;
	lq_transmitI2C(device, 0);
}

void lq_turnOffBacklight(struct LiquidCrystalDevice_t* device)
{
	device->Backlight = LCD_NOBACKLIGHT;
	lq_transmitI2C(device, 0);
}

void lq_clear(LiquidCrystalDevice_t* device)
{
	lq_sendCommand(device, LCD_CLEARDISPLAY); // clear display, set cursor position to zero
	_delay_us(2000);  // this command takes a long time!

	lq_setCursor(device, 0, 0);
}

void lq_setCursor(LiquidCrystalDevice_t* device, uint8_t row, uint8_t column)
{
	uint8_t row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };

	lq_sendCommand(device, LCD_SETDDRAMADDR | (column + row_offsets[row]));
}

void lq_returnHome(LiquidCrystalDevice_t* device)
{
	lq_sendCommand(device, LCD_RETURNHOME);  // set cursor position to zero
	_delay_us(2000);  // this command takes a long time!
};

void lq_turnOnDisplay(LiquidCrystalDevice_t* device)
{
	device->DisplayControl |= LCD_DISPLAYON;
	lq_sendCommand(device, LCD_DISPLAYCONTROL | device->DisplayControl);
};

void lq_turnOffDisplay(LiquidCrystalDevice_t* device)
{
	device->DisplayControl &= ~LCD_DISPLAYON;
	lq_sendCommand(device, LCD_DISPLAYCONTROL | device->DisplayControl);
};

void lq_turnOnCursor(struct LiquidCrystalDevice_t* device)
{
	device->DisplayControl |= LCD_CURSORON;
	lq_sendCommand(device, LCD_DISPLAYCONTROL | device->DisplayControl);
}

void lq_turnOffCursor(struct LiquidCrystalDevice_t* device)
{
	device->DisplayControl &= ~LCD_CURSORON;
	lq_sendCommand(device, LCD_DISPLAYCONTROL | device->DisplayControl);
}

void lq_turnOnBlink(struct LiquidCrystalDevice_t* device)
{
	device->DisplayControl |= LCD_BLINKON;
	lq_sendCommand(device, LCD_DISPLAYCONTROL | device->DisplayControl);
}

void lq_turnOffBlink(struct LiquidCrystalDevice_t* device)
{
	device->DisplayControl &= ~LCD_BLINKON;
	lq_sendCommand(device, LCD_DISPLAYCONTROL | device->DisplayControl);
}

void lq_scrollDisplayLeft(struct LiquidCrystalDevice_t* device)
{
	lq_sendCommand(device, LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}

void lq_scrollDisplayRight(struct LiquidCrystalDevice_t* device)
{
	lq_sendCommand(device, LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}

void lq_leftToRight(struct LiquidCrystalDevice_t* device)
{
	device->DisplayMode |= LCD_ENTRYLEFT;
	lq_sendCommand(device, LCD_ENTRYMODESET | device->DisplayMode);
}

void lq_rightToLeft(struct LiquidCrystalDevice_t* device)
{
	device->DisplayMode &= ~LCD_ENTRYLEFT;
	lq_sendCommand(device, LCD_ENTRYMODESET | device->DisplayMode);
}

void lq_turnOnAutoscroll(struct LiquidCrystalDevice_t* device)
{
	device->DisplayMode |= LCD_ENTRYSHIFTINCREMENT;
	lq_sendCommand(device, LCD_ENTRYMODESET | device->DisplayMode);
}

void lq_turnOffAutoscroll(struct LiquidCrystalDevice_t* device)
{
	device->DisplayMode &= ~LCD_ENTRYSHIFTINCREMENT;
	lq_sendCommand(device, LCD_ENTRYMODESET | device->DisplayMode);
}

void lq_createChar(struct LiquidCrystalDevice_t* device, uint8_t slot, uint8_t charmap[8])
{
	uint8_t i = 0;
	slot &= 0x7; // we only have 8 locations 0-7
	lq_sendCommand(device, LCD_SETCGRAMADDR | (slot << 3));

	for (i = 0; i < 8; i++) 
	{
		lq_writeDeviceByte(device, charmap[i], LCD_REGISTER_SELECT_BIT);
	}
}


void lq_sendCommand(LiquidCrystalDevice_t* device, uint8_t command)
{
	lq_writeDeviceByte(device, command, 0);
}

void lq_writeDeviceByte(LiquidCrystalDevice_t* device, uint8_t value, uint8_t mode)
{
	uint8_t highnib= value & 0xf0;
	uint8_t lownib= (value<<4) & 0xf0;

	lq_writeDevice4Bits(device, highnib | mode);
	lq_writeDevice4Bits(device, lownib | mode);
};

void lq_writeDevice4Bits(LiquidCrystalDevice_t* device, uint8_t value)
{
	lq_transmitI2C(device, value);
	lq_writeDevicePulse(device, value);
};

void lq_writeDevicePulse(LiquidCrystalDevice_t* device, uint8_t value)
{
	lq_transmitI2C(device, value | LCD_ENABLE_BIT);
	_delay_us(2);

	lq_transmitI2C(device, value & ~LCD_ENABLE_BIT);
	_delay_us(50);
};

void lq_transmitI2C(LiquidCrystalDevice_t* device, uint8_t value)
{
	
	Wire.beginTransmission(device->Address); // transmit to device
  Wire.write(value | device->Backlight);   // sends one byte
  Wire.endTransmission();    // stop transmitting

};




// ***********************************************
// DHT Definitions
// ***********************************************

//Port where DHT sensor is connected
#define DHT_DDR DDRB
#define DHT_PORT PORTB
#define DHT_PIN PINB
#define DHT_INPUTPIN 0  //Uno Pin 8

//Define sensor type
#define DHT_DHT11 0
#define DHT_DHT22 1
#define DHT_TYPE DHT_DHT22

//timeout retries
#define DHT_TIMEOUT 200


// ***********************************************
// LCD Routines
// ***********************************************


//function that communicates with DHT sensor 
#if DHT_TYPE == DHT_DHT22
int8_t dht_GetTemp(int16_t *temperature, int16_t *humidity) {
#elif DHT_TYPE == DHT_DHT11
int8_t dht_GetTemp(int8_t *temperature, int8_t *humidity) {
#endif
	uint8_t bits[5];
	uint8_t i,j = 0;

	memset(bits, 0, sizeof(bits));

	//prepare correct port and pin of DHT sensor
	DHT_DDR |= (1 << DHT_INPUTPIN); //output
	DHT_PORT |= (1 << DHT_INPUTPIN); //high
	_delay_ms(100);

	//begin send request
	DHT_PORT &= ~(1 << DHT_INPUTPIN); //low
	#if DHT_TYPE == DHT_DHT11
	_delay_ms(18);
	#elif DHT_TYPE == DHT_DHT22
	_delay_us(500);
	#endif
	DHT_PORT |= (1 << DHT_INPUTPIN); //high
	DHT_DDR &= ~(1 << DHT_INPUTPIN); //input
	_delay_us(40);

	//check first start condition
	if((DHT_PIN & (1<<DHT_INPUTPIN))) {
		return -1;
	}
	_delay_us(80);
	
	//check second start condition
	if(!(DHT_PIN & (1<<DHT_INPUTPIN))) {
		return -1;
	}
	_delay_us(80);

	//read-in data
	uint16_t timeoutcounter = 0;
	for (j=0; j<5; j++) { //for each byte (5 total)
		uint8_t result = 0;
		for(i=0; i<8; i++) {//for each bit in each byte (8 total)
			timeoutcounter = 0;
			while(!(DHT_PIN & (1<<DHT_INPUTPIN))) { //wait for an high input (non blocking)
				timeoutcounter++;
				if(timeoutcounter > DHT_TIMEOUT) {
					return -1;
				}
			}
			_delay_us(30);
			if(DHT_PIN & (1<<DHT_INPUTPIN))
				result |= (1<<(7-i));
			timeoutcounter = 0;
			while(DHT_PIN & (1<<DHT_INPUTPIN)) {
				timeoutcounter++;
				if(timeoutcounter > DHT_TIMEOUT) {
					return -1;
				}
			}
		}
		bits[j] = result;
	}

	//reset port
	DHT_DDR |= (1<<DHT_INPUTPIN); //output
	DHT_PORT |= (1<<DHT_INPUTPIN); //low
	_delay_ms(100);

	//compare checksum
	if ((uint8_t)(bits[0] + bits[1] + bits[2] + bits[3]) == bits[4]) {
		//return temperature and humidity
		#if DHT_TYPE == DHT_DHT22
		*temperature = ((bits[2] & 0x7F)<<8 | bits[3])*((bits[2] &0x80)?-1:1);
		*humidity = bits[0]<<8 | bits[1];
		
		#elif DHT_TYPE == DHT_DHT11
		*temperature = bits[2];
		*humidity = bits[0];
		#endif
		
		return 0;
	}

	return -1;
}


LiquidCrystalDevice_t lcd;
unsigned long int seconds = 0;

ISR(TIMER1_OVF_vect) {
    // on overflow 
    seconds ++;
}

void setup(){

  /**
  * URL: https://dbuezas.github.io/arduino-web-timers/#mcu=ATMEGA328P&timer=1&timerMode=FPWM&topValue=ICR1&OCR1A=3999&clockPrescalerOrSource=1024&CompareOutputModeA=clear-on-match%2C+set-at-max&interruptA=off&InterruptOnTimerOverflow=on&ICR1=15624&FCPU_UI=16Mhz&CompareOutputModeB=set-on-match%2C+clear-at-max&OCR1B=7812
  * Mode     : FPWM
  * Period   : 1 s
  * Frequency: 1 Hz
  * Outputs  : 
  *  - B1: 25.60%, clear-on-match, set-at-max
  *  - B2: 50.00%, clear-on-match, set-at-max
  */
  noInterrupts();
  TCCR1A = 
    1 << COM1A1 |
    1 << COM1B1 |
    1 << WGM11;
  TCCR1B = 
    1 << WGM13 |
    1 << WGM12 |
    1 << CS12 |
    1 << CS10;
  TIMSK1 = 
    1 << TOIE1;
  DDRB = 
    1 << DDB1 |
    1 << DDB2;      // Pin 9 | Pin 10
  OCR1A = 3999;
  OCR1B = 1;
  ICR1 = T1ICR1;     // FCPU/1024 -1
  interrupts();
  Serial.begin(9600);

  lcd = lq_init(I2C_ADDR, LCD_COLUMNS, LCD_LINES, LCD_5x8DOTS);
  lq_turnOnBacklight(&lcd);
	i2C_Init();
	Wire.begin(lcd.Address);

  // Pin 3 + 4 as INPUT_PULLUP
  MCUCR &= ~(1 << PUD);
  DDRD &= !((1 << DDD3) | (1 << DDD4));
  PORTD = (1 << PORTD3) | (1 << PORTD4);

  // Pin 5 as OUTPUT
  DDRD |= (1 << DDD5);
  //PORTD |= (1 << PD5); 
 /* pinMode(TUP,INPUT_PULLUP);
  pinMode(TDN,INPUT_PULLUP);
  pinMode(OUT, OUTPUT);*/
}


void loop() {
  static int16_t tempLast = 0, humLast = 0;
  static int16_t tempSet = 380;
  static byte outputState = LOW;
  static unsigned long lastSwitchTime = 0;
  char buf[10];
  int16_t temperature;
  int16_t humidity;
  
  //temperature = dht.readTemperature();
  //humidity = dht.readHumidity();
  dht_GetTemp(&temperature, &humidity);
  if ((tempLast != temperature) || (humLast != humidity)) {
    tempLast = temperature;
    humLast = humidity;
    lq_clear(&lcd);
    lq_setCursor(&lcd, 0, 0);
    lq_print(&lcd, "Temp: ");
    dtostrf(temperature/10.0, 3, 1, buf);
    lq_print(&lcd, buf);
    lq_print(&lcd, " \xDF");
    lq_print(&lcd, "C");
    lq_setCursor(&lcd, 1, 0);
    lq_print(&lcd, "Hum: ");
    dtostrf(humidity/10, 3, 1, buf);
    lq_print(&lcd, buf);
    lq_print(&lcd, " %");
  }

  if ((PIND & (1 << PIND4))==0) {  //digitalRead(TUP) == LOW) {
    tempSet++;
    lq_clear(&lcd);
    lq_setCursor(&lcd, 0, 0);
    lq_print(&lcd, "T. Set: ");
    dtostrf(tempSet/10.0, 3, 1, buf);
    lq_print(&lcd, buf);
    lq_print(&lcd, " \xDF");
    lq_print(&lcd, "C");
    delay(100);
  }

  if ((PIND & (1 << PIND3))==0) {  //(digitalRead(TDN) == LOW) {
    tempSet--;
    lq_clear(&lcd);
    lq_setCursor(&lcd, 0, 0);
    lq_print(&lcd, "T. Set: ");
    dtostrf(tempSet/10.0, 3, 1, buf);
    lq_print(&lcd, buf);
    lq_print(&lcd, " \xDF");
    lq_print(&lcd, "C");
    delay(100);
  }
  
  int error = tempSet - temperature;
  if (abs(error) > BAND) {
    if (error > 0) {
  OCR1B = 1;
  byte dutyCycle = (error > OFFSET)? 100 : (error * 100/OFFSET);
  unsigned long onTime = (dutyCycle * PERIOD) / 100;
  unsigned long offTime = PERIOD - onTime;
  
  Serial.print(error);
  Serial.print(" ");
  Serial.println(dutyCycle);

  unsigned long currentTime = seconds;

  if (outputState == HIGH && (currentTime - lastSwitchTime >= onTime))
  {
    lastSwitchTime = currentTime;
    outputState = LOW;
  }

  if (outputState == LOW && (currentTime - lastSwitchTime >= offTime))
  {
    lastSwitchTime = currentTime;
    outputState = HIGH;
  }

 if (outputState == HIGH) 
  PORTD |= (1 << PORTD5);
 else
  PORTD &= ~(1 << PORTD5);
    }
  else {
    //byte dutyCycle = (error < -OFFSET)? 100 : (-error * 100/OFFSET);
    PORTD &= ~(1 << PORTD5);
    outputState = LOW;
    OCR1B = (error < -OFFSET)? T1ICR1 : (T1ICR1/OFFSET * (-error)); 
     /* Serial.print(error);
  Serial.print(" ");
    Serial.println( T1ICR1/OFFSET * (-error));*/
  }
  }
}
NOCOMNCVCCGNDINLED1PWRRelay Module