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.
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.
In order to make this work, I started to put together a parts list:
Once I grabbed all the parts, I set up the printer to get the parts rolling while I did the rest.
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.
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:
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.
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:
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:
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:
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!