-//TODO:
-// have a minimun time for each state to protect on-off on the fan and the heating element
-
 //The MIT License (MIT)
 //Copyright (c) 2016 Gustavo Gonnet
 //

// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#include "application.h"
#include "elapsedMillis.h"
#include "PietteTech_DHT.h"
#include "FiniteStateMachine.h"

#define APP_NAME "Thermostat"
// IO mapping
// D0 : relay: fan
// D1 : relay: heat
// D2 : relay: cold
// D3 : relay:
// D4 : DHT22
// D5 : unused
// D6 : unused
// D7 : unused
// A0~A7 : unused

//initialize states with proper enter, update and exit functions
State initState = State( initEnterFunction, initUpdateFunction, initExitFunction );
State idleState = State( idleEnterFunction, idleUpdateFunction, idleExitFunction );
State heatingState = State( heatingEnterFunction, heatingUpdateFunction, heatingExitFunction );

  }
}

/*******************************************************************************
 * Function Name  : readTemperature
 * Description    : reads the temperature of the DHT22 sensor at every DHT_SAMPLE_INTERVAL
                    if testing the app, it returns right away
 * Return         : 0
 *******************************************************************************/
int readTemperature() {

  //TESTING_HACK
  //are we testing the app? then no need to acquire from the sensor
  if (testing) {
   return 0;
  }

  //time is up? no, then come back later
  if (dhtSampleInterval < DHT_SAMPLE_INTERVAL) {
   return 0;
  }

  //time is up, reset timer
  dhtSampleInterval = 0;

  // start the sample
  if (!bDHTstarted) {
    DHT.acquireAndWait(5);
    bDHTstarted = true;
  }

  //still acquiring sample? go away
  if (DHT.acquiring()) {
    return 0;
  }

  //sample acquired - go ahead and store temperature and humidity in internal variables
  publishTemperature( (float)DHT.getCelsius(), (float)DHT.getHumidity() );

  //reset the sample flag so we can take another
  bDHTstarted = false;

  return 0;
}

/*******************************************************************************
 * Function Name  : publishTemperature
 * Description    : the temperature/humidity passed as parameters get stored in internal variables
                    and then published
 * Return         : 0
 *******************************************************************************/
int publishTemperature( float temperature, float humidity ) {

 char currentTempChar[32];
 currentTemp = temperature;
 int currentTempDecimals = (currentTemp - (int)currentTemp) * 100;
 sprintf(currentTempChar,"%0d.%d", (int)currentTemp, currentTempDecimals);

 char currentHumidityChar[32];
 currentHumidity = humidity;
 int currentHumidityDecimals = (currentHumidity - (int)currentHumidity) * 100;
 sprintf(currentHumidityChar,"%0d.%d", (int)currentHumidity, currentHumidityDecimals);

 //publish readings into exposed variables
 currentTempString = String(currentTempChar);
 currentHumidityString = String(currentHumidityChar);

 //publish readings
 Particle.publish(APP_NAME, "Current temperature: " + currentTempString, 60, PRIVATE);
 Particle.publish(APP_NAME, "Current humidity: " + currentHumidityString, 60, PRIVATE);

 return 0;

}


/*******************************************************************************
********************************************************************************
********************************************************************************
 FINITE STATE MACHINE FUNCTIONS
********************************************************************************
********************************************************************************
*******************************************************************************/
void initEnterFunction(){
 Particle.publish(APP_NAME, "initEnterFunction", 60, PRIVATE);
 //start the timer of this cycle
 initTimer = 0;

}
void initUpdateFunction(){
 //time is up?
 if (initTimer > INIT_TIMEOUT) {
  thermostatStateMachine.transitionTo(idleState);
 }
}
void initExitFunction(){
 Particle.publish(APP_NAME, "initExitFunction", 60, PRIVATE);
}

void idleEnterFunction(){
 Particle.publish(APP_NAME, "idleEnterFunction", 60, PRIVATE);
 myDigitalWrite(fan, LOW);
 myDigitalWrite(heat, LOW);
 myDigitalWrite(cold, LOW);
}
void idleUpdateFunction(){
 if ( currentTemp <= (targetTemp - margin) ) {
  Particle.publish(APP_NAME, "Starting to heat", 60, PRIVATE);
  thermostatStateMachine.transitionTo(heatingState);
 }
}
void idleExitFunction(){
 Particle.publish(APP_NAME, "idleExitFunction", 60, PRIVATE);
}

void heatingEnterFunction(){
 Particle.publish(APP_NAME, "heatingEnterFunction", 60, PRIVATE);
 myDigitalWrite(fan, HIGH);
 myDigitalWrite(heat, HIGH);
 myDigitalWrite(cold, LOW);

 //start the minimum timer of this cycle
 minimumOnTimer = 0;
}
void heatingUpdateFunction(){
  //is minimum time up?
  if (minimumOnTimer < MINIMUM_ON_TIMEOUT) {
    //not yet, so get out of here
    return;
  }

  if ( currentTemp >= (targetTemp + margin) ) {
    Particle.publish(APP_NAME, "Desired temperature reached", 60, PRIVATE);
    thermostatStateMachine.transitionTo(endOfCycleState);
  }
}
void heatingExitFunction(){
 Particle.publish(APP_NAME, "heatingExitFunction", 60, PRIVATE);
 myDigitalWrite(fan, HIGH);
 myDigitalWrite(heat, LOW);
 myDigitalWrite(cold, LOW);
}

void endOfCycleEnterFunction(){
 Particle.publish(APP_NAME, "endOfCycleEnterFunction", 60, PRIVATE);
 myDigitalWrite(fan, HIGH);
 myDigitalWrite(heat, LOW);
 myDigitalWrite(cold, LOW);

 //start the timer of this cycle
 endOfCycleTimer = 0;

}
void endOfCycleUpdateFunction(){
 //time is up?
 if (endOfCycleTimer > END_OF_CYCLE_TIMEOUT) {
  thermostatStateMachine.transitionTo(idleState);
 }
}
void endOfCycleExitFunction(){
 Particle.publish(APP_NAME, "endOfCycleExitFunction", 60, PRIVATE);
}


/*******************************************************************************
********************************************************************************
********************************************************************************
 TESTING HACKS
********************************************************************************
********************************************************************************
*******************************************************************************/

/*******************************************************************************
 * Function Name  : setTesting
 * Description    : sets the testing variable to true - this enables me to override
                     the temperature read by the DHT sensor.
                    this is a hack that allows me to system test the project
 * Return         : 0
 *******************************************************************************/
int setTesting(String dummy)
{
  testing = true;
  return 0;
}

/*******************************************************************************
 * Function Name  : getOutputs
 * Description    : returns the outputs so we can test the program
                    this is a hack that allows me to system test the project
 * Return         : returns the outputs
 *******************************************************************************/
int getOutputs(String dummy)
{
  // int fan = D0;
  // int heat = D1;
  // int cold = D2;
  return coldTesting*4 + heatTesting*2 + fanTesting*1;
}

/*******************************************************************************
 * Function Name  : setCurrentTemp
 * Description    : sets the current temperature of the thermostat
                    newCurrentTemp has to be a valid float value, or no new current temp will be set
                    this is a hack that allows me to system test the project
* Return         : 0, or -1 if it fails to convert the temp to float
 *******************************************************************************/
int setCurrentTemp(String newCurrentTemp)
{
  float tmpFloat = newCurrentTemp.toFloat();

  //update the current temp only in the case the conversion to float works
  // (toFloat returns 0 if there is a problem in the conversion)
  // sorry, if you wanted to set 0 as the current temp, you can't :)
  if ( tmpFloat > 0 ) {
    currentTemp = tmpFloat;
    currentTempString = String(currentTemp);
    Particle.publish(APP_NAME, "New current temp: " + currentTempString, 60, PRIVATE);
    return 0;
  } else {
    Particle.publish(APP_NAME, "ERROR: Failed to set new current temp to " + newCurrentTemp, 60, PRIVATE);
    return -1;
  }
}

/*******************************************************************************
 * Function Name  : getOutputs
 * Description    : returns the outputs so we can test the program
                    this is a hack that allows me to system test the project
 * Return         : returns the outputs
 *******************************************************************************/
void myDigitalWrite( int input, int status){
  digitalWrite(input, status);
  if (input == fan){
