Building a friendship lamp when you're not sure you have any friends

It’s been a hot minute, hasn’t it? And here you thought I didn’t love blogging anymore. I didn’t like blogging any less, the fact is I just didn’t have much to talk about because <gestures at everything>.

But if 2020 and 2021 have taught me anything, it’s the importance of human contact and just knowing someone is around. And as often the case when I get bored, my mind starts thinking up new ways to entertain itself. And that’s how this dumb project was born.

The idea

I’d like to think of myself as a maker, so often times I’ll find myself browsing reddit, hardware sites, and other forums looking at all the cool stuff people are building. I stumbled across a bi-directional friendship lamp idea, where you can have two small, LED-powered lamps that when one person touches a sensor, the remote lamp lights up.

Which is extremely wholesome! Except, when I read this it became woefully apparent to me that this solution requires actual friends. That’s a pretty big barrier to entry for me.

It did get me thinking, though: what if I could take this idea and change it up a bit to where people could send me messages WITHOUT the need for them to have a lamp (and thereby give them plausible deniability of being, in fact, my friend). How would that work? In absence of a lamp, would a web application work? And what if we could let people pick a color in lieu of an actual message? You could send a whole mood!

And just like that, my motivation was restored. Time to get to work.

The hardware requirements

In order to make this work, I started to put together a parts list:

  • A small, low-power controller that supports network connectivity. I looked a few different options, and in order to keep power use and costs low, I went with an ESP8266 board, which has an on-board WiFi. Should make for a neater appearance and less wires. The network connectivity is a must here, since we want to be able to receive virtual color hugs from strangers.
  • Programmable LED lights: we’re spoiled for choice here, as there’s a ton of choices. The main challenge here is keep the footprint small, so while pre-built things like LED strips would work, but might be tough to fit into a small lamp. I opted for something that, in hindsight, probably made it a little harder than it needed to be: WS2812B Addressable 5050 Smart RGB LED Pixel Lights. You can fit so many of these bad boys into a small area, but writing them together can be a huge pain. More on that in a minute.
  • While I thought about making the lamp automatically poll for new virtual color hugs, I decided to add a switch to get messages on demand. I kept the touch sensor from the original creators’s idea since I like the idea of a non-tactile button. I used a TTP223B Capacitive Digital Touch Sensor for analog input, so I can capture my feeners on and off the button.
  • The lamp body itself is 3D printed. I used natural filament for the “shade” and the top, and some basic gray for the base. The .stl files can be downloaded here (from instructables):
  • Some 22 AWG wire (I used a kit like this)
  • Hot glue
  • Solder
  • A deep desire for human interaction

Making it all work

Once I grabbed all the parts, I set up the printer to get the parts rolling while I did the rest.

This took 9 hours

Next, I started assembling the electronics. By far, this was the most time consuming part of the process, mostly because the individual LED pixel lights are just that: individual little circles. Which means I had to lay them out and solder the ends together, in 3 sets of 4. Each light has 6 terminals: 5V in and out, ground in and out, and the “data” points for receiving signals for setting the color hue and brightness.

This image stolen from the Amazon seller I used

The first step was to figure out a way to bridge the gaps in the lights. Instead of trying to cut wire, I took some old resistors I had laying around and cut the legs off of those to bridge the gaps. It was painstaking, but the benefit to doing this is that the pixels will lay pretty much flat on whatever surface I glue them to.

After many attempts to get it right, and some 2nd and 3rd degree burns later, the finished chain looked like this:

There’s still a blister on one of my fingers. Stupid soldering gun.

You can see that I have the pixels in place, as well as the microcontroller. The 5V and GND are on one end of the chain (red and black, respectively) and the white wire is going to D1 (digital one) on the controller. All in all, not a bad little job if I say so myself. The toughest part was managing the bends in the resistor wires to keep the curve tight enough to fit within the shade (note the pencil marks on the base).

Next, I had to wire up the touch sensor. Before I wired it up to my hard work, I took a second, spare board and made some jumper connections. Then I wrote a quick sketch to capture output. I experimented with interrupts in Arduino, which seemed like a good idea at first, but things got really weird really quick.

Once I was satisfied I had a good handle on that, I decided to patch in the 5V and GND wires for this, and wired up the signal pin to a free space on the controller. All that was left to do was a final test of everything, and the glue the box together.

The software requirements

Of course, now that everything is wired up, the real fun can begin: programming. To make this work, we’d need a few things defined up front:

  • The actual code for the lamp. Since this is Arduino, we’re going to write this part in C. At a high level, it should be able to detect that the touch sensor is activated, and then retrieve a message containing a color code.
  • As to where those messages are going to come from, I chose to use Azure App Services to build a simple web application. The application has two distinct parts:
    • The public-facing UI, written in Python, will accept user input which writes messages to a queue. The messages in the queue are simple RGB codes to be read by the lamp when the button is pressed.
    • A protected API endpoint that pops the first message off the queue and deletes it. This is the endpoint the lamp will use.
  • For the queues, I’m using Azure Storage Queues. They’re extremely simple to use: messages go in, messages go out.
  • For good measure, I’m also using Azure Cosmos DB for logging.

When it’s all said and done, here’s the general flow: a user picks a color on the front end app. The interaction is saved first in Cosmos DB, and the color code is saved in a queue. When I press the button on the lamp, it makes an HTTP request to the backend to retrieve the top message in the queue, which is then translated into RGB format and used to set the lights.

It looks something like this:

Code goes here

As for the code, well, that’s the interesting part. The front-end code is pretty straight forward: take a form, read the results, put the results in both places. The main pieces I want to highlight here are first, how we use Python to insert a new document into Cosmos:

cosmos = CosmosClient(url, {'masterKey': key})
cosmos_database = cosmos.get_database_client(database_id)
cosmos_container = cosmos_database.get_contain
cosmos_container.upsert_item(
    {
        'name': '{0}'.format(name),
        'color': '{0}'.format(color),
        'message': '{0}'.format(message),
        'timestamp': '{0}'.format(timestamp)
    }
)

Four lines to insert a new document? Not bad at all. And then there’s the code for pushing messages to the queue:

queue_client = QueueClient.from_connection_string(q_conn_str, q_name)
queue_client.send_message(color)

When we want to pull messages out of the queue, all we need is this:

response = queue_client.receive_message()
//do something with the response object here...
queue_client.delete_message(response)

The code for the cube looks like this, though, and is worth sharing more of. Here’s the raw code, I’ll explain a bit more below:

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
 #include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif

#define LED_PIN    5
#define LED_COUNT 12

const int SENSOR_PIN = 12;

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

//One downside: you need to have microcontroller software to program your SSID and password via code. Didn't figure out a better way to do this...
char ssid[] = "";
char pass[] = "";
int status = WL_IDLE_STATUS;

unsigned long previousMillis = 0;
const long interval = 600000;

int lastState = LOW;
int currentState;

int    HTTP_PORT   = 80;
String HTTP_METHOD = "GET";
char   HOST_NAME[] = "";
String API_KEY = "";
String PATH_NAME   = "";

WiFiClient client;

void setup() {
 
  #if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
    clock_prescale_set(clock_div_1);
  #endif
  strip.begin();
  strip.show();
  strip.setBrightness(150);
   
  status = WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    theaterChase(strip.Color(127, 127, 127), 40, 5); // White, half brightness
  }
  rainbow(5); 
  strip.clear();
  strip.show();
  previousMillis = millis();
}

void loop() {
  unsigned long currentMillis = millis();
  currentState = digitalRead(SENSOR_PIN);

  if(lastState == LOW && currentState == HIGH) {
    String payload = "rgb(255, 255, 255)";
    HTTPClient http;
    //GET request URL goes in the quotes
    http.begin("");
    int httpCode = http.GET();
    if (httpCode > 0) { 
      payload = http.getString();
    }
    http.end();

    if (payload != "none") {

      String data = payload;
      data.replace("rgb(","");
      data.replace(" ","");
      data.replace(")","");   
  
      String rval = getValue(data, ',', 0);
      String gval = getValue(data, ',', 1);
      String bval = getValue(data, ',', 2);
  
      colorWipe(strip.Color(rval.toInt(), gval.toInt(), bval.toInt()), 70);
    }
    delay(2000); 
  }

  lastState = currentState;
}

String getValue(String data, char separator, int index)
{
    int found = 0;
    int strIndex[] = { 0, -1 };
    int maxIndex = data.length() - 1;

    for (int i = 0; i <= maxIndex && found <= index; i++) {
        if (data.charAt(i) == separator || i == maxIndex) {
            found++;
            strIndex[0] = strIndex[1] + 1;
            strIndex[1] = (i == maxIndex) ? i+1 : i;
        }
    }
    return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}

void colorWipe(uint32_t color, int wait) {
  for(int i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, color);
    strip.show();
    delay(wait);
  }
}

void theaterChase(uint32_t color, int wait, int repeat) {
  for(int a=0; a<repeat; a++) {
    for(int b=0; b<3; b++) {
      strip.clear();
      for(int c=b; c<strip.numPixels(); c += 3) {
        strip.setPixelColor(c, color);
      }
      strip.show();
      delay(wait);
    }
  }
}

void rainbow(int wait) {
  for(long firstPixelHue = 0; firstPixelHue < 5*65536; firstPixelHue += 256) {
    for(int i=0; i<strip.numPixels(); i++) {
      int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
      strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
    }
    strip.show();
    delay(wait);
  }
}

To help break this code down, you can basically think of this code in three parts. First, the top of the code all the way through the “setup” function is all about initializing the parts and pieces we need. From including required libraries, connecting to WiFi, and setting some static variables, that all happens up there. The main section (loop()) is responsible for all the actual actions. It calls the endpoint to retrieve the next queue message, and translates the results to RGB values, then sets the colors. Finally, the rest of the functions are for general “effects” that I took from other NeoPixel examples. I use those during the setup process to keep track of when the device finishes initializing, and connects to WiFi. It looks like this on boot:

If I could make it produce a sound I would

I just want you to love me

So it all looks good right? Well, now for the fun part: actually sending me messages! To do that, head on over to the friend box public web site:

https://friendbox.azurewebsites.net/

When you get there, enter some details; just tell me your name (or make one up), and pick a color. Just note, if you send black, it turns the lamp off (which is a feature, not a bug).

In building this friendship cube, I thought it’d be fun to at least create and build something cloud enabled, but then I realized it could be fun to see just how much reach this has, and who sends me messages. When you send a color, that’s all I see; the messages and names just go in the logs that I probably won’t look at till a later date, just to see who visited.

Anyway.

Hope you enjoyed this little diversion. Feel free to take what I have and build on it. For future states, I’ll probably add a little notification flash if there’s messages in the queue waiting for me to pick up, or maybe change up the form a bit. As for now though, I’m pretty happy with how it turned out. Thanks for reading!