/*-----------------------------------------------------------------------------------------
 *  This software is in the public domain, furnished "as is", without technical
 *  support, and with no warranty, express or implied, as to its usefulness for
 *  any purpose.
 *
 *  SwitchProp.ino
 *
 *  Dualswitch decoder and prportional part combined in one RC Channel.
 *
 *  The encoder should have 2 switches with different resistors and a trimmer with paralel resitor to compensate for
 *  the switch resistors to get the needed channel resistor bandwithe (normaly 0 to 5Kohms or 0 to 10 KOhms)
 *  This implementation on transmitter side is: 470Ohms (paralell to switch 1), 1KOhms (paralell to switch 2),
 *  5K trimmer with paralell resistor of 12KOhms.
 *  Therefor the calibration steps of the RC input must be done as described in pwmread_rcfailsafe.ino as well.
 *
 *  Author: Volker Frauenstein
 *  Date: 20/01/2021 Version: 1.0 implementation of switch-proportional decoder for Arduino Nano
 */

// pinout definitions for the switchfunctions
//const int spPin_a = 7;                        // uncomment because function in SW
//const int spPin_b = 8;                        // uncomment because function in SW

// Signal delta to separate switch states from proportional part
// must be adapted to real readings
const float S_delta[2] = {0.22 , 0.44};       // signal delta if switches are triggered
const float C_point = -0.44;                  // start position of encoder (s1 off, S2, off, Prop middle)
const float S_trigger = 0.09;                 // bandwithe for first recogition of trigger
const float S_range = 0.05;                   // bandwithe for save recogition of trigger

// internal pre definitions do not change
const float expand = 1/((1 - S_delta[0] - S_delta[1])*1.8); // multiplier to expand prop to full range
const float S_max[2] = {(S_delta[0] + S_trigger), (S_delta[1] + S_trigger)};
const float S_min[2] = {(S_delta[0] - S_trigger), (S_delta[1] - S_trigger)};
const float H_max[2] = {(S_delta[0] + S_range), (S_delta[1] + S_range)};
const float H_min[2] = {(S_delta[0] - S_range), (S_delta[1] - S_range)};

// generig definitions
int SP_S_trigger = 0;                         // semaphore on switch trigger state
int SP_S_threshold = 5;                       // hysterese on switch trigger semaphore
int SP_P_trigger = 0;                         // semaphore on proportional trigger state
int SP_P_threshold = 15;                      // hysterese on proportional trigger semaphore
int hysteresis = 0;                           // counter for hysteresis
float adelta;                                 // absolut of delta
bool switching[2] = {false, false};           // switch states of last cycle
float SP_now = 0;                             // actual value of SP RC channel
float SP_last = 0;                            // last value of SP RC channel
float Prop_last = 0;                          // last calculated proportional value
const int SPthreshold = 5;                    // threshold for hysteresis
bool SPcalibrated = false;                    // flag to chek if SP encoder state is known

/* definitions for main.ino to handle switchprop state global
bool switchOn[3] = {false,false,false};       // one bit per switch plus calibration flag
float Prop_now = 0;                           // calculated proportional value
*/

// generig definitions for debug
//#define DEBUG_SP 1

void setup_SwitchProp() {

//      pinMode(spPin_a, OUTPUT);             // uncomment because function in SW
//      pinMode(spPin_b, OUTPUT);             // uncomment because function in SW
}

void close_SwitchProp() {

//    digitalWrite(msPin_a, LOW);             // close all digital pins
//    digitalWrite(msPin_b, HIGH);            // Switch on defined with pulldown
    switchOn[0] = false;                      // and switch off internal function 1
    switchOn[1] = false;                      // and switch off internal function 2
    switchOn[2] = false;                      // and reset calibration flag
    Prop_now = 0;                             // also the proportional part to failsafe
    SPcalibrated = false;                     // new calibration needed after failsafe

}                                             // function close_MultiSwitch closed

void run_SwitchProp(int ch) {                 // function to check states of the switches and proportional part
                                              // and activate the pinouts if needed
   float  SP_now = RC_in[ch];                 // value of used RC channel for switchprop

   if (SPcalibrated) {                        // switchprop state was known, normal operation
    checktrigger(SP_now);                     // check and set switch state and proportional part
   }
   else {
    SP_Calibaration(SP_now);                  // SP not calibratet, check for next loop
   }

   #ifdef DEBUG_SP                            // show DEBUG_SP data if needed
    Serial.print(" State (S: ");Serial.print(SP_S_trigger);
    Serial.print(",P: ");Serial.print(SP_P_trigger);Serial.print("), ");
    Serial.print(" SP_Ch: ");Serial.print(": ");
    Serial.print(RC_in[ch]);Serial.print(" ->  ");
    for (int i = 0; i<2; i++){
         Serial.print(switchOn[i]);Serial.print(",");
        }
    Serial.print(" -> Prop: ");Serial.print(Prop_now);
    Serial.print(", SP_last: ");Serial.println(SP_last);
   #endif
}                                             // function run_SwitchProp closed

bool SP_Calibaration (float SP_in) {          // check if (re)start condition is fullfillt

  if ((SP_in+S_range > C_point) && (SP_in-S_range < C_point)) {
    SPcalibrated = true;                      // encoder is in calibration range
    switchOn[2] = true;                       // set calibration flag
    SP_last = SP_in;                          // store this good value for next loop
    Prop_last = 0;                            // store startpiont of proportional part
    #ifdef DEBUG_SP                           // show DEBUG_SP data if needed
     Serial.print(", SP calibrated: ");Serial.print(SPcalibrated);
    #endif
    return true;
  }
  else {
    #ifdef DEBUG_SP                            // show DEBUG_SP data if needed
     Serial.print(", SP NOT calibrated SP_in: ");Serial.print(SP_in);
    #endif
    return false;
  }
}                                             // function SP_Calibaration closed

void checktrigger (float SP_value) {          // function to check if switches have been triggerd

  SP_now = SP_value;
  int SP_state;
  float delta = SP_value - SP_last;
  adelta = abs(delta);

  if (adelta < S_range) {                     // no new input value
   SP_state = 0;                              // state case 0, do nothing
   if (SP_S_trigger > 0) SP_S_trigger--;      // degrade last switch activ trigger
   if (SP_P_trigger > 0) SP_P_trigger--;      // degrade last proportional activ trigger
  }

  // check if delta is in switch trigger area and set state
  else if ((adelta > S_min[0]) && (adelta < S_max[0])) {
    if (SP_P_trigger > 0) {                   // proportional change was ongoing
      SP_state = 3;                           // keep it running
    }
    else SP_state = 1;                        // check switch trigger for S1
  }
  else if ((adelta > S_min[1]) && (adelta < S_max[1])) {
    if (SP_P_trigger > 0) {                   // proportional change was ongoing
      SP_state = 3;                           // keep it running
    }
    else SP_state = 2;                        // check switch trigger for S2
  }
  else SP_state = 3;                          // no switch trigger conditions found


  switch (SP_state) {
  case 0:                                     // no further action needed
    Prop_now = AjustProp();                   // but recalibrate proportional value
    #ifdef DEBUG_SP
     Serial.print(" idle,");
    #endif
    break;

  case 1:                                     // check if switch 1 has been altered
    #ifdef DEBUG_SP
     Serial.print(" S1 trigger,");
     Serial.print("delta: ");Serial.print(delta);
    #endif
    SP_switch(delta, 0);
    SP_S_trigger = SP_S_threshold;            // and set switch trigger blocker
    break;

  case 2:                                     // check if switch 2 has been altered
    #ifdef DEBUG_SP
     Serial.print(" S2 trigger,");
     Serial.print("delta: ");Serial.print(delta);
    #endif
    SP_switch(delta, 1);
    SP_S_trigger = SP_S_threshold;            // and set switch trigger blocker
    break;

  case 3:                                     // value has been changed outside of switch deltas
    #ifdef DEBUG_SP                           // or propotional change was ongoing in the last loops
     Serial.print(" prop trigger, : ");
    #endif
    Prop_now = CalcProp (delta);              // now calculate proportional value
    SP_last = SP_value;                       // store actual SP_value for next loop
    Prop_last = Prop_now;                     // and prepare for next cycle
    SP_P_trigger = SP_P_threshold;            // set proportional trigger blocker
    SP_S_trigger = SP_P_threshold;            // and reset switch trigger blocker
    break;

  }                                           // switch case closed
}                                             // function checktrigger closed

void SP_switch (float delta, int i) {         // function to trigger Switch change

    if ((!switchOn[i]) && (delta > 0)) {      // Switch i was off, now it looks like it is on

      if (SP_run_hyst(i)) {                   // hyseresis done, change switch state
       switchOn[i] = true;                    // from OFF to ON
       SP_last = SP_now;                      // store actual SP_value for next loop
       Prop_now = AjustProp();                // and set proportional value
       #ifdef DEBUG_SP
        Serial.print(" S");Serial.print(i+1);Serial.print(" ON,");
       #endif
      }
      else {                                  // hyseresis running, change switch state later
       Prop_now = Prop_last;                  // and freeze proportional value
       #ifdef DEBUG_SP
        Serial.print(" S");Serial.print(i+1);Serial.print(" waitingON,");
       #endif
      }
    }

    else if (switchOn[i] && (delta < 0)) {    // Switch was on, now we have to switch off

      if (SP_run_hyst(i)) {                   // hyseresis done, change switch state
       switchOn[i] = false;                   // from ON to OFF
       SP_last = SP_now;                      // store actual SP_value for next loop
       Prop_now = AjustProp();                // and set proportional value
       #ifdef DEBUG_SP
        Serial.print(" S");Serial.print(i+1);Serial.print(" OFF,");
       #endif
      }
      else {                                  // hyseresis running, change switch state later
       Prop_now = Prop_last;                  // and frezze proportional value
       #ifdef DEBUG_SP
        Serial.print(" S");Serial.print(i+1);Serial.print(" waitingOFF,");
       #endif
      }
    }
    else {                                    // Switch was on (off) and we should switch on (off)
      Prop_now = CalcProp(delta);             // so it must be a (fast) proportional change
      float Prop_temp = AjustProp();
      if (!((Prop_now + S_range) > Prop_temp) && ((Prop_now - S_range) < Prop_temp)) {
       #ifdef DEBUG_SP
        Serial.print(" Prop out of sync: ");Serial.print(Prop_now);
        Serial.print(" != ");Serial.print(Prop_temp);
       #endif                                 // and set proportional value
      }
      SP_last = SP_now;                       // store actual SP_value for next loop
      #ifdef DEBUG_SP
       Serial.print(" just prop, ");
      #endif

    }                                         // within range of Switch delta
}                                             // function SP_switch closed

float CalcProp (float pdelta) {               // function to strech proportional part to full

  float delta = pdelta * expand;              // strech delta to fit full way
  float prop = Prop_last + delta;
  #ifdef DEBUG_SP                             // show DEBUG_SP data if needed
    // Serial.print(", Prop_last: ");Serial.print(Prop_last);
    // Serial.print(", pdelta: ");Serial.print(delta);
    // Serial.print(", calcprop: ");Serial.print(prop);
  #endif
  prop = constrain(prop, -1, 1);              // and trim proportional value
  return prop;
}                                             // function CalcProp closed

float AjustProp () {                          // recalibration function of proportional part

  float midpoint = C_point;                   // find midposition of proportional part
  float aprop;

  for (int i = 0; i < 2; i++) {               // add switch parts to midpoint if needed
   if (switchOn[i]) midpoint = midpoint + S_delta[i];
  }
  aprop = expand * (SP_now - midpoint);       // calculate streched value
  aprop = constrain(aprop, -1, 1);            // and trim proportional value
  #ifdef DEBUG_SP
   // Serial.print("aprop: ");Serial.print(aprop);
  #endif
  return aprop;
}                                             // function AjustProp closed

bool SP_run_hyst (int s) {

    if (hysteresis > SPthreshold) {           // yes we change
      hysteresis = 0;                         // reset hysteresis
      #ifdef DEBUG_SP
       Serial.print(" Change NOW ");
      #endif
      return true;                            // for next trigger
    }
    else {                                    // hysteresis is running
      if (hysteresis > SPthreshold - (SPthreshold/2)) {
       if ((adelta > H_min[s]) && (adelta < H_max[s])) {
         hysteresis++;                        // we change later
         #ifdef DEBUG_SP
          Serial.print(" changecount_H: ");Serial.print(hysteresis);
         #endif
       }
       else {                                 // bandwithe reduction after debounce
        hysteresis = 0;                       // not seen therefore try againg
        #ifdef DEBUG_SP
         Serial.print(" changecount_R: ");Serial.print(hysteresis);
        #endif
        return false;
       }
      }
      else {                                  // step one for debounce
         hysteresis++;                        // we change later
         #ifdef DEBUG_SP
          Serial.print(" changecount_S: ");Serial.print(hysteresis);
         #endif
      }
      return false;
    }
}                                             // function SP_run_hyst closed