#include <DHT.h>
#include <util/twi.h>

#define DHTPIN 6
#define DHTTYPE DHT22
#define TUP 4
#define TDN 3
#define OUT 5

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


DHT dht(DHTPIN, DHTTYPE);

#define PERIOD 5000
#define OFFSET 15

/*
#include <avr/io.h>
#include <util/delay.h>
#include <util/twi.h>
#include <stdint.h>
*/
#ifndef  F_CPU
#define F_CPU 16000000UL
#endif


// ***********************************************
// 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;


// ***********************************************
// I2C Definitions
// ***********************************************

#define I2C_SCL_FREQUENCY_100 100000UL // SCL frequency = 100 kHz
#define I2C_SCL_FREQUENCY_400 400000UL // SCL frequency = 400 kHz

#define I2C_READ 0x01
#define I2C_WRITE 0x00

#define I2C_STATUS_SUCCESS 0
#define I2C_STATUS_ERROR_START_WAS_NOT_ACCEPTED 10
#define I2C_STATUS_ERROR_TRANSMIT_OR_READ_WAS_NOT_ACKNOWLEDGED 20
#define I2C_STATUS_ERROR_TRANSMIT_NOT_ACKNOWLEDGED 21
#define I2C_STATUS_ERROR_READ_NOT_ACKNOWLEDGED 22
#define I2C_SCL_FREQUENCY_PRESCALER 1
//#define I2C_TWBR_VALUE 

#define I2C_PRESCALER_MASK 0xF8

// ***********************************************
// I2C Routines
// ***********************************************

void i2c_master_init(unsigned long frequency)
{
	
	// activate internal pullups for twi.
  //digitalWrite(SDA, 1);
  //digitalWrite(SCL, 1);

  // initialize twi prescaler and bit rate
  TWSR &= !((1<<TWPS0) | (1<<TWPS1));
	TWBR = (uint8_t)((((F_CPU / frequency) / I2C_SCL_FREQUENCY_PRESCALER) - 16 ) / 2);
	TWCR = (1<<TWEN) | (1<<TWIE) | (1<<TWEA);
}

uint8_t i2c_master_start(uint8_t address, uint8_t mode)
{
	uint8_t   twst;

	// reset control register
	TWCR = 0;

	// transmit START condition
	TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);

	// wait for end of transmission
	while( !(TWCR & (1<<TWINT)) );
	
	// check if the start condition was successfully transmitted. Mask prescaler bits.
	twst = TW_STATUS & I2C_PRESCALER_MASK;
	if ( (twst != TW_START) && (twst != TW_REP_START))
	{
		return I2C_STATUS_ERROR_START_WAS_NOT_ACCEPTED;
	}
	
	// load shifted slave address into data register with specified mode
	TWDR = (address << 1) | mode;

	// start transmission of address
	TWCR = (1<<TWINT) | (1<<TWEN);

	// wait for end of transmission
	while( !(TWCR & (1<<TWINT)) );
	
	// check if the device has acknowledged the READ / WRITE mode
	twst = TW_STATUS & I2C_PRESCALER_MASK;
	if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) )
	{
		return I2C_STATUS_ERROR_TRANSMIT_OR_READ_WAS_NOT_ACKNOWLEDGED;
	}
	
	return I2C_STATUS_SUCCESS;
};

uint8_t i2c_master_startWait(uint8_t address, uint8_t mode)
{
	uint8_t twst;

	while (1)
	{
		// send START condition
		TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
		
		// wait until transmission completed
		while(!(TWCR & (1<<TWINT)));
		
		// check value of TWI Status Register. Mask prescaler bits.
		twst = TW_STATUS & I2C_PRESCALER_MASK;
		if ( (twst != TW_START) && (twst != TW_REP_START)) continue;
		
		// send device address
		TWDR = (address << 1) | mode;
		TWCR = (1<<TWINT) | (1<<TWEN);
		
		// wail until transmission completed
		while(!(TWCR & (1<<TWINT)));
		
		// check value of TWI Status Register. Mask prescaler bits.
		twst = TW_STATUS & I2C_PRESCALER_MASK;
		if ( (twst == TW_MT_SLA_NACK )||(twst ==TW_MR_DATA_NACK) )
		{
			/* device busy, send stop condition to terminate write operation */
			TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
			
			// wait until stop condition is executed and bus released
			while(TWCR & (1<<TWSTO));
			
			continue;
		}
		break;
	}

	return I2C_STATUS_SUCCESS;
};

uint8_t i2c_master_write(uint8_t data)
{
	uint8_t twst;

	// put data into data register
	TWDR = data;

	// start transmission of data
	TWCR = (1<<TWINT) | (1<<TWEN);

	// wait for end of transmission
	while( !(TWCR & (1<<TWINT)) );
	
	// check value of TWI Status Register. Mask prescaler bits
	twst = TW_STATUS & I2C_PRESCALER_MASK;

	// If Slave device not acknowledged transmission - returns an error
	if( twst != TW_MT_DATA_ACK)
	{
		return I2C_STATUS_ERROR_TRANSMIT_NOT_ACKNOWLEDGED;
	}
	
	return I2C_STATUS_SUCCESS;
}

uint8_t i2c_master_readAck(void)
{
	// start TWI module and acknowledge data after reception
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);

	// wait for end of transmission
	while( !(TWCR & (1<<TWINT)) );

	return TWDR;
}

uint8_t i2c_master_readNack(void)
{
	// start receiving without acknowledging reception
	TWCR = (1<<TWINT) | (1<<TWEN);

	// wait for end of transmission
	while( !(TWCR & (1<<TWINT)) );

	// return received data from TWDR
	return TWDR;
}

uint8_t i2c_master_send(uint8_t address, uint8_t* data, uint16_t length)
{
	// starts the transmitting
	uint8_t status = i2c_master_start(address, I2C_WRITE);

	// in case of error - returns status code
	if (status) return status;
	
	for (uint16_t i = 0; i < length; i++)
	{
		status = i2c_master_write(data[i]);
		if (status) return status;
	}
	
	i2c_master_stop();
	
	return I2C_STATUS_SUCCESS;
}

uint8_t i2c_master_sendByte(uint8_t address, uint8_t data)
{
	uint8_t status = i2c_master_start(address, I2C_WRITE);
	if (status) return status;

	status = i2c_master_write(data);
  //Serial.println(status);
	if (status) return status;

	i2c_master_stop();
	
	return I2C_STATUS_SUCCESS;
};

uint8_t i2c_master_receive(uint8_t address, uint8_t* data, uint16_t length)
{
	uint8_t status = i2c_master_start(address, I2C_READ);
	if (status) return status;
	
	for (uint16_t i = 0; i < (length-1); i++)
	{
		data[i] = i2c_master_readAck();
	}
	data[(length-1)] = i2c_master_readNack();
	
	i2c_master_stop();
	
	return I2C_STATUS_SUCCESS;
}

void i2c_master_stop(void)
{
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
};


// ***********************************************
// 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)
{
	i2c_master_sendByte(device->Address, value | device->Backlight);
};

LiquidCrystalDevice_t lcd;

void setup() {
  Serial.begin(115200);
	i2c_master_init(I2C_SCL_FREQUENCY_400);
  lcd = lq_init(I2C_ADDR, LCD_COLUMNS, LCD_LINES, LCD_5x8DOTS);
  lq_turnOnBacklight(&lcd);
  
  dht.begin();
  pinMode(TUP,INPUT_PULLUP);
  pinMode(TDN,INPUT_PULLUP);
  pinMode(OUT, OUTPUT);
}



void loop() {
  static float tempLast = 0, humLast = 0;
  static float tempSet = 38;
  static byte outputState = LOW;
  static unsigned long lastSwitchTime = 0;
  char buf[10];
  float temperature;
  float humidity;
  
  temperature = dht.readTemperature();
  humidity = dht.readHumidity();
  if ((tempLast != temperature) || (humLast != humidity)) {
    tempLast = temperature;
    humLast = humidity;
    lq_clear(&lcd);
    lq_setCursor(&lcd, 0, 0);
    lq_print(&lcd, "Temp: ");
    dtostrf(temperature, 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, 3, 1, buf);
    lq_print(&lcd, buf);
    lq_print(&lcd, " %");
  }

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

  if (digitalRead(TDN) == LOW) {
    tempSet -= .1;
    lq_clear(&lcd);
    lq_setCursor(&lcd, 0, 0);
    lq_print(&lcd, "T. Set: ");
    dtostrf(tempSet, 3, 1, buf);
    lq_print(&lcd, buf);
    lq_print(&lcd, " \xDF");
    lq_print(&lcd, "C");
    delay(100);
  }

  //if (tempSet > temperature) digitalWrite(OUT, HIGH);
  //else digitalWrite(OUT, LOW);

  byte dutyCycle = map(constrain(tempSet - temperature, -OFFSET , OFFSET), -OFFSET, OFFSET, 0, 100);
  unsigned long onTime = (dutyCycle * PERIOD) / 100;
  unsigned long offTime = PERIOD - onTime;

  unsigned long currentTime = millis();

  if (outputState == HIGH && (currentTime - lastSwitchTime >= onTime))
  {
    lastSwitchTime = currentTime;
    outputState = LOW;
  }
  if (outputState == LOW && (currentTime - lastSwitchTime >= offTime))
  {
    lastSwitchTime = currentTime;
    outputState = HIGH;
  }
  digitalWrite(OUT, outputState);
}
NOCOMNCVCCGNDINLED1PWRRelay Module