doggie-door/doggie-door-esp32/doggie-door-esp32.ino

323 lines
11 KiB
C++

/*
Open and close a dog door with a servo and limit switches via the web
*/
#include <SPI.h>
#include <ArduinoMDNS.h>
#include <Servo.h>
#include <AsyncTimer.h>
#include <WiFi.h>
// Replace with your own network credentials
const char* ssid = "YourWifiSsid";
const char* password = "YourWifiPassword";
WiFiUDP udp;
MDNS mdns(udp);
// byte mac[] = {
// 0xD0, 0x99, 0x1E, 0xD0, 0x99, 0x1E
// };
//IPAddress ip(192, 168, 10, 99);
WiFiServer server(80);
bool USE_SERIAL=false; // ideally don't use serial during normal operation so it doesn't hang
bool USE_ETHERNET=true;
int SAFETY_STOP_TIME_MS=10000; // how many milliseconds to turn the servo before giving up
int SERVO_DEBOUNCE_TIME_MS=200; // how many milliseconds to wait before checking the limit switch status again
bool DETACH_SERVO_WHEN_DONE=true; // detaching lets the servo rotate semi-freely, remaining attached holds the servo at "stop"
Servo myservo;
AsyncTimer t;
int limitUpPin = 13;
int limitDownPin = 14;
int servoPin = 4;
int downward = 100; // lower to lessen down speed, max 180
int upward = 0; // raise to lessen up speed, min 0
int stop = 90; // adjust in case 90 isn't "stop" for your servo, but if you experience jitter probably adjust the servo potentiometer
bool isMaxUp = false;
bool isMaxDown = false;
// const unsigned long TIMER0_COUNT = 500; // 500 msec timer interval
/*
// TIMER0 interrupt handler
ISR( TIMER0_COMPA_vect ) {
// recheck the sensors periodically just in case
handleSensor();
}
*/
// the setup routine runs once when you press reset:
void setup() {
// give ethernet a bit of time to stabilize power on startup before init
// if (USE_ETHERNET) delay(5000);
// initialize serial communication at 115200 bits per second:
if (USE_SERIAL) Serial.begin(115200);
pinMode(limitUpPin, INPUT_PULLUP);
pinMode(limitDownPin, INPUT_PULLUP);
/*
// *** TIMER0 initialization ***
cli(); // turn off all interrupts
TIMSK0 = 0; // turn off timer0 for lower jitter
OCR0A = 0xBB; // arbitrary interrupt count
TIMSK0 |= _BV( OCIE0A ); // piggy back onto interrupt
sei(); // turn interrupts back on
*/
attachInterrupt(digitalPinToInterrupt(limitUpPin), handleSensor, CHANGE);
attachInterrupt(digitalPinToInterrupt(limitDownPin), handleSensor, CHANGE);
// initialize the ethernet shield
// if (USE_ETHERNET) Ethernet.init(ethernetPin);
// start the Ethernet connection via DHCP:
// if (USE_ETHERNET && Ethernet.begin(mac) == 0) {
// if (USE_SERIAL) Serial.println("Failed to configure Ethernet using DHCP");
// }
// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
/*
if (USE_ETHERNET && Ethernet.hardwareStatus() == EthernetNoHardware) {
if (USE_SERIAL) Serial.println("Ethernet shield was not found!");
//while (true) {
// delay(1); // do nothing, no point running without Ethernet hardware
//}
}
if (USE_ETHERNET && Ethernet.linkStatus() == LinkOFF) {
if (USE_SERIAL) Serial.println("Ethernet cable is not connected!");
}
*/
// start the server
if (USE_ETHERNET) server.begin();
if (USE_SERIAL) Serial.print("IP: ");
if (USE_SERIAL) Serial.println(WiFi.localIP());
// start mDNS
mdns.begin(WiFi.localIP(), "doggie-door");
if (USE_SERIAL) Serial.println("or doggie-door.local");
// Currently the mDNS library addServiceRecord function is buggy and causes reliability issues
/*mdns.addServiceRecord("Doggie Door Control._http",
80,
MDNSServiceTCP);*/
}
void stopServo() {
if (!myservo.attached()) myservo.attach(servoPin);
myservo.write(stop);
if (DETACH_SERVO_WHEN_DONE) myservo.detach();
}
void handleSensor(){
// the limit switches are pulled up but attached as Normally Closed and shorted to ground,
// which means true is unpressed and false is pressed. this allows "unplugged" to read as
// "pressed" aka fail-secure.
delay(SERVO_DEBOUNCE_TIME_MS); // debounce a bit and let the flag solidly contact the switch (there's a lot of bounce going up)
// only update state and continue if there's a change
bool changed = false;
if ((bool)digitalRead(limitUpPin) != isMaxUp) {
isMaxUp = !isMaxUp;
changed = true;
}
if ((bool)digitalRead(limitDownPin) != isMaxDown) {
isMaxDown = !isMaxDown;
changed = true;
}
if (changed) {
if (isMaxUp && myservo.read() == upward) {
stopServo();
}
if (isMaxDown && myservo.read() == downward) {
stopServo();
}
// String out = "interrupt: maxup: ";
// out = out + (isMaxUp ? "y" : "n")+" maxdown: "+(isMaxDown ? "y" : "n");
// if (USE_SERIAL) Serial.println(out);
}
}
void handleInput(char input, WiFiClient client){
// must be d,u,s,q
if (input == 'd') {
if (isMaxDown) {
if (USE_SERIAL) Serial.println("can't go more down!");
if (USE_ETHERNET) client.print("can't go more down");
return;
}
if (USE_SERIAL) Serial.println("down...");
if (USE_ETHERNET) client.print("going down");
stopServo();
myservo.attach(servoPin);
myservo.write(downward);
t.setTimeout([]() { // asynctimer
if (myservo.read() == downward) {
stopServo();
if (USE_SERIAL) Serial.println("safety stop.");
}
}, SAFETY_STOP_TIME_MS);
} else if (input == 'u') {
if (isMaxUp) {
if (USE_SERIAL) Serial.println("can't go more up!");
if (USE_ETHERNET) client.print("can't go more up");
return;
}
if (USE_SERIAL) Serial.println("up...");
if (USE_ETHERNET) client.print("going up");
stopServo();
myservo.attach(servoPin);
myservo.write(upward);
t.setTimeout([]() { // asynctimer
if (myservo.read() == upward) {
stopServo();
if (USE_SERIAL) Serial.println("safety stop.");
}
}, SAFETY_STOP_TIME_MS);
} else if (input == 's') {
stopServo();
if (USE_SERIAL) Serial.println("stop.");
if (USE_ETHERNET) client.print("stopped.");
} else if (input == 'q') {
if (USE_SERIAL) Serial.println("status via ethernet only");
if (USE_ETHERNET) {
client.print("{\"maxup\":");
client.print(isMaxUp ? "1" : "0");
client.print(",\"maxdown\":");
client.print(isMaxDown ? "1" : "0");
client.print("}");
}
// if (USE_SERIAL) Serial.println(out);
// if (USE_ETHERNET) client.print(out);
} else {
if (USE_SERIAL) Serial.println("inv:");
if (USE_SERIAL) Serial.println(input);
if (USE_ETHERNET) client.print("inv: ");
if (USE_ETHERNET) client.print(input);
}
}
// the loop routine runs over and over again forever:
void loop() {
t.handle(); //asynctimer
mdns.run(); // handle mDNS requests/responses
// ideally don't use serial during normal operation so it doesn't hang
if (!USE_ETHERNET && USE_SERIAL) {
while (Serial.available() > 0) {
String input = Serial.readStringUntil('\n');
handleInput(input[0], NULL);
}
}
// listen for incoming clients
else if (USE_ETHERNET) {
WiFiClient client = server.available();
if (client) {
if (USE_SERIAL) Serial.println("new client");
// an HTTP request ends with a blank line
bool currentLineIsBlank = true;
String clientBuf = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (USE_SERIAL) Serial.write(c);
//read char by char HTTP request up to 300 chars
if (clientBuf.length() < 300) {
//store characters to string
clientBuf += c;
}
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the HTTP request has ended,
// so you can send a reply
if (c == '\n' && currentLineIsBlank) {
long len = clientBuf.length();
long idx = clientBuf.indexOf("?");
long idxj = clientBuf.indexOf("json");
const char* cc = "Connection: close";
const char* cth = "Content-Type: text/html";
if (len > 0 && idx > 0) {
char cmd = clientBuf[idx+1];
// must be d,u,s,q
if (cmd == 'd' || cmd == 'u' || cmd == 's' || cmd == 'q') {
client.println("HTTP/1.1 200 OK");
} else {
client.println("HTTP/1.1 400 Bad Request");
}
if (idxj > 0 || len > 20) { // junk 123 logic to always go here despite compiler
client.println("Content-Type: application/json");
client.println(cc);
client.println();
handleInput(cmd, client);
} else {
client.println(cth);
client.println(cc);
client.println();
// client.print("<pre>");
handleInput(cmd, client);
// client.print("</pre><br><a href=/>Back</a>");
// client.print(clientBuf);
// client.print("-->");
}
} else {
const char* its = "<input type=submit name=";
// send a standard HTTP response header
client.println("HTTP/1.1 200 OK");
client.println(cth);
client.println(cc); // the connection will be closed after completion of the response
client.println("Refresh: 5"); // refresh the page automatically every 5 sec
client.println();
client.println("<!DOCTYPE HTML>");
client.println("<html>");
handleInput('q', client);
client.print("<br><form action=/ method=GET>");
client.print(its);
client.print("u value=Up />");
client.print(its);
client.print("d value=Down />");
client.print(its);
client.print("s value=Stop /></form></html>");
}
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
} else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
if (USE_SERIAL) Serial.println("client disconnected");
}
}
}