#include <EEPROM.h>

#define REC_PACKET_SIZE 11
#define SND_PACKET_SIZE 11
//#ifdef SERIAL_PORT_HARDWARE1
#  define SER1
//#endif

const int pinLED = 2, pin_rs485 = 4;
byte address = 0xFF;
unsigned long last_read_time = 0L;
byte rec_pckt[REC_PACKET_SIZE], rec_bytes_cnt = 0, rec_missed = 0, src_interfase = 0;
byte snd_pckt[SND_PACKET_SIZE] = {0xAC, 0x15, 0, 0, 0, 0, 0, 0, 0, 0, 0xEF};

const int pwm_duty = 3;
const int pwm_period = 26 /* 38 KHz */, pwn_on = pwm_period / (pwm_duty - 1), pwn_off = pwm_period - pwn_on;
const int us_bit0 = 1250, us_bit1 = 400, us_between_bits = 400, us_start0 = 3450, us_start1 = 1700 - 30, us_between_packets = 13000 - 1450;

enum FanSpeed {
	fsAuto,
	fsSpeed1,
	fsSpeed2,
	fsSpeed3,
	fsSpeed4,
};
enum Vane {
	vAuto,
	vPos1,
	vPos2,
	vPos3,
	vPos4,
	vPos5,
	vDynamic,
};
enum Mode {
	mFan,
	mCool,
	mDry,
	mHeat,
};

#pragma pack(push, 1)
struct Settings {
	byte on = 1;
	byte T = 22;
	byte mode = mCool;
	byte fan = fsAuto;
	byte vane = vAuto;
	byte eco = 0;
};
struct ACPacket {
	byte header[5] = {0xDC, 0x34, 0xD9, 0xFE, 0xFF};
	byte on = 0xFF;
	union {
		byte mode_raw = 0xFF;
		struct {
			byte mode_bits0: 3;
			byte mode      : 3;
			byte mode_bits1: 2;
		};
	};
	union {
		byte T_raw = 0xFF;
		struct {
			byte T      : 4;
			byte T_bits1: 4;
		};
	};
	union {
		byte mode2_raw = 0xCF;
		struct {
			byte mode2_bits0: 1;
			byte mode2      : 3;
			byte mode2_bits1: 4;
		};
	};
	union {
		byte fan_raw = 0xFF;
		struct {
			byte fan_speed: 3;
			byte vane     : 3;
			byte fan_auto : 2;
		};
	};
	byte byte_11 = 0xFF;
	byte timer[3] = {0xFF, 0xFF, 0xFF};
	byte eco = 0xFF;
	byte end[2] = {0xFF, 0xFF};
	byte cs = 0;

	void setOn(bool v) {on = v ? 0xDF : 0xFF;}
	bool getOn() const {return on == 0xDF;}

	void setEco(bool v) {eco = v ? 0xDF : 0xFF;}
	bool getEco() const {return eco == 0xDF;}

	void setT(byte t) {if (t < 16 || t > 31) return; T = 31 - t;}
	byte getT() const {return 31 - T;}

	void setMode(Mode m) {
		switch (m) {
		case mFan : mode = 0; break;
		case mHeat: mode = 6; break;
		case mCool: mode = 4; break;
		case mDry : mode = 5; break;
		}
		switch (m) {
		case mFan:
		case mHeat: mode2 = 7; break;
		case mCool: mode2 = 4; break;
		case mDry : mode2 = 5; break;
		}
	}
	Mode getMode() const {
		switch (mode) {
		case 0: return mFan ;
		case 6: return mHeat;
		case 4: return mCool;
		case 5: return mDry ;
		}
		return mCool;
	}

	void     setFanSpeed(FanSpeed s) {fan_speed = 7 - s;}
	FanSpeed getFanSpeed() const {return (FanSpeed)(7 - fan_speed);}

	void setVane(Vane v) {
		if (v == vDynamic) vane = 0;
		else vane = 7 - v;
	}
	Vane getVane() const {
		if (vane == 0) return vDynamic;
		return (Vane)(7 - vane);
	}

	void prepare() {
		bool is_auto = (getVane() == vAuto) || (getFanSpeed() == fsAuto);
		fan_auto = is_auto ? 1 : 2;
		if (getMode() == mFan || getMode() == mDry)
			setT(22);
	};
	byte calcCS() const {
		byte ret = 0;
		for (byte i = 0; i < sizeof(*this) - 1; ++i) {
			ret += ((byte*)this)[i];
		}
		return ret + 0x10;
	}
	void setCS() {prepare(); cs = calcCS();};
};
#pragma pack(pop)

Settings sett;
ACPacket packet;


void pwm(unsigned long us) {
	int cnt = us / pwm_period * 3 / 4;
	for (int i = 0; i < cnt; ++i) {
		digitalWrite(pinLED, HIGH);
		delayMicroseconds(pwn_on);
		digitalWrite(pinLED, LOW);
		delayMicroseconds(pwn_off);
	}
}


void sendBit(byte v) {
	pwm(us_between_bits);
	delayMicroseconds(v == 0 ? us_bit0 : us_bit1);
}


void sendPacket() {
	pwm(us_start0);
	delayMicroseconds(us_start1);
	for (byte b = 0; b < sizeof(ACPacket); ++b) {
		byte v = ((byte*)(&packet))[b];
		sendBit(bitRead(v, 0));
		sendBit(bitRead(v, 1));
		sendBit(bitRead(v, 2));
		sendBit(bitRead(v, 3));
		sendBit(bitRead(v, 4));
		sendBit(bitRead(v, 5));
		sendBit(bitRead(v, 6));
		sendBit(bitRead(v, 7));
	}
	sendBit(0);
}


void sendIR() {
	packet.setOn(sett.on == 1);
	packet.setT(sett.T);
	packet.setFanSpeed((FanSpeed)sett.fan);
	packet.setVane((Vane)sett.vane);
	packet.setMode((Mode)sett.mode);
	packet.setEco(sett.eco == 1);
	packet.setCS();
	noInterrupts();
	sendPacket();
	delayMicroseconds(us_between_packets);
	sendPacket();
	interrupts();
}


void writeAddress(byte a) {
	address = a;
	EEPROM.write(1, address);
}


void writeSettings() {
	EEPROM.put(2, sett);
}


void sendPacket(byte c) {
	snd_pckt[2] = address;
	snd_pckt[3] = c;
	digitalWrite(LED_BUILTIN, HIGH);
	if (src_interfase == 1) {
		if (Serial)
			Serial.write(snd_pckt, SND_PACKET_SIZE);
	} else {
#ifdef SER1
		digitalWrite(pin_rs485, HIGH);
		delay(2); /// WARNING!
		Serial1.write(snd_pckt, SND_PACKET_SIZE);
		Serial1.flush();
		//delay(9);
		digitalWrite(pin_rs485, LOW);
#endif
	}
	digitalWrite(LED_BUILTIN, LOW);
}


bool readSerial(Stream * s, byte interface) {
	if (s->available()) {
		unsigned long cm = millis();
		if ((cm - last_read_time >= 5) && (rec_missed > 1))
			rec_bytes_cnt = 0;
		last_read_time = cm;
		int ab = min(s->available(), REC_PACKET_SIZE - rec_bytes_cnt);
		if (ab < 0) {
			rec_bytes_cnt = 0;
			return false;
		}
		s->readBytes(&(rec_pckt[rec_bytes_cnt]), ab);
		rec_bytes_cnt += ab;
		src_interfase = interface;
		return true;
	}
	return false;
}


void readPacket() {
	bool readed = false;
	readed |= readSerial(&Serial , 1);
#ifdef SER1
	readed |= readSerial(&Serial1, 2);
#endif
	if (!readed || (rec_bytes_cnt < REC_PACKET_SIZE)) {
		rec_missed++;
		return;
	}
	rec_bytes_cnt = rec_missed = 0;
	if (rec_pckt[0] != 0xAC || rec_pckt[1] != 0x15 || rec_pckt[REC_PACKET_SIZE - 1] != 0xEF) return;
	if (rec_pckt[2] != address && rec_pckt[2] != 0xFF) return;
	switch (rec_pckt[3]) {
	case 0x10:
		sett = *(Settings*)(&(rec_pckt[4]));
		snd_pckt[4] = 1;
		sendPacket(0x11);
		sendIR();
		break;
	case 0x12:
		*(Settings*)(&(snd_pckt[4])) = sett;
		sendPacket(0x13);
		break;
	case 0x22:
		sendPacket(0x23);
		break;
	case 0x25:
		if (rec_pckt[4] == rec_pckt[5] && rec_pckt[4] == rec_pckt[6] && rec_pckt[4] == rec_pckt[7])
			writeAddress(rec_pckt[4]);
		snd_pckt[4] = address;
		sendPacket(0x23);
		break;
	default: break;
	}
}


void setup() {
	pinMode(pinLED, OUTPUT);
	digitalWrite(LED_BUILTIN, HIGH);
	
	address = EEPROM.read(1);
	if (address == 0xFF)
		writeAddress(0);
	EEPROM.get(2, sett);
	if (sett.on == 0xFF) {
		sett = Settings();
		writeSettings();
	}
	
	Serial.setTimeout(0);
	Serial.begin(9600);
#ifdef SER1
	pinMode(pin_rs485, OUTPUT);
	digitalWrite(pin_rs485, LOW);
	Serial1.setTimeout(0);
	Serial1.begin(9600);
#endif

	digitalWrite(LED_BUILTIN, LOW);
	//sendIR();
}


void loop() {
	readPacket();
	//digitalWrite(2, state == 1 ? HIGH : LOW);
}

