Brian Harrah
Published © GPL3+

A Cooler Way to Warm Up Your Car

It's winter. Don't let your car run for 20 minutes just for a little warmth. Build this WiFi-connected heater and spare the pollution!

IntermediateShowcase (no instructions)3 hours1,046
A Cooler Way to Warm Up Your Car

Things used in this project

Hardware components

NodeMCU Development Board
×1
5V/30A Relay Module
×1
DHT22 Temperature Sensor
DHT22 Temperature Sensor
×1
LED (generic)
LED (generic)
×1
5V USB Charger
×1
Plastic Enclosure
×1
Rubber Grommet
×2
Extension Cord
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Wiring

Note: I used a relay module that was powered separately by the power supply, not by the GPIO pins from the MCU as shown in this picture.

Code

wifi_car_heater.ino

Arduino
Arduino sketch with all the embedded code...
#include <WiFiClient.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <DHT.h>
#include "root.h"

#define DHTPIN  D5              
#define DHTTYPE DHT22  

ESP8266WiFiMulti wifiMulti;
ESP8266WebServer server(80);
DHT dht(DHTPIN, DHTTYPE);

const int RELAY = D6;
const int LED = D7;
const long checkRelayInterval = 3600000;  // 1 hour safety shutoff
unsigned long checkTempInterval = 1000;

String text;
double temperature;
float bTemp = (dht.readTemperature(true)) - 2.9;  // A backup temp in case getTemperature() returns NaN. (-2.9 for calibration)
boolean getRelayOnTimeHasRun = false;
boolean checkSafetyTempHasRun = false;
unsigned long currentMillis = 0;
unsigned long relayOnMillis = 0;
unsigned long checkTempMillis = 0;

void setup(void){
  Serial.begin(115200);
  
  pinMode(RELAY, OUTPUT);
  digitalWrite(RELAY, 0);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, 1);

  dht.begin();
  temperature = getTemperature();

  wifiMulti.addAP("your ssid 1", "your pw 1");
  wifiMulti.addAP("your ssid 2", "your pw 2");

  Serial.print("Connecting");
  int attemptCounter = 0;
  
  while (wifiMulti.run() != WL_CONNECTED) {   // Scan for wifi networks and connect to the strongest of the networks listed above
    Serial.print('.');
    digitalWrite(LED, 0);   // Blink LED slowly while wifi is connecting
    delay(450);
    digitalWrite(LED, 1);
    delay(450);

    if (attemptCounter >= 15) {  // If wifi can't connect, blink LED fast to indicate error
      while(1){
        digitalWrite(LED, 0);
        delay(150);
        digitalWrite(LED, 1);
        delay(150);
      }
    }
    attemptCounter ++;
  }
  Serial.println();
  Serial.print("Connected to: ");
  Serial.println(WiFi.SSID());              
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());          
  
  if (MDNS.begin("wifiheat", WiFi.localIP())) {   // Start the mDNS responder for wifiheat.local
    Serial.println("mDNS responder started");
  } else {
    Serial.println("Error setting up MDNS responder!");
  }

  server.on("/", []() {server.send_P(200, "text/html", PAGE_ROOT);});
  server.on("/heatOn", handleHeatOn);
  server.on("/heatOff", handleHeatOff);
  server.on("/data.txt", handleTemperature);
  server.on("/heatState", handleHeatState);
    
  server.begin();
  Serial.println("Web server started");

  for (int i=0; i<4; i++) {
    digitalWrite(LED, 0);   // Setup was successful, fast blink LED 4 times
    delay(70);
    digitalWrite(LED, 1);
    delay(70);
  }
}

void handleTemperature() {
  text = String(temperature, 1);
  server.send(200, "text/html", text);
}

void handleHeatOn() { 
  Serial.println("Heat ON");
  digitalWrite(RELAY, HIGH);
  server.send(200, "text/html", "");
}

void handleHeatOff() { 
  Serial.println("Heat OFF");
  digitalWrite(RELAY, LOW);
  server.send(200, "text/html", "");
}

void handleHeatState() { 
  String heatState = String(digitalRead(RELAY));
  server.send(200, "text/html", heatState);
}

float getTemperature() {
  float temp = (dht.readTemperature(true)) - 2.9;  // Returns temperature in F, subtracted 2.9 for calibration
  
  if(!isnan(temp)){  // Prevents the UI from displaying the NaN that is occasionally returned from the DHT22
    bTemp = temp;
    return(temp);
  }
  else{
    return(bTemp);
  }
}

void checkTemperatureOnInterval() {
  if (currentMillis - checkTempMillis >= checkTempInterval) {
    checkTempMillis = currentMillis;
    temperature = getTemperature();
  }
}

void checkSafetyTemp() {
  if (digitalRead(RELAY) && temperature >= 90) {
    if (checkSafetyTempHasRun == false) { 
      Serial.println("Safety temperature limit reached!");
      Serial.println("Turning heat OFF");
      digitalWrite(RELAY, LOW);
      checkSafetyTempHasRun = true;
    }
  } 
  if (digitalRead(RELAY) && checkSafetyTempHasRun) {
    checkSafetyTempHasRun = false;
  }
}

void checkSafetyTimeout() {
  if (getRelayOnTimeHasRun == false) {   
    if (digitalRead(RELAY)) {   // Save millis time when relay is turned on. Do this one time only.
      relayOnMillis = currentMillis;
      getRelayOnTimeHasRun = true;
    }  
  }

  if (!digitalRead(RELAY)) {   // If relay was turned off anytime before timeout is met, toggle getRelayOnTimeHasRun to reset the counter
    getRelayOnTimeHasRun = false;
  }
  
  if (getRelayOnTimeHasRun && currentMillis - relayOnMillis >= checkRelayInterval) {
    Serial.println("Safety time limit reached!");
    Serial.println("Turning heat OFF");
    digitalWrite(RELAY, LOW);
    getRelayOnTimeHasRun = false;
  }
}

void loop() {
  while (wifiMulti.run() != WL_CONNECTED) {  
    digitalWrite(RELAY, LOW);   // Turn heat off if wifi has disconnected
    Serial.println("WiFi disconnected!");
    digitalWrite(LED, 0);   // Blink LED fast to indicate disconnected state
    delay(150);
    digitalWrite(LED, 1);
    delay(150);
  }
  
  server.handleClient();
  currentMillis = millis();
  checkTemperatureOnInterval();
  checkSafetyTimeout();
  checkSafetyTemp();
}

root.h

HTML
Web code loaded from PROGMEM
const char PAGE_ROOT[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
   <head>
      <title>Wifi Car Heater</title>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width">
      <link href='https://fonts.googleapis.com/css?family=Advent+Pro' rel="stylesheet" type="text/css">
      <style>
         html {height: 100%;}
         h2 {font-size: 90px;font-weight: 400;margin: 0;display: inline-block;}
         h4 {color: white;text-align: center;padding: 10px;}
         body {height: 100%;overflow: hidden;}
         .content {color: #fff;font-family: 'Advent Pro';font-weight: 400;left: 50%;position: absolute;text-align: center;top: 43%;transform: translateX(-50%) translateY(-50%);width: 250px;}
         .warning {margin: 0 auto;margin-top: 60px;background-color: red;width: 320px;border-radius: 10px;visibility: hidden;}
         .cold {background: linear-gradient(to bottom, #cee0ff, #0665e0 );}
         .medium {background: linear-gradient(to bottom, #cee0ff, #ffb97c);}
         .hot {background: linear-gradient(to bottom, #fcdb88, #d32106);}
         .button {background-color: grey;border: 2px solid white;outline: none;color: white;min-width: 130px;min-height: 50px;text-align: center;display: block;font-size: 16px;border-radius: 12px;position: relative;top: 25px;left:60px;}
      </style>
      </style>
   </head>
   <body class="">
      <div class="warning" id="warning">
         <h4>Temperature safety limit exceeded</h4>
      </div>
      <div class="content">
         <h1>Cabin Temperature</h1>
         <h2 id="data">--.-</h2>
         <h2>&nbsp;<small>&deg;F</small></h2>
         <button class="button" id="button" onclick="buttonToggle()">Heat OFF</button>
      </div>
      <script>  
          loadData("data.txt", updateData);
          loadData("heatState", heatStateCallback);
          
          setInterval(function () {
             loadData("data.txt", updateData);
          }, 5000);
          setInterval(function () {
             loadData("heatState", heatStateCallback);
          }, 1000);

          function loadData(url, callback) {
             var request = new XMLHttpRequest();
             request.onreadystatechange = function () {
                if (this.readyState == 4 && this.status == 200) {
                   callback.apply(request);
                }
             };
             request.open("GET", url, true);
             request.send();
          }

          function heatStateCallback() {
             var button = document.getElementById("button");
             if (this.response == 1) {
                button.innerHTML = "Heat ON";
                button.style.background = "red";
                button.style.border = "2px solid white";
             } else {
                button.innerHTML = "Heat OFF";
                button.style.background = "grey";
                button.style.border = "2px solid white";
             }
          }

          function updateData() {
             document.getElementById("data").innerHTML = this.responseText;
             changeBackgroundColor(this.response);
             tempLimitWarning(this.response);
          }

          function tempLimitWarning(temperature) {
            var warning = document.getElementById("warning");
            var button = document.getElementById("button");
            if (temperature >= 90) {
              warning.style.visibility = "visible";
              button.disabled = true;
            } else {
              warning.style.visibility = "hidden";
              button.disabled = false;
            }
          }
          
          function changeBackgroundColor(temperature) {
             if (temperature < 35) {
                document.body.className = "cold";
             } else if (temperature > 65) {
                document.body.className = "hot";
             } else document.body.className = "medium";
          }

          function buttonToggle() {
             var button = document.getElementById("button");
             if (button.innerHTML === "Heat OFF") {
                button.style.border = "2px solid purple";
                loadData("heatOn", heatOnCallback);
                function heatOnCallback() {}
             } else {
                button.style.border = "2px solid purple";
                loadData("heatOff", heatOffCallback);
                function heatOffCallback() {}
             }
          }
      </script>
   </body>
</html>
)=====";

Credits

Brian Harrah

Brian Harrah

1 project • 2 followers

Comments