Things used in this project

Hardware components:
Photon new
Particle Photon
×1
Echo
Amazon Alexa Amazon Echo
×1
solid state relay, 100A
×7
Software apps and online services:
ControlEverything.com mobicle.io
Hand tools and fabrication machines:
09507 01
Soldering iron (generic)

Schematics

Hot Tub Time Machine schematics
Hottubtimemachineschematics

Code

Hot Tub Time Machine Control SystemArduino
// This #include statement was automatically added by the Particle IDE.
#include "Adafruit_DHT/Adafruit_DHT.h"
#include "elapsedMillis/elapsedMillis.h"
#include "math.h"

 /*                                    +-----+
 *                          +----------| USB |----------+
 *                          |          +-----+       *  |
 *                          | [ ] VIN           3V3 [ ] |
 *                          | [ ] GND           RST [ ] |
 *                          | [ ] TX           VBAT [ ] |
 *                          | [ ] RX  [S]   [R] GND [ ] |
 *                          | [ ] WKP            D7 [*] |<- XRelay
 *                          | [ ] DAC +-------+  D6 [*] |<- HeaterPumpRelay
 *                          | [ ] A5  |   *   |  D5 [*] |<- AirRelay
 *                          | [ ] A4  |Photon |  D4 [*] |<- Pump3Relay
 *              DHT22 Sensor| [ ] A3  |       |  D3 [*] |<- Pump2Relay
 *           Current Trans->| [ ] A2  +-------+  D2 [*] |<- Pump1Relay 
 *10K Pullup+TempOutHeater->| [*] A1             D1 [*] |<- HeaterRelay
 *10K Pullup+TempInHeater ->| [*] A0             D0 [ ] |
 *                          |                           |
 *                           \    []         [______]  /
 *                            \_______________________/
 *
 *
 */


# define tempoutheaterpin A1
# define tempinheaterpin    A0
# define xrelaypin          D7
# define heaterpumprelaypin D6
# define airrelaypin        D5
# define pump3relaypin      D4
# define pump2relaypin      D3
# define pump1relaypin      D2
# define heaterrelaypin     D1
# define sensoroverheat     125

# define ctpin              A2
# define ctzero             2048


# define ctcal              0.069042969 //141.4A/2048counts
# define biasresistortempin     10000
# define biasresistortempout    9775

# define tempfilt           4

# define maxsettemp 104
# define minsettemp 38
# define heatercheckinterval 30000
# define houroftime         3600000

//--------------Begin DHT------------------
// Example testing sketch for various DHT humidity/temperature sensors
// Written by ladyada, public domain

#define DHTPIN A3     // what pin we're connected to

// Uncomment whatever type you're using!
//#define DHTTYPE DHT11		// DHT 11 
#define DHTTYPE DHT22		// DHT 22 (AM2302)
//#define DHTTYPE DHT21		// DHT 21 (AM2301)

// Connect pin 1 (on the left) of the sensor to +5V
// Connect pin 2 of the sensor to whatever your DHTPIN is
// Connect pin 4 (on the right) of the sensor to GROUND
// Connect a 10K resistor from pin 2 (data) to pin 1 (power) of the sensor

DHT dht(DHTPIN, DHTTYPE);
//---------------End DHT-------------------



unsigned int interval = 100;
unsigned int particleinterval = 100;
unsigned int particlefuncinterval = 10000;
unsigned int heatinterval = 5000;
unsigned int dhtinterval = 30000;
//unsigned long initialsleepinterval = 21600000;
unsigned long initialsleepinterval = houroftime*3;
unsigned long sleepdurationon = houroftime*3;
unsigned long sleepdurationoff = houroftime*9;

elapsedMillis timeElapsed; //declare global if you don't want it reset every time loop runs
elapsedMillis timeElapsed2; //declare global if you don't want it reset every time loop runs
elapsedMillis timeElapsed3;
elapsedMillis timeElapsed4;
elapsedMillis timeElapsed5;
elapsedMillis timesleeptimer;

elapsedMillis timeheatercheck;

int hotTubFunction(String command);

int minsafetytemp = 40; //farenheit minimum safety temp.  Turns on pump and heat to prevent freezing

int rssival = 0;
bool heaterenable = FALSE;
bool circenable = FALSE;
double sleep = 0;

double settemp = 0;
double temphtrin = 0;
double lasttemphtrin = 0;
double temphtrout = 0;
double lasttemphtrout = 0;
double watts = 0;
int maxcurrent = 0;
int instcurrent = 0;
double rmscurrent = 0;

double htelecbxtemp = 0;
double htelecbxhumd = 0;
   


char publishString[40];
char tubpublishstring[40];
char tubstatusstring[40];

SYSTEM_MODE(SEMI_AUTOMATIC); //allows for control of Spark.connect() Code runs even if no wifi connection.

// This routine runs only once upon reset
void setup() {
    //timeElapsed = 0;
    Serial1.begin(230400);   // open serial over USB
    //ip = {"IP:", WiFi.localIP()};
    //String ip = WiFi.localIP();

    Particle.function("hottub", hotTubFunction);
    Particle.variable("settemp", &settemp, DOUBLE);
    Particle.variable("temphtrin", &temphtrin, DOUBLE);  //variables update automatically. Max 4 per particle
    Particle.variable("temphtrout", &temphtrout, DOUBLE);  //variables update automatically. Max 4 per particle
    Particle.variable("tubstatus", tubstatusstring, STRING); //variables are viewable on mobicle
    Particle.variable("sleep", &sleep, DOUBLE);
    Particle.variable("watts", &watts, DOUBLE);
    //Particle.variable("htelecbxtemp", &htelecbxtemp, DOUBLE);
    //Particle.variable("htelecbxhumd", &htelecbxhumd, DOUBLE);
    

    pinMode(tempoutheaterpin, INPUT);
    pinMode(tempinheaterpin, INPUT);
    pinMode(ctpin, INPUT);
    
    pinMode(xrelaypin, OUTPUT);
    pinMode(heaterpumprelaypin, OUTPUT);
    pinMode(airrelaypin, OUTPUT);
    pinMode(pump3relaypin, OUTPUT);
    pinMode(pump2relaypin, OUTPUT);
    pinMode(pump1relaypin, OUTPUT);
    pinMode(heaterrelaypin, OUTPUT);
    
    digitalWrite(xrelaypin, LOW);
    digitalWrite(heaterpumprelaypin, LOW);
    digitalWrite(airrelaypin, LOW);
    digitalWrite(pump3relaypin, LOW);
    digitalWrite(pump2relaypin, LOW);
    digitalWrite(pump1relaypin, LOW);
    digitalWrite(heaterrelaypin, LOW);
    //WiFi.selectAntenna(ANT_AUTO);
    //WiFi.selectAntenna(ANT_INTERNAL);
    WiFi.selectAntenna(ANT_EXTERNAL);
    
    //Do DHTINIT
    pinMode(DHTPIN, INPUT); //sensor needs pullup resistor.
	dht.begin();
	//End DHTINIT
    
}

void loop() {
    if (Particle.connected() == false) {
        Particle.connect();
    }
            instcurrent = analogRead(ctpin)-ctzero;
            maxcurrent = max(maxcurrent,instcurrent);
            
            //analog1raw = analogRead(A0);
            //analog1 = (analog1 *63 +analog1raw *3.3/4095)/64;
            double temphtrdelta;
            
            if (timeElapsed> interval) //sets up interval timer and fires on interval 50ms
            {  
            rmscurrent = 0.707*maxcurrent*ctcal;
            maxcurrent = 0;
            
            watts = 240*rmscurrent;
            //watts = instcurrent;
            
            int tempincounts = analogRead(tempinheaterpin);
            int tempoutcounts = analogRead(tempoutheaterpin);
            
            temphtrin = (lasttemphtrin*tempfilt+thermistor(tempincounts, biasresistortempin))/(tempfilt+1);
            lasttemphtrin = temphtrin;
            
            temphtrout = (lasttemphtrout*tempfilt+thermistor(tempoutcounts, biasresistortempout))/(tempfilt+1);
            lasttemphtrout = temphtrout;
            
            temphtrdelta = temphtrout - temphtrin;
            
            //temphtrin = thermistor(tempincounts, biasresistortempin);
            //temphtrout = thermistor(tempoutcounts, biasresistortempout);            

                        //strcpy(tubpublishstring, "Open4Biz");
                        //Particle.publish("TUBEVENT",tubpublishstring);
                        //strcpy(tubstatusstring, "Open"); //update particle variable.
                        
                timeElapsed = 0;    //resets interval timer.
            }
/*            
            if (timeElapsed2>particleinterval)  //Run code every .1 seconds
            { 
            Particle.process(); //Process wifi events 
            timeElapsed2 = 0;
            }
*/          
            if (timeElapsed3> particlefuncinterval) //Run code every 10 seconds
            {
                rssival = WiFi.RSSI();
                sprintf(publishString,"%d",rssival);
                Particle.publish("RSSI",publishString);
                timeElapsed3 = 0; //reset interval timer
            }

            if (timeElapsed4 > heatinterval) //Run code every 10 seconds
            {
                if ((temphtrin < minsafetytemp) || (temphtrout < minsafetytemp)) { //if temps is too low, trigger heater and pump on for freeze safety
                    heaterenable == TRUE;
                    circenable == TRUE;
                } else {
                    heaterenable == FALSE;
                }
                
                
                if (heaterenable == TRUE){ //if the heater is enabled, proceed
                    if ((temphtrin < settemp)&&(digitalRead(heaterrelaypin) == 0)){
                        timeheatercheck = 0; //catch the edge when the heater is enabled but the output is off and reset the timeheatercheck
                    }
                    if (temphtrin < settemp){ //if the temperature is lower than the set point, turn on the heater
                        digitalWrite(heaterpumprelaypin, HIGH);  //for safety, make sure circ pump is enabled
                        digitalWrite(heaterrelaypin, HIGH);
                    } else {    //if temp to too high, keep circ pump on and turn off heater
                        digitalWrite(heaterpumprelaypin, HIGH);  //for safety make sure circ pump is enabled
                        digitalWrite(heaterrelaypin, LOW);
                         timeheatercheck = 0; 
                    
                    
                    if (timeheatercheck > heatercheckinterval){
                        if ((temphtrdelta < 1.5) || (temphtrin >sensoroverheat) || (temphtrout >sensoroverheat)) {
                            //trigger error and shutdown heater/pump
                            heaterenable == FALSE;
                            circenable == FALSE;
                            strcpy(tubstatusstring, "Heater Error"); 
                        }
                    }
                }
                    
                } else {    //if heater is disabled, turn off heater.
                        digitalWrite(heaterrelaypin, LOW);
                }
                
                timeElapsed4 = 0; //reset interval timer
            }
            
            if (timeElapsed5> dhtinterval) 
            {
                //getDht();
                timeElapsed5 = 0; //reset interval timer
                if (heaterenable == TRUE) {
                    sprintf(publishString,"*H%.1f*",temphtrin);
                } else if (circenable == TRUE){
                    sprintf(publishString,"*%.1f*",temphtrin);
                } else {
                     sprintf(publishString,"%.1f",temphtrin);
                }
                Particle.publish("TUBTEMP",publishString);
            }
            
            //handle sleep events
            unsigned int sleepswitch = (unsigned int)sleep;
            switch (sleepswitch) {
                case 3:  // pump off wait to turn on
                if (timesleeptimer > sleepdurationoff) {
                    timesleeptimer = 0;
                    circenable = TRUE;
                    sleep = 2;
                }
                break;
            
                case 2:  // pump on wait to turn off
                if (timesleeptimer > sleepdurationon) {
                    timesleeptimer = 0;
                    circenable = FALSE;
                    sleep = 3;
                }
                break;
            
                case 1: 
                if (timesleeptimer > initialsleepinterval){
                    timesleeptimer = 0;
                    sleep = 2;
                }
                break;
            }

            
            //handle circenable state
            if (circenable == TRUE){
                digitalWrite(heaterpumprelaypin, HIGH); //turn on pump if circ pump is enabled
            } else {
                digitalWrite(heaterpumprelaypin, LOW);  //turn off pump is circ pump is disabled
            }            
            
            //Heater safety override
            int heatstatus = digitalRead(heaterrelaypin);
            if(heatstatus == HIGH){
                digitalWrite(heaterpumprelaypin, HIGH);    
            }
            //override heater pump to on if heater is on
        
}
    
/*******************************************************************************
 * Function Name  : hotTubFunction
 * Description    : controls the hot tub
 * Input          : 
 * Output         : 
 * Return         : 
                
 *******************************************************************************/
int hotTubFunction(String controlstring)
{
    controlstring = controlstring.toUpperCase();    //force all control chars to uppercase
    strcpy(tubstatusstring, controlstring); //update particle variable.
    
    if(controlstring.startsWith("TEMP"))
	    {
		 sleep = 0;
		 controlstring =  controlstring.substring(4, controlstring.length());
		 settemp = controlstring.toFloat();
		 settemp = min(settemp, maxsettemp);
		 settemp = max(settemp, minsettemp);
		 heaterenable = TRUE;
		 circenable = TRUE;
		 timeheatercheck = 0;
		 return controlstring.toInt();
		 //settemp = 98.0;
        }
	else if (controlstring.startsWith("P1"))
		{
	        sleep = 0;
	        if (controlstring.endsWith("ON")){
	            digitalWrite(pump1relaypin, HIGH);
	        }
	        else if (controlstring.endsWith("T")){
	            int setpin = !digitalRead(pump1relaypin);
	            digitalWrite(pump1relaypin, setpin);
	            return setpin;
            }
	        else {
	            digitalWrite(pump1relaypin, LOW);
	        } 
	        return 1;
		}
	else if (controlstring.startsWith("P2"))
		{
		    sleep = 0;
		    if (controlstring.endsWith("ON")){
	            digitalWrite(pump2relaypin, HIGH);
	        }
	        else if (controlstring.endsWith("T")){
	            int setpin = !digitalRead(pump2relaypin);
	            digitalWrite(pump2relaypin, setpin);
	            return setpin;
            }
	        else {
	            digitalWrite(pump2relaypin, LOW);
	        }
	        return 2;
        }
	else if (controlstring.startsWith("P3"))
		{
		    sleep = 0;
		    if (controlstring.endsWith("ON")){
	            digitalWrite(pump3relaypin, HIGH);
	        }
	        else if (controlstring.endsWith("T")){
	            int setpin = !digitalRead(pump3relaypin);
	            digitalWrite(pump3relaypin, setpin);
	            return setpin;
            }
	        else {
	            digitalWrite(pump3relaypin, LOW);
	        }
	        return 3;
		}
	else if (controlstring.startsWith("AIR"))
		{
		    sleep = 0;
		    if (controlstring.endsWith("ON")){
	            digitalWrite(airrelaypin, HIGH);
	        }
	        else if (controlstring.endsWith("T")){
	            int setpin = !digitalRead(airrelaypin);
	            digitalWrite(airrelaypin, setpin);
	            return setpin;
            }
	        else {
	            digitalWrite(airrelaypin, LOW);
	        }
	        return 4;
		}
	else if (controlstring.startsWith("CIRC"))
		{
		    sleep = 0;
		    if (controlstring.endsWith("ON")){
                circenable = TRUE;
            }
            else if (controlstring.endsWith("T")){
	            circenable = !circenable;
	            heaterenable = FALSE;
	            return circenable;
            }

	        else {
	            circenable = FALSE;
	            heaterenable = FALSE;
	        }
	        return 5;
		}
	else if (controlstring.startsWith("O3"))
		{
		    sleep = 0;
		    if (controlstring.endsWith("ON")){
	            digitalWrite(xrelaypin, HIGH);
	        }
	        else if (controlstring.endsWith("T")){
	            int setpin = !digitalRead(xrelaypin);
	            digitalWrite(xrelaypin, setpin);
	            return setpin;
            }
	        else {
	            digitalWrite(xrelaypin, LOW);
	        }
	        return 6;
		}
	
	else if (controlstring.startsWith("HEAT"))
		{
		    sleep = 0;
		    if (controlstring.endsWith("ON")){
    	        heaterenable = TRUE;
    	        circenable = TRUE;
    	        timeheatercheck = 0;
	        }
	        else if (controlstring.endsWith("T")){
	            heaterenable = !heaterenable;
	            circenable = heaterenable;
	            timeheatercheck = 0;
	            return heaterenable;
            }

	        else {
                heaterenable = FALSE;
	        }
	        return 7;
		}
	
	else if (controlstring.startsWith("FUN"))
		{
            sleep = 0;
            controlstring =  controlstring.substring(3, controlstring.length());
		    float tempsettemp = controlstring.toFloat();
		    if (tempsettemp > 0){
		        settemp = tempsettemp;
		    }
		    settemp = min(settemp, maxsettemp);
		    settemp = max(settemp, minsettemp);
		    
            heaterenable = TRUE;
            timeheatercheck = 0;
            circenable = TRUE;
		    digitalWrite(xrelaypin, HIGH);
            digitalWrite(airrelaypin, HIGH);
            digitalWrite(pump3relaypin, HIGH);
            digitalWrite(pump2relaypin, HIGH);
            digitalWrite(pump1relaypin, LOW);
    		return controlstring.toInt();

		}		

	else if (controlstring.startsWith("SLEEP"))
		{
            heaterenable = FALSE;
            settemp = 0;
            circenable = TRUE;
            sleep = 1;
            timesleeptimer = 0;
            digitalWrite(heaterpumprelaypin, HIGH);
		    digitalWrite(xrelaypin, LOW);
            digitalWrite(airrelaypin, LOW);
            digitalWrite(pump3relaypin, LOW);
            digitalWrite(pump2relaypin, LOW);
            digitalWrite(pump1relaypin, LOW);
            digitalWrite(heaterrelaypin, LOW);
            return 9;
		}

	else if (controlstring.startsWith("OFF"))   //command shuts off all outputs.
		{
            heaterenable = FALSE;
            circenable = FALSE;
		    sleep = 0;
		    digitalWrite(xrelaypin, LOW);
            digitalWrite(heaterpumprelaypin, LOW);
            digitalWrite(airrelaypin, LOW);
            digitalWrite(pump3relaypin, LOW);
            digitalWrite(pump2relaypin, LOW);
            digitalWrite(pump1relaypin, LOW);
            digitalWrite(heaterrelaypin, LOW);
		}

	return 99;
	
}


double thermistor(int RawADC, float biasresistor) {
 double Temp;
 //Temp = log(10000.0*((4096.0/RawADC-1))); 
 Temp =log(biasresistor/(4096.0/RawADC-1)); // for pull-up configuration
 Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp ))* Temp );
 Temp = Temp - 273.15;            // Convert Kelvin to Celcius
 Temp = (Temp * 9.0)/ 5.0 + 32.0; // Convert Celcius to Fahrenheit
 return Temp;
}



/*******************************************************************************
 * Function Name  : getDht
 * Description    : gets temp and humidity
 * Input          : 
 * Output         : 
 * Return         : void
                    
 *******************************************************************************/
void getDht() {
// Wait at least 2 seconds between measurements.
	

// Reading temperature or humidity takes about 250 milliseconds!
// Sensor readings may also be up to 2 seconds 'old' (its a 
// very slow sensor)
	float h = dht.getHumidity();
// Read temperature as Celsius
	float t = dht.getTempCelcius();
// Read temperature as Farenheit
	float f = dht.getTempFarenheit();
  
   htelecbxtemp = f; //updates spark variable with current temp.
   htelecbxhumd = h;
   
// Check if any reads failed and exit early (to try again).
	if (isnan(h) || isnan(t) || isnan(f)) {
		Serial.println("Failed to read from DHT sensor!");
		return;
	}

// Compute heat index
// Must send in temp in Fahrenheit!
	float hi = dht.getHeatIndex();
	float dp = dht.getDewPoint();
	float k = dht.getTempKelvin();

	Serial.print("Humid: "); 
	Serial.print(h);
	Serial.print("% - ");
	Serial.print("Temp: "); 
	Serial.print(t);
	Serial.print("*C ");
	Serial.print(f);
	Serial.print("*F ");
	Serial.print(k);
	Serial.print("*K - ");
	Serial.print("DewP: ");
	Serial.print(dp);
	Serial.print("*C - ");
	Serial.print("HeatI: ");
	Serial.print(hi);
	Serial.println("*C");
	Serial.println(Time.timeStr());
}

Credits

10487434 10101544397202548 602652689234148955 n
John R McAlpine V Mac

www.MACSBOOST.com Assistant Professor at UNC Charlotte MEGR3171 Instrumentation, Motorsports Research

Contact

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Similar projects you might like

Silent Echo
Advanced
  • 78
  • 2

Protip

Speak to Alexa without Talking. Use a simple web interface to talk to Alexa.

Silent Echo

Team Bespoken

Alexa BrowserHelp
Advanced
  • 48
  • 2

Full instructions

BrowserHelp allows full voice control of your Chrome browser! Open any link, search with Google, scroll, and more

Mailbox 2.0
Advanced
  • 4,063
  • 80

Work in progress

Real time notifications for when your precious packages and letters arrive.

Mailbox 2.0

Team CKTS

Temperature and Humidity Notifier
Advanced
  • 305
  • 3

It is a notifier with temperature and humidity sensor in it. The LED screen will show the temperature and humidity at present.

Captain
Advanced
  • 816
  • 17

Work in progress

Captain is a system for automatically detecting mental health and anxiety in first responders using video & audio analysis.

Captain

Team First Support

Gotham
Advanced
  • 1,737
  • 32

Gotham is an Alexa-based tool that maps a novel real-time safety index on the street-level by aggregating OpenData and community feedback.

Gotham

Team City Guard

ProjectsCommunitiesTopicsContestsLiveAppsBetaFree StoreBlogAdd projectSign up / Login