Door 2 - Fishy business

If you haven’t yet, read my writeup on the first iteration of the door opener for important context.

After my first door authenticator was indicted then stolen, I realized that it had two problems.

  1. The HID R40 used to scan badges was plainly visible, causing issues with residential services.
  2. The outside half of the system was not physically tethered to the inside, making it very easy to walk away with.

Luckily for me, I stumbled across the rubber shell of a Big Mouth Billy Bass soon after, enabling a solution to both problems.

New Parts

WPI CollabLab

  • PC Power supply
  • Wire nuts
  • Limit switch
  • 2 conductor shielded wire

Amazon

eBay

Exterior hardware

In case you didn’t notice from the video, a R40 badge reader is jammed inside the rubber fish shell. The system also has access to two speakers and a servo in the tail.

fish electrical diagram

Sketch of the circuitry, button not pictured. I later switched the entire unit to 5v after burning out a buck converter when running the speakers on high volume. I also removed a servo after realizing that fish have very little room inside after adding a R40.

fish guts

Interior of the fish baseplate. The top screw terminal is 5v power, the bottom one is for the button (normally open).

Interior hardware

After installing the speaker and amplifier, I began to have issues with the R40 sending bogus data when playing music over the speakers. I narrowed down the issue to the R40 browning out when the system voltage drops too far below five volts. The solution to this problem is to run the R40 on the 12v circuit instead of the 5v one, but to avoid running an additional wire under the door I instead installed a beefier power supply.

winch

Updated winch subsystem. The CTRE VRM and 12v 5a power supply are no longer in use, but they remained on the door as they became load-bearing.

As you can see in the picture, I am now running the system off of a PC “Peripheral” port, typically used to power SATA hard drives.

ATX power supplies are wildly useful for hobby electronics projects, they supply all the voltages you typically need with plenty of current. Many are color coded, so you can just strip the wires that match what you need.

color use
black ground
green on (pull down)
orange 3.3v
red 5v
yellow 12v

Badge scan

Entry

When a user scans their badge their badge number is compared against a hardcoded list of hashes to find their name, and if that fails badges sent from the network are checked. If the user is allowed to enter, a command is sent to the winch as in the previous iteration of the system.

impl Authorized for Name {
    fn authorized(&self) -> bool {
        match self {
            Name::Andy => true,
            Name::Evan => true,
            Name::Michael => true,
            Name::Felix => true,
            Name::Phil => true,
            _ => false,
        }
    }
}

The current list of users allowed to open the door.

Greeting

In addition to opening the door, the fish also plays a sound byte related to who scanned their badge. We recorded and found audio clips to play for badges that are granted or denied entry. A subset of each are marked as “suffixable”, meaning that they can be followed up by calling the name of the person in question. “Welcome home” is suffixable, but “Howdy partner” is not.

let call: bool;
if accepted {
    let index = rng.next_u32() % ACCEPTED_SUFFIXABLE.len() as u32;
    play_song("a", Some((index+1) as u8), &mut uart).await;
    call = ACCEPTED_SUFFIXABLE[index as usize];
} else {
    let index = rng.next_u32() % DENIED_SUFFIXABLE.len() as u32;
    play_song("d", Some((index+1) as u8), &mut uart).await;
    call = DENIED_SUFFIXABLE[index as usize];
}

A portion of the introduction routine. A random accepted or denied sound is played, and the flag to call the user’s name is set based on the sound.

Wagging

The final component of the fish is the wiggle manager. Whenever music plays, the tail will randomly wiggle between five set positions related to the length of the sound byte

let positions = [6200,4800,6248,4700, 5500];

let mut rng = RoscRng;
for _ in 0..wags {

    let idx = rng.next_u32();
    c.compare_a = positions[idx as usize % positions.len()];
    pwm.set_config(&c);

    Timer::after_millis(idx as u64 % 600).await;
}

The subset of the wiggle manager in charge of wiggle control

Button press

When the “Try Me” button is pushed, sounds are instead chosen from among a list of sentence fragments from WPI’s traditions page.

#[embassy_executor::task]
async fn button_manager(pin: PIN_4) -> ! {
    let mut button = Input::new(pin, embassy_rp::gpio::Pull::Up);
    loop {
        button.wait_for_rising_edge().await;
        Timer::after_millis(8).await;
        if button.is_low() {
            continue;
        }

        COMMANDS.send(music::MusicCommand::Button()).await;
        Timer::after_millis(800).await;
    }
}

The button manager, which sends requests to the music manager.

An 800 ms debounce is used and the music command channel is limited to three messages to dampen the effect of rapidly pressing the button.

Remote operation

In addition to opening the door with a badge, the system is also operable with a web page or iOS shortcut.

When a request is sent from the iOS shortcut, it first hits the nginx reverse proxy on this website where it is password checked.

location /john/ {

    proxy_pass http://[SERVER]/;
    auth_basic "Restricted Content";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

nginx configuration to allow access to the control panel with a password.

Once the request is authenticated, it is sent to my desktop PC which has two internet adapters. The PC forwards the request through a non-internet-connected access point that the winch controller connects to.

stateDiagram-v2
    direction LR
    u: User
    s: ank.dev
    p: Desktop PC
    r: Airgapped router
    w: winch
    f: fish
    u --> s
    s --> p : Authentication
    p --> r
    r --> p
    r --> w
    f --> r
    r --> f

The PC also has the role of periodically pinging the mobile parts of the system to make sure they still exist.

if last.fish_reachable != status.fish_reachable {
    warn!("fish connected: {}", status.fish_reachable);
}
if last.door_reachable != status.door_reachable {
    warn!("door connected: {}", status.door_reachable);
}
if last.router_reachable != status.router_reachable {
    warn!("router connected: {}", status.router_reachable);
}

Any component disconnection is logged to help prevent fish disappearance

Front panel

In addition to opening the door, a user logged into the fish online has the ability to correlate badges with names, play any sound in the fish’s vocabulary and change the volume of the fish. Since frontend design is not my area of focus, I stuck with default form styling for a professional look.