This season is one of gift-giving.

Somehow, I found myself participating in three fairly high-effort gift exchanges at the same time: a crafting Secret Santa, a words of affirmation Secret Santa, and a creative tech Secret Santa.

As someone who’s chronically overcommitted, I decided to use the creative tech Secret Santa as a prompt for my final project in Connections Lab.

Out of all the many strangers and acquaintances in the hat, the person I was assigned was my good friend Connie.

IMG_7297.JPG

The Hangout Keychain

There are two things Connie loves doing with her friends: hanging out, and locking in. This is well-documented through her many messages and Partiful invites. I wanted to capture this chase for both in a physical object: something she could carry with her that expressed her current state in a simple, legible way… like a keychain.

Exhibit A

Exhibit A

Exhibit B

Exhibit B

Exhibit C

Exhibit C

I wanted to make her a physical button connected to the internet. Where pressing the button switches Connie’s status between “hanging out” and “locked in,” which is then reflected live on a website that anyone can view.


The Hardware

The technical challenge was straightforward in theory but nontrivial in practice. I had an ESP32 microcontroller connected to my laptop via USB. I wrote code so that when the built-in button is pressed, it outputs a 1; when pressed again, it outputs a 0.

if (reading == LOW && lastStable == HIGH) {
   state = !state;
   Serial.println(state ? "1" : "0");
}

The Connections

But The problem is that the website lives online, and I needed to make an HTTPS post directly from the development board. This was the system we needed to build:

image.png

The ESP32 sends its button state directly to a cloud-hosted Express server on Replit via HTTPS. The server then broadcasts the updated state to every connected browser using WebSockets.

Using the WiFi library, I could translate between physical hardware and the internet. By adding my Wifi information, I can connect the board directly to the internet, and send its button state as an HTTP POST request, and sends it to the server.

I wasn’t the most familiar with implementing the Arduino → web connection, particularly best practices in detecting a connection from a port we learned in the summer. ChatGPT assisted with writing functions to cover errors, a heartbeat message, and edge cases to help me catch disconnections, from both the hardware component, and the browser.

void sendState(bool state) {
  if (WiFi.status() != WL_CONNECTED) return;

  HTTPClient http;
  http.begin(SERVER_URL);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("X-API-Key", API_KEY);

  String body = "{\\"state\\": " + String(state ? 1 : 0) + "}";
  http.POST(body);
  http.end();
}

Once the server receives a state update, it needs to propagate that change instantly. I used WebSockets to maintain persistent connections between the server and all connected clients. A single button press triggers the server to broadcast the new state and timestamp to every open browser immediately.

const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ server });

function broadcastState() {
  const message = JSON.stringify({ 
    state: currentState, 
    timestamp: getTimestamp(),
    lastButtonPress: lastButtonPress,
  });
  wss.clients.forEach((client) => {
    if (client.readyState === 1) {
      client.send(message);
    }
  });
}

The Browser

Using the broadcasted state, I rendered a floating text GIF corresponding to whether Connie was hanging out vs. locking in, along with a timestamp (which Replit Agent helped me write code to extract from the data):

image.png

image.png

But thinking through the user experience, I realized that no one is actually going to keep a dedicated webpage open and visible at all times (unless they are stalking her), so I started treating the browser tab itself as part of the design. Just like any other DOM element, I can also change the tab title and favicon the same way.