Supercon 2019 just wrapped up last night, and this one was even better than last year. It seems to be one of the highest-value conferences on the calendar for good conversations, and it’s almost shamefully worth leaving the talks for week-after youtubing to maximize time spend talking and hacking. Shamefully, because they’re always just great.
Luckily, I feel like I struck that balance this year. Besides a lot of valuable business cards, I also managed to check off a couple projects on my list of “decently fast stuff I really want to try but can’t find time for”. Also lucky, Adafruit had great conference presence in the form of hardware, actual humans, and killer rapid-prototyping software with hyper-extensive documentation (mostly but not exclusively, I mean CircuitPython).
Badge Projects
CO2 Dosimeter/Logger
This one’s been on my list forever. If the danger of having a hammer is that everything looks like a nail, the danger of having a ruler is that suddenly there aren’t enough notebooks to hold all the data. As soon as I found out about compact, cheap NDIR CO2 sensors, I knew I wanted to carry one around to make pretty pictures and prove to people that I am, indeed, breathing. Seeed Studio makes a grove-breakout of the Sensirion SCD30, which is a great NDIR sensor good to 30ppm+3% absolute accuracy when pressure-compensated. I ordered one pre-con and was delighted to find that I didn’t even have to solder a cable – the Edge Badge comes with a grove-compatible i2c connector right on it. I was further delighted to find that a MicroPython library for the SCD30 wasn’t too difficult to port to CircuitPython.
Dwell a bit to get the picture just right, and CO2 spikes way up. Eventually I’ll have to add a “meeting too sleepy” warning with bright flashing red LEDs across the bottom.
LightCommands Google Home Activator
The other badge hack came about after reading the Light Commands hack on like Monday. Long story short: MEMS microphones (all microphones in everything, now) respond to light as well as sound. I basically skimmed far enough to read “5mW” and “650nm” and though “yep gotta try that.”
I set up a basic and super-fast test case with an Analog Discovery and a 1-NPN current driver just to see if I could get anything, and found out the diode I had on hand had some kind of driver built-in that wouldn’t modulate fast enough. I ordered some cheap (read: bare) diodes and MEMS mics on Amazon for further testing, and lo and behold, it works.
We did a whole bunch more testing on the PyBadge, using the analog output MEMS mic, other badges hacked into oscilloscopes and spectrum analyzers, and so on and so forth, but it really wasn’t necessary at all. Implementing this takes literally two components, and one is just a connector for convenience.
Plug a laser diode into the speaker port, and it basically just works. Audio quality of the original recording you use does matter, since the laser distorts that quite a but, but listening to the mems-mic-demodulated result yourself, it sounds totally understandable and google agrees:
Here is the entire CircuitPython code running on my badge right now (with scd30.py from the github repo above sitting next to it):
from adafruit_pybadger import PyBadger import time #from machine import I2C, Pin import board #import busio import displayio import terminalio from adafruit_display_text import label from scd30 import SCD30 import audioio def plot_data(data): # Set up plotting window bottom_margin = 15 #pixels on the bottom edge top_margin = 15 #pixels on the top edge y_height = display.height-bottom_margin-top_margin data_max = max(data) data_min = min(data) if ((data_max-data_min)==0): data_max = data_max+1 # Create a bitmap with two colors bitmap = displayio.Bitmap(display.width, display.height, 2) # Create a two color palette palette = displayio.Palette(2) palette[0] = 0x000000 palette[1] = 0xffffff # Create a TileGrid using the Bitmap and Palette tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette) # Create a Group group = displayio.Group() # Add the TileGrid to the Group group.append(tile_grid) # Add the Group to the Display display.show(group) for i in range(0, len(data)): y = y_height-round((data[i]-data_min)/(data_max-data_min) * y_height) #print("max: {} min: {} y: {}".format(data_max, data_min, y)) bitmap[i,y+top_margin] = 1 # Set text, font, and color font = terminalio.FONT # Create the tet label current_val_text = label.Label(font, text="{} ppmCO2".format(round(data[-1])), color=0x00FF00) # Set the location (_, _, width, height) = current_val_text.bounding_box current_val_text.x = display.width - width current_val_text.y = height//2 min_val_text = label.Label(font, text="{} ppmCO2".format(round(data_min)), color=0xFFFFFF) (_, _, width, height) = min_val_text.bounding_box min_val_text.x = 0 min_val_text.y = display.height-(height//2) max_val_text = label.Label(font, text="{} ppmCO2".format(round(data_max)), color=0xFFFFFF) (_, _, width, height) = max_val_text.bounding_box max_val_text.x = 0 max_val_text.y = height//2 # Show it #display.show(text_area) group.append(current_val_text) group.append(min_val_text) group.append(max_val_text) pybadger = PyBadger() display = pybadger.display pybadger.auto_dim_display(delay=10, movement_threshold=20) i2c = board.I2C() scd30 = SCD30(i2c, 0x61) state = 3 state_changed = True # Accumulate data from sensor data = [] while True: if pybadger.button.start: state = 0 state_changed = True elif pybadger.button.a: state = 1 state_changed = True elif pybadger.button.b: state = 2 state_changed = True elif pybadger.button.down: state = 3 state_changed = True if (state==0 and state_changed): # Wait for sensor data to be ready to read (by default every 2 seconds) try: while scd30.get_status_ready() != 1: time.sleep(0.200) except SCD30.CRCException: print("CRC Exception in get_status_ready") print("getting measurement") #print(scd30.get_firmware_version()) try: co2, temp, relh = scd30.read_measurement() except SCD30.CRCException: print("CRCException") print("({}, {}, {})".format(co2, temp, relh)) pybadger.show_badge(name_string="{:.0f} ppmCO2".format(co2), hello_scale=2, my_name_is_scale=2, name_scale=2) elif (state==1 and state_changed): state_changed = False pybadger.show_business_card(image_name="supercon.bmp", name_string="Changeme in code.py", name_scale=1, email_string_one="[email protected]", email_string_two="https://alexwhittemore.com/") elif (state==2 and state_changed): state_changed = False pybadger.show_qr_code(data="https://alexwhittemore.com/") pybadger.play_file("square_root_2_20k.wav") elif (state==3 and state_changed): # pybadger.display.height is 128 # pybadger.display.width is 160 #state_changed = False try: while scd30.get_status_ready() != 1: time.sleep(0.200) except SCD30.CRCException: print("CRC Exception in get_status_ready") try: co2, temp, relh = scd30.read_measurement() except SCD30.CRCException: print("CRCException") if len(data)==display.width: data.pop(0) try: data.append(round(co2)) plot_data(data) except ValueError: pass
Lots of state machine stuff to do different things in different modes, but the “play sound” bit is like one line. Record your own command, save as .wav, and try it!
Rapid-Aged Whiskey
Thought Emporium put out a video the other day of rapid-aging liquid in wood chips using sonication. I’ve heard of colleagues doing similar work, and I’ve toured Lost Spirits Distillery, and I need an ultrasonic cleaner for other things anyway, so I had to try it and bring some samples to the con.
Verdict: VERY WORTH WHILE experiment, with much more to try.
The input alcohol I used (white moonshine) was not great – lots of fusel alcohols (hangover fuel), a nose of hand sanitizer, and only the most mild corn sweetness on the finish. The first batch was done at 50C for 30m, and definitely pulled out some of the wood esters for a good aged flavor. The second try was 50C for 1h, and WOW is the aged flavor profile THERE. Next step is to introduce some charcoal to the process to adsorb some of those nosy nasties, and then I think we’ll be in business to make custom aged flavors. I’m excited to try introducing a little smoke, personally.
Next year can’t come soon enough!
[…] Alex’s blog post with some nice pics and videos is here. […]