Introduction: DIY Video-conferencing MacroBoard

About: Follow Sven via @hansamann on Twitter. Listen to KidsLab Podcast for cool educational ideas - of if you're a geek and simply want to have fun. Sven works at SAP in Munich, where he applies all kind of new tech…

This project is about a DIY MacroBoard / MacroPad that I built to have fun with all these video conferences via Zoom, Slack or Teams. To keep things simple and because I did not want to wait for a shipment from China, I decided to use a Adafruit Circuit Playground Express, which you should be able to get in a few days almost everywhere.

You don't necessarily need to 3D print and lasercut for this project - just be creative. For example, the pins of the Circuit Playground Express are capacitive, they react to touches. You could easily extend them with some wire and aluminium foil, add some printed icon for sound/video etc on top and stick these flat, capacitive buttons to your table insteadd of lasercutting and 3d printing. Let me know what you make!

Let me also give credit to the blog post by Andy that inspired me to create a macroboard. The code I used is very similar, I just made a few changes here and there and my main goal was to make this useful for video-conferences. My main video-conferencing tool these days is Zoom, so support is best here. For Slack and Teams support is just minimal, but of course I'd love to see how you extend my project!

Supplies

  • Adafruit Circuit Playground Express or similar CircuitPython board
  • Arcade Buttons like these
  • Some wire, soldering iron solder...

Step 1: Lasercutting and 3D Printing

I started by creating the lasercut front plate of the MacroBoard. The holes snuggly fit the arcade push buttons, but they are also not too close together as the 1.5mm Kraftplex wood would break. If you have access to a lasercutter such as Mr Beam, I highly recommend Kraftplex for the material. It is strong and holds the buttons well while also being a bit flexible and easy to spray/color later on.

The SVG files include the front and bottom plate to lasercut. I decided to ground the front plate and sprayed some yellow acrylic color on it, but that's totally up to you of course.

To hold the front and bottom plate in place, I designed some 3d printable stand via OpenSCAD. The stand_side.stl file needs to be mirrored in your slicer such as PrusaSlicer to print the second part.

All the parts are simply glued together which results in a very robust board.

To add some icons to my buttons, I first vectorized some nice icons and then made sure they can be lasercut well - e.g. no completely cutout elements as they don't hold in place. I then lasercut the icons with circles around them and positioned them onto the buttons. Via a somple marker, I then added the icons to the buttons - not always worked perfectly, but way better than me scribbling...

Finally, add all the buttons to the board. Make sure the connectors align nicely on the back.

Step 2: Wiring

When it comes to wiring, first connect one pin of each button for a common ground wire. The input pins for the Circuit Playground Express will later be bulled high, so 1 / ON will be the normal state when the buttons are not pressed. The buttons go low when you press them, as they then connect to ground.

Next, connect the other pins of the buttons to the pins of the Circuit Playground Express. The pinout is here and you can also see that you have only 8 pins for general purpose in/out - this is also the reason why I limited the board to 8 buttons. Of course you can create a button matrix or use a board with more pins, but for this little DIY project that was all too complex and time consuming for me. So one button = one pin on the Circuit Playground Express. Easy.

Step 3: Beer and Software

Next, it is recommended to celebrate your physical creation and have a beer. If you are under 16, it might be more appropriate to get a low-sugar lemonade instead, please consult your parents.

You will first need to update the Circuit Playground Express to the latest version and then copy the adafruit_hid (Human Interfacer Device) library to the board. I suggest you familiarize youself a bit with the board, then continue with adding the lib and also the code below. All coding was done in the excellent mu:editor, which detects the board automatically.

Below is the full code and it can probably be optimized in many many ways - again I'd love to hear how you optimize it and what else you add.

The two A/B Button on the Playground Express itself are used to toggle between the mode - Zoom/Slack/Teams for now/. If you press a button in Slack mode and it has not been implemented, e.g. the codes Array/Dict has no KeyCodes for this button, then it will quickly flash the NeoPixel LEDs of the board. Otherwise it simply sends the KeyCodes. You need to make sure that controls in Zoom are always visible (search Zoom In-Meeting Controls), the shortcut to toggle this once should be CTRL-\ on Mac. Also, as we're sending simple keystrokes, make sure the Zoom/Slack/Teams window is active and focused.

Have fun!

import time
import board
from digitalio import DigitalInOut, Direction, Pull
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
import usb_hid
from adafruit_circuitplayground import cp

cp.pixels.brightness = 0.1

# configure device as keyboard
kbd = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(kbd)

modes = ["zoom", "slack", "teams"]
mode = 0  # zoom

desc = {
    "d3": "audio",
    "d2": "video",
    "d0": "share",
    "d1": "raise hand",
    "d6": "fullscreen",
    "d12": "gallery",
    "d10": "chat",
    "d9": "participants",
}

codes = [
    {
        "d3": [Keycode.COMMAND, Keycode.SHIFT, Keycode.A],
        "d2": [Keycode.COMMAND, Keycode.SHIFT, Keycode.V],
        "d0": [Keycode.COMMAND, Keycode.SHIFT, Keycode.S],
        "d1": [Keycode.OPTION, Keycode.Y],
        "d6": [Keycode.COMMAND, Keycode.SHIFT, Keycode.F],
        "d12": [Keycode.COMMAND, Keycode.SHIFT, Keycode.W],
        "d10": [Keycode.COMMAND, Keycode.SHIFT, Keycode.H],
        "d9": [Keycode.COMMAND, Keycode.U],
    },
    {"d3": [Keycode.M], "d2": [Keycode.V], "d9": [Keycode.A]},
    {
        "d3": [Keycode.COMMAND, Keycode.SHIFT, Keycode.M],
        "d0": [Keycode.COMMAND, Keycode.SHIFT, Keycode.SPACE],
        "d2": [Keycode.COMMAND, Keycode.SHIFT, Keycode.O],
        "d6": [Keycode.COMMAND, Keycode.SHIFT, Keycode.F],
    },
]

colors = [[0, 0, 255], [255, 0, 150], [100, 50, 255]]

# little function to open apps via spotlight
def open_app(app):
    kbd.send(Keycode.COMMAND, Keycode.SPACE)
    time.sleep(0.2)
    layout.write(app)
    time.sleep(0.2)
    kbd.send(Keycode.ENTER)


def send(btn):
    if codes[mode] and btn in codes[mode]:
        kbd.send(*codes[mode][btn])
        print(modes[mode], desc[btn])
    else:
        print("Not implemented!")
        blink()
    time.sleep(0.5)


def blink():
    for i in range(4):
        cp.pixels.fill(colors[mode])
        time.sleep(0.2)
        cp.pixels.fill([0, 0, 0])
        time.sleep(0.1)


d0 = DigitalInOut(board.D0)
d0.direction = Direction.INPUT
d0.pull = Pull.UP

d1 = DigitalInOut(board.D1)
d1.direction = Direction.INPUT
d1.pull = Pull.UP

d3 = DigitalInOut(board.D3)
d3.direction = Direction.INPUT
d3.pull = Pull.UP

d2 = DigitalInOut(board.D2)
d2.direction = Direction.INPUT
d2.pull = Pull.UP

d6 = DigitalInOut(board.D6)
d6.direction = Direction.INPUT
d6.pull = Pull.UP

d12 = DigitalInOut(board.D12)
d12.direction = Direction.INPUT
d12.pull = Pull.UP

d10 = DigitalInOut(board.D10)
d10.direction = Direction.INPUT
d10.pull = Pull.UP

d9 = DigitalInOut(board.D9)
d9.direction = Direction.INPUT
d9.pull = Pull.UP


# loop forever
while True:
    cp.pixels.fill(colors[mode])

    if cp.button_a:
        print("Button A pressed!")
        mode += 1
        if mode > 2:
            mode = 0
        print("mode is", modes[mode])
        time.sleep(0.5)

    if cp.button_b:
        print("Button B pressed!")
        mode -= 1
        if mode < 0:
            mode = 2
        print("mode is", modes[mode])
        time.sleep(0.5)

    if not d3.value:
        send("d3")

    if not d2.value:
        send("d2")

    if not d0.value:
        send("d0")

    if not d1.value:
        send("d1")

    if not d6.value:
        send("d6")

    if not d12.value:
        send("d12")

    if not d10.value:
        send("d10")

    if not d9.value:
        send("d9")