//***********************************************//
//******Car Engine RPM and Shift Light***********//
//******with Arduino, HC-05 Bluetooth Module*****//
//**********and ELM-327 OBDII Bluetooth**********//
//***********************************************//
//**********Designed and Programmed**************//
//************by Kostas Kokoras******************//
//************[email protected]*****************//

#include <Timer.h>
#include <EEPROM.h>
#include <SoftwareSerial.h>

#define RxD 7                //Arduino pin connected to Tx of HC-05
#define TxD 8                //Arduino pin connected to Rx of HC-05
#define Reset 9              //Arduino pin connected to Reset of HC-05 (reset with LOW)
#define PIO11 A2             //Arduino pin connected to PI011 of HC-05 (enter AT Mode with HIGH)
#define ledpin_green A0      //Arduino output pin for Shift Light Green led 
#define ledpin_yellow A1     //Arduino output pin for Shift Light Yellow led 
#define ledpin_red 13        //Arduino output pin for Shift Light Red led 
#define led_dec 10           //Arduino output pin for decades 7-segment commone anode display
#define led_mon 11           //Arduino output pin for monades 7-segment commone anode display
#define sel_sw 12            //Arduino input for storing curent Shift Light RPM
#define BT_CMD_RETRIES 5     //Number of retries for each Bluetooth AT command in case of not responde with OK
#define OBD_CMD_RETRIES 3    //Number of retries for each OBD command in case of not receive prompt '>' char
#define RPM_CMD_RETRIES 5    //Number of retries for RPM command
int BinaryPins[] = {3,4,5,6};//Arduino Pins connected to 74LS47 BCD-to-7-Segment
                  //A,B,C,D//  A is LSB

int addr=0;                  //EEPROM address for storing Shift Light RPM
unsigned int rpm,rpm_to_disp;//Variables for RPM
int shift_light_rpm;         //Variable for Shift Light RPM
unsigned int decades;        //Variable of RPM number decades for 7-seg disp
unsigned int monades;        //Variable of RPM number monades for 7-seg disp
boolean but_pressed_flag;    //Variable if RPM Shift Light button is pressed
boolean bt_error_flag;       //Variable for bluetooth connection error
boolean obd_error_flag;      //Variable for OBD connection error
boolean rpm_error_flag;      //Variable for RPM error
boolean rpm_retries;         //Variable for RPM cmd retries
int disp_bright;             //Variable for 7-seg disp brightness
                 
SoftwareSerial blueToothSerial(RxD,TxD);
Timer t;
                  
//-----------------------------------------------------//
void setup()
{
   
   pinMode(RxD, INPUT);
   pinMode(TxD, OUTPUT);
   pinMode(PIO11, OUTPUT);
   pinMode(Reset, OUTPUT);
   
   digitalWrite(PIO11, LOW);    //Set HC-05 to Com mode
   digitalWrite(Reset, HIGH);   //HC-05 no Reset
   
   pinMode(3, OUTPUT);   
   pinMode(4, OUTPUT);
   pinMode(5, OUTPUT);
   pinMode(6, OUTPUT);
 
   pinMode(ledpin_green, OUTPUT);
   pinMode(ledpin_yellow, OUTPUT);
   pinMode(ledpin_red, OUTPUT);
  
   pinMode(led_dec, OUTPUT);
   pinMode(led_mon,OUTPUT);
   
   pinMode(sel_sw,INPUT);
   
   //7-seg disp and shift light test
   demo();
   
   //Read Stored Shift Light RPM//
   shift_light_rpm=EEPROM.read(addr);                                  //Read EEPROM for stored value
   if ((shift_light_rpm<0) or (shift_light_rpm>99)) shift_light_rpm=0; //if value not correct set it to 0
   
   rpm_retries=0;
   but_pressed_flag=false;
   disp_bright=0;//Full Bright
 
   t.every(250,rpm_calc);//Every 250ms read RPM value from OBD
   
   //start Bluetooth Connection
   setupBlueToothConnection();
 
   //in case of Bluetoth connection error
   if (bt_error_flag){
     bt_err_flash();
   } 
 
   //OBDII initialitation
   obd_init();
   
   //in case of OBDII connection error   
   if (obd_error_flag){
     obd_err_flash();
   }  
}

//-----------------------------------------------------//
//---------------flashes fast red light----------------//
//-------in case of Bluetooth connection error---------//
//------loops for ever, need to restart Arduino--------//
void bt_err_flash(){
   
  while(1){
    digitalWrite(ledpin_red,HIGH);
    delay(100);
    digitalWrite(ledpin_red,LOW);
    delay(100);
  }
}

//-----------------------------------------------------//
//---------------flashes slow red light----------------//
//---------in case of OBDII connection error-----------//
//------loops for ever, need to restart Arduino--------//
void obd_err_flash(){
   
  while(1){
    digitalWrite(ledpin_red,HIGH);
    delay(1000);
    digitalWrite(ledpin_red,LOW);
    delay(1000);
  }
}

//-----------------------------------------------------//
//-------Just a demo-test for 7-seg disp and leds------//
void demo(){
  
  //Turn 7-seg disp OFF
  digitalWrite(led_dec,HIGH);
  digitalWrite(led_mon,HIGH);

  //Shift Light LEDS Test
  for(int i=0;i<3;i++){
    digitalWrite(ledpin_green,HIGH);
    digitalWrite(ledpin_yellow,HIGH);
    digitalWrite(ledpin_red,HIGH);
    delay(50);
    digitalWrite(ledpin_green,LOW);
    digitalWrite(ledpin_yellow,LOW);
    digitalWrite(ledpin_red,LOW);
    delay(50);
    
    }

  
  //7-seg diplays test
  for(int i=0;i<3;i++){

    for (int j=0;j<4;j++){
      sevenSegWriteDec(8);
      delay(10);
      sevenSegWriteMon(8); 
      delay(10);
    }
   //Turn 7-seg disp OFF
   digitalWrite(led_dec,HIGH);
   digitalWrite(led_mon,HIGH);
   delay(50);
  }
}

//-----------------------------------------------------//
//----------Decades 7-seg disp driver------------------//
void sevenSegWriteDec(int digit) {
    
  if (digit>0) analogWrite(led_dec,disp_bright); else digitalWrite(led_dec,HIGH); //if value is 0 stays OFF
  digitalWrite(led_mon,HIGH);                                                     //monades 7-seg disp OFF
  
  //convert dec number to 4 bits binary
  for (byte i = 0; i < 4; i++) {
    int dec_val=digit&1;                  //interested only for LSB of digit
    digitalWrite(BinaryPins[i],dec_val);  //set it to BinaryPin i=0 is LSB and goes to pin A of 74LS47 BCD-to-7-Segment
    digit=digit>>1;                       //shift right digit 1 bit 
  }
  
}

//-----------------------------------------------------//
//----------Monades 7-seg disp driver------------------//
void sevenSegWriteMon(int digit) {
  
  digitalWrite(led_dec,HIGH);              //decades 7-seg disp OFF
  analogWrite(led_mon,disp_bright);        //monades 7-seg disp ON
  
  //convert dec number to 4 bits binary
  for (byte i = 0; i < 4; i++) {
    int mon_val=digit&1;                   //interested only for LSB of digit
    digitalWrite(BinaryPins[i],mon_val);   //set it to BinaryPin i=0 is LSB and goes to pin A of 74LS47 BCD-to-7-Segment
    digit=digit>>1;                        //shift right digit 1 bit 
  }
  
}

//-----------------------------------------------------//
//----------display stored shift light RPM-------------//
//-----------------------for 3 secs--------------------//
void flash_store(int num){
  
   unsigned long start=millis();      //set start as current millis
   unsigned long dlay=3000UL;         //set delay to 3000ms
   
   while (millis()<(start+dlay))      //for 3 secs
   {
     //display num
     sevenSegWriteDec(num/10);
     delay(10);
     sevenSegWriteMon(num%10); 
     delay(10);
     //turn red led ON
     digitalWrite(ledpin_red,HIGH);
     digitalWrite(ledpin_green,LOW);
     digitalWrite(ledpin_yellow,LOW);
   } 
  
}

//-----------------------------------------------------//
//----------Retrieve RPM value from OBD----------------//
//---------convert it to readable number---------------//
void rpm_calc(){
   boolean prompt,valid;  
   char recvChar;
   char bufin[15];
   int i;

  if (!(obd_error_flag)){                                   //if no OBD connection error

     valid=false;
     prompt=false;
     blueToothSerial.print("010C1");                        //send to OBD PID command 010C is for RPM, the last 1 is for ELM to wait just for 1 respond (see ELM datasheet)
     blueToothSerial.print("\r");                           //send to OBD cariage return char
     while (blueToothSerial.available() <= 0);              //wait while no data from ELM
     i=0;
     while ((blueToothSerial.available()>0) && (!prompt)){  //if there is data from ELM and prompt is false
       recvChar = blueToothSerial.read();                   //read from ELM
       if ((i<15)&&(!(recvChar==32))) {                     //the normal respond to previus command is 010C1/r41 0C ?? ??>, so count 15 chars and ignore char 32 which is space
         bufin[i]=recvChar;                                 //put received char in bufin array
         i=i+1;                                             //increase i
       }  
       if (recvChar==62) prompt=true;                       //if received char is 62 which is '>' then prompt is true, which means that ELM response is finished 
     }

    if ((bufin[6]=='4') && (bufin[7]=='1') && (bufin[8]=='0') && (bufin[9]=='C')){ //if first four chars after our command is 410C
      valid=true;                                                                  //then we have a correct RPM response
    } else {
      valid=false;                                                                 //else we dont
    }
    if (valid){                                                                    //in case of correct RPM response
      rpm_retries=0;                                                               //reset to 0 retries
      rpm_error_flag=false;                                                        //set rpm error flag to false
      
     //start calculation of real RPM value
     //RPM is coming from OBD in two 8bit(bytes) hex numbers for example A=0B and B=6C
     //the equation is ((A * 256) + B) / 4, so 0B=11 and 6C=108
     //so rpm=((11 * 256) + 108) / 4 = 731 a normal idle car engine rpm
      rpm=0;                                                                                            
      for (i=10;i<14;i++){                              //in that 4 chars of bufin array which is the RPM value
        if ((bufin[i]>='A') && (bufin[i]<='F')){        //if char is between 'A' and 'F'
          bufin[i]-=55;                                 //'A' is int 65 minus 55 gives 10 which is int value for hex A
        } 
         
        if ((bufin[i]>='0') && (bufin[i]<='9')){        //if char is between '0' and '9'
          bufin[i]-=48;                                 //'0' is int 48 minus 48 gives 0 same as hex
        }
        
        rpm=(rpm << 4) | (bufin[i] & 0xf);              //shift left rpm 4 bits and add the 4 bits of new char
       
      }
      rpm=rpm >> 2;                                     //finaly shift right rpm 2 bits, rpm=rpm/4
    }
      
    }
    if (!valid){                                              //in case of incorrect RPM response
      rpm_error_flag=true;                                    //set rpm error flag to true
      rpm_retries+=1;                                         //add 1 retry
      rpm=0;                                                  //set rpm to 0
      //Serial.println("RPM_ERROR");
      if (rpm_retries>=RPM_CMD_RETRIES) obd_error_flag=true;  //if retries reached RPM_CMD_RETRIES limit then set obd error flag to true
    }
}

//----------------------------------------------------------//
//---------------------Send OBD Command---------------------//
//--------------------for initialitation--------------------//

void send_OBD_cmd(char *obd_cmd){
  char recvChar;
  boolean prompt;
  int retries;
 
   if (!(obd_error_flag)){                                        //if no OBD connection error
    
    prompt=false;
    retries=0;
    while((!prompt) && (retries<OBD_CMD_RETRIES)){                //while no prompt and not reached OBD cmd retries
      blueToothSerial.print(obd_cmd);                             //send OBD cmd
      blueToothSerial.print("\r");                                //send cariage return

      while (blueToothSerial.available() <= 0);                   //wait while no data from ELM
      
      while ((blueToothSerial.available()>0) && (!prompt)){       //while there is data and not prompt
        recvChar = blueToothSerial.read();                        //read from elm
        if (recvChar==62) prompt=true;                            //if received char is '>' then prompt is true
      }
      retries=retries+1;                                          //increase retries
      delay(2000);
    }
    if (retries>=OBD_CMD_RETRIES) {                               // if OBD cmd retries reached
      obd_error_flag=true;                                        // obd error flag is true
    }
  }
}
 
//----------------------------------------------------------//
//----------------initialitation of OBDII-------------------//
void obd_init(){
  
  obd_error_flag=false;     // obd error flag is false
  
  send_OBD_cmd("ATZ");      //send to OBD ATZ, reset
  delay(1000);
  send_OBD_cmd("ATSP0");    //send ATSP0, protocol auto

  send_OBD_cmd("0100");     //send 0100, retrieve available pid's 00-19
  delay(1000);
  send_OBD_cmd("0120");     //send 0120, retrieve available pid's 20-39
  delay(1000);
  send_OBD_cmd("0140");     //send 0140, retrieve available pid's 40-??
  delay(1000);
  send_OBD_cmd("010C1");    //send 010C1, RPM cmd
  delay(1000);
  
}

//----------------------------------------------------------//
//-----------start of bluetooth connection------------------//
void setupBlueToothConnection()
{
  
  bt_error_flag=false;                    //set bluetooth error flag to false
  
  enterATMode();                          //enter HC-05 AT mode
  delay(500);

  sendATCommand("RESET");                  //send to HC-05 RESET
  delay(1000);
  sendATCommand("ORGL");                   //send ORGL, reset to original properties
  sendATCommand("ROLE=1");                 //send ROLE=1, set role to master
  sendATCommand("CMODE=0");                //send CMODE=0, set connection mode to specific address
  sendATCommand("BIND=1122,33,DDEEFF");    //send BIND=??, bind HC-05 to OBD bluetooth address
  sendATCommand("INIT");                   //send INIT, cant connect without this cmd 
  delay(1000); 
  sendATCommand("PAIR=1122,33,DDEEFF,20"); //send PAIR, pair with OBD address
  delay(1000);  
  sendATCommand("LINK=1122,33,DDEEFF");    //send LINK, link with OBD address
  delay(1000); 
  enterComMode();                          //enter HC-05 comunication mode
  delay(500);
}

//----------------------------------------------------------//
//------------------reset of HC-05--------------------------//
//-------set reset pin of HC-05 to LOW for 2 secs-----------//
void resetBT()
{
 digitalWrite(Reset, LOW);
 delay(2000);
 digitalWrite(Reset, HIGH);
}

//----------------------------------------------------------//
//--------Enter HC-05 bluetooth moduel command mode---------//
//-------------set HC-05 mode pin to LOW--------------------//
void enterComMode()
{
 blueToothSerial.flush();
 delay(500);
 digitalWrite(PIO11, LOW);
 //resetBT();
 delay(500);
 blueToothSerial.begin(38400); //default communication baud rate of HC-05 is 38400
}

//----------------------------------------------------------//
//----------Enter HC-05 bluetooth moduel AT mode------------//
//-------------set HC-05 mode pin to HIGH--------------------//
void enterATMode()
{
 blueToothSerial.flush();
 delay(500);
 digitalWrite(PIO11, HIGH);
 //resetBT();
 delay(500);
 blueToothSerial.begin(38400);//HC-05 AT mode baud rate is 38400

}

//----------------------------------------------------------//

void sendATCommand(char *command)
{
  
  char recvChar;
  char str[2];
  int i,retries;
  boolean OK_flag;
  
  if (!(bt_error_flag)){                                  //if no bluetooth connection error
    retries=0;
    OK_flag=false;
    
    while ((retries<BT_CMD_RETRIES) && (!(OK_flag))){     //while not OK and bluetooth cmd retries not reached
      
       blueToothSerial.print("AT");                       //sent AT cmd to HC-05
       if(strlen(command) > 1){
         blueToothSerial.print("+");
         blueToothSerial.print(command);
       }
       blueToothSerial.print("\r\n");
      
      while (blueToothSerial.available()<=0);              //wait while no data
      
      i=0;
      while (blueToothSerial.available()>0){               // while data is available
        recvChar = blueToothSerial.read();                 //read data from HC-05
          if (i<2){
            str[i]=recvChar;                               //put received char to str
            i=i+1;
          }
      }
      retries=retries+1;                                  //increase retries 
      if ((str[0]=='O') && (str[1]=='K')) OK_flag=true;   //if response is OK then OK-flag set to true
      delay(1000);
    }
  
    if (retries>=BT_CMD_RETRIES) {                        //if bluetooth retries reached
      bt_error_flag=true;                                 //set bluetooth error flag to true
    }
  }
  
}

//----------------------------------------------------------//

void loop(){
  while (!(obd_error_flag)){            //while no OBD comunication error  
    if ((rpm>=0) && (rpm<10000)){       //if rpm value is between 0 and 10000 

      rpm_to_disp=int(rpm/100);         //devide by 100, cause we have only two 7-seg disps
      decades=rpm_to_disp / 10;         //calculate decades
      monades=rpm_to_disp % 10;         //calculate monades
     
    
      sevenSegWriteDec(decades);        //display decades to decades 7-seg disp
      delay(10);
      sevenSegWriteMon(monades);        //display monades to monades 7-seg disp
      delay(10);     
     
      if (shift_light_rpm>0){    //if shift light rpm is >0
        if (rpm_to_disp>=shift_light_rpm-10) digitalWrite(ledpin_green,HIGH); else digitalWrite(ledpin_green,LOW); //green led on -1000rpms 
        if (rpm_to_disp>=shift_light_rpm-5) digitalWrite(ledpin_yellow,HIGH); else digitalWrite(ledpin_yellow,LOW);//yellow led on -500rpms
        if (rpm_to_disp>=shift_light_rpm) digitalWrite(ledpin_red,HIGH); else digitalWrite(ledpin_red,LOW);        //red led on stored shift light rpm value
      } else digitalWrite(ledpin_green,HIGH);//if no value is stored in EEPROM
  
      if (rpm_error_flag){              //if rpm error yellow led ON
        digitalWrite(ledpin_green,LOW); 
        digitalWrite(ledpin_yellow,HIGH);
        digitalWrite(ledpin_red,LOW);
      }
      
      //if button pressed then store current rpm to EEPROM
      if ((digitalRead(sel_sw)) && (!but_pressed_flag)){
        but_pressed_flag=true;
        shift_light_rpm=rpm_to_disp;
       
        EEPROM.write(addr,shift_light_rpm);
        flash_store(shift_light_rpm); 
      } 
      if (!(digitalRead(sel_sw))) but_pressed_flag=false;
    
    }
    else //if no correct rpm value received
    {
      digitalWrite(led_dec,HIGH);
      digitalWrite(led_mon,HIGH);
      digitalWrite(ledpin_red,LOW);
      digitalWrite(ledpin_green,LOW);
      digitalWrite(ledpin_yellow,LOW);
    }
    t.update();  //update of timer for calling rpm_calc
  }
  if (obd_error_flag) obd_err_flash();    //if OBD error flag is true
}