/* License: CC-BY-SA 3.0 and upper http://creativecommons.org/licenses/by-sa/3.0/
 * Author: Vladimir Ermakov <vooon341@gmail.com>
 */
/**
 * Program for DIY halogen lamp controller
 * with infrared remote function.
 *
 * BTN algo:
 * 	BTN1:	one click triggers CH1 (on) / CH1 & CH2 (off),
 * 		double click enable CH1 & CH2
 * 	BTN2:	click triggers CH3
 *
 * If any CH was enabled then PWR also enabled.
 */

#include <Bounce.h>
#include <IRremote.h>
#include <IRremoteInt.h>
#include <avr/eeprom.h>

/* IO definition */
#define CH1_COIL	2
#define CH2_COIL	3
#define CH3_COIL	4
#define PWR_COIL	5
#define BTN1		6
#define BTN2		7
#define IR_RX		11
#define IR_LED		13

/* Options */
#define BTN_DEB_MS	50
#define BTN_DOUBLE_MS	500
#define BTN_PROGRAM_IR	10

//#define T_SHOW_CH
//#define T_SHOW_IR

struct ircode {
	uint32_t	code;
	byte		type;
} irb[3];

EEMEM struct ircode ee_irb[3];

/* global variables */
boolean ch1, ch2, ch3;

Bounce btn1(BTN1, BTN_DEB_MS), btn2(BTN2, BTN_DEB_MS);
unsigned long btn1_time_ms, btn2_time_ms;
byte btn2_cnt = 0;

IRrecv irrecv(IR_RX);
decode_results irresults;

void setup()
{
	/* coils */
	pinMode(CH1_COIL, OUTPUT);
	pinMode(CH2_COIL, OUTPUT);
	pinMode(CH3_COIL, OUTPUT);
	pinMode(PWR_COIL, OUTPUT);

	/* Buttons */
	pinMode(BTN1, INPUT);
	pinMode(BTN2, INPUT);

	/* IR */
	eeprom_read_block(&irb, &ee_irb, sizeof(irb));
	pinMode(IR_RX, INPUT);
	pinMode(IR_LED, OUTPUT);
	irrecv.blink13(true);
	irrecv.enableIRIn();

#if defined(T_SHOW_CH) || defined(T_SHOW_IR)
	/* Serial */
	Serial.begin(115200);
	Serial.println("RUN AWAY!");
#endif
	/* RUN AWAY! */
}

void do_bind_ir()
{
	uint32_t prev_code = REPEAT;
	byte cnt = 0;
	boolean blink = HIGH;
	unsigned long blink_ms = millis();

	digitalWrite(PWR_COIL, LOW);
	while (cnt < 3) {
		if (millis() - blink_ms > 1000) {
			blink_ms = millis();
			blink ^= HIGH;
		}

		digitalWrite(CH1_COIL, cnt == 0 && blink);
		digitalWrite(CH2_COIL, cnt == 1 && blink);
		digitalWrite(CH3_COIL, cnt == 2 && blink);

		if (irrecv.decode(&irresults)) {
			irrecv.resume();
			if (irresults.value == REPEAT || irresults.value == prev_code)
				continue;
			irb[cnt].type = irresults.decode_type;
			irb[cnt].code = irresults.value;
			cnt++;
		}
	}

	eeprom_write_block(&irb, &ee_irb, sizeof(irb));
}

void loop()
{
	static uint32_t prev_code = REPEAT;
#ifdef T_SHOW_CH
	boolean show_ch = false;
#endif

	btn1.update();
	btn2.update();

	if (btn2.risingEdge()) {
		ch3 ^= HIGH;

		if (millis() - btn2_time_ms < BTN_DOUBLE_MS) {
			if (++btn2_cnt >= BTN_PROGRAM_IR)
				do_bind_ir();
		} else
			btn2_cnt = 0;

		btn2_time_ms = millis();
#ifdef T_SHOW_CH
		show_ch = true;
#endif
	}

	if (btn1.risingEdge()) {
		if (millis() - btn1_time_ms > BTN_DOUBLE_MS) {
			/* one click */
			btn1_time_ms = millis();
			ch1 ^= HIGH;
			if (!ch1)
				ch2 = LOW;

		} else if (ch1) {
			/* double click */
			ch2 = HIGH;
		}

#ifdef T_SHOW_CH
		show_ch = true;
#endif
	}

	if (irrecv.decode(&irresults)) {
#ifdef T_SHOW_IR
		Serial.print("TIME="); Serial.print(millis());
		Serial.print("IR=0x"); Serial.println(irresults.value, HEX);
#endif
		irrecv.resume();

		if (prev_code == irresults.value || REPEAT == irresults.value) {
			/* NOTE: NOP, just catch repeat */
		} else if (irresults.value == irb[0].code &&
				irresults.decode_type == irb[0].type) {
			ch1 ^= HIGH;
		} else if (irresults.value == irb[1].code &&
				irresults.decode_type == irb[1].type) {
			ch2 ^= HIGH;
		} else if (irresults.value == irb[2].code &&
				irresults.decode_type == irb[2].type) {
			ch3 ^= HIGH;
		}

		prev_code = irresults.value;
#ifdef T_SHOW_CH
		show_ch = true;
#endif
	}

	digitalWrite(PWR_COIL, (ch1 || ch2 || ch3));
	delay(10);
	digitalWrite(CH1_COIL, ch1);
	digitalWrite(CH2_COIL, ch2);
	digitalWrite(CH3_COIL, ch3);

#ifdef T_SHOW_CH
	if (show_ch) {
		Serial.print("TIME=");	Serial.print(millis());
		Serial.print(";CH1=");	Serial.print(ch1, DEC);
		Serial.print(";CH2=");	Serial.print(ch2, DEC);
		Serial.print(";CH3=");	Serial.println(ch3, DEC);
	}
#endif
}
