A Weekend Full Of Hacking, and Projects Realized

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.

Photo of co2 badge "hello. my name is 700 ppmCO2"
Overview of sensor and badge

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!

One Comment

Leave a Reply

Your email address will not be published.