#include <EEPROM.h>

const int ledPin =  13;      // the number of the LED pin
const int daliRX = 3;
const int daliTX = 8;
const int rs485 = 4;
const int daliPower = 12;
const int daliFail = 7; /// EROOR need 6 (27) but txled(22) 
const int button1 = A1;
const int button2 = A2;

const int TE = 830/2; // 350 - 500;  750 - 900

#define COM_DALI_SEND 0x12
#define COM_DALI_READ 0x13
#define COM_DALI_TIMEOUT 0x14
#define COM_DALI_SENDTWICE 0x15
//#define COM_DALI_READTWICE 0x16
#define COM_DALI_FADEUP 0x18
#define COM_DALI_FADEDOWN 0x19
#define COM_ADDR_CHECK 0x22 
#define COM_ADDR_OK 0x23 
#define COM_ADDR_ERROR 0x24 
#define COM_ADDR_WRITE 0x25
#define COM_DALI_POWER_OFF 0x31
#define COM_DALI_POWER_ON 0x32
#define COM_DALI_POWER_FAIL 0x33


volatile unsigned long ctime = 0;
unsigned long ptime[32];
unsigned long sendtime = 0;
byte index = 0;
byte adress = 255;
 
int button1State = HIGH, button2State = HIGH; 
int button1PrevState = HIGH, button2PrevState = HIGH;
int src_interfase = 1; // 1-485 2-usb


void sendBit(byte v) {
  if (v == 1) {
      digitalWrite(daliTX, LOW);
      delayMicroseconds(TE);  
      digitalWrite(daliTX, HIGH);  
      delayMicroseconds(TE);  
  }
  if (v == 0) {
      digitalWrite(daliTX, HIGH);
      delayMicroseconds(TE);  
      digitalWrite(daliTX, LOW);      
      delayMicroseconds(TE);  
  }
}

void sendDali(byte addr, byte data) {
   // start bit
    unsigned long send_delay = millis() - sendtime;
    if (send_delay < 10) delay(10-send_delay);
    noInterrupts();
    sendBit(1);

    // address
    sendBit(bitRead(addr, 7));
    sendBit(bitRead(addr, 6));
    sendBit(bitRead(addr, 5));
    sendBit(bitRead(addr, 4));
    sendBit(bitRead(addr, 3));
    sendBit(bitRead(addr, 2));
    sendBit(bitRead(addr, 1));
    sendBit(bitRead(addr, 0));

    // command
    sendBit(bitRead(data, 7));
    sendBit(bitRead(data, 6));
    sendBit(bitRead(data, 5));
    sendBit(bitRead(data, 4));
    sendBit(bitRead(data, 3));
    sendBit(bitRead(data, 2));
    sendBit(bitRead(data, 1));
    sendBit(bitRead(data, 0));
    
    // stop
    digitalWrite(daliTX, HIGH);      
    delayMicroseconds(4*TE);
    sendtime = millis();
    interrupts();
}


void daliRecv() {
    ctime = micros();
}


int daliDecode() {
	byte data = 0;
	byte curbit = 1;
	byte cnt = -1;
	byte bits = 0;
	unsigned long cd;
	for (byte i=1; i<index; i++) {
		cd = ptime[i] - ptime[i-1];
		if (cd > 350 && cd < 550) {
			cnt++;
		}
		if (cd > 750 && cd < 950) {
			if (i == 1) return -1;
			curbit = !curbit;
			cnt+=2;
		}
		if (cnt%2 == 0 && i != 1) {
                        //Serial.println(curbit);
			data |= curbit;
			bits++;
			if (bits == 8) return data;
			data <<= 1;
		}
	}
	return -1;
}


void sendTo485(byte command, byte data1, byte data2) {
	byte amsg[7];
	amsg[0] = 0xDA;
	amsg[1] = 0x71;
	amsg[2] = adress;
	amsg[3] = command;
	amsg[4] = data1;
	amsg[5] = data2;
	amsg[6] = 0xEF;
	if (src_interfase == 2)
		Serial.write(amsg, 7);
	else {
		digitalWrite(rs485, HIGH);
		Serial1.write(amsg, 7);
		Serial1.flush();
		//delay(10);
		digitalWrite(rs485, LOW);
	}
}


void setup() {
	// initialize the LED pin as an output:
	pinMode(ledPin, OUTPUT);      
	digitalWrite(ledPin, HIGH); 
	adress = EEPROM.read(1);
	digitalWrite(ledPin, LOW); 
	// initialize the pushbutton pin as an input:
	//pinMode(buttonPin, INPUT);
	pinMode(rs485, OUTPUT);
	digitalWrite(rs485, LOW);   
	pinMode(daliRX, INPUT);
	pinMode(daliTX, OUTPUT);
	digitalWrite(daliTX, HIGH); 
	pinMode(daliPower, OUTPUT);
	digitalWrite(daliPower, HIGH); 
	pinMode(daliFail, INPUT);
	digitalWrite(daliFail, HIGH); 
	pinMode(button1, INPUT);
	pinMode(button2, INPUT);  
	digitalWrite(button1, HIGH); 
	digitalWrite(button2, HIGH); 
	//noInterrupts();    
	attachInterrupt(1, daliRecv, CHANGE);
	int timeout = 7 * 1000 * 1.5 / (9600 / 10);
	Serial1.begin(9600); 
	Serial1.setTimeout(timeout);
	Serial.begin(9600); 
	Serial.setTimeout(timeout);
}


void loop() {
  //Serial.println(digitalRead(daliFail));
	button1State = digitalRead(button1);
	button2State = digitalRead(button2);
	if (button1State == LOW && button1PrevState == HIGH) {     
		digitalWrite(ledPin, HIGH);  
		sendDali(0xFF, 0x0);
		digitalWrite(ledPin, LOW);
	}
	if (button2State == LOW && button2PrevState == HIGH) {     
		digitalWrite(ledPin, HIGH);  
		sendDali(0xFF, 0x5);
		digitalWrite(ledPin, LOW);
	}
	byte msg[7];
	byte read_ok = 0;
	if (Serial1.available() && read_ok == 0) {
		Serial1.readBytes((char*)msg, 7);
		if (msg[0] == 0xDA && msg[1] == 0x71 && (msg[2] == 255 || msg[2] == adress) && msg[6] == 0xEF) {
			read_ok = 1;
			src_interfase = 1;
		}
	}
	if (Serial.available() && read_ok == 0) {
		Serial.readBytes((char*)msg, 7);
		if (msg[0] == 0xDA && msg[1] == 0x71 && (msg[2] == 255 || msg[2] == adress) && msg[6] == 0xEF) {
			read_ok = 1;
			src_interfase = 2;
		}
	}
    if (read_ok == 1) {
		byte caddr;
		int fade_time;
		switch(msg[3]) {
		case COM_DALI_SEND :
			digitalWrite(ledPin, HIGH);  
			sendDali(msg[4], msg[5]);
			digitalWrite(ledPin, LOW);
			break;
		case COM_DALI_READ:
			digitalWrite(ledPin, HIGH);  
			sendDali(msg[4], msg[5]);
			digitalWrite(ledPin, LOW);
			daliAnswer();
			break;
		case COM_ADDR_CHECK:
			sendTo485(COM_ADDR_OK, 0, 0);
			break;
		case COM_ADDR_WRITE:
			if (msg[4] == 0 || msg[5] == 0 || msg[4] != msg[5]) {
				sendTo485(COM_ADDR_ERROR, 0, 0);
			} else {
				byte oldaddr = adress;
				EEPROM.write(1, msg[5]);
				adress = EEPROM.read(1);
				if (adress == msg[5])
					sendTo485(COM_ADDR_OK, oldaddr, msg[5]);
				else
					sendTo485(COM_ADDR_ERROR, oldaddr, msg[5]);
			}
			break;
		case COM_DALI_SENDTWICE:
			digitalWrite(ledPin, HIGH);  
			sendDali(msg[4], msg[5]);
			digitalWrite(ledPin, LOW);
			delay(50);
			digitalWrite(ledPin, HIGH);  
			sendDali(msg[4], msg[5]);
			digitalWrite(ledPin, LOW);
			break;
		// case COM_DALI_READTWICE:
			// digitalWrite(ledPin, HIGH);  
			// sendDali(msg[4], msg[5]);
			// digitalWrite(ledPin, LOW);
			// delay(10);
			// digitalWrite(ledPin, HIGH);  
			// sendDali(msg[4], msg[5]);
			// digitalWrite(ledPin, LOW);
			// daliAnswer();
			// break;
		case COM_DALI_FADEUP:
			digitalWrite(ledPin, HIGH);
			caddr = msg[4] & 0xFE;
			fade_time = int(msg[5])*10;
			for (int i=0; i<256; i++) {
				sendDali(caddr, byte(i));
				delay(fade_time);
			}
			digitalWrite(ledPin, LOW);
			break;
		case COM_DALI_FADEDOWN:
			digitalWrite(ledPin, HIGH);
			caddr = msg[4] & 0xFE;
			fade_time = int(msg[5])*10;
			for (int i=255; i>=0; i--) {
				sendDali(caddr, byte(i));
				delay(fade_time);
			}
			digitalWrite(ledPin, LOW);
			break;
		case COM_DALI_POWER_OFF:
			digitalWrite(daliPower, LOW);
			break;
		case COM_DALI_POWER_ON:
			digitalWrite(daliPower, HIGH);
			break;
		default:
			break;
		}
    }
	button1PrevState = button1State;
	button2PrevState = button2State;
}
 
 
void daliAnswer() {
	unsigned long recv_time = millis();
	ctime = 0;
	index = 0;
	while (millis() - recv_time < 20) {
		if (ctime != 0) {
			if (index < 32) {
				ptime[index] = ctime;
				index++;
				ctime = 0; 
				digitalWrite(ledPin, HIGH);  
			}
		}
	}
	//Serial.print("receive ");
	//Serial.println(index);
	//Serial.print(" intervals ");
	//for (byte i=1; i<index; i++) {
	//  Serial.print(ptime[i] - ptime[i-1]);
	//	Serial.print(" ");
	//}
	//Serial.println(" ");
	//Serial.println(daliDecode());
	int res = daliDecode();
	if (res != -1) sendTo485(COM_DALI_READ, 0, res);
	else sendTo485(COM_DALI_TIMEOUT, 0, 0);
	digitalWrite(ledPin, LOW);
}
  
 
