306 lines
10 KiB
Arduino
306 lines
10 KiB
Arduino
|
/*
|
||
|
Open and close a dog door with a servo and limit switches via the web
|
||
|
*/
|
||
|
|
||
|
#include <SPI.h>
|
||
|
#include <Ethernet.h>
|
||
|
#include <EthernetUdp.h>
|
||
|
#include <ArduinoMDNS.h>
|
||
|
#include <Servo.h>
|
||
|
#include <AsyncTimer.h>
|
||
|
|
||
|
|
||
|
EthernetUDP udp;
|
||
|
MDNS mdns(udp);
|
||
|
|
||
|
byte mac[] = {
|
||
|
0xD0, 0x99, 0x1E, 0xD0, 0x99, 0x1E
|
||
|
};
|
||
|
//IPAddress ip(192, 168, 10, 99);
|
||
|
|
||
|
EthernetServer 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 = 2;
|
||
|
int limitDownPin = 3;
|
||
|
int servoPin = 9;
|
||
|
int ethernetPin = 10;
|
||
|
|
||
|
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 9600 bits per second:
|
||
|
if (USE_SERIAL) Serial.begin(9600);
|
||
|
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");
|
||
|
}
|
||
|
|
||
|
|
||
|
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(Ethernet.localIP());
|
||
|
|
||
|
// start mDNS
|
||
|
mdns.begin(Ethernet.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() {
|
||
|
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, EthernetClient 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");
|
||
|
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");
|
||
|
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) {
|
||
|
EthernetClient 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");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|