/*  A steering wheel controls proxy to translate between the steering wheel control resistor values
 *  and other hardware like a stereo or a 3rd party cruise control. I used a ESP32 because it has 2 ADC
 *  inputs that I can use for resistance detection. A arduino nano would have works too 
 *  Arduino is a 10 bit ADC 1024) and ESP32 is a 12 bit ADC (4096), but voltage is 5 vs 3.3.
 *  This code works with both
 *  
 *  I'm using this with a Pioneer using the wired remote input using a digital potentiometer
 *  with SPI to set the right value for the right button to translate. The buttons are currently
 *  Mitsubishi Lancer/Outlander ~2010 model, but these only have 5 buttons for audio, and that
 *  in combination with the Pioneer means choosing between next/previous or preset up/down for the up 
 *  and down arrows.
 *  
 *  The Cruise control is a GoldCruise from JohnGold which is relatively simple 
 *  in that it drives a few inputs from orange (+12v) for On/off into brown, Cancel into green, and 
 *  Up into yellow. The only odd one is Down which is orange into 1500 Ohm into yellow.
 *  I do this one with a cheap 4 channel relay board I can drive with logic from the ESP32
 *  Alternatively a Toshiba TL222A-2 Photo MOSFET that would work too and isn't too expensive, 
 *  you would need to flip the RELAYON RELAYOFF levels as it is inverted currently.
 *  
 *  Blinks the onboard led when a button is succesfully detected.
 *  
 *  This came from a the Arduino forum and cobled together from some other places and was 
 *  originally only for driving a car stereo. I added support for a 2nd set of buttons.
 *  http://forum.arduino.cc/index.php?topic=7497.msg59812#msg59812
 *  This is the example that uses the SPI digi pot, as the PWM method didn't work for me.
 *  https://forum.arduino.cc/index.php?topic=230068.0
 *  
 *  You need to shutdown the Digipot using a TCON command so that it disconnects the terminals.
 *  The functions for these are from https://kner.at/home/40.avr/arduino/projekte.digitalpoti/index.html 
 *  I wasn't able to find the author.  
 *  
 *  The schema for this is on http://iserv.nl/files/pics/imiev/arduino
 *  
 *  Comment out debug to stop printing to Serial if you want to.
 */
 
#define DEBUG
#define DEBUG2

// Is this a ESP32?
// #define ESP32

#include <stdio.h>
#include <stdlib.h>
#ifdef ESP32
  #include "freertos/FreeRTOS.h"
  #include "freertos/task.h"
  #include "driver/gpio.h"
  #include "driver/adc.h"
  #include "driver/dac.h"
  #include "esp_adc_cal.h"
#endif

#include <SPI.h>

#define USLEEPTIME 100

#define ATOTAL_BUTTONS 5          // Total audio buttons on the steering wheel
#define ANONE -1                  // When no button is pressed
#define AVOLUME_UP 0              // Index corresponding to the VOLUME_UP button resistance
#define AVOLUME_DOWN 1            // Index corresponding to the VOLUME_DOWN button resistance
#define AMODE 2                   // Index corresponding to the MODE button resistance
#define ANEXT 3                   // Index corresponding to the NEXT button resistance
#define APREVIOUS 4               // Index corresponding to the PREVIOUS button resistance

#define CTOTAL_BUTTONS 4          // Total cruise buttons on the steering wheel
#define CNONE -1                  // When no button is pressed
#define CCRUISE_UP 0              // Index corresponding to the CRUISE_UP button resistance
#define CCRUISE_DOWN 1            // Index corresponding to the CRUISE_DOWN button resistance
#define CPWR 2                    // Index corresponding to the MODE button resistance
#define CCANCEL 3                 // Index corresponding to the NEXT button resistance

#ifdef ESP32
  #define ATSW_PIN 11               // Digital Output on esp32 - Switch audio tip on
  #define ARSW_PIN 12               // Digital Output on esp32 - Switch audio ring to sleeve
  #define CPWR_PIN 17               // Digital Output on esp32 - JohnGold GoldCruise Brown wire 0 Ohm to Orange
  #define CCANCEL_PIN 22            // Digital Output on esp32 - JohnGold GoldCruise Green wire 0 Ohm to Orange
  #define CCRUISE_UP_PIN 16         // Digital Output on esp32 - JohnGold GoldCruise Yellow wire 0 Ohm to Orange
  #define CCRUISE_DOWN_PIN 32       // Digital Output on esp32 - JohnGold GoldCruise Yellow wire 1500 Ohm to Orange
  #define ARESISTANCE_PIN 34        // Analogue Input on esp32
  #define CRESISTANCE_PIN 35        // Analogue Input on esp32
  #define ADAC_PIN 25               // analogue Output on esp32 (currently used as PWM, not DAC), modify code to use as DAC. ledcWrite > dac_output_voltage
  #define LED_BUILTIN 2
  #define SPI_PIN 5 // Chip Select
  #define ADCDIV 4096
#else
  #define ATSW_PIN 3               // Digital Output on nano - Switch audio tip on
  #define ARSW_PIN 5               // Digital Output on nano - Switch audio ring to sleeve
  #define CPWR_PIN 4               // Digital Output on nano - JohnGold GoldCruise Brown wire 0 Ohm to Orange
  #define CCANCEL_PIN 7            // Digital Output on nano - JohnGold GoldCruise Green wire 0 Ohm to Orange
  #define CCRUISE_UP_PIN 8         // Digital Output on nano - JohnGold GoldCruise Yellow wire 0 Ohm to Orange
  #define CCRUISE_DOWN_PIN 2       // Digital Output on nano - JohnGold GoldCruise Yellow wire 1500 Ohm to Orange
  #define ARESISTANCE_PIN A0       // Analogue Input on nano
  #define CRESISTANCE_PIN A1       // Analogue Input on nano
  #define ADAC_PIN 5               // PWM Output on nano (currently used as PWM, not DAC), modify code to use as DAC.
  #define LED_BUILTIN 13
  #define SPI_PIN 10 // Chip Select
  #define ADCDIV 1024
#endif

#define TOLERANCE_PERCENT 12.f    // Match tolerance
#define AR_KNOWN 680.f            // The known resistor (it's a 680)
#define CR_KNOWN 180.f            // The known resistor (it's a 180)
#define DIGIPOT_DEF 256           // 0-255 for a value between 0 and 100k, 256 for shutdown
/* 
 * Depending on what you use for the Cruise control output, either Drive LOW or HIGH, you can flip these around if yours is pull up.
 * The example with the Toshiba TL222 requires driving them high instead of low. Cheap 4 channel relay boards from ali express I use drive low. 
 */
#define RELAYON LOW
#define RELAYOFF HIGH

#ifdef ESP32
  /* Button resistance input values and indexes { VOLUME_UP, VOLUME_DOWN, MODE, NEXT, PREVIOUS} */
  float ABUTTONS[ATOTAL_BUTTONS] = { 2577.f, 4039.f, 299.f, 825.f, 1494.f};
  /* Button resistance input values and indexes { CCRUISE_UP, CCRUISE_DOWN, CPWR, CCANCEL} */
  float CBUTTONS[CTOTAL_BUTTONS] = { 3177.f, 770.f, 1f, 220.f};
/* This value needs to be known for being able to measure the resistor value, the 3.3Volt rail is pretty good */
  float VOLTS_IN = 3.3f;             // Vcc (+3.3 Volts on ESP32)
#else
  /* The 3.3 Volt rail is to beefy enough for more reliable readings on the arduino nano and values are different. */
  /* Button resistance input values and indexes { VOLUME_UP, VOLUME_DOWN, MODE, NEXT, PREVIOUS} */
  float ABUTTONS[ATOTAL_BUTTONS] = { 2162.f, 3188.f, 275.f, 752.f, 1320.f};
  /* Button resistance input values and indexes { CCRUISE_UP, CCRUISE_DOWN, CPWR, CCANCEL} */
  float CBUTTONS[CTOTAL_BUTTONS] = { 1868.f, 617.f, 10.f, 204.f};
  /* This value needs to be known for being able to measure the resistor value, the 5Volt rail is USB, but often just 4.5 Volt, depends on cable*/
  float VOLTS_IN = 4.97f;             // Vcc (+5v on Arduino Nano)
#endif 

/* Button resistance output values and indexes for the mcp4151 { VOLUME_UP 16k, VOLUME_DOWN 24k, MODE 1k6, NEXT 8k, PREVIOUS 11k} for 100k DigiPot */
int AOBUTTON[ATOTAL_BUTTONS] = {(256 - 42), (256 - 62), (256 - 4), (256 - 20), (256 - 28)}; // Twice the steps for the 4151 over the 4131, wrong pin connected is subtract from 256

/* 
 * There shouldn't be much below here that you would want to edit. 
 * 
 */

const int freq = 25000;
const int ledChannel = 0;
const int resolution = 8;

static const int spiClk = 1000000;
byte address = 0x00;
SPIClass * hspi = NULL;

int AcurrentButton = ANONE;        // Currently selected button
int ApreviousButton = ANONE;        // Previous selected button
int CcurrentButton = CNONE;        // Currently selected button
int CpreviousButton = CNONE;        // Currently selected button

int ArawVolts = 0;                // The raw analogue value
uint32_t Areading = 0;
float AvoltsOut = 0.f;            // Voltage at point between resistors
float Aresistance = 0.f;          // Unknown resistance.

int CrawVolts = 0;                // The raw analogue value
uint32_t Creading = 0;
float CvoltsOut = 0.f;            // Voltage at point between resistors
float Cresistance = 0.f;          // Unknown resistance.


void setup() {
  Serial.begin(9600);

  Serial.print("Hi\n");
  Serial.print("https://iserv.nl/files/pics/imiev/arduino/swc-button-proxy/\n");
  pinMode(CPWR_PIN, OUTPUT);
  pinMode(CCANCEL_PIN, OUTPUT);
  pinMode(CCRUISE_UP_PIN, OUTPUT);
  pinMode(CCRUISE_DOWN_PIN, OUTPUT);

  digitalWrite(ARSW_PIN, RELAYOFF);
  digitalWrite(ATSW_PIN, RELAYOFF);
  digitalWrite(CPWR_PIN, RELAYOFF);
  digitalWrite(CCANCEL_PIN, RELAYOFF);
  digitalWrite(CCRUISE_UP_PIN, RELAYOFF);
  digitalWrite(CCRUISE_DOWN_PIN, RELAYOFF);

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);   // turn the LED off 

  pinMode(SPI_PIN, OUTPUT);
  #ifdef ESP32
      hspi = new SPIClass(HSPI);
      hspi->begin(); 
  
      // dac_output_enable(DAC_CHANNEL_1);
      ledcSetup(ledChannel, freq, resolution);
      ledcAttachPin(ADAC_PIN, ledChannel);
      // Reading voltage on ADC1 channel 6,7 (GPIO 34,35):
      adcAttachPin(ARESISTANCE_PIN);
      adcAttachPin(CRESISTANCE_PIN);
  #else
    SPI.begin();
  #endif  
  digitalPotWrite(DIGIPOT_DEF);
}

// Main loop that measures resistance and detects buttons for channel 0 and 1 (audio and cruise) 
void loop() {
  detectCurrentButton(0);
  detectCurrentButton(1);
  printResistance(1);
  delay(USLEEPTIME);
}

// Calculates the resistance.
void calculateResistance(int channel) {
  //Serial.print("calculateResistance channel: ");
  //Serial.print(channel);
  //Serial.print("\n");

 switch(channel) {
  case 0:
    ArawVolts = analogRead(ARESISTANCE_PIN);            // Read in raw value (0-1023)
    AvoltsOut = (VOLTS_IN / ADCDIV) * float(ArawVolts);    // Convert to voltage
    Aresistance = AR_KNOWN*((VOLTS_IN/AvoltsOut) - 1);    // Calculate the resistance
    break;
  case 1:
    CrawVolts = analogRead(CRESISTANCE_PIN);            // Read in raw value (0-1023)
    CvoltsOut = (VOLTS_IN / ADCDIV) * float(CrawVolts);    // Convert to voltage
    Cresistance = CR_KNOWN*((VOLTS_IN/CvoltsOut) - 1);    // Calculate the resistance
    break;
 }
}

// Detects which button is currently pressed.
// Channel 0 Audio, 1 Cruise
void detectCurrentButton(int channel) {
  //Serial.print("detectCurrentButton channel: ");
  //Serial.print(channel);            
  //Serial.print("\n");
  //printCurrentButton(channel);
  calculateResistance(channel);
  switch(channel) {
    case 0:
      for (int i = 0; i < ATOTAL_BUTTONS; i++) {
        if (buttonPressed(ABUTTONS[i], Aresistance)) {
           AcurrentButton = i;
           break;
        } else {
           AcurrentButton = ANONE; 
        }
      }
      // Only print on change
      if (AcurrentButton != ApreviousButton) {
        ActivateCurrentButton(channel);
        ApreviousButton = AcurrentButton;
        #ifdef DEBUG
          printResistance(channel);
          printCurrentButton(channel);
        #endif
      }
      break;
    case 1:
      for (int i = 0; i < CTOTAL_BUTTONS; i++) {
        if (buttonPressed(CBUTTONS[i], Cresistance)) {
           CcurrentButton = i;
           break;
        } else {
           CcurrentButton = CNONE; 
        }
      }
      if (CcurrentButton != CpreviousButton) {
        ActivateCurrentButton(channel);
        CpreviousButton = CcurrentButton;
        #ifdef DEBUG
          printResistance(channel);
          printCurrentButton(channel);
        #endif
      }
      break;
 }
}

void ActivateCurrentButton(int channel) {
  // Serial.print("ActivateCurrentButton channel: ");
  // Serial.print(channel);
  // Serial.print("\n");
  switch(channel) {
  case 0:
      // Audio pins
      switch(AcurrentButton) {
       case AVOLUME_UP:
        digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
        digitalPotWrite(AOBUTTON[0]); // 16kOhm
        digitalWrite(ATSW_PIN, RELAYON);   // turn the Audio switch
        #ifdef ESP32
          ledcWrite(ledChannel, AOBUTTON[0]);
        #endif
        // dac_output_voltage(DAC_CHANNEL_1, AOBUTTON[0]);
        break;
       case AVOLUME_DOWN:
        digitalWrite(ATSW_PIN, RELAYON);   // turn the Audio switch
        digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
        digitalPotWrite(AOBUTTON[1]); // 24k
        #ifdef ESP32
          ledcWrite(ledChannel, AOBUTTON[1]);
        #endif
        // dac_output_voltage(DAC_CHANNEL_1, AOBUTTON[1]);
        break;
       case AMODE:
        digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
        digitalPotWrite(AOBUTTON[2]); // 1.6k
        digitalWrite(ATSW_PIN, RELAYON);   // turn the Audio switch
        #ifdef ESP32
          ledcWrite(ledChannel, AOBUTTON[2]);
        #endif
        // dac_output_voltage(DAC_CHANNEL_1, AOBUTTON[2]);
        break;
       case ANEXT:
        digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
        digitalPotWrite(AOBUTTON[3]); // 8k0
        digitalWrite(ARSW_PIN, RELAYOFF);   // turn the Audio Ring switch on
        digitalWrite(ATSW_PIN, RELAYOFF);   // turn the Audio Tip switch on
        #ifdef ESP32
          ledcWrite(ledChannel, AOBUTTON[3]);
        #endif
        // dac_output_voltage(DAC_CHANNEL_1, AOBUTTON[3]);
        break;
       case APREVIOUS:
        digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
        digitalPotWrite(AOBUTTON[4]); // 11k
        digitalWrite(ARSW_PIN, RELAYOFF);   // turn the Audio Ring switch on
        digitalWrite(ATSW_PIN, RELAYOFF);   // turn the Audio Tip switch on
        #ifdef ESP32
          ledcWrite(ledChannel, AOBUTTON[4]);
        #endif
        // dac_output_voltage(DAC_CHANNEL_1, AOBUTTON[4]);
        break; 
       default:
        digitalWrite(ARSW_PIN, RELAYOFF);   // turn the Audio Ring switch off
        digitalWrite(ATSW_PIN, RELAYOFF);   // turn the Audio Tip switch off
        // Shutdown digipot, disconnects wiper
        digitalPotWrite(DIGIPOT_DEF);
        #ifdef ESP32
          ledcWrite(ledChannel, 0 );
        #endif
        // dac_output_voltage(DAC_CHANNEL_1, 0);
        // Only turn off LED if neither button is pressed
        if((CcurrentButton = CNONE) && (AcurrentButton = ANONE)) {
          digitalWrite(LED_BUILTIN, LOW);   // turn the LED off 
        }  
        break;
      }
      break;
  case 1:
      // Cruise control pins
      switch(CcurrentButton) {
       case CPWR:
        digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
        digitalWrite(CPWR_PIN, RELAYON);
        break;
       case CCANCEL:
        digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
        digitalWrite(CCANCEL_PIN, RELAYON);
        break;
       case CCRUISE_UP:
        digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
        digitalWrite(CCRUISE_UP_PIN, RELAYON);
        break;
       case CCRUISE_DOWN:
        digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on 
        digitalWrite(CCRUISE_DOWN_PIN, RELAYON);
        break;
       default:
        digitalWrite(LED_BUILTIN, LOW);   // turn the LED off 
        digitalWrite(CPWR_PIN, RELAYOFF);
        digitalWrite(CCANCEL_PIN, RELAYOFF);
        digitalWrite(CCRUISE_UP_PIN, RELAYOFF);
        digitalWrite(CCRUISE_DOWN_PIN, RELAYOFF);
        // Only turn off LED if neither button is pressed
        if((CcurrentButton = CNONE) && (AcurrentButton = ANONE)) {
          digitalWrite(LED_BUILTIN, LOW);   // turn the LED off 
        }
        break;
      }
      break;
  }
}


// Prints the current button, only if DEBUG is enabled.
void printCurrentButton(int channel) {
  //Serial.print("printCurrentButton channel: ");
  //Serial.print(channel);            
  //Serial.print("\n");
 switch(channel) {
  case 0:
      #ifdef DEBUG
        Serial.print("Current A Button: ");
        switch(AcurrentButton) {
         case AVOLUME_UP:
          Serial.println("AVOLUME_UP");
          break;
         case AVOLUME_DOWN:
          Serial.println("AVOLUME_DOWN");
          break;
         case AMODE:
          Serial.println("AMODE");
          break;
         case ANEXT:
          Serial.println("ANEXT");
          break;
         case APREVIOUS:
          Serial.println("APREVIOUS");
          break; 
         default:
          Serial.println("ANONE");          
        }
      #endif
      break;
  case 1:
      #ifdef DEBUG
        Serial.print("Current C Button: ");
        switch(CcurrentButton) {
         case CPWR:
          Serial.println("CPWR");
          break;
         case CCANCEL:
          Serial.println("CCANCEL");
          break;
         case CCRUISE_UP:
          Serial.println("CCRUISE_UP");
          break;
         case CCRUISE_DOWN:
          Serial.println("CCRUISE_DOWN");
          break;
         default:
          Serial.println("CNONE");
        }
      #endif
      break;
 }
}

// Prints the resistance (if DEBUG is enabled)
// Channel 0 Audio, 1 Cruise
void printResistance(int channel) {
  //Serial.print("printResistance channel: ");
  //Serial.print(channel);            
  //Serial.print("\n");
 switch(channel) {
  case 0:
    #ifdef DEBUG2
      if(AvoltsOut > 0) {
        Serial.print("AVoltage: ");
        Serial.print(AvoltsOut);            
        Serial.print(", AResistance: ");
        Serial.println(Aresistance);
      }
    #endif
    break;
  case 1:
    #ifdef DEBUG2
      if(CvoltsOut > 0) {
        Serial.print("CVoltage: ");
        Serial.print(CvoltsOut);            
        Serial.print(", CResistance: ");
        Serial.println(Cresistance);
      }
    #endif
    break;
 }
}


int digitalPotWrite(int value)
{
  #ifdef ESP32
    hspi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0));
    digitalWrite(SPI_PIN, LOW);
    hspi->transfer(address);
    hspi->transfer(value);
    digitalWrite(SPI_PIN, HIGH);
    hspi->endTransaction();
  #else
    if(value == 256) {
      // Shutdown
       digitalPotWrite16Bit(0x40F7);
    } else {
      // Activate
      digitalPotWrite16Bit(0x40FF);
      digitalPotWrite8Bit(value);
    }
  #endif
}

void digitalPotWrite8Bit(int value) {
  digitalWrite(SPI_PIN,LOW);
  SPI.transfer((value>>8)&0b00000001);  //high byte least significant bit
  SPI.transfer(value & 0xff);
  digitalWrite(SPI_PIN,HIGH); 
}

void digitalPotWrite16Bit(int value) {
  digitalWrite(SPI_PIN,LOW);
  SPI.transfer(value>>8);  //the short way also works
  SPI.transfer(value);
  digitalWrite(SPI_PIN,HIGH); 
}

// Determines if the current resistance is within tolerance of a button resistance.
boolean buttonPressed(float buttonResistance, float resistance) {
  float decimalPercent = TOLERANCE_PERCENT / 150.f;
  float highRange = (buttonResistance * (1.f + decimalPercent)) + 10.f;
  float lowRange = (buttonResistance * (1.f - decimalPercent)) - 10.f;
  return lowRange <= resistance && resistance <= highRange;
}
