Skip to content

Instantly share code, notes, and snippets.

@dltj
Last active April 25, 2026 13:00
Show Gist options
  • Select an option

  • Save dltj/644b91c959ffce4e8ea32318355767e4 to your computer and use it in GitHub Desktop.

Select an option

Save dltj/644b91c959ffce4e8ea32318355767e4 to your computer and use it in GitHub Desktop.
Arduino program for Bluebird Box Door
/*
Bird box gate (servo) controller - Arduino Nano
RF receiver is in MOMENTARY mode and drives a relay contact:
Relay COM -> GND
Relay NO -> D2
We use D2 with INPUT_PULLUP:
- D2 reads HIGH normally
- D2 reads LOW while the relay is energized (button held)
Servo:
- Signal on D9
- Servo powered from a solid external 5V rail
Behavior:
- On startup: command servo to 550 µs
- Each button press toggles servo between 550 µs and 2360 µs
*/
#include <Servo.h>
const int SERVO_PIN = 9;
const int RF_PIN = 2;
// Pulse widths in microseconds
const int PULSE_CLOSED = 550;
const int PULSE_OPEN = 2360;
// Smooth motion tuning
const int STEP_US = 5; // smaller = smoother, slower; larger = faster
const int STEP_DELAY_MS = 10; // delay per step
int currentPulse = PULSE_CLOSED; // track current command pulse width
// Debounce / lockout
const unsigned long DEBOUNCE_MS = 30; // relay contacts are pretty clean, but keep small debounce
const unsigned long PRESS_LOCKOUT_MS = 250; // prevents multiple toggles if button is held
Servo gateServo;
bool isOpen = false;
unsigned long lastToggleTime = 0;
void moveServoSmoothTo(int targetPulse) {
// Clamp to a reasonable range, just to be safe
// Adjust if you truly want to allow wider pulses.
if (targetPulse < 500) targetPulse = 500;
if (targetPulse > 2500) targetPulse = 2500;
int startPulse = currentPulse;
int dir = (targetPulse > startPulse) ? +1 : -1;
for (int p = startPulse; p != targetPulse; p += dir * STEP_US) {
gateServo.writeMicroseconds(p);
currentPulse = p;
delay(STEP_DELAY_MS);
}
// Ensure final value is commanded exactly
gateServo.writeMicroseconds(targetPulse);
currentPulse = targetPulse;
}
void goClosed() {
moveServoSmoothTo(PULSE_CLOSED);
isOpen = false;
}
void goOpen() {
moveServoSmoothTo(PULSE_OPEN);
isOpen = true;
}
void toggleGate() {
if (isOpen) goClosed();
else goOpen();
}
void setup() {
pinMode(RF_PIN, INPUT_PULLUP);
gateServo.attach(SERVO_PIN);
// Startup position
goClosed();
delay(500);
}
void loop() {
// Momentary relay closes to GND when pressed
static bool lastStable = HIGH;
static bool lastReading = HIGH;
static unsigned long lastDebounceTime = 0;
bool reading = digitalRead(RF_PIN);
// Debounce: detect stable change
if (reading != lastReading) {
lastDebounceTime = millis();
lastReading = reading;
}
if (millis() - lastDebounceTime > DEBOUNCE_MS) {
if (reading != lastStable) {
lastStable = reading;
// Treat HIGH->LOW transition as a button press event
if (lastStable == LOW) {
if (millis() - lastToggleTime > PRESS_LOCKOUT_MS) {
lastToggleTime = millis();
toggleGate();
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment