/* Open and close a dog door with a servo and limit switches via the web */ #include #include #include #include #include #include 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("
");
                handleInput(cmd, client);
                // client.print("

Back"); // client.print(clientBuf); // client.print("-->"); } } else { const char* its = ""); client.println(""); handleInput('q', client); client.print("
"); client.print(its); client.print("u value=Up />"); client.print(its); client.print("d value=Down />"); client.print(its); client.print("s value=Stop />
"); } 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"); } } }