This is the multi-page printable view of this section.
Click here to print.
Return to the regular view of this page.
BADGE.TEAM documentation
Welcome!
On this website you will find the documentation and API reference of the BADGE.TEAM project.
We’re currently in the process of finalizing our APIs and the platform firmware itself. Meanwhile we ask you to keep an eye out for mistakes.
If you find a bug, documentation mistake or want to contribute please create a github issue describing what you want.

1 - Badges
BADGE.TEAM badges
ESP32 based
Other badges
Collaborations
These badges were made in collaboration with BADGE.TEAM
Other ESP32 based badges
These badges were not developed by us, but we’ve added support for them to our ESP32 platform firmware. Our efforts for these badges are more of an “after market upgrade” so to say…
The CCC camp 2019 “CARD10” badge
The CARD10 uses the hatchery as it’s app repository. For all other details about this project (the hardware, firmware and API) please have a look at the CARD10 project over at the CCC website.
(An incomplete) list of badges
An incomplete but slowly growing list of event badges and their derrivatives. Help us extend this list by pointing us towards badges that are missing.
1.1 - MCH2022 badge
The MCH2022 badge is our most advanced badge yet. Shaped like a game console this badge is a powerhouse filled with cool technology.

The hardware
The badge contains an Espressif ESP32 Wrover-e WiFi module with 16MB of flash storage and 8MB of PSRAM, an Raspberry pi RP2040 microcontroller chip for advanced USB communication and board management and a Lattice ICE40UP5K FPGA for hardware accelerated graphics.
Block diagram
The badge contains a huge amount of awesome chips, so many that a block diagram is necessary to explain how everything is interconnected.

The ESP32 is at the center of the operation. It has access to almost all the peripherals on the badge and using it’s WiFi connectivity it can load new firmware and applications from the internet.
The RP2040 microcontroller provides USB connectivity consisting of two serial ports (for the ESP32 and the FPGA), WebUSB for managing the badge using your browser and HID for acting like a keyboard, mouse or joystick. It also drives the SK6812-EC15 addressable LEDs, giving the badge a lot of bling and eyecandy. To top it off a lot of the I/O pins of the RP2040 have been broken out, both as the IO pins of the SAO connector and as testpads next to the prototyping areas on the back of the badge.
The ICE40UP5K FPGA is programmed over an SPI connection by the ESP32. Using this connection the FPGA can also communicate with the application running on the ESP32. Our goal is to enable people to learn about HDL programming so new bitstreams can easily be loaded into the FPGA by user applications, to provide any function you want ranging from a simple LED blinker to a RISC-V SoC. To accomodate more advanced designs the FPGA is connected to the LCD display via a parallel bus, enabling it to update the display at high refresh rates, as well as 8MB of PSRAM via a Quad-SPI bus. 8 of the I/O pins of the FPGA have been broken out as an industry standard PMOD header, allowing users to connect standard expansion modules or their own creations.
The software
When powered on the ESP32 will load an application chooser menu, from where the user can boot into the preinstalled applications such as a Micropython scripting environment, a sensor playground for the Bosch sensors, sponsor provided apps and of course the applications the user downloads from the Hatchery, our badge application repository.
The software is still in active development, more information will be published here soon.
1.1.1 - Getting started
Congratulations, you’re the proud owner of a shiny new MCH badge! It’s a fully-functional computer with three different processors in the palm of your hand, and we’ve done everything we can to make using it as friendly and intuitive as possible. We had a lot of fun making it, and we hope you’ll have a lot of fun using it.
In the pack
(Picture of contents of pack here)
Your MCH badge should have arrived in a bag, and if you are reading this in a tent at MCH itself, you should have received it when you entered the camp. Inside the bag are the following items:
(These will obviously change subject to what is in the pack)
- The badge itself
- A lithium-polymer battery for the badge
- A self-adhesive Velcro patch for attaching the battery
- A printed badge lanyard
- A leaflet containing basic information about the badge
Fitting the battery
The battery is a silver pouch with a short cable terminated in a trailing socket connector. This mates with a PCB mounted plug which you’ll find on the component side of the badge. Place your badge screen side down with the USB-C connector facing towards you, and you’ll find the on-board battery connector at the bottom right next to the gold squares of the prototyping area.
(Photo showing connector location and the two connectors facing each other)
The trailing socket on the battery has a small lug on one side that locates with a notch in the on-board socket. With the lug facing upwards, carefully slot the two connectors together.
The battery should now be attached to the badge. Offer it up to see which side lies most naturally against the badge while the two are connected together, this will tell you on which side to stick the Velcro patch. Peel off the protective paper from one side of the patch and fix it to the side of the battery that will attach to the badge, in the centre. Then remove the paper fromt he pooosite side of the patch, and stick it to the badge. We suggest fixing it to the Espressif silver can containing the ESP32 microcontroller.
Now what?
All being well, you should now have a working badge. We strongly suggest that you hook it up to a live USB power source to fully charge the battery.
While the battery is charging, it’s time to explore the badge a little. When you turn it on it will start with a splash screen and details of all the sponsors who have made the event and the badge possible. You’ll then see the badge menu screen, so now’s a goo time to move along to the next step in this introduction: using the MCH 2022 badge.
1.1.1.1 -
title: “The Badge Software”
linkTitle: “On-board Software”
nodateline: true
weight: 1
Your MCH badge comes with installed software which allows you to select and run applications, install new applications from our online hatchery, and set up badge functions such as the Wi-Fi password.
This page is a high-level introduction to the installed software. If you came here looking for details on how to write software for the badge, then take a look at our software development guide.
(The software isn’t ready at time of writing, so this where this page stops)
1.1.2 - MCH2022 badge pinouts
Connectors
SAO (Shitty AddOn)
Addon connector following the SHITTY ADD-ON V1.69BIS standard.
Pin |
Description |
Direction |
Connection |
1 |
VCC |
Power output |
3.3v supply voltage output |
2 |
GND |
Power output |
Ground reference |
3 |
SDA |
Data IO |
I2C bus data |
4 |
SCL |
Data output |
I2C bus clock |
5 |
GPIO1 |
Data IO |
User configurable IO, connected to RP2040 GPIO18 |
6 |
GPIO2 |
Data IO |
User configurable IO, connected to RP2040 GPIO19 |
PMOD (peripheral module interface)

The PMOD connector is wired up to the iCE40 FPGA. Note that while the connector is physically located on the backside of the badge, it has been wired up such that the PMOD’s top side must be pointed in the same direction as the badge’s top.
PMOD pin |
ICE40 pin |
Note |
1 |
47 |
IOB_2a (paired with PMOD pin 7 IOB_3b_G6 ) |
2 |
48 |
IOB_4a (paired with PMOD pin 8 IOB_5b ) |
3 |
4 |
IOB_8a (paired with PMOD pin 9 IOB_9b ) |
4 |
2 |
IOB_6a |
7 |
44 |
IOB_3b_G6 (paired with PMOD pin 1 IOB_2a ) |
8 |
45 |
IOB_5b (paired with PMOD pin 2 IOB_4a ) |
9 |
3 |
IOB_9b (paired with PMOD pin 3 IOB_8a ) |
10 |
46 |
IOB_0a |
Chips
ESP32
ESP32 GPIO |
Direction |
Function |
Note |
0 |
Both |
I2S master clock output / UART download select input |
Drives I2S DAC / driven by RP2040 through resistor |
1 |
Output |
UART TX |
Connected to RP2040 |
2 |
Both |
SD card data 0 |
SD card slot |
3 |
Input |
UART RX |
Connected to RP2040 |
4 |
Output |
I2S channel select (LR) |
|
5 |
Output |
LED data |
Connected to the SK6805 LEDs in the kite |
12 |
Output |
I2S clock |
|
13 |
Output |
I2S data |
|
14 |
Output |
SD clock |
SD card slot |
15 |
Output |
SD command |
SD card slot |
18 |
Output |
SPI clock |
Connected to LCD and FPGA |
19 |
Output |
SD card and kite LED power control |
Set high to enable power to LEDs and SD card |
21 |
Output |
I2C clock |
Connected to RP2040, BNO055, BME680, Qwiic connector and SAO addon connector |
22 |
Both |
I2C data |
Connected to RP2040, BNO055, BME680, Qwiic connector and SAO addon connector |
23 |
Output |
SPI MOSI |
Data from ESP32 to LCD / FPGA |
25 |
Both |
LCD reset |
Set to output low to reset LCD, leave floating normally |
26 |
Output |
LCD mode |
Low: LCD in SPI mode, high: LCD in parallel mode |
27 |
Output |
SPI chip select for ICE40 |
Low: select ICE40, high: deselect ICE40 |
32 |
Both |
SPI chip select for LCD |
Low: select LCD, high: deselect LCD. Note: output in LCD SPI mode, input in LCD parallel mode |
33 |
Both |
LCD DC (data or command) selection |
Note: output in LCD SPI mode, input in LCD parallel mode |
34 |
Input |
Interrupt from RP2040 |
|
35 |
Input |
SPI MISO |
Connected to ICE40 |
36 (SENSOR_VP) |
Input |
Interrupt from position sensor (BNO055) |
|
39 (SENSOR_VN) |
Input |
Interrupt from ICE40 FPGA |
|
RP2040
RP2040 GPIO |
Direction |
Pull |
Function |
Description |
0 |
Output |
|
UART0 TX |
ESP32 UART |
1 |
Input |
|
UART0 RX |
ESP32 UART |
2 |
Both |
|
I2C1 SDA |
I2C bus data (RP2040 is in slave mode) |
3 |
Input |
|
I2C1 SCL |
I2C bus clock |
4 |
Input |
Up |
GPIO |
Button: MENU |
5 |
Input |
Up |
GPIO |
Button: HOME |
6 |
Input |
Up |
GPIO |
Button: ACCEPT |
7 |
Input |
Up |
GPIO |
Button: Joystick A |
8 |
Input |
Up |
GPIO |
Button: Joystick B |
9 |
Input |
Up |
GPIO |
Button: Joystick C |
10 |
Input |
Up |
GPIO |
Button: Joystick D |
11 |
Input |
Up |
GPIO |
Button: Joystick E |
12 |
Both |
|
GPIO |
ESP32 bootloader mode¹ |
13 |
Output |
|
GPIO |
ESP32 enable |
14 |
Both |
|
GPIO |
ESP32 interrupt¹ |
15 |
Output |
|
PWM |
LCD backlight brightness |
16 |
Both |
|
GPIO |
Available next to prototyping area |
17 |
Both |
|
GPIO |
Available next to prototyping area |
18 |
Both |
|
GPIO |
SAO GPIO1 |
19 |
Both |
|
GPIO |
SAO GPIO2 |
20 |
Input |
|
GPIO |
FPGA done |
21 |
Output |
|
GPIO |
FPGA reset |
22 |
Input |
Up |
GPIO |
Button: START |
23 |
Input |
|
GPIO |
LiPo charger state |
24 |
Output |
|
UART1 TX |
FPGA UART |
25 |
Input |
|
UART1 RX |
FPGA UART |
26 |
Input |
Up |
GPIO |
Button: BACK |
27 |
Output |
|
GPIO |
Infrared LED |
28 |
Input |
|
ADC |
Voltage measurement: USB input |
29 |
Input |
|
ADC |
Voltage measurement: Battery |
¹: Set to input normally and force low to activate
ICE40 FPGA
ICE40 pin |
ICE40 GPIO |
Direction |
Description |
Notes |
2 |
IOB_6a |
Both |
PMOD pin 4 |
|
3 |
IOB_8a |
Both |
PMOD pin 3 |
|
4 |
IOB_9b |
Both |
PMOD pin 9 |
|
6 |
IOB_13b |
Input |
UART RX |
|
9 |
IOB_16a |
Output |
UART TX |
|
10 |
IOB_18a |
Output |
Interrupt |
Active-low |
11 |
IOB_20a |
Output |
LCD register select |
|
12 |
IOB_22b |
Both |
RAM SPI D2 |
|
13 |
IOB_24a |
Both |
RAM SPI D1 |
|
14 |
IOB_32a_SPI_SO |
Output |
SPI MISO |
|
15 |
IOB_34b_SPI_SCK |
Input |
SPI SCK |
|
16 |
IOB_35b_SPI_SS |
Input |
SPI SS |
|
17 |
IOB_33b_SPI_SI |
Input |
SPI MOSI |
|
18 |
IOB_31b |
Output |
RAM SPI CS |
|
19 |
IOB_29b |
Output |
RAM SPI SCK |
|
20 |
IOB_25b_G3 |
Both |
RAM SPI D3 |
|
21 |
IOB_23b |
Both |
RAM SPI D0 |
|
23 |
IOT_37a |
Output |
LCD write |
|
25 |
IOT_36b |
Input |
LCD frame sync |
|
26 |
IOT_39a |
Output |
LCD data 0 |
|
27 |
IOT_38a |
Output |
LCD data 1 |
|
28 |
IOT_41a |
Output |
LCD CS |
|
31 |
IOT_42b |
Output |
LCD data 2 |
|
32 |
IOT_43a |
Output |
LCD data 3 |
|
34 |
IOT_44b |
Output |
LCD data 4 |
|
35 |
IOT_46b_G0 |
Input |
12MHz clock |
|
36 |
IOT_48b |
Output |
LCD reset |
Active-low, drive open-drain |
37 |
IOT_45a_G1 |
Output |
LCD data 5 |
|
38 |
IOT_50b |
Output |
LCD data 6 |
|
39 |
RGB0 |
Output |
LED |
|
40 |
RGB1 |
Output |
LED |
|
41 |
RGB2 |
Output |
LED |
|
42 |
IOT_51a |
Output |
LCD data 7 |
|
43 |
IOT_49a |
Input |
LCD mode |
Should be driven by ESP and monitored by FPGA |
44 |
IOB_3b_G6 |
Both |
PMOD pin 7 |
|
45 |
IOB_5b |
Both |
PMOD pin 8 |
|
46 |
IOB_0a |
Both |
PMOD pin 10 |
|
47 |
IOB_2a |
Both |
PMOD pin 1 |
|
48 |
IOB_4a |
Both |
PMOD pin 2 |
|
1.1.3 - Software Development
This is a shameless placeholder for the software development section
Arduino
The ESP32 on the badge can be programmed using the Aduino IDE.
- Install the Arduino IDE if you havn’t already.
- Install ESP32 support using these instructions
- Install PyUSB for Python 3 using pip or the package manager provided by your distro. On Debian you can run
sudo apt install python3-usb
- Download ESP32-platform-firmware
Now write your Arduino sketch as usual, by selecting the ESP32 wrover module.
But instead of uploading your sketch, use Sketch > Export compiled binary
(ctrl+alt+s)
Now you need to plug in the badge, turn it on, and launch webusb_push.py from the ESP32-platform-firmware repo with the path of the binary that Arduino generate in your sketch folder.
python path/to/webusb_push.py "my cool app" path/to/my_app.ino.esp32.bin --run
After a few seconds your app should be runnin on the badge.
Controling the display
The easiest way to control the display is by using the Adafruit ILI9341 library.
Go to Tools > Manage Libraries...
and search for the Adafruit GFX library and the Adafruit ILI9341 library and install both.
Include them as follows
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#define PIN_LCD_CS 32
#define PIN_LCD_DC 33
#define PIN_LCD_RST 25
Adafruit_ILI9341 tft = Adafruit_ILI9341(PIN_LCD_CS, PIN_LCD_DC, PIN_LCD_RST);
And then add the following lines to the setup
function.
tft.begin(LCD_FREQ);
tft.setRotation(1);
And now you can use regular GFX commands like so
tft.fillScreen(ILI9341_PURPLE);
tft.setCursor(0, 0);
tft.setTextColor(ILI9341_YELLOW);
tft.setTextSize(3);
tft.println("MCH2022");
Controlling the LEDs
The LEDs are controlled using the FastLED library, which can once again be installed from the library manager.
First define and include all the things.
#include <FastLED.h>
#define PIN_LED_DATA 5
#define PIN_LED_ENABLE 19
#define NUM_LEDS 5
CRGB leds[NUM_LEDS];
And then run the folowing setup code
FastLED.addLeds<SK6812, PIN_LED_DATA, GRB>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
FastLED.setBrightness(96);
// This has to be placed after SPI (LCD) has been initialized (Arduino wants to use this pin as SPI MISO...)
pinMode(PIN_LED_ENABLE, OUTPUT);
digitalWrite(PIN_LED_ENABLE, HIGH);
And you can now just set the LED colors as follows
The buttons are controller by the RP2040, and can be read over I2C. Here is a simple example.
#include <Wire.h>
#define PIN_I2C_SDA 22
#define PIN_I2C_SCL 21
#define PIN_RP2040_INT 34
#define RP2040_ADDR 0x17 // RP2040 co-processor
#define BNO055_ADDR 0x28 // BNO055 position sensor
#define BME680_ADDR 0x77 // BME680 environmental sensor
#define RP2040_REG_LCD_BACKLIGHT 4
#define RP2040_REG_INPUT1 0x06
#define RP2040_REG_INPUT2 0x07
void set_backlight(uint8_t brightness) {
Wire.beginTransmission(RP2040_ADDR);
Wire.write(RP2040_REG_LCD_BACKLIGHT);
Wire.write(brightness);
Wire.endTransmission();
}
uint16_t read_inputs() {
Wire.beginTransmission(RP2040_ADDR);
Wire.write(RP2040_REG_INPUT1);
Wire.endTransmission();
Wire.requestFrom(RP2040_ADDR, 4);
uint16_t input = Wire.read() | (Wire.read()<<8);
uint16_t interrupt = Wire.read() | (Wire.read()<<8);
return interrupt;
}
void setup() {
Serial.begin(115200);
Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL);
pinMode(PIN_RP2040_INT, INPUT);
read_inputs();
}
uint8_t brightness = 0;
void loop() {
if (!digitalRead(PIN_RP2040_INT)) {
Serial.println(read_inputs(), BIN);
}
set_backlight(brightness);
brightness++;
//delay(500);
}
A full list of all the registers can be found here
Reset the ESP32
Restting the ESP32 can be done using the following snippet.
#include <esp_system.h>
#include "soc/rtc.h"
#include "soc/rtc_cntl_reg.h"
void return_to_launcher() {
REG_WRITE(RTC_CNTL_STORE0_REG, 0);
esp_restart();
}
You can now trigger this when the home button is pressed like so
if (!digitalRead(PIN_RP2040_INT)) {
if (read_inputs() & (1<<0)) {
return_to_launcher();
}
}
1.2 - SHA2017

On this page you’ll find all the hints, tips, datasheets, secret codes and assorted stuff you’ll need to hack the SHA2017 Badge!
Badge presentation
Please see the talk (slides) we gave during SHA for a nice overview on how we managed to pull this project off.
1.2.1 - Getting started
Please remember the badge project is a huge volunteer effort - please approach it as a hacker, not as a consumer :). Lots of things can still be improved, and your help is much appreciated! This still holds true August 2019!
Unpacking and assembling
WiFi setup
You can configure the WiFi network by starting the WiFi setup app on your badge.
By default the badge will try to connect to an open network called “SHA2017-insecure”.
OTA update
The first thing to do after starting up the badge for the first time is do an OTA (Over The Air) update.
This will make a connection with the hatchery and download the latest available version of the badge software.
Booting the badge for the first time and general use
When booting for the first time the badge will still be on the (now very old) firmware that it came with out of the box.
Following the steps below allows you to easily get to the latest-greatest firmware we have to offer.
Nickname configuration
After the badge starts for the first time you will be prompted to enter your nickname. You can do so by selecting keys on the on-screen keyboard and pressing A to press the selected key.
Once you are done you can press the select button to switch to the OK/CANCEL buttons. The OK button is selected by default. Press A to click on the on-screen OK button.
If you don’t enter a nickname you will instantly skip the sponsor reel and drop into the menu!
Configuring WiFi
When you first enter the main menu the badge will try to connect to the SHA2017 network. Wait for the WiFi connection to fail and press START to enter the app launcher.
Select the “WiFi setup” app, pick your network from the list and enter the password.
Then wait (again) for the main menu to do it’s thing and press START to open the launcher again.
This time select “OTA update” or “Firmware update” from the menu. This will start the upgrade process.
Connecting to your computer
When connected to your computer using the USB connection of your badge you can access a handy menu system to configure your badge as well as a full Python prompt. Connecting to your computer allows you to see what’s going on inside the software of your badge, allowing you to debug your app, test new code snippets, upload files and load new or custom firmware.
To get started install the driver, download a terminal emulation program of your choice (for Windows we recommend either TeraTerm or Putty) and connect to your badge at 115200 baud. After waking up your badge from sleep mode you should be presented with a menu.

For more information on the serial console of your badge please have a look at the USB-serial connection article of the App development section.
And now?
Congratulations! You should now be on our new platform firmware. Note that not everything works yet and that you might experience some problems.
Having problems? We have a troubleshooting page just for that.
Want to start developing apps? check out out App development section.
1.2.2 - Driver installation
The SHA2017 badge uses a Silicon Labs CP2102 USB to serial converter for communication with your computer.
You can find the driver for this chip on the Silicon Labs website.
The badge expects you to connect to it at 115200 baud. Note that the badge will not respond when in sleep mode. After connecting over USB be sure to wake the badge up either by touching one of the touch buttons or by pressing the RESET button on the back.
1.2.3 - Hardware
Components
E-ink display: the DKE Group DEPG0290B1
The DKE Group DEPG0290B1 is used on the SHA Badge.
Datasheet: DEPG0290B01V3.0.pdf
In case you want to build a SHA2017 badge yourself or in case you broke the display that came with your badge back in 2017 you might have noticed that the display is hard to come by so we also support a pinout compatible alternative: the GDEH029A1.
For this alternative display to function you need to set a flag in the non-volitile memory of your badge. Go to the shell of your device and type in the following command to switch the display type: import machine;machine.nvs_set_u8('system','eink.dev.type',1)
. To reset your badge to the default DEPG0290B1 type display enter the following command: import machine;machine.nvs_set_u8('system','eink.dev.type',2)
.
The datasheet does a very good job explaining how to initialize the display and get it to picture something.
The LUT is explained in the section below, because THAT isn’t really documented at all…
Look Up Table (LUT)
The LUT is a small ‘program’ the display executes each time you refresh the display.
It is arranged in two sections of 35 bytes. The first half configures voltages (TBD).
The second half is the program.
// Voltages and other settings? Timing?
0xA0, 0x90, 0x50, 0x0, 0x0, 0x0, 0x0,
0x50, 0x90, 0xA0, 0x0, 0x0, 0x0, 0x0,
0xA0, 0x90, 0x50, 0x0, 0x0, 0x0, 0x0,
0x50, 0x90, 0xA0, 0x0, 0x0, 0x0, 0x0,
0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0,
// Update program
//
// Top three lines are the main program (bottom 4 have unknown function)
// Line 1: Negative image
// Line 2: White/Black flashing
// Line 3: Positive image
//
// Line construction
// First two bytes denote Intensity (range 0x00 to 0x0F)
// Second two bytes denote lenght of each 'pulse' (range 0x00 to 0xFF)
// Last byte denotes number of repeats (0 = line runs 1 time, range 0x00 to 0xFF)
// If you don't want a line to do anything, set all bytes to 0x0.
// This way you can make a quick update cycle between two screens.
// Maybe not as pretty/crisp but nice and fast is also awesome!
// Negative image
// first two bytes negative image, length white pulse (0-FF), length black pulse (0-FF), last byte repeats
0xF, 0xF, 0x0, 0x0, 0x0,
// White or black flash
// white flash intensity, black flash intensity, length white pulse (0-FF), length black pulse (0-FF), repeats
0xF, 0xF, 0x0, 0x0, 0x02,
// Positive image
// first byte or second byte positive image (don't know why you need both), rest same as above
0xF, 0xF, 0x0, 0x0, 0x0,
// Unknown what lines below actually do.
// They seem to be programs to, but have no visible effect on dislay.
0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0,
Microcontroller: the Espressif ESP32 Wroom module
The SHA2017Badge uses a Special ESP-WROOM-32 module with a 128 Mbit flash
ESP32 is a series of low cost, low power system on a chip microcontrollers with integrated Wi-Fi & dual-mode Bluetooth.
The ESP32 series employs a Tensilica Xtensa LX6 microprocessor in both dual-core and single-core variations.
ESP32 is created and developed by Espressif Systems, a Shanghai-based Chinese company, and is manufactured by TSMC using their 40 nm process.
It is a successor to the ESP8266 microcontroller.
Features
Features of the ESP32 include the following:
- CPU: Xtensa Dual-Core 32-bit LX6 microprocessor, operating at 160 or 240 MHz and performing at up to 600 DMIPS
- Memory: 520 KiB SRAM
- Wireless connectivity:
- Wi-Fi: 802.11 b/g/n/e/i
- Bluetooth: v4.2 BR/EDR and BLE
- 12-bit SAR ADC up to 18 channels
- 2 × 8-bit DACs
- 10 × touch sensors
- Temperature sensor
- 4 × SPI
- 2 × I²S
- 2 × I²C
- 3 × UART
- SD/SDIO/MMC host
- Slave (SDIO/SPI)
- Ethernet MAC interface with dedicated DMA and IEEE 1588 support
- CAN bus 2.0
- IR (TX/RX)
- Motor PWM
- LED PWM up to 16 channels
- Hall effect sensor
- Ultra low power analog pre-amplifier
- IEEE 802.11 standard security features all supported, including WFA, WPA/WPA2 and WAPI
- Secure boot
- Flash encryption
- 1024-bit OTP, up to 768-bit for customers
- Cryptographic hardware acceleration: AES, SHA-2, RSA, elliptic curve cryptography (ECC), random number generator (RNG)
- Internal LDO
- Individual power domain for RTC
- 5uA deep sleep current
- Wake up from GPIO interrupt, timer, ADC measurements, capacitive touch sensor interrupt

Touch controller: the MPR121 Touch Sensor and GPIO expander
The Freescale/NXP MPR121 serves as both the capacitive touch controller and as a GPIO expander on the badge. It is connected to the ESP32 through I2C and an interrupt line.
Documents
Connections
-
The MPR121 is connected to the ESP32 through I2C on pins IO26 (SDA) and IO27 (SCL).
-
Software pullups are not necessary, as there are two pullup resistors on the board.
-
The MPR’s interrupt pin is connected to IO25 on the ESP.
-
Its I2C slave address is 0x5A.
The MPR121 has twelve electrode connections (ELE0-11), of which eight can be used as GPIO. We are using the last four electrode connections as I/O.
Electrode |
GPIO |
Function / direction |
Connection |
ELE0 |
- |
Touch |
A |
ELE1 |
- |
Touch |
B |
ELE2 |
- |
Touch |
Start |
ELE3 |
- |
Touch |
Select |
ELE4 |
GPIO0 |
Touch |
Down |
ELE5 |
GPIO1 |
Touch |
Right |
ELE6 |
GPIO2 |
Touch |
Up |
ELE7 |
GPIO3 |
Touch |
Left |
ELE8 |
GPIO4 |
Push/pull output |
Vibration motor |
ELE9 |
GPIO5 |
Input |
TP4056 Charge status |
ELE10 |
GPIO6 |
Push/pull output |
WS2812 / SD Card power enable |
ELE11 |
GPIO7 |
Input |
SD Card detect NOT FUNCTIONAL |
Touch
The most important function of the MPR121: capacitive touch. I (Kartoffel) will describe how I was able to get it to work, though it might not be ideal and definitely needs tweaking. I left a lot of registers unexplored, and did not implement the over current detection which can halt the IC.
The basic setup steps:
-
Initialize global baseline filter (registers 0x2B to 0x40) - see AN3891 for information about the baseline system.
-
Set the touch and release thresholds for each electrode (registers 0x41 to 0x5A).
-
Set electrode sample interval (register 0x5D) - this directly influences the current consumption.
Finally, to get the MPR121 into run mode:
- Enable the electrodes for touch detection (register 0x5E) - set this to 0x08 to enable just ELE0-ELE7 to make sure we can use the rest as GPIO.
Now the MPR is in run mode and scanning the touch electrodes.
When the state of an electrode changes the interrupt pin will go low, and the state should be read by the ESP. Register 0x0 holds the touch status of ELE0 to ELE7.
GPIO
We are using ELE8-11 (GPIO4-7) as GPIO. The MPR uses eight registers to control its GPIO pins:
Register |
Function |
0x73 |
GPIO Control 0 |
0x74 |
GPIO Control 1 |
0x75 |
GPIO Data |
0x76 |
GPIO Direction |
0x77 |
GPIO Enable |
0x78 |
Data set |
0x79 |
Data clear |
0x7A |
Data toggle |
In order to use the GPIO pins, we first have to initialize them:
-
Set the GPIO direction of IO4 and IO6 as output, IO5 and IO7 as input. (adress 0x76, data 0x50)
-
Set the control registers. For CMOS outputs and inputs without pullups, both of these should be set to 0 for GPIO4-7. (adress 0x73, data 0x00 and adress 0x74, data 0x00)
-
Enable GPIO4-7 by writing 0xF0 to the GPIO Enable register. (adress 0x77, data 0xF0)
Next, the two output pins can be set to HIGH, LOW, or their state can be toggled with the Data Set, Data Clear, and Data Toggle registers. The state of the input pins can be read in register 0x01.
The GPIO5 and GPIO7 inputs have external pullup resistors, so they do not need internal bias.
IRQ
The IRQ-pin is connected to the ESP32 on IO25. It is an active-low pin that triggers on a touch-event (being touched or no longer being touched) and resets upon reading the registers via I2c. That way you can easily do an interrupt in your code or choose to ignore inputs until you have time to handle them.
LEDs: the blinky LEDs you can add
There are six pads for WS2812 or SK6812 LEDs on the front. Guess what? You can add them on yourself! Why? Because adding components to the front of the board is expensive (the board has to go through the machine twice). Have no fear, at camp there are plenty of capable hackers to help you if soldering isn’t your thing.
How to blink
The LEDs are powered via a mosfet switched on by ELE10 on the MPR121 (i2c adress 0x5A, write to register 0x78, data 0x40).
After that, blast your favorite WS2812 or SK6812 routine through GPIO32 on the ESP32! Have fun burning your eyes!
Using our platform firmware? See the neopixel API description for more information.
import neopixel
data = [0xFF, 0xFF, 0xFF, 0xFF]*6 #Fully turn on all the LEDs
neopixel.send(bytes(data))
Expanding
More you say? You want more? Sure, The data-out from the last LED is broken out on the expansion connector.
Be careful with drawing power from this connector, you could burn out the regulator, a fuse or just drain your battery really fast!
Power and battery
- Pin-compatible with the AMS1117, but we do not recommend that one because of its high quiescent current consumption!
Battery
The SHA2017 badge uses a lithium polymer battery.
|
|
Technology |
Lithium Polymer |
Capacity |
1000mAh |
Cells |
1 (1S, 3.7v) |
Protection |
Built-in: over/under voltage and over current |
Connector |
JST-PH3 |
USB-serial: Silicon Labs CP2102
Requires driver under macOS, found at the Silicon Labs website
Add-ons
Connector pinout:
|
|
|
|
|
|
GND |
LED_POWER |
IO_33 |
IO_16 |
IO_17 |
I2C_SCL |
3V3_SWITCHED |
GND |
IO_12 |
IO_4 |
LED_DATA_OUT |
I2C_SDA |
Hacks
Weatherproofing
-
Nailpolish seems to do the trick. Switches on the back will probably be unusable after applying it…
-
Plastik70 from Kontakt Chemie works ok (cover switches, USB and SD card slot with tape before spraying it)
1.2.4 - Troubleshooting
Boot issues
When on battery
Brownout protection might be kicking in on boot, try plugging in the micro USB and press the reset button.
With USB plugged in
Try disconnecting the battery to see if that causes the problems. If the badge still does not respond try connecting using a terminal emulator to see what’s going on.
Display
Sluggish
When your display responds sluggish (more than on other badges) or is for instance unable to clear the display in one pass, check the soldering on the display connector first.
Ghosting
When you update the display too frequently without proper clearing cycles (inverted image, black screen, white screen, positive image) you may experience something that looks like it was burned in. You can recover your screen by doing the black and white flashes (LOTS of them). Also letting the display rest (without power!) seems to alleviate the issue. So expect ghosting/burn-in when you are doing animations. We do not know the long term effect of (ab)using the display like this.
Removing
Removing the display is not something we recommend. Break it at your own risk. The trick seems to be to first remove the cable from the connector on the back, pull it through the hole. Now you can carefully push and wiggle the display downward towards the buttons. If you’re lucky the glue-dots havent hardened yet and you can remove the display. Come by the badge tent for new gluedots when you’re done (limited supplies).
Check the soldering on the MP121. Reflow if necessary.
LED power not working
If your LEDs aren’t getting any power either the MPR121 or the transistor is suspect.
Buzzer motor not working
Either the MPR121 or the transistor are suspect.
Other issues
Please contact us to help you figure things out either online or by visiting us at a camp or event.
1.3 - CampZone 2020
Available Python API modules:
1.4 - Hackerhotel 2020

The project
Welcome to Hackerhotel 2020, where you may check-out any time you want but you may never leave…
…just kidding of course, but our Egyptian cat goddess badge will be watching over you both during and after the event.
This badge is a bit different from our other badges: it’s a challenge badge. No apps, no Python, but instead a story for you to experience filled with puzzles and lore!
The Hackerhotel 2020 badge is a mixed reality escape room. Reminiscent of the classic ‘text adventures’ but with interactions in the real world, it will present you with many challenges to overcome in both the virtual and the real hotel.
The story so far…

Want a big version to make a poster? Click here
In the bag
Your badgekit contains all the essentials:
If you forgot to bring your USB to Serial adapter, you can pick one up at the badge hacking area if needed. We didn’t buy 350 of them, so please only pick one up if you need one!
The same goes for the headphones. They won’t be in the bag, but pick up a pair if you need one. Note: ours won’t be as nice as the one you already have!
Getting started
Did you just receive your badge at the event? Great! Plug in the batteries and you can start playing the minigames on the badge right away. There are four buttons on the front of the device using which you can control the games. Good luck figuring out how it works, as we’re leaving that part as a little secret!
Please pay attention to the batteries when plugging them in. Orient them like so:

Before plugigng in shitty addons please read the notes mentioned in the Errata section of this page!
Please do not bring front of badge in contact with anything metallic. All exposed metal is GND, and the battery-terminals poke out. Short them: battery overheats. When storing badge: please remove batteries.
Playing the game
To play the “escape from Hackerhotel” challenge you need to connect your badge to a computer. You can do this by connecting a USB-serial converter with 3.3 volt signal levels to the GND, RX and TX pins of the shitty-addon (SAO) connector. The TX pin is the pin transmitting data to your computer, the RX pin is for sending data from your computer to the badge.
The badge will present you with a text entry prompt when you connect to it using a terminal emulator configured for 115200 baud, 8-bit data width, no pairity bit and 1 stop-bit (115200 8n1). You might have to type an “h” followed by ENTER to get the badge to show it’s prompt.
Installing a terminal emulator
Errata
Some mistakes were made both in the design and during assembly, which we couldn’t fix in time for the event.
Troubleshooting
3 red lights
Two red eyes and a red diamond an an unresponsive badge are the notification that the EEprom has been corrupted. Either you broke it, the code broke it, or it was another fault. No worries, visit a friendly badge.team member and they can program that chip for you in under 15 seconds!
SAO
The SAO (shitty add-on) connector has been placed on the bottom of the badge, while it was intended to be placed on the top side. This means that the pinout of the SAO connector is mirrored when compared to the SAO specifications. The pinout mentioned on the silkscreen of your badge does properly match the connector, so no worries there. Should you want to plug in a shitty-addon, then you will have to remove and replace the connector.
At the badge assembly, both during Hackerhotel 2020 and during future events where we attend we will be sure to take some extra SAO connectors with us, together with the necessary equipment for doing this small rework step.
Undo the rework (if you want to)
Other mistakes are more visible, but less obvious: we’ve mirrored the pinout of the LEDs on this badge. To work around this issue we’ve removed the N-mosfets used to drive the LED-matrix and replaced them with bodgewires. To get the most light intensity out of your badge and to restore your badge to it’s full potential you can flip the leds (they’re symmetric), solder some SOT23 N-mosfets back in place and re-flash the firmware to drive the LED-matrix the right way round. Doing this rework takes a lot of time (30 minutes or more), but we’re glad to be of assistance should you want to attempt this.
Get the firmware (To be released after event) and go to resources.h
and enable #define PURIST_BADGE
and flash following instructions.
You can find a manual for fully reworking your badge here.
CYBER SCARAB
Our friends at Tilde Industries made a very nice addon for the badge. Find out more on their website.
1.4.1 - Connecting on Linux
We get it. The square black Lenovo is still number one 😊
Preparations
We assume you’re running a modern version of Linux.
Picocom
Install Picocom using sudo apt install picocom
or yum install picocom
or dnf install picocom
or pacman -S picocom
or emerge -atv picocom
which ever looks familiar.
Done. It’s that easy.
Connecting to your badge on Linux
Plug in a USB-Serial board, and maybe install some drivers to get it working.
On your terminal type ls /dev/tty.*
and hit enter:
- Serial chips are usually labeled /dev/ttyUSB0
If your USB-Serial doesn’t show up in /dev/tty*
then the driver hasn’t been installed or isn’t working properly (or you have a dead USB port or a dead USB-Serial)
Connect the 3.3v and GND to the header on the back of the badge. Connect the RX of the badge to the TX of the USB-Serial, and the TX of the badge to the RX of the USB-Serial.
Using Picocom
Picocom is a bit spartan. Start it using
picocom --imap delbs -b 115200 /dev/ttyUSB0
Instead of /dev/ttyUSB0
you should possibly use the device name you found earlier.
When you see a blank screen, press the Enter key twice. A welcoming prompt should be displayed.
Type in ?
and get going in the awesome experience. Type in a
and verify the symbols you see match the symbols you see on the badge. If you get question marks in blocks, weird symbols etc, your locale is not set right.
Press control-a and then control-h to see Picocom help
Press control-a and then control-x to exit Picocom
Setting Locale (troubleshooting)
1.4.2 - Connecting on Mac
We get it. The fruity aluminum and glass has a certain appeal. However getting a decent serial connection is a bit of work. Not really hard and a nice way to get started with serial hacking on your Mac!
Preparations
We assume you’re running a modern version of Mac OS. First we’ll install brew (if you already have it, just skip ahead.
Brew
Visit https://brew.sh and use the oneliner you find there to install it. It will take a bit of time but you’ll love it!
Brew is the installer every Mac should ship with. A ton of open source apps will become available to you without the hassle. Just type in brew install $appname
and it will happen!
Picocom
Install Picocom using brew install picocom
.
Done. It’s that easy.
Connecting to your badge on Mac
Plug in a USB-Serial board, and maybe install some drivers to get it working.
On your terminal type ls /dev/tty.*
and hit enter:
- CP210x chips are usually labeled /dev/tty.SLAB_USBtoUART
- CH340 chips are labeled …
- FTDI chips are labeled …
- Prolific 2303 chips should just die. Please discard.
If your USB-Serial doesn’t show up in /dev/tty.*
then the driver hasn’t been installed or isn’t working properly (or you have a dead USB port or a dead USB-Serial)
Connect the 3.3v and GND to the header on the back of the badge. Connect the RX of the badge to the TX of the USB-Serial, and the TX of the badge to the RX of the USB-Serial.
Using Picocom
Picocom is a bit spartan. Start it using
picocom --imap delbs -b 115200 /dev/tty.SLAB_USBtoUART
Instead of /dev/tty.SLAB_USBtoUART
you should use the device name you found earlier.
When you see a blank screen, press the Enter key twice. A welcoming prompt should be displayed.
Type in ?
and get going in the awesome experience. Type in a
and verify the symbols you see match the symbols you see on the badge. If you get question marks in blocks, weird symbols etc, your locale is not set right.
Press control-a and then control-h to see Picocom help
Press control-a and then control-x to exit Picocom
Setting Locale (troubleshooting)
1.4.3 - Connecting on Windows
We get it. You re a gamer. Or thing you don’t have time to debug Linux drivers or don’t have the money for a Mac.
Preparations
We assume you’re running a modern version of Windows.
PuTTY
Download PuTTY from https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html
Install PuTTY.
Connecting to your badge on Windows
Configure the PuTTY menu as follows:
- Under Connection type, select Serial.
- In the Serial line field, enter the COM# for your board, such as COM7.
- Note: If you did not identify your COM# when setting up your board, navigate to the Device Manager and check for an entry called USB Serial Port
- In the Speed field, type 115200
- Click Open.
Using PuTTY
When you see a blank screen, press the Enter key twice. A welcoming prompt should be displayed.
Type in ?
and get going in the awesome experience. Type in a
and verify the symbols you see match the symbols you see on the badge. If you get question marks in blocks, weird symbols etc, your locale is not set right.
1.4.4 - Playing after the event

Mixed reality
Since the Hackerhotel 2020 badge game features some mixed reality elements, you will run into some parts in the game where you will need interact with some elements that were only available during the event.
This page will assist you in working around those challenges so you can complete (or start) the game on your own.
The magnetic maze
When you have read the picture frame in the reception, the hall sensor on the badge is activated to play the magnetic maze in the recption of the real hotel. As you don’t have access to the picture frame with the magnetic maze, here is a picture of it with the magentic orientation of all the magnets behind the hieroglyphs. Use a (strong) magnet to enter a sequenze of N/S orientations to the badges Hall sensor.
Please note that it does not matter if you start with N or S, the game just looks for a sequence of same/different magnetic fields.

Connecting to other badges (sometimes referred to as badge-sex)
During Hackerhotel, jack-2-jack cables were available to connect the badges together. We devided all badges in four types (Anubis, Bes, Thonsu and Thoth). You had to connect to all three other badge types to form a team. Without being a team, the Guard in the Dungeon will not give you the hints you need to decide what to offer at the Altar.
If you have not been able to connect to all the other badge types, there is a cheat code that can be used to simulate that you did. Enter #124W9
in the game to make sure your badge thinks it has connected to all other badge types so that you can continue the game in the Dungeon.
Make the right offering to the high-priest
In the dungeon you will encounter a guard and an altar. The guard gives you some hints, but you need the hints given to all 4 badge characters to solve the puzzle and make the right offering to please the high-priest. So to be able to solve this puzzle on your own, here are the 4 parts of the hints that are given to each badge character:
Anubis receives the following hints from the guard:
- Khonsu will offer Incense
- The one who kneels 3 times will bring element Water.
Bes receives the following hints from the guard:
- Khonsu will kneel more than once.
- The one who offers Incense will bring element Fire.
Khonsu receives the following hints from the guard:
- Bes will bring element Air
- Anubis will be kneeling once more than the one bringing element Earth
Thoth receives the following hints from the guard:
- Anubis will bring a Robe as offering, he will not kneel 2 times.
- The one bringing the element Air will offer something other than Fruit
When you do your offering, you will be asked how many times you kneeled and which element you will bring with you. This will result in a code that you will need later. Here is a python script that will generate the codes for you.
#!/usr/bin/env python3
badges = ['a','b','k','t']
badge = ""
while not badge in badges:
inp = input("Are you [A]nubis, [B]es, [K]honsu or [T]hoth? ")
badge = inp.lower()[0]
badge = badges.index(badge)
offerings = ['w','r','i','f']
offering = ""
while not offering in offerings:
inp = input("Are you offering [F]ruit, [I]ncense, [R]obe or [W]ine? ")
offering = inp.lower()[0]
offering = offerings.index(offering)
elements = ['e','a','w','f']
element = ""
while not element in elements:
inp = input("Will you bring [A]ir, [E]arth, [F]ire or [W]ater? ")
element = inp.lower()[0]
element = elements.index(element)
kneelings = -1
while kneelings < 0 or kneelings > 3:
inp = input("How many times did you kneel? ")
kneelings = int(inp)-1
answer = ((offering & 2) << 19) + ((offering & 1) << 8) + \
((element & 2) << 15) + ((element & 1) << 4) + \
((kneelings & 2) << 11) + ((kneelings & 1))
answer = answer << (3-badge)
print("Your part of the code is {}".format(answer))
If you don’t have python3 on your system, you can execute this code online at https://repl.it/languages/python3
Picture frames
There were two other picture frames spread accross the bar. Use at your own risk ;-)

That’s it folks…
With these hints and tricks you should be able to play the whole badge adventure!
Good luck and have fun!
1.5 - Disobey 2020
Getting started
To navigate the menus on your badge you use the touchbuttons. These buttons might be a bit hidden, but if you look closely at the artwork on the front of your badge you will find the following Gameboy inspired buttons:
- START. this button is usually used to enter or exit an app or menu
- A. used to accept input or to select a menu item
- B. used to go back
- SELECT. used to navigate submenus
- UP/DOWN/LEFT/RIGHT. used to navigate through menu options
Exact button functions differ from app to app as the developers can decide to use the buttons as they wish.
You can also use the badge through the USB-serial connection. When connecting to your computer be sure to configure your terminal emulation application to use serial port settings 115200 8n1. On this serial port you will be greeted with a menu through which you can start apps or drop into a Python shell.
Using the badge
Once you turn on your badge using the slideswitch you will be greeted by the homescreen, showing the Disobey logo and a welcome message.
To enter the application launcher you press the START button.
If your badge doesn’t start or starts an app different to the homescreen on power-on then that app might have been configured to be the default app. To restore the homescreen app to be the default app hold down the START button while switching on power to your badge. This will enter the recovery menu. Select the restore default app menu option using the A button and you’re done.
Installing apps
You can install apps using the installer application. You can browse the available apps and publish your own apps online by going to the Hatchery.
Setting your nickname
The message displayed on the homescreen can be replaced by your nickname. You can configure your nickname using the nickname app.
Using WiFi
During the event the badge will automatically connect to WiFi. Note that there is no internet access available on the badge WiFi network. When you get home you can easily connect the badge to your own WiFi network by selecting the WiFi settings app on the main menu or by navigating to Settings > WiFi settings on the serial port menu.
The keyboard
You select the character you want to type using the arrow keys. Then press A to enter the character. Pressing B removes the character before the cursor.
You can switch between the input mode, cursor mode and confirmation mode by pressing the SELECT button.
In the cursor mode you can control the cursor using the arrow keys.
In the confirmation mode you can either accept your input using the A button or cancel by pressing the B button.
Exiting apps
Most apps can be quit using the START button. This will return you to the launcher application.
1.6 - ETH0 2019
The project
This was a small, simple and most of all very fun badge to make. It’s a protoboard for building your own circuits: a true DIY badge!
Credits
The artwork has been made by Nikolett, the quickly thrown together PCB design was made by Renze and the prototyping board layout was found on the internet. It’s an amazing protoboard design made especially for working with SMD components, put online under the CC-BY license by Electronic Eel. You can find his project here.

1.7 - CampZone 2019
Work in progress doc
In Ubuntu
Install screen:
sudo apt install screen
Then add yourself to the network users
sudo usermod -a -G dialout -currentUser-
login or reboot
then connect and switch on the badge.
Then in the terminal execute the following:
screen /dev/ttyUSB0 115200
1.8 - Hackerhotel 2019

The HackerHotel 2019 badge
This badge was handed out at HackerHotel 2019. It consists mostly of left-over parts from the SHA2017 badge project, combined with some new functions.
In addition to the SHA2017 badge the HackerHotel 2019 badge has the following new features:
- 8MB (of which 4MB is addressable) of extra (PS)RAM
- Infrared transmitter and receiver
- Stereo audio output
- Grove I2C connector
- SAO (Shitty AddOn) connector
Problems with the audio jack
The audio jack is mounted in reverse due to a design error. To make the audio output function properly the first and third ring of the jack need to be swapped. Without this fix one of the channels is wired to ground while the ground of your speakers is wired to one of the audio channels.
1.9 - Disobey 2019

This badge has been produced for participants, sponsors, and organizers of the Finnish event Disobey in year 2019. It had a custom PCB with variations in art and color depending on the participant’s ticket. It was programmed to contain pointers as part of a hacker puzzle competition. As a stand alone device after the event, the Disobey 2019 badge would be able to run micropython on its esp32.
Getting started
Attendees received the badge along with 2 alkaline AAA 1.5V batteries, provided separately. First step was to insert the batteries, and see the badge boot. It was supposed to start up first time during the event at the venue, so it could connect to the wireless network called “badge” and download most recent version of the software. As the wireless credentials were hardcoded into the firmware, anyone who missed that window of opportunity would have to manually re-flash the badge with badge.team’s micropython configured for Disobey 2019 badge. After booting correctly, the badge would allow changing the configuration of the wireless network.
The badge needs a wireless connection to access the Hatchery, where micropython applications (called eggs) are stored. Badges can be used to download the eggs directly and use them without needing to connect to a computer.
The badge can be connected to a computer via USB. It communicates via serial at 115200 baudrate. In Linux it should appear as /dev/ttyACM0 (or the first free number, higher than 0). To connect to it, you can use e.g. screen:
screen /dev/ttyACM0 115200
Users can open the menu and navigate it, or invoke a micropython shell and live-code on the hardware. There is also an on-screen menu. There, users can trigger an OTA firmware update or change the WiFi credentials to use the badge post-event.
Hardware
This badge has buttons, a small screen with backlight, a buzzer, and both an infrared receiver and transmitter. However, the most used feature during the event were multiple SMD RGB LEDs going around the outline of the PCB, attached to the back.
Programming API
Most of the API is provided by the micropython and the modded version of the badge.team. For most basic micropython development, official documentation will suffice.
To program hardware-specific features, please see the following code examples that are valid for the software that badges were flashed with in 2019 before and during the event. This could have changed, if the badge has been flashed with updated badge.team micropython.
import badge
# to turn leds on:
# badge.led(LED_NR, R, G, B)
badge.led(0, 255, 0, 0) # set LED 0 to red
badge.led(2, 0, 0, 0) # turn LED 2 off
# backlight:
badge.backlight(255) # sets backlight to full brightness
badge.backlight(0) # turns off backlight
voltage = badge.battery_volt_sense()
# button-presses - use with ugfx:
def function(button_status):
print("Button pressed.", button_status)
ufgx.input_init()
ugfx.input_attach(ugfx.BTN_START, button_status)
badge.off()
# use it to turn off all power-hungry stuff (samd peripherals: leds, buzzer, backlight)
# note that ir stays on
# sound:
badge.buzzer(frequency, duration)
badge.buzzer(3000, 5)
# screen rotation:
badge.lcd_set_rotation(False)
ugfx.flush()
# now screen displays, upside-down because that's how it is attached
badge.lcd_set_rotation(True)
ugfx.flush()
# now screen displays upside-down hardware-wise, right-way up for people looking at the badge
# memory:
badge.nvs_get_str()
badge.nvs_get_str('badge', 'owner', 'default')
# this returns default if nothing was stored in
badge.nvs_set_str(group, item, value)
badge.nvs_set_str('badge', 'owner', 'Jukka')
# raw i2c:
badge.i2c_read_reg()
badge.i2c_write_red()
# debugging:
# for getting raw bit value of the button being pressed
badge.read_touch()
# raw state of the badge (it's a bit value, needs a bitmap to decode)
badge.read_state()
# exit app:
import appglue
appglue.home()
# auto-Scrolling text:
import easydraw
easydraw.msg("This is a test", "Title", True)
# services:
import virtualtimers
virtualtimers.activate()
def function():
print("Hello World")
return 1000
virtualtimers.add(function, 500)
2 - ESP32 platform firmware
This section of the documentation describes the ESP32 platform firmware used on the SHA2017 and many other ESP32 related badges. For the MCH2022 badge new, modernized firmware will be developed which will of course be made available for the earlier badges at a later date. For MCH2022 related documentation please check out the MCH2022 section of the documentation.
2.1 - Hatchery
The hatchery lives at badge.team and is a repository of apps for use on your badge.
Registration
Registration is simple, email can be whatever, for example test@test.com, it is only used for password resets.
App model
Apps are folders with as a minimal requirement a init.py
file.
Hatchery will add an empty version of that file for you.
main()
This is what should be run after import
by the [[SHA2017Badge/Launcher|Launcher]].
on_boot.py
This is what will be started on boot (if present) like so from app import on_boot
You can use this to run TSR apps (take a look at the flashlight app for an example of this..
Unless you upload or create such a file, Hatchery will generate one . .
This contains at-minimum the description of the app and weather or not it should be shown in the [[SHA2017Badge/Launcher|Launcher]].
Hatching eggs
Installation of apps on the badge is done with [[SHA2017Badge/woezel|woezel]] via REPL or with help of a graphical [[SHA2017Badge/Installer|Installer]] on the badge.
API
There’s an api available, used by [[SHA2017Badge/woezel|woezel]] and [[SHA2017Badge/Installer|Installer]]:
/eggs/get/[app]/json - get json data for a the egg named [app]
/eggs/list/json - a list of all eggs with name, slug, description, revision
/eggs/search/[words]/json - json data for search query [words]
/eggs/categories/json - json list of categories
/eggs/category/[cat]/json - json data for category [cat]
Since the badge.team merger there are now baskets for different badges
/basket/[badge]/list/json - a list of all eggs for specific [badge]
/basket/[badge]/search/json - [badge] specific search for [words]
/basket/[badge]/category/[cat]/json - json data for category [cat] on [badge]
You can play around with this API here at: https://badge.team/api
===Code===
Hatchery on Github
2.2 - ESP32: app development
This section describes the API of the BADGE.TEAM ESP32 platform firmware and serves as a reference for developing apps for our ESP32 based badges.
The getting started section will help you get started with all this awesomeness..
Once your first “hello world” app is up-and-running you’re probably wondering “how do I do…”. The API reference gives you detailed, in-depth information about the things you can do with your badge.
Now that your app is ready to be shared with the world the hatchery section will help you with publishing your app.
2.2.1 - Getting started
One of the aims of the BADGE.TEAM project is to ensure that as many people as possible can develop software for the platform. This ensures that badges and other hardware running our firmware are more likely to have a life beyond the event for which they were created.
The problem with many event badges has been that the learning curve for new developers is too steep and the acceptance process for new software too difficult. When detailed knowledge of a toolchain is required to write code and each addition must wait to be built into a fresh badge firmware update, most would-be developers go away and enjoy the event instead.
With an online app store we refer to as the hatchery and MicroPython apps we refer to as eggs, publishing new software for badges running our firmware is a much simpler process.
Not everybody is immediately familiar with a new platform though, so to help with your first badge egg we’ve created this tutorial. The aim is not to teach you Python but to introduce you to the structure of an extremely basic egg as well as get you going with the user interface. We’ll be following the time-honoured tradition of introducing you to badge programming with a “Hello world” egg.
Connecting to your badge
First make sure you’re able to connect to your badge. The exact driver needed for the USB to serial bridge on your badge differs. Make sure to follow the guide for your specific badge.
After you have installed the correct driver you can connect to your badge using a terminal emulation program.
For Windows we recommend either TeraTerm or Putty).
Connect to your badge at 115200 baud. After waking up your badge from sleep mode you should be presented with a menu. You can wake your badge up from sleep mode either by pressing or touching a button or by pressing the RESET button (if available).
Which type of badge do you have?
The different badges do not all have exactly the same hardware, so there are some slight differences in the setup process.
Please click on the badge you have to go to the getting started guide for your badge.
2.2.1.1 - Your first egg
In this tutorial you will create an app which displays “Hello, world!” on the display of your badge, while reacting to input from the buttons on your badge.
Executing code
After you connect to your badge (and wake it up) you will be greeted by the built in menu. Selecting the “Python shell” menu option and pressing RETURN to accept you will be greeted by a prompt.

On this shell you can type python commands like you would on a computer. For example you could enter print("Hello, world!")
to have your badge echo that same text back to you.
Should you want to paste bigger chuncks of code at once then you can use the builtin “paste mode” to do so. You can access this mode by pressing CTRL+E
on your keyboard. You can then press CTRL+D
to execute the pasted code or press CTRL+C
to cancel.
Pressing CTRL+D
outside of paste mode will reboot your badge, returning you back to the menu.
Pressing CTRL+C
outside of paste mode will stop the currently running command or app and return to the shell.
The display
To display text, images, shapes or other graphics on the display of your badge you use the display API.
The following example code demonstrates how to display some text on the display of your badge. It consists of four commands.
First we import the display library, allowing us to use the functions of this library in our app.
Then we fill the display with white (0xFFFFFF) and draw on top using black (0x000000). These colors are in RGB24 format, which is also commonly used for web-development. If you never heard of colors in the #000000 format then you might want to look up a tutorial on web colors first.
Even after filling the screen with white and drawing some text the display hasn’t been updated, it will still be showing whatever it did before we started… To send the buffer we just manipulated to the display you use the flush command. This way of working allows you to assemble an image before updating the screen.
import display
# Fill the framebuffer with white
display.drawFill(0xFFFFFF)
# Draw text at (0,0) in black using the 'PermanentMarker22' font
display.drawText(0,0,"Hello, world!", 0x000000, "PermanentMarker22")
# Flush the contents of the framebuffer to the display
display.flush()
Depening on your badge it might be wise to use a smaller font to test with, for example the 7x5
font.
import display
display.drawFill(0xFFFFFF)
display.drawText(0,0,"Hello, world!", 0x000000, "7x5")
display.flush()
For working with the buttons on your badge you use the buttons library.
Each button can be attached to a function with the following structure: def myCallback(pressed):
. The argument is True
when the function was called because the button was pressed and False
when the function was called because the buttton got released.
You can assign a function to each button separately using buttons.attach(<button>, <function>)
.
The following demonstration code shows how to react to a button:
import buttons
def myCallback(pressed):
if pressed:
print("Button callback: pressed!")
else:
print("Button callback: released!")
buttons.attach(buttons.BTN_A, myCallback)
Combining the two!
import display, buttons
def message(text):
print(text)
display.drawFill(0xFFFFFF)
display.drawText(0,0, text, 0x000000, "7x5")
display.flush()
def myCallback(pressed):
if pressed:
message("Pressed!")
else:
message("Released!")
buttons.attach(buttons.BTN_A, myCallback)
message("Press the A button!")
If your badge does not have the A
button then you can substitute that button with any other button. The Python prompt on your badge has tab completion. Just enter buttons.BTN_
and press TAB
on your keyboard for a list of available buttons.
And further?
Documenting is hard and a very slow process for us hackers. Therefore we suggest you take a look at one of the many apps published in the Hatchery to gain some inspiration and to publish your own app.
2.2.2 - USB-serial connection
You can communicate with your badge when it is not sleeping.
You can use a terminal application such as picocom to start talking to your
badge. Hit ‘?’ to open the text menu, which you can use to enter a
micropython shell.
You can use tools like ampy and mpfshell to transfer files between your PC
and the badge, and execute python code from there. Sometimes you need a couple
attempts for a request to succeed.
2.2.3 - API reference
Welcome to the API reference for the BADGE.TEAM platform firmware.
This reference describes all officially supported APIs of our platform. We try to keep these APIs as stable as possible. There are many more (undocumented) APIs in the firmware, all of which may change at any time!
Our platform firmware uses MicroPython at it’s core. Most of the libraries and APIs from the upstream MicroPython project are available in the BADGE.TEAM firmware.
The MicroPython documentation describes the builtin libraries and functions.
Specifically, the MicroPython core in our firmware is based on the ESP32 port of MicroPython by Loboris. He changed some parts of MicroPython to suit the ESP32 better. The wiki of his project describes the changes he made.
We have made a lot of changes on top of the work done by Loboris. We’ve added some badge specific APIs, a brand new framebuffer system for displaying graphics and drivers for the hardware specific to the supported badges.
By doing this we aim to take the resource intensive parts of driving the hardware to the C level beneath Python. This allows for a much more speedy experience and a lot more possibilities and flexibility.
Things to keep in mind while looking up documentation
- There is currently no API available for directly controlling the SPI bus(ses) of your badge from within Python.
- I2C should be used with caution as the I2C bus on most badges is used for system peripherals as well.
- The Neopixel (LED) driver differs greatly from the neopixel API in the Loboris port.
- The Display driver differs greatly from the display API in the Loboris port.
If you want to help with firmware development please tell us! We’re always happy to accept PRs and improvements.
Should you have ideas, problems or observations but no means to act on them then you can always create an issue on Github.
Library |
Function |
SHA2017 |
Disobey 2019 |
HackerHotel 2019 |
CampZone 2019 |
CampZone 2020 |
display |
Control the display of your badge: create and display text and graphics |
✅ |
✅ |
✅ |
✅ |
✅ |
buttons |
Read button status and attach callback functions to button interactions |
✅ |
✅ |
✅ |
✅ |
❌ |
wifi |
Abstraction layer wrapping the network API for connection to WiFi networks |
✅ |
✅ |
✅ |
✅ |
✅ |
system |
Abstraction layer for starting apps and controlling badge behaviour and sleep mode |
✅ |
✅ |
✅ |
✅ |
✅ |
consts |
Reference containing constants describing your badge and it’s firmware |
✅ |
✅ |
✅ |
✅ |
✅ |
audio |
Easy to use wrapper around sndmixer for playing audio files |
❌ |
❌ |
❌ |
❌ |
✅ |
sndmixer |
Audio related functions in active development, may change at ANY time |
❌ |
Partially |
✅ |
❌ |
✅ |
terminal |
Helper functions for presenting a user interface over the serial port or telnet |
✅ |
✅ |
✅ |
✅ |
✅ |
opus |
Opus encoding and decoding |
❌ |
❌ |
❌ |
❌ |
❌ |
neopixel |
Control the addressable LEDs on your badge |
✅ |
❌ |
✅ |
❌ |
❌ |
mpu6050 |
MPU6050 accelerometer and gyroscope control |
❌ |
❌ |
❌ |
✅ |
❌ |
ugTTS |
A small library to generate and play back Text-to-Speech voice messages |
❌ |
❌ |
❌ |
❌ |
✅ |
espnow |
Mesh networking API utilizing the Espressif ESPNOW features of the ESP32 |
✅ |
✅ |
✅ |
✅ |
❌ |
hid |
Send keyboard and mouse events over USB (only on supported boards) |
❌ |
❌ |
❌ |
❌ |
✅ |
midi |
Send MIDI messages over USB (only on supported boards) |
❌ |
❌ |
❌ |
❌ |
✅ |
keypad |
CampZone 2020 specific silicon keypad button event handler |
❌ |
❌ |
❌ |
❌ |
✅ |
touchpads |
Register callbacks that trigger when ESP32 touch pads are touched |
❌ |
❌ |
❌ |
❌ |
✅ |
samd |
Disobey 2019 specific hardware interface module |
❌ |
✅ |
❌ |
❌ |
✅ |
rgb |
Legacy display API for CampZone 2019 badges |
❌ |
❌ |
❌ |
✅ |
❌ |
keyboard |
Display a text entry form complete with on-screen-keyboard |
✅ |
✅ |
✅ |
❌ |
❌ |
umqtt |
MQTT client library |
✅ |
✅ |
✅ |
✅ |
✅ |
ssd1306 |
Direct SSD1306 display control (will be removed in a future release) |
❌ |
❌ |
❌ |
❌ |
❌ |
erc12864 |
Direct ERC12864 display control (will be removed in a future release) |
❌ |
✅ |
❌ |
❌ |
❌ |
eink |
Direct E-INK display control (will be removed in a future release) |
✅ |
❌ |
❌ |
❌ |
❌ |
rtc |
Legacy real-time-clock API (please use machine.RTC and utime instead) |
✅ |
✅ |
✅ |
✅ |
✅ |
_buttons |
Generic GPIO button handler API, usefull for adding extra buttons to GPIO headers |
✅ |
✅ |
✅ |
✅ |
✅ |
voltages |
API for reading various voltages, exact functionality differs per badge |
❌ |
❌ |
❌ |
❌ |
❌ |
esp32_ulp |
Collection of helper functions for using the Ultra Low Power co-processor |
✅ |
✅ |
✅ |
✅ |
✅ |
APIs that differ from their upstream counterparts
Other libraries and APIs
This section lists most of the other libraries that you can use in your apps.
Library |
Function |
Documentation |
math |
Mathematical functions |
MicroPython |
cmath |
Mathematical functions for complex numbers |
MicroPython |
ubinascii |
Utilities for working with binary data (Hex-string, base64 and CRC32 calculation |
MicroPython |
ucollections |
Collection and container types |
MicroPython |
uerrno |
System error code reference |
MicroPython |
uhashlib |
SHA1 and SHA256 hashing algorithms |
MicroPython |
uheapq |
Heap queue algorithm |
MicroPython |
uio |
Input/output streams |
MicroPython |
ujson |
JSON encoding and decoding |
MicroPython |
uos |
Basic “operating system” services |
MicroPython |
ure |
Simple regular expressions |
MicroPython |
uselect |
Wait for events on a set of streams |
MicroPython |
usocket |
Sockets (TCP, UDP) |
MicroPython |
ussl |
SSL/TLS module |
MicroPython |
ustruct |
Pack and unpack primitive data types |
MicroPython |
utime |
Time related functions |
MicroPython |
uzlib |
Zlib decompression |
MicroPython |
_thread |
Multithreading support |
MicroPython |
gc |
Control the garbage collector |
MicroPython |
sys |
System specific functions |
MicroPython |
machine |
Functions related to the hardware (Note: different from upstream version) |
[BADGE.TEAM]](machine) |
micropython |
Access and control MicroPython internals |
MicroPython |
network |
Network configuration (Please use the wifi library instead when possible) |
MicroPython |
esp |
ESP32 specific functions (Note: direct flash access has been disabled) |
MicroPython |
Utilities
Library |
Function |
pye |
Built-in text editor |
2.2.3.1 - Display
The display module is available on platforms which have the framebuffer driver enabled. It allows for controlling the display of your device.
Available on: ✅ CampZone 2020 ✅ Disobey 2020 ✅ CampZone 2019 ✅ HackerHotel 2019
✅ Disobey 2019 ✅ SHA2017
Reference
Command |
Parameters |
Description |
flush |
[flags] |
Flush the contents of the framebuffer to the display. Optionally you may provide flags (see the table down below) |
size |
[window] |
Get the size (width, height) of the framebuffer or a window as a tuple |
width |
[window] |
Get the width of the framebuffer or a window as an integer |
height |
[window] |
Get the height of the framebuffer or a window as an integer |
orientation |
[window], [angle] |
Get or set the orientation of the framebuffer or a window |
getPixel |
[window], x, y |
Get the color of a pixel in the framebuffer or a window |
drawRaw |
[window], x, y, width, height, data |
Copy a raw bytes buffer directly to the framebuffer or the current frame of a window. The length of the bytes buffer must match the formula width*height*(bitsPerPixel//8). This is a direct copy: color format (bitsPerPixel) must match the specific display of the badge this command is used on. |
drawPixel |
[window], x, y, color |
Draw a pixel in the framebuffer or a window |
drawFill |
[window], color |
Fill the framebuffer or a window |
drawLine |
[window], x0, y0, x1, y1, color |
Draw a line from (x0, y0) to (x1, y1) |
drawTri(angle) |
[window], x0, y0, x1, y1, x2, y2 |
Draws a filled triangle |
drawRect |
[window], x, y, width, height, filled, color |
Draw a rectangle at (x, y) with size (width, height). Set the filled parameter to False to draw only the border, or set it to True to draw a filled rectangle. |
drawQuad* |
[window], x0, y0, x1, y1, x2, y2, x3, y3 |
Draws a four-pointed shape between (x0, y0), (x1, y1), (x2, y2) and (x3, y3), always filled |
drawCircle |
[window], x0, y0, radius, a0, a1, fill, color |
Draw a circle with center point (x0, y0) with the provided radius from angle a0 to angle a1, optionally filled (boolean) |
drawText |
[window], x, y, text, [color], [font], [x-scale], [y-scale] |
Draw text at (x, y) with a certain color and font. Can be scaled (drawn with rects instead of pixels) in both the x and y direction |
drawPng |
[window], x, y, [data or filename] |
Draw a PNG image at (x, y) from either a bytes buffer or a file |
getTextWidth |
text, [font] |
Get the width a string would take if drawn with a certain font |
getTextHeight |
text, [font] |
Get the height a string would take if drawn with a certain font |
pngInfo |
[data or filename] |
Get information about a PNG image |
windowCreate |
name, width, height |
|
windowRemove |
name |
|
windowMove |
name, x, y |
|
windowResize |
name, width, height |
|
windowVisibility |
name, [visible] |
|
windowShow |
name |
|
windowHide |
name |
|
windowFocus |
name |
|
windowList |
- |
|
translate* |
[window], x, y |
Move the canvas of the window by (x, y) |
rotate* |
[window], angle |
Rotate the canvas of the window by an angle (in randians) |
scale* |
[window], x, y |
Scale the canvas of the window by (x, y) |
pushMatrix* |
[window] |
Save the current transformation for later, may be more than one |
popMatrix* |
[window] |
Restore the transformation pushed earlier, may be more than one |
clearMatrix* |
[window], [keep-stack] |
Clears the current matrix, and also the matrix stack unless keep-stack is true |
getMatrix* |
[window] |
Returns an array representing the current matrix for the window |
setMatrix* |
[window], [matrix] |
Sets the current matrix to the array representing it |
matrixSize* |
[window] |
Returns the current size of the matrix stack for the window |
* This command is only available if you run a firmware with graphics acceleration, and the respective part enabled in the component config under Driver: framebuffer.
Currently, badges have these features disabled by default.
flag |
platform |
description |
FLAG_FORCE |
All |
Force flushing the entire screen. |
FLAG_FULL |
All |
Force flushing the entire screen. |
FLAG_LUT_GREYSCALE |
All with greyscale: SHA2017 |
Simulate greyscale. |
FLAG_LUT_NORMAL |
All with e-ink |
Normal speed flush. |
FLAG_LUT_FAST |
All with e-ink |
Faster flush. |
FLAG_LUT_FASTEST |
All with e-ink |
Much faster flush. |
Color representation
Colors are always represented in 24-bit from within Python, in the 0xRRGGBB format. This matches HTML/CSS colors which are #RRGGBB as well.
Devices with a smaller colorspace will not actually store the exact color information provided.
For displays with a color depth of less than 24-bit the display driver will automatically mix down the colors to the available color depth.
This means that even if you have a black and white display 0x000000 is black and 0xFFFFFF is white.
Examples
Setting one pixel
import display
x = 2
y = 3
display.drawPixel(x, y, 0x00FF00) # Set one pixel to 100% green
display.flush() # Write the contents of the buffer to the display
Drawing a line
import display
display.drawFill(0x000000) # Fill the screen with black
display.drawLine(10, 10, 20, 20, 0xFFFFFF) # Draw a white line from (10,10) to (20,20)
display.flush() # Write the contents of the buffer to the display
Drawing text
import display
display.drawFill(0x000000) # Fill the screen with black
display.drawText(10, 10, "Hello world!", 0xFFFFFF, "permanentmarker22") # Draw the text "Hello world!" at (10,10) in white with the PermanentMarker font with size 22
display.flush() # Write the contents of the buffer to the display
Drawing a rectangle
import display
display.drawFill(0x000000) # Fill the screen with black
display.drawRect(10, 10, 10, 10, False, 0xFFFFFF) # Draw the border of a 10x10 rectangle at (10,10) in white
display.drawRect(30, 30, 10, 10, True, 0xFFFFFF) # Draw a filled 10x10 rectangle at (30,30) in white
display.flush() # Write the contents of the buffer to the display
Spinning a box
Note: as described earlier, matrix transformations are not enabled in the firmware by default
import display, math
# Note: radians are an angle unit where PI (math.pi) is half a rotation
display.clearMatrix() # Clear the matrix stack, just in case it wasn't already
display.translate(display.width() / 2, display.height() / 2) # Go to the middle of the screen
# Everything is now offset as if the middle of the screen is X/Y (0, 0)
while True:
display.drawFill(0xffffff) # Fill the screen with white
display.rotate(math.pi * 0.1) # This will continually rotate the screen by a small amount
display.drawRect(-20, -20, 40, 40, True, 0x000000) # Simply draw a rectangle, which will then spin
display.flush() # Flush, show everything
Spinning text
Note: as described earlier, matrix transformations are not enabled in the firmware by default
Similarly to spinning a box, you can also spin text this way.
import display, math
# Note: radians are an angle unit where PI (math.pi) is half a rotation
text = "Well hello there!" # Whatever you want to show
font = "7x5" # Pick a font
scale = 2 # You can scale text, too!
display.clearMatrix() # Clear the matrix stack, just in case it wasn't already
display.translate(display.width() / 2, display.height() / 2) # Go to the middle of the screen
# Everything is now offset as if the middle of the screen is X/Y (0, 0)
while True:
display.drawFill(0xffffff) # Fill the screen with white
display.rotate(math.pi * 0.1) # This will continually rotate the screen by a small amount
textWidth = display.getTextWidth(text, font) # Get the size so we can center the text
textHeight = display.getTextHeight(text, font)
display.pushMatrix() # Save the transformation for later
display.scale(scale, scale) # Scale the text
display.translate(-textWidth / 2, -textHeight / 2) # Move the canvas so the text is centered
# It is important you scale first, then translate
display.drawText(0, 0, text, 0x000000, font) # Spinny texts
display.popMatrix() # Restore the transformation
display.flush() # Flush, show everything
More complex graphics
Note: as described earlier, matrix transformations are not enabled in the firmware by default
Now you’ve spun a box and some text, what about something more complicated?
Let’s say we draw a boat on a wave!
First, we draw the boat using some shapes:
import display, math
def drawBoat():
display.pushMatrix()
drawBoatBottom(0x000000)
display.translate(-4, 0) # Move just a little so the mast lines up nicely
drawBoatMast(0x000000, 0x000000)
display.popMatrix()
def drawBoatMast(mastColor, flagColor):
# The points drawn, by place:
# 0--1
# | |
# | 6
# | |\
# | 5-4
# | |
# 3--2
x0, y0 = 0, -23
x1, y1 = 3, -23
x2, y2 = 3, 0
x3, y3 = 0, 0
x4, y4 = 12, -10
x5, y5 = 3, -10
x6, y6 = 3, -20
display.drawQuad(x0, y0, x1, y1, x2, y2, x3, y3, mastColor) # This is the mast: points 0, 1, 2, 3
display.drawTri(x4, y4, x5, y5, x6, y6, flagColor) # This is the flag: points 4, 5, 6
def drawBoatBottom(color):
# The points drawn, by place:
# 0--------1
# \ /
# 3----2
x0, y0 = -20, 0
x1, y1 = 20, 0
x2, y2 = 16, 8
x3, y3 = -16, 8
display.drawQuad(x0, y0, x1, y1, x2, y2, x3, y3, color)
Now, to test your boat drawing action:
import display, math
# Put the boat drawing functions here
display.clearMatrix() # Don't forget
display.translate(30, 30) # Move to where you want to draw the boat
display.drawFill(0xffffff) # Clear the screen once more
drawBoat() # Draw the boat of course
display.flush() # Flush display; boat is now visible
Then, we’ll draw a wave and a sun:
import display, math
def drawSun(color): # Draws the sun with a circle and some lines
display.pushMatrix()
display.translate(-3, -3 - display.height()) # This is where the sun will orbit around
# We do - display.height() here because we set the origin to be at the bottom of the screen earlier
display.drawCircle(0, 0, 30, 0, 360, True, color) # The sun
display.rotate(sunOffset)
for i in range(20): # Draw lines as the sun's rays
display.rotate(math.pi / 10)
display.drawLine(0, 35, 0, 45, color)
display.popMatrix()
# For good measure.
display.clearMatrix()
display.translate(0, display.height())
sunOffset = 0
offset = 0
boatX = display.width() / 6
boatAngle = 0
boatY = 0
while True:
display.drawFill(0xffffff)
drawSun(0x000000) # Draw the sun
for i in range(display.width()): # Draw the waves by plotting points
wave = math.sin((i + offset) * math.pi / 35) * 8 - 35
display.drawPixel(i, wave, 0x000000)
if i & 1:
for j in range(round(wave - 1) | 1, 0, 2):
display.drawPixel(i, j + ((i >> 1) & 1) + 1, 0x000000)
offset += 8 # Move the waves over by a bit
sunOffset += math.pi * 0.025 # Spin the sun by a bit
display.flush()
Finally, you can draw the boat on the wave, by adding some code:
while True:
display.drawFill(0xffffff)
drawSun(0x000000)
for i in range(display.width()):
wave = math.sin((i + offset) * math.pi / 35) * 8 - 35
display.drawPixel(i, wave, 0x000000)
if i & 1:
for j in range(round(wave - 1) | 1, 0, 2):
display.drawPixel(i, j + ((i >> 1) & 1) + 1, 0x000000)
# vvvv HERE vvvv
display.pushMatrix() # Save the transformation, we're going to mess with it
waterLevelBeforeBoat = math.sin((boatX + 2 + offset) * math.pi / 35) * 8 - 35
boatY = math.sin((boatX + offset) * math.pi / 35) * 8 - 35
# Calculate the two water levels, one at and one before the boat
# By doing this, we know how and where to position the boat
boatAngle = math.atan2(waterLevelBeforeBoat - boatY, 2) # Using atan2 to find the angle required to rock the boat with the wave
display.translate(boatX, boatY - 6) # Now, position the boat
display.rotate(boatAngle)
drawBoat() # And draw the boat
display.popMatrix() # Undo our changes to the transformation
# ^^^^ HERE ^^^^
offset += 8
sunOffset += math.pi * 0.025
display.flush()
The source code for the boat can be found here:
gist: boat.py
Known problems
- Rotation of the contents of windows does not work correctly in combination with rotation of the screen itself
- There is no method available to list the fonts available on your platform
- There is no method for providing a custom font
- There is no anti-aliassing support
2.2.3.2 - Buttons
The buttons API allows you to read the state of the buttons on a badge.
This API encapsulates the drivers for different button types.
Badge support
This API is currently supported on the following badges:
- SHA2017
- Hackerhotel 2019
- Disobey 2019
- CampZone 2019
- Disobey 2020
- Fri3dcamp 2018
Support for GPIO buttons and touch-buttons via the MPR121 touch controller IC are supported. Touch buttons using the touch button features of the ESP32 can not be used (yet).
Reference
Command |
Parameters |
Description |
attach |
button, callback function |
Attach a callback to a button |
detach |
button |
Detach a callback from a button |
value |
button |
Get the current value of a button |
getCallback |
button |
Get the current callback of a button |
pushMapping |
[mapping] |
Switch to a new button mapping |
popMapping |
none |
Switch back to the previous button mapping |
rotate |
degrees |
Adapt the button layout to an orientation. Accepts 0, 90, 180 and 270 as values. |
Name |
SHA2017 |
Hackerhotel 2019 |
Disobey 2019 |
CampZone 2019 |
Disobey 2020 |
Fri3dCamp 2018 |
OpenHardwareSummit 2018 |
A |
Yes |
Yes |
Yes |
Yes |
Yes |
|
|
B |
Yes |
Yes |
Yes |
Yes |
Yes |
|
|
SELECT |
Yes |
Yes |
No |
No |
Yes |
|
|
START |
Yes |
Yes |
No |
No |
Yes |
|
|
UP |
Yes |
Yes |
Yes |
Yes |
Yes |
|
|
DOWN |
Yes |
Yes |
Yes |
Yes |
Yes |
|
|
LEFT |
Yes |
Yes |
Yes |
Yes |
Yes |
|
|
RIGHT |
Yes |
Yes |
Yes |
Yes |
Yes |
|
|
Name |
SHA2017 |
Hackerhotel 2019 |
Disobey 2019 |
CampZone 2019 |
Disobey 2020 |
Fri3dCamp 2018 |
OpenHardwareSummit 2018 |
A |
|
|
|
|
|
|
|
B |
|
|
Exit app |
Exit app |
|
|
|
SELECT |
|
|
|
|
|
|
|
START |
Exit app |
Exit app |
|
|
Exit app |
|
|
UP |
|
|
|
|
|
|
|
DOWN |
|
|
|
|
|
|
|
LEFT |
|
|
|
|
|
|
|
RIGHT |
|
|
|
|
|
|
|
2.2.3.3 - System
The system API allows you to control basic features your app needs to provide a smooth experience to the user.
Reference
Command |
Parameters |
Description |
reboot |
- |
Reboot the badge into the currently running app |
sleep |
[duration], [status] |
Start sleeping forever or for the provided duration (in seconds). By defaut the function shows the fact that the badge is sleeping on the serial console, this can be disabled by setting the status argument to False. |
start |
app, [status] |
Start an app. Optionally shows that an app is being started on the screen and in the serial console, for this to happen the status variable must be set to True. |
home |
[status] |
Start the splash screen / default application. To show a message to the user set the optional status flag to True. |
launcher |
[status] |
Start the application launcher. To show a message to the user set the optional status flag to True. |
shell |
[status] |
Start a raw Python REPL prompt. To show a message to the user set the optional status flag to True. |
ota |
[status] |
Initiate an Over-The-Air update session. Does NOT check if a newer firmware is available. To prevent hijacking other peoples badges it is NOT possible to provide a different update server or URL at this time. |
serialWarning |
- |
Show a message telling the user that the currently running app can only be controlled over the USB-serial connection. |
crashedWarning |
- |
Show a message telling the user that the currently running app has crashed. |
isColdBoot |
- |
Returns True if the badge was started from RESET state. This function will only ever return true if the currently runing app was set as the default app. |
isWakeup |
[timer], [button], [infrared], [ulp] |
Returns True if the badge was started from a WARM state. Normally this can be any warm state, however by setting the parameters specific wake reasons can be selected or ruled-out. |
currentApp |
- |
Returns the name of the currently running app. |
Examples
Starting an app
import system
system.start("2048") # Start the 2048 app (fails if this app has not been installed)
Going back to the launcher
import system
system.launcher()
Going back to the homescreen
import system
system.home()
Restarting the current app
import system
system.reboot()
Sleep for 60 seconds, then return to the current app
import system
system.sleep(60000)
Querying the name of the currently runnig app
import system
appName = system.currentApp()
if not appName:
print("This code is running either in the shell or in the boot context")
else:
print("Currently running app: "+appName)
2.2.3.4 - Appconfig
The appconfig API apps to register their user-configurable settings. By using this API, app settings are shown in the Settings page of the WebUSB website for supported badges.
Available on: ✅ CampZone 2020
Example
import appconfig
settings = appconfig.get('my_app_slug_name', {'option_1': 'defaultvalue', 'awesomeness': 1337, 'option_3': [1,2,3]})
mynumber = settings['awesomeness']
Reference
Function |
Parameters |
Returns |
Description |
get |
app_slug_name, default_options |
Object |
Gets the user-set options for the app with the given name. If no configuration exists yet, returns the object passed into default_options. |
2.2.3.5 - Audio
The audio API allows you to easily play audio files or stream URLs (.mp3, .wav, and modtracker .mod, .s3m, .xm). It is a wrapper around sndmixer, which can do much more but is a bit more verbose.
Available on: ✅ CampZone 2020
Example
import audio
channel_id = audio.play('/apps/myapp/doom.mp3', volume=150)
Reference
Function |
Parameters |
Returns |
Description |
play |
filename_or_url, [volume], [loop], [sync_beat], [start_at_next], [on_finished] |
Channel ID (int) |
Play a file (e.g. ‘/apps/myapp/sound.mp3’) or stream from a url (e.g. ‘http://my.stream/song.mp3'). Filename or url needs to end with the filetype (.mp3, .wav, .mod, .s3m, .xm).
Use volume (0-255) to set the volume for this channel (defaults to system volume).
Use loop=True to repeat after playback is done.
Use sync_beat=(BPM of the music, e.g. 120) and start_at_next (1-32) to start playback at the next x-th 8th note (example: 1 starts at next 8th, 2 at next 4th (namely 2x an 8th), 4 at half note, 8 at whole note, 32 at whole bar).
If on_finished is a function, it is called when the playback ends.
Resources are automatically freed after playback finishes. |
stop_looping |
channel_id |
- |
Cancel the looping status of a channel. This will end playback after the sound is finished with its current playback. |
stop_channel |
channel_id |
- |
Cancel the playback of a channel immediately, and free its resources. |
Known problems
- Due to a bug in (presumably) our MicroPython version, stopping audio playback from a streaming URL causes a freeze in the MicroPython task. Therefore, you have to reboot your badge before you can play a different URL.
- The current implementation can play around 4 wav files or 2 mp3 files at the same time without glitches or slowdowns. Any more can cause noticable artifacts.
2.2.3.6 - HID Keyboard & Mouse
The HID API allows you to make your CampZone 2020 badge act like a keyboard and mouse over USB. You can use it to type text, press key combinations, and click or move the mouse cursor.
Available on: ✅ CampZone 2020
Example
import hid, keycodes
hid.keyboard_type("Cyber")
Reference
Function |
Parameters |
Returns |
Description |
keyboard_type |
text |
- |
Automatically sends the right key press and release events for the keys needed to type a text. Will use the SHIFT modifier for uppercase keys too. Blocks until the whole text has been typed. |
keyboard_press_keys |
keys, [modifier] |
- |
Send key down commands over USB for the given keys. The optional modifier can be used to convey pressing ctrl, alt, shift, or the GUI/Windows button. |
keyboard_release_keys |
- |
- |
Cancels all current key presses by sending a release command. |
You can learn more in-depth about how this module works by checking out its source here
A more complex example
import hid, keycodes, time
# Presses ctrl+alt+delete
keys = bytes([keycodes.DELETE])
modifier = bytes([keycodes.MOD_LEFT_CONTROL & keycodes.MOD_LEFT_ALT])
hid.keyboard_press_keys(keys, modifier)
time.sleep(0.1)
hid.keyboard_release()
Known problems
- The USB mouse interface is not yet present in the firmware at time of writing. A future Over-the-Air update will include it.
2.2.3.7 - Keypad
The keypad API allows you to call functions when someone presses a button on the silicone keypad of their CampZone 2020 badge.
Available on: ✅ CampZone 2020
Example
import keypad
def on_key(key_index, is_pressed):
# Print to the serial port when a button is pressed or released
print('Key ' + key_index + ': ' + is_pressed)
keypad.add_handler(on_key)
Reference
Command |
Parameters |
Returns |
Description |
add_handler |
handler |
- |
Registers a handler function, that is called any time a keypad button is pressed or released. The first argument is the key index (0 top left, 3 top right, 12 bottom left, etc.), and the second argument is whether the button is currently pressed or not. |
remove_handler |
handler |
- |
Removes previously registered handler function, so it won’t be called anymore. |
get_state |
- |
touch_state |
Returns a list of 16 booleans indicating for each button whether they are currently pressed. |
2.2.3.8 - MIDI Music Controller
The MIDI API allows you to make your CampZone 2020 badge act like a MIDI music controller over USB. You can use it to play music on your computer, or control music making programs like Ableton Live.
Available on: ✅ CampZone 2020
Example
import midi, time
midi.note_on(midi.CENTRAL_C)
time.sleep(1)
midi.note_off(midi.CENTRAL_C)
midi.note_on(midi.CENTRAL_C+2) # D note (C plus two half tones)
time.sleep(1)
midi.note_off(midi.CENTRAL_C+2)
Reference
Function |
Parameters |
Returns |
Description |
note_on |
note, [velocity], [midi_channel] |
- |
Sends a note start command with the given optional velocity (“volume”, 0-127, default 127). You can change the MIDI channel if wanted (0-15). |
note_off |
note, [velocity], [midi_channel] |
- |
Sends a note stop command with the given optional velocity (“volume”, 0-127, default 127). You can change the MIDI channel if wanted (0-15). |
The CampZone 2020 hardware supports not only MIDI OUT, but also IN. This means you can receive messages from e.g. your audio program. Ableton Live uses this to command the LEDs on MIDI controllers. However, there is currently no Python API for this yet. It may be included in a future Over-the-Air update.
2.2.3.9 - Touchpads
The touchpads API allows you to call functions when someone presses a touchpad.
Available on: ✅ CampZone 2020
Example
import touchpads
def on_left(is_pressed):
print('Left button: ' + is_pressed)
def on_ok(is_pressed):
print('OK button: ' + is_pressed)
touchpads.on(touchpads.LEFT, on_left)
touchpads.on(touchpads.OK, on_ok)
Reference
Function |
Parameters |
Returns |
Description |
on |
touchpad, callback |
- |
Set a callback that gets called whenever the given touchpad touch state changes. First argument to this function is the pressed state. Touchpad can be touchpads.LEFT, RIGHT, HOME, CANCEL, or OK. |
off |
touchpad |
- |
Remove a previously set callback. |
2.2.3.10 - ugTTS Text-to-Speech
The ugTTS API allows you to turn text into synthesized speech by querying Google Translate over WiFi. Either save it as an mp3 file, or play it directly. This module is based on the popular gTTS library.
Available on: ✅ CampZone 2020
Example
import wifi, ugTTS
wifi.connect()
if not wifi.wait():
print('Oh no panic no WiFi')
import system; system.launcher()
ugTTS.speak('This is a test') # Plays over speakers
ugTTS.text_to_mp3('This is a test too', '/cache/test_speech.mp3') # Saves to file for later playback
ugTTS.speak("Slaap kindje slaap", lang='nl') # Dutch
ugTTS.speak("Dommage", lang='fr', volume=100) # French and set volume
Reference
Function |
Parameters |
Returns |
Description |
speak |
text, [lang], [volume] |
- |
Send piece of text to Google Translate and plays back the synthesized speech at given volume (0-255, default 255). You can optionally change the language, for values check gTTS library. |
text_to_mp3 |
text, filename, [lang] |
- |
Same as speak() except it saves to the given filename. |
Known problems
- There is a finite length for the text before Google starts rejecting it.
- We don’t expose the interface to set details like speech speed. Pull requests welcome.
2.2.3.11 - WiFi
The wifi API allows you to connect to WiFi networks easily.
Available on: ✅ CampZone 2020 ✅ Disobey 2020 ✅ CampZone 2019 ✅ HackerHotel 2019
✅ Disobey 2019 ✅ SHA2017
Example
import wifi
wifi.connect() # Connect to the WiFi network using the stored credentials
if not wifi.wait():
print("Unable to connect to the WiFi network.")
else:
print("You are now connected to WiFi!")
Reference
Function |
Parameters |
Description |
connect |
[ssid], [password] |
Connect to a WiFi network. By default the stored credentials are used, but you can optionally provide the SSID (and password) of the network to connect to. |
disconnect |
- |
Disconnect from the WiFi network. |
status |
- |
Returns True if connected and False if not connected. |
wait |
[timeout] |
Wait until a connection with the WiFi network has been made or until the timeout time is reached. Timeout is in seconds but may be provided in 10ths of seconds. If no timeout is provided the default timeout is used. Returns True if connected after waiting and False if a connection could not be made before the timeout. |
ntp |
[only-if-needed], [server] |
Synchronize the Real-Time-Clock with the network. Normally the synchronisation is only started when the system clock has not yet been set since the last reset. This can be overruled by setting the only-if-needed parameter to False. By default the “‘pool.ntp.org” server pool is used. |
Wait, is that all you can do with WiFi?!
No, of course not. The whole network API from the mainline MicroPython project is available on the badge.team firmware.
Here are some examples for doing the stuff you’re probably looking for:
Connecting to a WiFi network, the hard way…
import network, machine, time
# First we fetch the stored WiFi credentials
ssid = machine.nvs_getstr("system", "wifi.ssid")
password = machine.nvs_getstr("system", "wifi.password")
# Create the station (WiFi client) interface
sta_if = network.WLAN(network.STA_IF)
# Activate the station interface
sta_if.active(True)
# Connect using the credentials
if ssid and password:
sta_if.connect(ssid, password) # Secured WiFi network
elif ssid: # Password is None
sta_if.connect(ssid) # Open WiFi network
else:
print("ERROR: no SSID provided. Please configure WiFi (or manually set the variables at the top of this example)")
wait = 50 # 50x 100ms = 5 seconds
while not sta_if.isconnected() and wait > 0:
wait -= 1
time.sleep(0.1) # Wait 100ms
if sta_if.isconnected():
print("Connected!")
ip_addr, netmask, gateway, dns_server = sta_if.ifconfig()
print("My IP address is '{}', with netmask '{}'.".format(ip_addr, netmask))
print("The gateway is at '{}' and the DNS server is at '{}'.".format(gateway, dns_server))
else:
print("Failed to connect to the WiFi network.")
Scanning for networks
import network
sta_if = network.WLAN(network.STA_IF)
sta_if.active(True)
data = sta_if.scan()
for item in data:
print("SSID: {}, BSSID: {}. CHANNEL: {}, RSSI: {}, AUTHMODE: {} / {}, HIDDEN: {}".format(item[0], item[1], item[2], item[3], item[4], item[5], item[6]))
Creating an access-point
import network
ap_if = network.WLAN(network.AP_IF)
ap_if.config(essid="badgeNetwork", authmode=network.AUTH_WPA2_PSK, password="helloworld") # Create a network called "badgeNetwork" with password "helloworld"
ap_if.active(True)
Note: if you get “unknown error 0x000b” after running the config command then the password you chose is too short.
We used the loboris micropython fork (<- link) as the core of our badge firmware. The network API comes directly from his project.
The API looks a lot like the official MicroPython network API (<- link).
2.2.3.12 - Terminal
The term API allows you to make more advanced use of the serial console.
Reference
Command |
Parameters |
Description |
goto |
x, y |
Move the cursor to (x, y) |
home |
- |
Move the cursor to (1, 1) |
clear |
- |
Clear the terminal |
color |
[foreground], [backgrund], [style] |
Set the color used for writing to the terminal |
header |
[clear], [text] |
Prints a header on the top of the screen. Optionally clears the screen. You can include your own text to be added after the device name. |
menu |
title, items, [selected], [text], [item-width] |
Shows a menu with a specified title and menu-items. The selected menu item can be set. If not set the first item will be selected by default. Optionally a text can be provided which gets printed at the top of the menu screen. If the maximum string length of one of the menu options exceeds 32 characters a different length may be provided to make the menu options match in length. The fuction returns the location of the selected menu-item. This function is blocking. |
prompt |
prompt, x, y, [buffer] |
Prompt for text to be entered by the user. The prompt will appear at (x, y) and before the prompt the prompt text will appear. If a buffer is provided the text buffer will contain the provided value. This function is blocking. |
setPowerManagement |
pointer to the power-management task |
By providing a pointer to the power-management task running in your app this function will reset the timer to 5 minutes each time the user changes his selection in the menu shown by the menu() function. This was mainly intended as an internal function and a more refined version will probably be defined somewhere in the future… |
2.2.3.13 - Machine
The machine API makes it possible to access certain hardware interfaces directly, allowing for example direct control of GPIOs, busses (I2C) and other interfaces.
This API is variation on the standard MicroPython machine API which has been extended and modified.
Not all features described in the official MicroPython documentation are available on the BADGE.TEAM platform firmware. And additionally some functions will differ in syntax from the official MicroPython for ESP32 firmware.
The NVS functions allow for storage and retrieval of small amounts of data to be stored. This API is used to access WiFi credentials and other system information and can be used to manipulate system settings as well as for storing settings specific to your app.
The Pin API can be used to directly control GPIOs of your badge.
The machine API for I2C allows you to control the system I2C bus of your badge, the I2C bus exposed on the SAO, Grove, Qwiic or other extension ports as well as a second I2C bus on any two broken out GPIOs of your choice.
Direct control over the SPI bus is currently not supported on the BADGE.TEAM platform firmware. Sorry!
2.2.3.13.1 - Non Volatile Storage
This page describes the Non-Volatile-Storage (NVS) functions of the machine API. This NVS is used to store settings such as WiFi credentials and your nickname.
The NVS storage is a function of the ESP-IDF which allows for settings to be stored in a special partition on the flash of the ESP32 and is ment for small quantities of data.
If you want to store large(er) amounts of data we suggest you use the filesystem functions of MicroPython to store your data on the FAT partition instead.
Reference
Command |
Parameters |
Description |
nvs_set_u8 |
[space], [key], [value] |
Store an unsigned 8-bit value |
nvs_get_u8 |
[space], [key] |
Retrieve an unsigned 8-bit value |
nvs_set_u16 |
[space], [key[, [value] |
Store an unsigned 16-bit value |
nvs_get_u16 |
[space], [key] |
Retreive an unsigned 16-bit value |
nvs_setint |
[space], [key], [value] |
Store a signed integer value |
nvs_getint |
[space], [key] |
Retreive a signed integer value |
nvs_setstr |
[space], [key], [value] |
Store a string |
nvs_getstr |
[space], [key] |
Retreive a string |
nvs_erase |
[space], [key] |
Remove an entry from the NVS |
nvs_erase_all |
[space] |
Remove all entries in a space from the NVS |
NVS settings used by the firmware
The following list describes the settings stored by the BADGE.TEAM firmware.
Space |
Key |
Type |
Function |
owner |
name |
string |
The nickname of the owner of the badge |
system |
default_app |
string |
The app/egg launched on powerup |
NVS settings for your app
Please use the slug name of your app as the name of the space used to store your settings.
Examples
Nickname
Reading the nickname
import machine
nickname = machine.nvs_getstr("owner", "name")
print("Your nickname is '{}'!".format(nickname))
Setting the nickname
import machine
machine.nvs_setstr("owner", "name", "badge.team")
2.2.3.13.2 - The I2C bus
The machine API for I2C allows you to control the system I2C bus of your badge, the I2C bus exposed on the SAO, Grove, Qwiic or other extension ports as well as a second I2C bus on any two broken out GPIOs of your choice.
The ESP32 has two I2C controllers, each of which can be set to master or slave mode. Most of our badges use one of these I2C controllers for their internal I2C bus.
You can take control over this system I2C bus using the machine API without directly causing issues but be adviced that doing this might possibly disrupt communications with one or more system components like the touch-button controller IC or the display.
Alternatively you can use the I2C API to define a secondary I2C bus on any two broken out GPIO pins.
Direct I2C access
The firmware contains a second API for working with the system I2C bus, allowing you to directly call some ESP-IDF I2C functions from within MicroPython.
(to-do)
Using the MicroPython machine.I2C API
While the directly exposed functions do already allow you to control i2c devices it is also possible to use the MicroPython I2C API on the same bus, simply by creating the bus using the exact settings used by the firmware itself.
The following snippet redefines i2c
to be the MicroPython variant of the API instead of our direct functions. This snippet should work on all badges since it automatically uses the right pins for SDA and SCL as well as the correct bus speed for the board you are using.
import i2c, machine
i2c = machine.I2C(sda=machine.Pin(i2c.GPIO_SDA), scl=machine.Pin(i2c.GPIO_CLK), freq=i2c.SPEED)
If your board does not have a system I2C bus or if you want to use separate GPIOs for connecting your I2C device then you can also define a custom I2C interface on pins you choose. Keep in mind that the ESP32 can handle up to two I2C busses at once so if the firmare itself uses one then you can create only one custom i2c bus interface.
import machine
my_i2c_interface = machine.I2C(sda=machine.Pin(22), scl=machine.Pin(21), freq=100000)
2.2.3.13.3 - Pin: direct GPIO control
Direct GPIO control
The machine.Pin API allows you to directly control GPIOs of the ESP32 on your badge.
Please check the schematics of your badge before using this API. If you configure the GPIOs of the ESP32 in a wrong way your might cause your badge to crash, stop responding or even permanently damage it.
Be carefull!
from machine import Pin
myInput = Pin(0) # GPIO0 (exposed as the "flash" button on most badges)
value = myInput.value()
print("The value of GPIO0 is {}.".format(value))
Basic digital output
from machine import Pin
myOutput = Pin(<GPIO NUMBER>, Pin.OUT) # Check the schematic of your badge to find the numbers which can be entered here
myOutput.value(True) # Set the pin state to 1 or "HIGH"
Interrupts
(To-Do)
Pulse Width Modulation (PWM)
(To-Do)
Wakeup from deep-sleep
(To-Do)
2.2.3.14 - _buttons
2.2.3.15 - consts
2.2.3.16 - eink
2.2.3.17 - erc12864
2.2.3.18 - esp32_ulp
2.2.3.19 - espnow
2.2.3.20 - hub75
2.2.3.21 - keyboard
2.2.3.22 - mpu6050
2.2.3.23 - neopixel
Import the library and start the driver
import neopixel
neopixel.enable()
Sending data to the LEDs
Once you have enabled the driver you can start sending data. The driver expects a bytes object containing a byte per channel. The exact meaning of these bytes depends on the type of addressable leds your device uses. The easiest way to generate the needed bytes object is by converting a list into one by wrapping it with bytes()
.
import neopixel
neopixel.enable()
ledData = [0xFF,0x00,0x00,0x00]
neopixel.send(bytes(ledData))
You can easily repeat patterns by using a simple Python trick: you can “multiply” a list by an amount to have python repeat the list that amount of times. The next example shows this, expecting 3 channels per led and 12 leds to be on the badge. If this is the case then all LEDs on the badge should light up in the same color.
import neopixel
neopixel.enable()
ledData = [0xFF,0x00,0x00] * 12
neopixel.send(bytes(ledData))
Turning all LEDs off
import neopixel
neopixel.enable()
amount_of_channels = 3
amount_of_leds = 12
ledData = [0x00] * amount_of_channels * amount_of_leds
neopixel.send(bytes(ledData))
neopixel.disable()
2.2.3.24 - opus
Encoding data
To encode data, you have to know the sampling rate and number of channels and create an Encoder
:
import opus
sampling_rate = 8000
stereo = False
encoder = opus.Encoder(sampling_rate, stereo)
Then you can use the encoder to encode audio frames. Those may have lengths of 2.5, 5, 10, 20, 40, or 60 milliseconds. Input data should be of type bytes
or bytearray
and contain 16-bit signed integers:
# One frame of data containing 480 null samples
input = bytearray(960)
# Encode the data, using at most 128 bytes for the frame. This would be around 2 kByte/s. At 8 kHz sampling rates, opus will use around 1 kByte/s for mono audio.
output = encoder.encode(input, 128)
Each encoded frame has some metadata at the beginning containing the channel,
frequency, and the encoded size of the frame. This allows combining frames
into one packet.
Decoding data
Decoders do not take any arguments with their constructor, because they take the necessary information from their input frames:
import opus
decoder = opus.Decoder()
The created decoder can handle any data created by opus.Encoder
, even if the
number of channels or the sampling rate differs - it will get reinitialized to
match the new settings.
encoder = opus.Encoder(8000, 0)
decoder = opus.Decoder()
input = bytearray(960)
encoded = encoder.encode(input, 128)
decoded = decoder.decode(encoded)
2.2.3.25 - rgb
2.2.3.26 - samd
2.2.3.27 - sndmixer
(This page is still an ongoing effort and might contain mistakes.)
Starting the audio subsystem
The sound-mixer task runs on the second CPU core of the ESP32 and is able to mix together multiple auto streams and feed them to the DAC of your device.
The sound-mixer task needs to be started before any other audio related function can be used. Starting the sound-mxier task is done using the following API command:
import sndmixer
sndmixer.begin(<number-of-channels>, <enable-stereo>)
Starting the sound-mixer with only one channel and with stereo enabled (sndmixer.begin(1, True)
) results in the best audio quality but does not allow you to play multiple audiostreams at the same time. We recommend you start the sound-mixer task with an amount of channels you actually plan on using.
Note: it is currently not possible to stop the sound-mixer task, change the number of channels or stereo mode without restarting your badge. We’re working on adding this functionality in the future.
Playing MP3 music
MP3 files can be played by reading the MP3 compressed sample data from a bytearray buffer or by reading from a stream by means of the file-descriptor.
Playing an MP3 file directly from a bytearray has the added benefit that you can play the MP3 file multiple times at the same time. This allows you to create basic soundboard effects where the same sample can be triggered while it is already playing.
Playing an MP3 file from a stream (fd) allows for playing larger files without loading them fully into ram before playing, this is usefull for background music as longer MP3 files can be played easily. This method can also be used to play MP3 streams directly from the internet.
Playing an MP3 file using the bytearray method
# Preparation: start the sound-mixer task
import sndmixer
sndmixer.begin(2, True)
# First we load the MP3 file into memory
with open("music.mp3", "rb") as fd:
mp3data = fd.read()
# Then we play the mp3 file
audioStreamId = sndmixer.mp3(mp3data)
# Now that the stream is playing we can set the volume (0-255)
sndmixer.volume(audioStreamId, 50)
Playing an MP3 file using the stream method
# Preparation: start the sound-mixer task
import sndmixer
sndmixer.begin(2, True)
# First we create a file-descriptor pointing to the MP3 file
mp3file = open("music.mp3", "rb")
# Then we play the mp3 file by passing the file-descriptor to the sound-mixer task
audioStreamId = sndmixer.mp3_stream(mp3file)
# Now that the stream is playing we can set the volume (0-255)
sndmixer.volume(audioStreamId, 50)
Playing opus-encoded data
Playback of opus works the same as MP3. You just have to replace mp3
with opus
:
import sndmixer
sndmixer.begin(2, True)
sndmixer.opus_stream(open('snd.opus', 'rb'))
Opus data is expected to have frames formed like this: u8: channels | u8:
sampling_rate / 400 | u16: len | u8[len]: data
, where data
is the actual
opus-encoded data. This is the format produced by the opus
module.
Playing tracker music
The sound-mixer can play mod, s3m and other tracker music files (your mileage may vary).
# Preparation: start the sound-mixer task
import sndmixer
sndmixer.begin(2, True)
# First we load the tracker music file into memory
with open("music.s3m", "rb") as fd:
moddata = fd.read()
# Then we play the tracker music file
audioStreamId = sndmixer.mod(moddata)
# Now that the stream is playing we can set the volume (0-255)
sndmixer.volume(audioStreamId, 50)
Playing wave files
This is ment for playing short sound effects or samples. You could even generate the samples using python if you wanted to!
# Preparation: start the sound-mixer task
import sndmixer
sndmixer.begin(2, True)
# First we load the wave file into memory
with open("music.wav", "rb") as fd:
wavdata = fd.read()
# Then we play the wave file
audioStreamId = sndmixer.wav(wavdata)
# Now that the stream is playing we can set the volume (0-255)
sndmixer.volume(audioStreamId, 50)
Synthesizer
A (very) basic synthesizer is available as well. It currently generates sine, square and saw waves at a frequency and volume of your choosing. Each waveform generated uses one mixer channel.
# Preparation: start the sound-mixer task
import sndmixer
sndmixer.begin(3, True)
# Create the synthesizer channel
synthId = sndmixer.synth()
# Set the volume of the synthesizer channel
sndmixer.volume(synthId, 50)
# Set the frequency to 100Hz
sndmixer.freq(synthId, 100)
2.2.3.28 - ssd1306
2.2.3.29 - umqtt
2.2.3.30 - ussl
The ussl
API provides low-level SSL encryption and certificate verification functions and is used by other APIs such as urequests
.
Reference
Command |
Parameters |
Description |
disable_warning |
Boolean: disable warning |
Disables the warning notifying users that the SSL connection isn’t secure because the server certificate isn’t verified |
verify_letsencrypt |
Boolean: verify server certificate against Letsencrypt root |
Enables verification of the server certificate against the Letsencrypt root certificate |
wrap_socket |
(See upstream Micropython documentation) |
(See upstream Micropython documentation) |
debug_level |
0-4 |
controls the amount of debug information printed |
2.2.3.31 - voltages
2.2.4 - Jupyter Notebook
When coding the badge in (micro)python, it can be useful to use a
Jupyter Notebook. This allows you to keep a
‘scratch pad’ of code snippets that you can easily and quickly adapt
and run on the badge, without having to manually copy-paste code between your
editor and the REPL all the time.
Normally a Jupyter Notebook would run the python code on your development
machine. To make it run the code on your badge instead, you use the
Jupyter MicroPython Kernel.
You can see a quick video of a notebook in action
here.
Installation
This setup works best with Python 3. The easiest way to install is to create a
virtualenv:
~$ mkdir badgehacking
~$ cd badgehacking
~/badgehacking$ python3 -m venv environment
~/badgehacking$ source environment/bin/activate
Install jupyter:
~/badgehacking$ pip install jupyter
Download and install the Jupyter MicroPython Kernel:
~/badgehacking$ git clone https://github.com/goatchurchprime/jupyter_micropython_kernel.git
~/badgehacking$ pip install -e jupyter_micropython_kernel
~/badgehacking$ python -m jupyter_micropython_kernel.install
If all went well, jupyter should now show micropython in the list of available
kernels:
~/badgehacking$ jupyter kernelspec list
Available kernels:
micropython /home/aengelen/.local/share/jupyter/kernels/micropython
python3 /home/aengelen/badgehacking/environment/share/jupyter/kernels/python3
Usage
To start the notebook, first enter the virtualenv again:
~$ cd badgehacking
~/badgehacking$ source environment/bin/activate
Start Jupyter:
~/badgehacking$ jupyter notebook
This should start the jupyter server on your machine, and open a browser window
to interact with it. In that browser window, choose ‘New…’ and select
‘MicroPython - USB’. This will open a new MicroPython-enabled Notebook.
This will show a page with a ‘block’ that accepts python code. You can use
Ctrl+Enter to execute the code in the block, and Alt+Enter to create a new
block.
Before you can execute any commands, you will need to connect the notebook to
your badge via the serial bus by adding the special command %serialconnect
to a block and executing it. When you see Ready.
the connection was
succesful. On some badges you need to issue this command twice.
Limitations
Currently, a disadvantage of the Jupyter Notebook over using the REPL directly
is that code completion (tab completion) is not yet supported in the Jupyter
MicroPython Kernel. Jupyter does support completion with other kernels, so it
is likely possible to add this feature in the future.
Links
The documentation for the Jupyter MicroPython Kernel
is quite good.
2.3 - ESP32: firmware development
New to development for the ESP32 platform firmware? This section will help you get up-and-running with firmware development.
Did you receive an ESP32 based badge at an event for which support is not yet available? Did you build your own ESP32 based device or badge? This section helps get you started with adding support for your badge.
Interested in releasing a badge using our firmware? This section explains the factory testing and provisioning features, as well as how OTA updates and other release and support releated parts of our project work.
2.3.1 - Getting started
Welcome developer! This section describes how to get your development environment set-up so that you can build the BADGE.TEAM ESP32 platform firmware for any of the supported targets.
Introduction
Our firmware has been set-up as an ESP-IDF project. The ESP-IDF is the development framework or SDK released by Espressif. Espressif is improving and updating the ESP-IDF constantly. Unfortunately these updates often introduce breaking changes. Because of this we have included the exact version of the ESP-IDF that we use as a submodule in our firmware GIT repository.
Downloading the project
You can clone the project by running git clone https://github.com/badgeteam/ESP32-platform-firmware.git --recursive
. Git will then create a folder called “ESP32-platform-firmware” containing the project files.
Installing required packages (Linux only)
Debian / Ubuntu: sudo apt-get install make unzip git libncurses5-dev flex bison gperf python-serial libffi-dev libsdl2-dev libmbedtls-dev perl
Currently we’re using ESP-IDF version 3.2 to build the firmware. After cloning our repository you will find the exact version of the ESP-IDF used in the ESP32-platform-firmware/esp-idf
folder. You don’t need to download or install a version of the ESP-IDF yourself.
The toolchain is another story: the newest ESP-IDF version (v4.x and newer) uses a different toolchain than the older (v3.3 / stable) version of the IDF. Because of this you can’t simply download the “latest greatest” ESP32 toolchain, instead you need to use a specific version.
You can download the correct version of the toolchain directly from Espressif using the following URLs:
You can find the official toolchain installation instructions here:
The very, very short version of these instructions for Linux is as follows:
- Extract the archive
- Add the path to the
bin
folder in the archive, containing the toolchain executables, to your path export PATH="$PATH:/path/to/my/toolchain/xtensa-esp32-elf/bin"
Configuring the firmware
The firmware can be built for many different targets. Because of this the firmware has to be configured before it can be built. By default a “generic” configuration is used. Building the firmware using the generic configuration will get you to a Python shell on any ESP32 based device. However: almost all hardware support will be missing.
To apply the configuration for a specific badge navigate to the firmware/configs
folder and overwrite (/create) the file firmware/sdkconfig
with the configuration relevant to your badge.
(Note that running make clean
to remove the build output is a bit broken / insufficient at the moment. Please remove the firmware/build
folder manually after switching configurations to make sure the firmware is built correctly.)
Manually changing the configuration of the firmware is explained in the menuconfig section.
Building the firmware
After you’ve downloaded the firmware, applied the correct configuration and installed the correct toolchain you have to build the Micropython cross compiler. This extra compiler converts Python scripts into a Micropython specific binary format so that these Python scripts can be integrated inside the firmware image.
Building the Micropython cross compiler can be done by running bash mpy_cross.sh
from inside the firmware
folder.
After you’ve built the Micropython cross compiler you can build the firmware by navigating to the firmware
folder and running make
. On multi-core systems compilation speeds can be improved by adding the number of threads to be used: make -j 4
.
2.3.1.1 - Menuconfig
You can start menuconfig either by running ./config.sh
in the root of the repository or by executing make menuconfig
in the firmware
folder.
You will then be greeted by the main menu.

The menu contains the following submenus:
- SDK tool configuration
- Bootloader config
- Security features
- Serial flasher config
- Firmware & device configuration
- Partition table
- Compiler options
- Component config
In the SDK tool configuration
menu you can configure the compiler toolchain path and prefix and the Python 2 interpretter to use. The default settings configured here use the toolchain found in your $PATH
and the python2
executable found in your path as the Python interpretter. You should not have to change these settings.
Bootloader config
The bootloader config
menu allows configuration of the bootloader. Changing these settings requires advanced knowledge of the ESP32 platform. The default values configured here should work.
Security features
The security features
menu allows for configuring secure boot by encrypting the flash and signing the firmware. Use of these features on a badge would defeat the purpose of a hackable device and is thus not supported. Do not attempt to enable any of the options in this menu: you will brick your device!
Serial flasher config
This is the first interesting item in the list. In the serial flasher config
menu you can configure the serial port to use when executing make flash
, as well as the baudrate. This menu also allows you to tell the bootloader about the flash chip mode, speed an size. Most of the BADGE.TEAM badges have a 16MB flash chip, the CampZone2019 has a 8MB chip and most chinese boards have 4MB.
Firmware & device configuration
This menu allows you to configure the identity of your device.
Item |
Description |
Code-name of the firmware |
Name of your device, lowercase and without spaces or special characters |
Build number of the firmware |
Version information in the format “YYMMDDNN”: year, month, day and build |
Name of the device |
Human readable name of the device |
MicroPython modules directory |
subdirectory of /firmware/python_modules to use for the built-in MicroPython modules |
Name of the badge on the app hatchery |
Name of your device or a compatible device supported by our Hatchery, lowercase and without spaces or special characters |
Hostname of server for OTA updates |
Domain name of the server used for OTA updating (Example: “badge.team”) |
Use HTTPS for OTA updates |
If enabled HTTPS can be used with a Letsencrypt SSL certificate. Other certificate authorities are not supported. |
Port of server for OTA updates |
Port to use for OTA updates. Usually 443 for HTTPS or 80 for HTTP |
Path on the server for OTA updates |
Path of the OTA firmware binary on the server, starting with a / |
Path on the server for OTA update version |
Path to the JSON file with version information (used by the update check Python app shipped with some badges) |
Hostname of server for app hatchery that contains user apps |
Domain name at which the Hatchery is hosted (used by the installer app shipped with some badges) |
Default WiFi ssid |
The default WiFi SSID to use when the user has not yet configured WiFi |
Default WiFi password |
The default WiFi password to use when the user has not yet configured WiFi (leave empty for ipen/insecure network) |
Default display orientation |
For badges which use the same display as another type of badge but in a different orientation (explained below) |
The HackerHotel 2019 badge is a direct derrivative of the SHA2017 badge, but with the display mounted in portrait mode instead of landscape. To allow for backwards compatibility with SHA2017 apps the real orientation has been set to landscape, while HackerHotel 2019 apps can call import orientation; orientation.default()
to switch to the real orientation of the badge they are running on. The default orientation is configured here.
Partition table
In this menu a partition table can be selected. A set of partition tables has been provided in the /firmware/partitions
folder. The partitions.ods
spreadsheet can help you when designing a custom partition table.
The partition table offset and the MD5 checksum options should be left at their default settings.
Compiler options
Advanced options for compiler optimization level and assertions. We suggest leaving the assertions enabled.
Component config
The component config
submenu allows for configuring various components fo the firmware such as the MicroPython interpretter and the device drivers.
MicroPython configuration
To-do
Device driver configuration
The BADGE.TEAM firmware contains drivers for multiple devices such as displays and sensors. These drivers are written in C and part of the firmware itself, but they can be accessed from withing MicroPython using the bindings provided.
Is a menu empty? Does a feature not work?
- To be able to use I2C based devices you have to enable the I2C bus driver first.
- To be able to use SPI based devices you have to enable the VSPI driver first.
- To be able to access displays using the “display” API from within MicroPython you have to enable the framebuffer driver.
- The “double buffered” mode of the framebuffer driver is only relevant for devices which do not have their own buffer such as the Flipdot and HUB75 drivers. In all other cases it’s a waste of memory! Only enable this to use the
display.flush()
command with displays that stream directly from the framebuffer.
2.3.2.1 - Adding drivers
If you need low-level support for hardware that isn’t available yet, you can write your own drivers, and can expose them to the Python app layer.
- Create the folder
/firmware/components/driver_<category>_<name>
.
- In this folder, create files
component.mk
, Kconfig
, and driver_<name>.c
. Kconfig allows you to add configurable switches and variables from the ./config.sh
step. The driver source file exposes an initialisation function that will be called upon boot. Have a look at e.g. /firmware/components/driver_bus_i2c
to see how to populate these files.
- In
/main/platform.c:platform_init()
, add INIT_DRIVER(<name>)
to have your driver actually initialise during boot.
- Add your driver’s header directory to
firmware/micropython/component.mk
, e.g. MP_EXTRA_INC += -I$(PROJECT_PATH)/components/driver_<category>_<name>/include
.
- Add python bindings to your driver by creating
components/micropython/esp32/mod<name>.c
(see e.g. modi2c.c).
- Tell micropython about your bindings by adding the following to
firmware/micropython/component.mk
:
ifdef CONFIG_DRIVER_<NAME>_ENABLE
SRC_C += esp32/mod<name>.c
endif
- Add the following to
components/micropython/esp32/mpconfigport.h
to add the module symbols to the python environment (replace i2c with your name):
#ifdef CONFIG_DRIVER_I2C_ENABLE
extern const struct _mp_obj_module_t i2c_module;
#endif
#ifdef CONFIG_DRIVER_I2C_ENABLE
#define BUILTIN_MODULE_I2C { MP_OBJ_NEW_QSTR(MP_QSTR_i2c), (mp_obj_t)&i2c_module },
#else
#define BUILTIN_MODULE_I2C
#endif
(to the define called MICROPY_PORT_BUILTIN_MODULES, add the following line after the other drivers):
BUILTIN_MODULE_I2C \
2.3.3 - Factory tests and provisioning
(To-Do)
2.3.4 - Device status
Badge |
Current OTA firmware |
Platform support status |
SHA2017 |
ESP32 platform Build 50 (“Rise of skywalker”) |
Fully supported |
Disobey 2019 |
Legacy SHA2017 firmware Unknown, OTA server is currently offline. |
Everything works, needs testing Not officially released yet |
HackerHotel 2019 |
Legacy SHA2017 firmware |
Everything works but audio needs improvement Not officially released yet |
CampZone 2019 |
ESP32 platform |
Fully supported |
Disobey 2020 |
ESP32 platform |
Fully supported, audio support still in progress |
Fri3dCamp 2018 |
ESP32 platform |
Proof of concept |
OHS2018 |
ESP32 platform |
Display works, proof of concept |
Odroid Go |
ESP32 platform |
Proof of concept. Display and audio work. No support for buttons or SD. |
TTGO LoRa |
ESP32 platform |
Proof of concept. Basics of the LoRa radio work, no interrupts, no WAN. |