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 Lecturer at UNC Charlotte MEGR3171 Instrumentation, Motorsports Research

Replications

Did you replicate this project? Share it!

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

Give feedback

Comments

Similar projects you might like

Gotham
Advanced
  • 804
  • 27

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

Captain
Advanced
  • 417
  • 15

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

Smart City Tree Management
Advanced
  • 246
  • 5

Protip

Smart City Tree Management uses sensors to monitor wellness of the trees to combat air pollution as well as sound pollution.

Automated Cat Feeder with Alexa and Amazon Dash
Advanced
  • 3,065
  • 20

Full instructions

Use Alexa to control your Raspberry Pi Powered Cat Feeder. The cat feeder can also re-order cat food using Amazon DRS.

Wanago
Advanced
  • 113
  • 2

Work in progress

Accessibility to directions for key locations in a city

Wanago

Team Ded-Sirius

Smart City Street Light Analysis
Advanced
  • 661
  • 5

Full instructions

Use of Alexa custom skills to relay faulty streetlight information analyzed from raw data provided by the City of Las Vegas.

ProjectsCommunitiesContestsLiveJobsFree StoreBlogAdd projectSign up / Login
Respect project