Update readme, adjust dovetail, add Arduino
This commit is contained in:
parent
34696640ae
commit
156409eb45
143
README.md
143
README.md
|
@ -6,29 +6,37 @@
|
|||
|
||||
## Materials
|
||||
|
||||
- Hobby servo
|
||||
- Small pointy screws (apx 4mm head, 5mm length, 2mm thread, coarse)
|
||||
- [Medium sized PetSafe Wall Entry doggie door](https://www.petsafe.com/product/wall-entry-pet-door/?sku=ZPA00-16201&collection-location=/shop/dog/doors/)
|
||||
- 3D Printer
|
||||
- Arduino Ethernet
|
||||
- 9v Power Supply within reach of the door
|
||||
- Normal-size continuous-rotation hobby servo (~40mm x ~20mm) with appropriate mounting plate/arms
|
||||
- A 180-degree steering servo can sometimes be made continuous by taking it apart and removing the limiting pin.
|
||||
- Small pointy screws (apx 4mm head, 5mm length, 2.5mm thread, coarse)
|
||||
- 9v (or whatever your voltage regulator and Arduino will take) Power Supply within reach of the door
|
||||
- Appropriate connectors and wire to split out the 9v power supply to both the Arduino and breadboard
|
||||
- 7805 or similar voltage regulator
|
||||
- Ethernet cable within reach of the door
|
||||
- 5v voltage regulator and breadboard/wires
|
||||
- 2x Medium-sized limit switches
|
||||
- 3D Printer
|
||||
- About 2.5 feet of ball-chain (like ceiling fan pull chain)
|
||||
- About 40 inches of 3mm or 1/8" ball-chain (like ceiling fan pull chain) -- packaged it's often sold in shorter lengths so look for it online or by-the-foot at your local chain supplier (hardware/drapery/locksmith/etc)
|
||||
- 2x Ball-chain mounting brackets (small eyelets and cups for attaching ball-chain to things with a screw)
|
||||
- A nut and ~1cm x ~4mm bolt for attaching the limit switch flag to the doggie door (or however you prefer)
|
||||
- A thin sturdy piece of metal to disable the door latch
|
||||
- M-M jumper wires
|
||||
- A breadboard, proto-shield, or other wire prototyping method
|
||||
- Moderately sized capacitor (about 220uF, at least 100uF or more, for reducing voltage spikes on the servo)
|
||||
- Appropriate cable management
|
||||
|
||||
## Printing
|
||||
|
||||
Print the following files:
|
||||
- [[doggie-door.stl]]
|
||||
- It's currently split up with dovetails into two parts; feel free to edit the SCAD to split it elsewhere for your printer.
|
||||
- I also suggest printing only a small section of dovetail and screw hole at first to see if tolerances need adjusting. Try using the Hole or Subtraction/Difference feature of your slicer or OpenSCAD to remove all but these areas.
|
||||
- 2x [[parametric_ball_pulley-improved-with-flares.stl]]
|
||||
- [[limit-switch-mount-sideways.stl]]
|
||||
- [[limit-switch-mount-sideways-right.stl]]
|
||||
- [[limit-switch-flag-and-chain-catch-printable.stl]]
|
||||
- [[limit-switch-flag-and-chain-mount.stl]]
|
||||
- I suggest printing it on its flattest side (rotate 90 degrees)
|
||||
- [[LBracket_Parametric_rev9.stl]]
|
||||
- [[Futaba_3001_Servo_Mount.stl]]
|
||||
|
||||
The other files are just for posterity.
|
||||
|
||||
|
@ -38,22 +46,24 @@ The other files are just for posterity.
|
|||
|
||||
Refer to the doggie-door.scad file or screenshots above for assembly.
|
||||
|
||||
- Solder jumper wires to the limit switches: they should be Normally Closed so that the Arduino knows that they're connected (fails safe if they get disconnected)
|
||||
- Attach the parts of the doggie-door.stl together
|
||||
- Attach the Servo Mount to the wide short mounting block at the top facing left
|
||||
- Attach the servo to the servo mount
|
||||
- Attach the LBracket to the tall narrow mounting block at the top also facing left
|
||||
- Attach the limit switches to the left and right limit switch mounts: each one attaches with two screws on the back of the switch, so pay attention to which mounting direction makes sense
|
||||
- Attach the limit switch mounts to the backboard using the provided pilot holes
|
||||
- Attach the ball pulleys to the servo and to the LBracket
|
||||
- Attach the limit switch flag and chain catch to the bottom-right corner of the doggie door, just above the corner radius and snugly so the flag can't rotate
|
||||
- Solder jumper wires to the limit switches: they should be Normally Closed so that the Arduino knows that they're connected (fails safe if they get disconnected) -- on my switches these are the first and third pins, not middle.
|
||||
- Attach the parts of the doggie-door.stl together, gently use a hammer and/or knife if it's too tight, or firm plastic glue if too loose.
|
||||
- Attach the LBracket to the tall narrow mounting block (bottom area) with the hole facing left
|
||||
- Attach the limit switches to the top and bottom limit switch mounts: each one attaches with two screws on the back of the switch, and they need to make reliable contact with the door's limit switch flag -- top switch pointing down and to the left, bottom switch pointing up and to the left -- so pay attention to which flip or rotation of the switch makes sense.
|
||||
- Attach the servo to the servo mount (top area with triangles labeled Futaba) with the axle at the far end of the assembly (for maximum door travel distance)
|
||||
- Attach the ball pulleys to the servo and to the LBracket. Leave the LBracket pulley a bit loose so it spins freely.
|
||||
- Attach the ball-chain mounts to the thick far end of the limit switch flag, stacked onto one screw, one pointing each way
|
||||
- Attach the limit switch flag and chain mount to the bottom-right corner of the doggie door, just above the corner radius and snugly so the flag can't rotate. I drilled a hole into the door about 20mm from the bottom of the main body and about 10mm from the right edge, removed some of the bracing, and attached it with a bolt to accomplish this. Alternately, find a piece of wood or plastic the right thickness and cut to size to form a new door and attach the flag/mount to that (may require some reengineering of the flag). Hold the printed flag along the edge of the door to guide where to drill/etc.
|
||||
- The limit switch flag should firmly contact the bottom limit switch when the door is closed, so ultimately try to place it appropriately based on that.
|
||||
|
||||
## Programming
|
||||
|
||||
- Flash your Arduino Ethernet with doggie-door.ino
|
||||
- Look in your router's DHCP logs or use an IP scanner for the MAC: DE:AD:BE:EF:FA:DE
|
||||
- Go to the Arduino's IP in your browser: http://192.168.1.100
|
||||
- It should display a status page with buttons
|
||||
- Go to the Arduino's mDNS URL in your browser: http://doggie-door.local
|
||||
- If for some reason mDNS doesn't work, look for the IP Address corresponding to D0:99:1E:D0:99:1E in your router's DHCP table.
|
||||
- It may take 10-30 seconds to boot and register on the network.
|
||||
- It should display a status page with buttons that you can use to control the door.
|
||||
- Web requests that include `?u`, `?d`, `?s` or `?q` in the URL and `application/json` in the header will return JSON.
|
||||
|
||||
## Wiring
|
||||
|
||||
|
@ -61,10 +71,10 @@ Refer to the doggie-door.scad file or screenshots above for assembly.
|
|||
|
||||
```
|
||||
[ 7805 ]
|
||||
| | |
|
||||
+9v G +5v
|
||||
---------IN | OUT----- Servo +
|
||||
+---------- Servo -
|
||||
| | | Capacitor (+ to +, - to -)
|
||||
+9v G +5v /
|
||||
---------IN | OUT----o--- Servo +
|
||||
+---------o--- Servo -
|
||||
```
|
||||
|
||||
- Wire the servo's control pin to Arduino pin 9
|
||||
|
@ -80,30 +90,85 @@ Refer to the doggie-door.scad file or screenshots above for assembly.
|
|||
## Installation
|
||||
|
||||
- Wire the 9v power and an ethernet cable to the door's location.
|
||||
- Insert a thin sturdy piece of material in the door's latch so it never fully latches.
|
||||
- Unscrew the right-hand side screws of the doggie door and use them to install the doggie door control assembly. You might need to install the door before installing the control assembly to avoid interference.
|
||||
- Ensure that the chain is tight: slack will cause the pulleys to skip and fail to move the door.
|
||||
- Insert a thin sturdy piece of material in between the door's latch and the door body so it never fully latches. I used a found street sweeper bristle (a thin strip of hardened steel often found in gutters) for this, but anything could work, including simply cutting out the latch.
|
||||
- Reinsert the doggie door with limit switch into its rightful place in the door frame.
|
||||
- Unscrew the right-hand side screws of the doggie door and use them to install the doggie door control assembly.
|
||||
- Connect one end of the ball chain to the door, and loop it gently around the pulleys without attaching the other end.
|
||||
- Power everything on and run a final test.
|
||||
- Measure and cut (if neede) the chain to the proper length, and attach the other end. Ensure that the chain is tight: slack will cause the pulleys to skip, fail to move the door, and eventually strip out the pulley plastic.
|
||||
|
||||
## Usage
|
||||
|
||||
- Either use the buttons on the Arduino's webpage
|
||||
OR
|
||||
- Create a RESTful Command entry in HomeAssistant or similar:
|
||||
- Create RESTful entries in HomeAssistant or similar:
|
||||
|
||||
```configuration.yaml
|
||||
|
||||
rest:
|
||||
- scan_interval: 30
|
||||
resource: "http://doggie-door.local/"
|
||||
params:
|
||||
q: "q"
|
||||
headers:
|
||||
Accept: "application/json"
|
||||
binary_sensor:
|
||||
- name: "Doggie Door Maxup"
|
||||
value_template: "{{ value_json['maxup'] }}"
|
||||
- name: "Doggie Door Maxdown"
|
||||
value_template: "{{ value_json['maxdown'] }}"
|
||||
|
||||
rest_command:
|
||||
doggie-door-up:
|
||||
url: http://192.168.1.100/?u
|
||||
doggie-door-down:
|
||||
url: http://192.168.1.100/?d
|
||||
doggie-door-status:
|
||||
url: http://192.168.1.100/??
|
||||
doggie_door_up:
|
||||
url: "http://doggie-door.local/?u"
|
||||
doggie_door_down:
|
||||
url: "http://doggie-door.local/?d"
|
||||
doggie_door_stop:
|
||||
url: "http://doggie-door.local/?s"
|
||||
```
|
||||
- Restart HomeAssistant and create buttons or automations to the appropriate services and entities:
|
||||
|
||||
```
|
||||
square: false
|
||||
type: grid
|
||||
cards:
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: rest_command.doggie_door_up
|
||||
target: {}
|
||||
icon: mdi:window-shutter-open
|
||||
name: Open
|
||||
state_color: true
|
||||
entity: binary_sensor.doggie_door_maxup
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: rest_command.doggie_door_down
|
||||
target: {}
|
||||
icon: mdi:window-shutter
|
||||
name: Close
|
||||
state_color: true
|
||||
entity: binary_sensor.doggie_door_maxdown
|
||||
- show_name: true
|
||||
show_icon: true
|
||||
type: button
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: rest_command.doggie_door_stop
|
||||
target: {}
|
||||
icon: mdi:alert-octagon
|
||||
name: Stop
|
||||
columns: 3
|
||||
title: Doggie Door
|
||||
```
|
||||
|
||||
- Restart HomeAssistant and create buttons or automations to the `rest.doggie-door-*` services.
|
||||
- Note that depending on installation specifics, the "up" (maxup) status may not be very reliable. It's easy for the door to shift downwards slightly after opening.
|
||||
|
||||
## TODO
|
||||
|
||||
- Screw holes for the chain mount
|
||||
- Safety wall, mount holes and cable management for Arduino
|
||||
- Drill out the centers of the pulleys for rotation and attachment
|
||||
- Mount holes and cable management for Arduino
|
322
doggie-door-esp32/doggie-door-esp32.ino
Normal file
322
doggie-door-esp32/doggie-door-esp32.ino
Normal file
|
@ -0,0 +1,322 @@
|
|||
/*
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
305
doggie-door.ino
Normal file
305
doggie-door.ino
Normal file
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -168,7 +168,7 @@ difference(){
|
|||
bottomPanel();
|
||||
}
|
||||
for(i=[0: len(splitAt)-1]){
|
||||
translate([doorGutter+19.5,splitAt[i]-60,20]) scale([0.5,0.5,1]) partition_cut_mask(gap=0, cutpath="dovetail", $slop=0.75);
|
||||
translate([doorGutter+19.5,splitAt[i]-60,20]) scale([0.5,0.5,1]) partition_cut_mask(gap=0, cutpath="dovetail", $slop=0.4);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
BIN
doggie-door.stl
BIN
doggie-door.stl
Binary file not shown.
63
limit-switch-flag-and-chain-mount.scad
Normal file
63
limit-switch-flag-and-chain-mount.scad
Normal file
|
@ -0,0 +1,63 @@
|
|||
showAssembled=1;
|
||||
|
||||
$fn=20;
|
||||
|
||||
flagHeight=20;
|
||||
flagWidth=29;
|
||||
flagDepth=10;
|
||||
flagThickness=1.5;
|
||||
baseThickness=4;
|
||||
chainHolderDiameter=4;
|
||||
|
||||
gutterHeight=5;
|
||||
gutterWidth=4;
|
||||
|
||||
// slice it for printing
|
||||
if (showAssembled) {
|
||||
wholeModel();
|
||||
} else {
|
||||
translate([0,0,baseThickness]) rotate([180,0,180])
|
||||
difference(){
|
||||
wholeModel();
|
||||
translate([-50,-50,baseThickness]) cube(100);
|
||||
}
|
||||
|
||||
translate([-20,0,-baseThickness]) difference(){
|
||||
wholeModel();
|
||||
translate([-50,-50,-100+baseThickness]) cube(100);
|
||||
}
|
||||
}
|
||||
|
||||
module wholeModel(){
|
||||
difference() {
|
||||
union() {
|
||||
translate([0,-gutterWidth/2,gutterHeight/2-(gutterHeight-1)]){
|
||||
cube([flagDepth,flagWidth-gutterWidth,gutterHeight-2], center=true);
|
||||
}
|
||||
|
||||
translate([0,-gutterWidth,(flagHeight-gutterHeight)/2]){
|
||||
cube([flagDepth,flagWidth,flagHeight-gutterHeight], center=true);
|
||||
}
|
||||
|
||||
translate([0,-(flagWidth+20)/2,baseThickness/2]){
|
||||
difference(){
|
||||
cube([flagDepth,20,baseThickness], center=true);
|
||||
translate([0,0,0])
|
||||
cylinder(h=baseThickness+1,r=2.5,center=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// thin flag
|
||||
/*translate([-flagThickness,flagWidth*.25,(flagHeight-gutterHeight)/2-1]){
|
||||
cube([flagDepth,flagWidth,flagHeight], center=true);
|
||||
}*/
|
||||
// screw hole
|
||||
translate([0,-flagWidth*.45,flagHeight/2]) rotate([0,0,0]) cylinder(h=flagHeight+1,d=2.3, center=true);
|
||||
}
|
||||
}
|
||||
|
||||
module chainHolder() {
|
||||
translate([-flagDepth/3,-flagWidth/2+2,0]) rotate([0,90,0]) cylinder(h=flagDepth/2+1,d=chainHolderDiameter, center=true);
|
||||
translate([flagDepth/3,-flagWidth/2+2,0]) rotate([0,90,0]) cylinder(h=flagDepth/2+1,d=chainHolderDiameter, center=true);
|
||||
translate([0,-flagWidth/2+2,0]) rotate([0,90,0]) cylinder(h=flagDepth,d=1, center=true);
|
||||
}
|
BIN
limit-switch-flag-and-chain-mount.stl
Normal file
BIN
limit-switch-flag-and-chain-mount.stl
Normal file
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
import("parametric_ball_pulley-improved-with-flares.stl");
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user