Created
January 2, 2022 23:51
-
-
Save mostlygeek/b1fd63fbb3bb962bd7df03239351de3d to your computer and use it in GitHub Desktop.
Code for a little robot that open/closes a door in my AV console when it gets too hot inside.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <OneWire.h> | |
#include <DallasTemperature.h> | |
#include <ESP8266WiFi.h> | |
#include <ESP8266WebServer.h> | |
#include <WiFiManager.h> | |
// *** -= WIFI & Web =- ************************************** | |
ESP8266WebServer server(80); // listen on port 80 | |
// *********************************************************** | |
// *** -= L298n MOTOR CONTROLLER =- ************************** | |
// L298n motor controller | |
const int ACTUATOR_PIN1 = D3; | |
const int ACTUATOR_PIN2 = D4; | |
const int FAN_PIN1 = D5; | |
const int FAN_PIN2 = D6; | |
const int ACTUATOR_ON = 5800; // ms to move actuator | |
// *** -= DS18B20 THERMAL PROBE =- *************************** | |
OneWire oneWire(D2); | |
DallasTemperature sensors(&oneWire); | |
// *********************************************************** | |
// *** -= SYSTEM STATE =- ************************************ | |
const float TEMP_LOW_THRESHOLD = 24.5; | |
const float TEMP_HIGH_THRESHOLD = 27.0; // pretty hot | |
const byte STATE_OFF = 0; // auto, default state | |
const byte STATE_COOLING_AUTO = 1; // auto cooling - on | |
const byte STATE_COOLING_HOLD = 2; // cooling, hold until timeout | |
// how frequently to check temperature | |
const unsigned int CHECK_TEMP_TIMEOUT = 1000; | |
const unsigned int HOLD_COOLING_TIMEOUT = 1000 * 60 * 15; // 15 minutes | |
// Global state variables | |
int gState = STATE_OFF; | |
float gTemp = 0; // temperature in celcius | |
unsigned long gLastTempCheck = 0; | |
unsigned long gCoolingHoldStart = 0; | |
// *********************************************************** | |
void setup() { | |
// Start the Serial Monitor | |
Serial.begin(115200); | |
// Start the DS18B20 sensor | |
sensors.begin(); | |
pinMode(ACTUATOR_PIN1, OUTPUT); | |
pinMode(ACTUATOR_PIN2, OUTPUT); | |
pinMode(FAN_PIN1, OUTPUT); | |
pinMode(FAN_PIN2, OUTPUT); | |
// reset to default initial state | |
fanOff(); | |
closeDoor(); | |
// setup WiFI | |
WiFi.softAP(WIFI_SSID, WIFI_PW); | |
// WIFI gateway can be same as wifi IP. No routing anyways. | |
WiFi.softAPConfig(WIFI_IP, WIFI_IP, WIFI_SN); | |
delay(100); | |
server.on("/", handle_OnHome); | |
server.on("/toggle", handle_OnHoldCooling); | |
server.on("/info", handle_GetInfo); | |
server.onNotFound(handle_NotFound); | |
server.begin(); | |
Serial.println("HTTP server started"); | |
} | |
void loop() { | |
server.handleClient(); | |
updateTemp(); | |
updateState(); | |
} | |
void updateTemp() { | |
// update temperature value | |
unsigned long now = millis(); | |
if (abs(now - gLastTempCheck) >= CHECK_TEMP_TIMEOUT) { | |
gLastTempCheck = now; | |
gTemp = getTemperature(); | |
Serial.print("TEMP: "); | |
Serial.print(gTemp); | |
Serial.print("ºC | High: "); | |
Serial.print(TEMP_HIGH_THRESHOLD); | |
Serial.print("ºC ("); | |
Serial.print(TEMP_HIGH_THRESHOLD - gTemp); | |
Serial.print("ºC) | Low: "); | |
Serial.print(TEMP_LOW_THRESHOLD); | |
Serial.print("ºC ("); | |
Serial.print(gTemp - TEMP_LOW_THRESHOLD); | |
Serial.println("ºC)"); | |
} | |
} | |
// does state transitions | |
void updateState() { | |
// STATE_OFF -> STATE_COOLING_AUTO | |
if (gState == STATE_OFF && gTemp >= TEMP_HIGH_THRESHOLD) { | |
Serial.println("STATE_OFF -> STATE_COOLING_AUTO"); | |
setState(STATE_COOLING_AUTO); | |
return; | |
} | |
// STATE_COOLING_AUTO -> STATE_OFF | |
if (gState == STATE_COOLING_AUTO && gTemp <= TEMP_LOW_THRESHOLD ) { | |
Serial.println("STATE_COOLING_AUTO -> STATE_OFF"); | |
setState(STATE_OFF); | |
return; | |
} | |
// STATE_COOLING_HOLD -> STATE_OFF | |
if (gState == STATE_COOLING_HOLD) { | |
unsigned long now = millis(); | |
if (abs(now - gCoolingHoldStart) >= HOLD_COOLING_TIMEOUT) { | |
Serial.println("STATE_COOLING_HOLD -> STATE_OFF"); | |
setState(STATE_OFF); | |
} | |
return; | |
} | |
} | |
void setState(byte newState) { | |
if (newState == gState) { | |
return; | |
} | |
switch (newState) { | |
case STATE_OFF: | |
fanOff(); | |
closeDoor(); | |
gState = STATE_OFF; | |
Serial.println("New State: STATE_OFF"); | |
break; | |
case STATE_COOLING_AUTO: | |
fanOn(); | |
openDoor(); | |
gState = STATE_COOLING_AUTO; | |
Serial.println("New State: STATE_COOLING_AUTO"); | |
break; | |
case STATE_COOLING_HOLD: | |
fanOn(); | |
openDoor(); | |
gState = STATE_COOLING_HOLD; | |
gCoolingHoldStart = millis(); | |
Serial.println("New State: STATE_COOLING_HOLD"); | |
break; | |
} | |
} | |
float getTemperature() { | |
sensors.requestTemperatures(); | |
return sensors.getTempCByIndex(0); | |
} | |
void fanOn() { | |
digitalWrite(FAN_PIN1, HIGH); // spin in correct direction | |
digitalWrite(FAN_PIN2, LOW); | |
} | |
void fanOff() { | |
digitalWrite(FAN_PIN1, LOW); | |
digitalWrite(FAN_PIN2, LOW); | |
} | |
void openDoor() { | |
digitalWrite(ACTUATOR_PIN1, LOW); | |
digitalWrite(ACTUATOR_PIN2, HIGH); | |
} | |
void closeDoor() { | |
digitalWrite(ACTUATOR_PIN1, HIGH); | |
digitalWrite(ACTUATOR_PIN2, LOW); | |
} | |
void handle_OnHome() { | |
server.send(200, "text/html", SendHTML()); | |
} | |
void handle_OnHoldCooling() { | |
// send http response before making the changes to state | |
server.sendHeader("Location", "/"); | |
server.send(303, "text/plain", "OK"); | |
if (gState == STATE_COOLING_HOLD) { | |
setState(STATE_OFF); | |
} else { | |
setState(STATE_COOLING_HOLD); | |
} | |
} | |
void handle_GetInfo() { | |
String out = String(gTemp) + "|"; | |
if (gState == STATE_OFF) { | |
out += "OFF"; | |
} else if (gState == STATE_COOLING_AUTO) { | |
out += "Cooling Auto"; | |
} else if (gState == STATE_COOLING_HOLD) { | |
float remain = (HOLD_COOLING_TIMEOUT - (millis() - gCoolingHoldStart))/1000; | |
out += "Cooling Hold for " + String(remain) + "s"; | |
} | |
server.send(200, "text/plain", out); | |
} | |
void handle_NotFound() { | |
server.send(404, "text/plain", "Not found"); | |
} | |
String SendHTML() { | |
String ptr = "<!DOCTYPE html> <html>\n"; | |
ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n"; | |
ptr += "<title>Cooler Bot - 192.168.1.1</title>\n"; | |
ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n"; | |
ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n"; | |
ptr += ".button {display: block;width: 160px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n"; | |
ptr += ".button:active {background-color: #16a085;}\n"; | |
ptr += ".button-off {background-color: #990000;}\n"; | |
ptr += ".button-off:active {background-color: #800000;}\n"; | |
ptr += ".button:active {background-color: #16a085;}\n"; | |
ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n"; | |
ptr += "table {width: 220px; margin-left: auto; margin-right: auto;}\n"; | |
ptr += "td { border-bottom: 1px solid black; }\n"; | |
ptr += "td.a { text-align: right; font-weight: bold; }\n"; | |
ptr += "</style>\n"; | |
// background update the temperature | |
ptr += "<script>\n"; | |
ptr += "function update() { \n"; | |
ptr += " fetch(\"/info\").then(resp => {\n"; | |
ptr += " resp.text().then(d => {\n"; | |
ptr += " var s = d.split(\"|\");\n"; | |
ptr += " document.getElementById(\"curtemp\").innerText=s[0];\n"; | |
ptr += " document.getElementById(\"state\").innerText=s[1];\n"; | |
ptr += " setTimeout(update, 1000);\n"; // set another automatic update on success | |
ptr += " })\n"; | |
ptr += " })\n"; | |
ptr += "}\n"; | |
ptr += "update();\n"; // start it running | |
ptr += "</script>\n"; | |
ptr += "</head>\n"; | |
ptr += "<body>\n"; | |
ptr += "<h1>Cooler Bot</h1>\n"; | |
ptr += "<h3>State: <span id=state>--</span></h3>"; | |
ptr += "<table>\n<tr><td class=a>Low</td><td>"+String(TEMP_LOW_THRESHOLD) + "°C</td></tr>\n"; | |
ptr += "<tr><td class=a>Current</td><td><span id=curtemp>--</span>°C</td></tr>\n"; | |
ptr += "<tr><td class=a>High</td><td>"+ String(TEMP_HIGH_THRESHOLD)+"°C</td></tr>\n"; | |
ptr += "</table>\n"; | |
if (gState == STATE_COOLING_HOLD) { | |
ptr += "<p><a class=\"button button-off\" href=\"/toggle\">STOP HOLD</a></p>"; | |
} else { | |
ptr += "<p><a class=\"button\" href=\"/toggle\">HOLD COOLING</a></p>"; | |
} | |
ptr += "</body>\n"; | |
ptr += "</html>\n"; | |
return ptr; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment