This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Bornhack 2026 badge

Introduction

The BornHack 2026 badge is the Cyber Ægg. It is an egg-shaped, low-power hacker badge, inspired by the Tamagotchi of the 1990s. One battery charge runs the badge for the full BornHack camp. A long-range LoRa mesh keeps you connected to everyone else on the field.

Under the playful shell is a serious radio computer. A Nordic nRF52840 drives a 1.54 inch black, red and white e-paper display. The badge talks Bluetooth Low Energy to your phone, and it emulates an NFC tag on its back. A dedicated SX1262 LoRa radio connects the badge to the wider MeshCore network. Between messages, BornPets entertains you. BornPets is a virtual pet with a set of mini-games.

Front (display and buttons) and back (nRF52840, USB-C connectors, NFC coil).

Features

  • Egg-shaped badge, inspired by the classic Tamagotchi
  • Nordic nRF52840 microcontroller (BLE, USB and NFC)
  • 1.54 inch 152 × 152 black, red and white e-paper display
  • SX1262 LoRa radio, part of the MeshCore mesh network
  • Bluetooth Low Energy companion connection to the MeshCore app
  • NFC tag on the back, for location games and station taps
  • 5-way joystick, Select, Execute and Cancel buttons, RGB LED and a piezo buzzer
  • USB-C for charging and for file transfer with drag and drop

Start with the Getting started guide. The Games page describes the virtual pet. The Hardware page tells you what is inside the badge.

Source code

The Cyber Ægg is open source. The hardware design and the firmware are on Codeberg:

The Flash page also offers four alternative images. Each image has its own home:

Hardware sponsors

  • Nordic Semiconductor sponsored their low power yet very capable and fast NRF52840 microcontroller with Bluetooth Low Energy and NFC, making it possible for us to build a device that runs on one battery charge, the whole camp long!
  • ALLNET China is our production partner, they take care of sourcing most components and oversee the production process in China, saving us a lot of work and potential headaches and allowing us to focus on the product!
  • Procolix sponsored the SX1262 LoRa radio chips, converting the badge into a capable LoRa communications device. Check out their managed hosting solutions for a truly sovereign cloud built on European open source solutions!
  • deFEEST sponsored part of the badge hardware, helping us get the components we needed to build it. Find out more at defeest.nl!
  • Mollerup Automation sponsored the 3D printed housing for the badge. They are automation, robotics and PLC specialists from Odense, Denmark — see mollerup.info!

1 - Getting started

This guide takes you from the unboxing of the BornHack 2026 Cyber Ægg badge to the first use. It takes a few minutes.

First power-on

A new badge runs a factory self-test on the first start. The badge shows a FACTORY TEST screen with a small PASS/FAIL grid, and then ALL PASS. After that, the badge starts the application directly at every start. The self-test does not run again, unless you erase the firmware.

The first start then plays a sponsor slideshow. It shows the event logos and then the badge-sponsor logos, a few seconds each. The slideshow plays only once. To see it again, select Main → Badge sponsors.

The LED sequence at every start is:

  1. Pulsing orange — the hardware initializes
  2. Pulsing blue — the display and the LoRa radio start (about 13 seconds)
  3. One green flash — the badge is ready

The badge then shows the Main screen.

Controls

The badge has a 5-way joystick on the left and two thumb buttons on the right:

ControlAction
Execute / joystick press (Fire)Select or activate
CancelGo back or dismiss
Up / DownMove the cursor in the current screen
Left / RightGo to the next top-level screen

Top-level screens

The interface is a carousel. Left and Right move through the top-level screens:

ScreenWhat it is
GameBornPets — the virtual pet, the mini-games and the hatchery
MainRoot menu: Bornagotchi · Settings · About · Badge sponsors
PMsInbox for private mesh messages
ChannelGroup or room mesh chat
AdvertsMesh adverts the badge heard recently
TokensThe NFC tokens you collected
ClockDigital or analog watch face, and the alarm
CalendarMonth grid and the timeline for each day
NameLarge conference-badge name view
My QRYour mesh identity as a QR code, to share with other badges

Pair with the MeshCore app

The badge speaks the MeshCore companion protocol over Bluetooth Low Energy. Install the MeshCore app on Android or iOS. You can also open https://app.meshcore.nz/ in a browser with Web Bluetooth, such as Chrome or Edge on desktop or on Android.

  1. Make sure that Bluetooth is on. Main → Settings → Bluetooth must show BLE: ON.
  2. Scan for devices in the app. The badge advertises as Cyber Ægg XXYY. XXYY is four hex characters, unique to your badge.
  3. The phone shows a passkey prompt, and the badge shows a 6-digit passkey on its display. Type that number into the phone.
  4. After the bond, the app can set the clock, manage contacts, send and receive mesh messages, and change the LoRa preset.

Set the time

The badge has no real-time clock with a backup battery. The clock therefore returns to “not set” at every start. You can set it in two ways:

  • With the MeshCore app. Connect over Bluetooth, and the app sends the time of your phone to the badge.
  • Near a synchronized repeater. A known-good mesh repeater advertises its time regularly, and your badge takes it automatically.

Set your timezone once, under Main → Settings → Timezone. The badge keeps that setting.

Charging

Connect any USB-C cable to the badge to charge it. The battery icon on the screen shows the charge state. There is no separate charge LED.

Two effects look like faults, but they are correct. If the charge symbol disappears while USB stays connected, the charge is complete. The symbol comes back when the cell drains. The battery icon can also be up to a minute behind, because the badge measures the battery only every 60 seconds.

USB drag-and-drop

When you connect USB-C, the badge appears as a small drive with the name CYBR<4 hex>. You can put these files in its root:

FileEffect
ALARMS.ICSiCalendar file — imports alarms and calendar events
030000.PCX030009.PCXSponsor slides for the splash carousel
<6 hex>.PCXGame sprites
PETS.CFGAdds or renames pets, with their sprite PCX files — see Games
BORNPETS.CFGReplaces the BornPets game balance
LUT.CFGCustom e-paper waveform (advanced — a calibrated display LUT)

After you copy files, restart the badge with a new connection of the USB cable. The changes then take effect.

LUT.CFG is an advanced change. It replaces the built-in display waveform of the panel with a calibrated one, for example for a faster refresh. If a custom LUT gives a bad image, hold Fire (the joystick press) during the start. The badge then uses the safe built-in waveform for that start. Delete the file, or correct it. The badge rejects a damaged LUT.CFG, or one for a different panel, automatically.

Firmware update

The Flash page is the easiest method. It writes the firmware and the badge’s asset files from a Chromium-family browser. You install no toolchain.

To do it by hand, enter the bootloader (DFU) mode first. Slide the ON/OFF switch at the top left of the front off, then back on, while you hold Execute. The LED then blinks red. The battery keeps the badge running, so a disconnection of USB does not restart it. You can then write a new firmware image with dfu-util:

dfu-util -d 1915:521f -D cyber-aegg.bin

The firmware is open source, and we build it with Rust and Embassy. See Ranzbak/bornhack-firmware-2026 for the source, the build instructions and prebuilt images.

2 - Quick reference

This is a one-page cheat sheet for the Cyber Ægg. Print it and put it under the strap.

        ┌─────────────────────────────┐
        │      e-paper display        │
        ├─────────────────────────────┤
        │                             │
        │   [UP]                      │
        │ [L][F][R]      [CAN] [EXE]  │
        │   [DN]                      │
        │                             │
        └─────────────────────────────┘
            5-way joystick   2 buttons
            (F = press in)

Buttons

ButtonFunction in the menu
Execute / FireSelect · open · confirm
CancelBack · cancel · close
Up / DownMove the cursor in a screen
Left / RightGo to the next top-level screen

Top-level screens

Left and Right move through the screens:

Game → Main → PMs → Channel → Adverts → Tokens → Clock → Calendar → Name → My QR

LED meanings

ColorMeaning
Pulsing orangeStart — the hardware initializes
Pulsing blueThe display and LoRa start (about 13 s)
One green flashThe start is complete
Red flickerThe screen refreshes
Blue flickerThe badge writes to the USB drive
Blinking greenThe badge erases the contacts
One red, green or blue flashA person pinged you over the mesh (blinkme)

Power-on combos

Hold the button while you start the badge. Slide the ON/OFF switch at the top left of the front off, then back on. The battery keeps the badge running, so a disconnection of USB does not restart it.

HoldResult
ExecuteUSB firmware update (DFU mode)
Fire (joystick press)Forces the safe e-paper waveform, and ignores a bad LUT.CFG for that start
Execute + Cancel + FireFactory reset (about 40 s — erases the data and the settings)

If the application slot is empty, the badge enters DFU mode without a button.

USB drag-and-drop

Connect USB-C. The badge mounts as the CYBR<4 hex> drive.

File you put on the driveWhat it does
ALARMS.ICSImports alarms and calendar events
030000.PCX030009.PCXSponsor slides
<6 hex>.PCXGame sprite asset
PETS.CFGAdds or renames pets, with sprite PCX files
BORNPETS.CFGCustom pet balance (KEY=VALUE)
LUT.CFGCustom e-paper waveform (advanced)

Restart the badge after you copy the files.

Firmware update (DFU)

dfu-util -d 1915:521f -D cyber-aegg.bin

The bootloader LEDs in DFU mode are: a red blink for idle, solid blue during the write, solid green when the write is complete. Then power cycle the badge.

Charging

USB-C in any port charges the badge. There is no separate charge LED. The battery icon on the watch face and in the status bar shows the level.

3 - Mesh & messaging

The badge has a LoRa SX1262 radio, and it speaks the MeshCore mesh protocol. Other badges, MeshCore phones and separate repeaters all appear as peers. Four carousel screens use the mesh: PMs, Channel, Adverts and My QR. The Contacts list holds the peers behind them.

Getting on the same network

Three things must be the same on every badge in the local mesh:

  1. LoRa preset. This is the frequency, the bandwidth and the spreading factor. The default preset is BornHack 2026, and the firmware includes it. Change it under Main → Settings → LoRa Radio.
  2. Public channel key. The preset shares this key automatically.
  3. Antenna. Make sure that the LoRa antenna is connected.

If you see no adverts from other badges after a minute, check the preset first.

Adverts

Every badge, phone and repeater on the mesh broadcasts an advert at regular intervals. The advert holds the public name, the identity hash and the capabilities. Your badge lists the adverts it receives on the Adverts screen.

KeyAction
Up / DownMove through the advert list
Execute / FireSaves the selected advert as a contact
CancelGo back
Left / RightGo to the next carousel screen

Private messages (PMs)

The PMs screen is your private inbox. Each row is a peer that sent you a message.

MarkerMeaning
Heard less than 5 minutes ago
*Favorite
+Discovered, but not saved as a contact
RRepeater
#Room or channel server
SSensor

Use Up and Down to move through the list. Use Execute / Fire to open a thread or to start a reply. Use Cancel to go back. A reply uses the keyboard on the screen. Move the joystick to select a character, press Execute to accept it, and press Cancel to delete the last character. About 70 emoji are available.

Channels (group chat)

The Channel screen is the group chat, or room chat. It uses the same protocol with a broadcast scope. Each row is a channel, for example the default Public channel in the preset. The controls are the same as on the PMs screen. Every badge with the same preset hears every message in a public channel.

My QR

The My QR screen shows your mesh identity as a QR code. Show the code to another MeshCore phone or badge for an immediate pairing. The other device does not have to wait for an advert.

Contacts

The Contacts list shows every peer the badge heard or knows: nearby strangers, saved friends, repeaters and rooms. Open it under Main → Bornagotchi → Contacts, or from the Adverts screen.

KeyAction
Up / DownMove through the list
Up on the top rowOpens the filter: All, Favorites, People, Repeaters, Rooms or Sensors
Execute / FireOpens a popup: PM · Info · Add · Save / Unsave · Forget
CancelGo back

The popup actions are:

  • PM. Opens the message thread.
  • Info. Shows the hex identity prefix, the last-heard time and the advert capabilities.
  • Add / Save. Writes the contact to flash, so it survives a restart.
  • Unsave. Removes the contact from flash. It stays in the discovery cache until the next restart.
  • Forget. Removes the contact immediately, also from the discovery cache.

Pinging & visibility

When another badge pings you with the mesh blinkme command, your LED flashes shortly in the requested color. This helps you find friends in a crowd. Your badge also sends its own adverts, so other people can see you.

Battery note

The LoRa radio uses the most battery power. To save power, mute the notification sounds under Main → Settings → MeshCore. The e-paper display uses no power after it shows an image.

Using your phone instead

You can also chat from your phone. Install MeshCore on Android or iOS, or open https://app.meshcore.nz/. Then pair the phone over Bluetooth. See Getting started. After the bond, the app gives you the contacts, the chat and the settings.

4 - NFC & tokens

The back of the badge has an NFC antenna. Touch a phone or a station reader against it to interact. You do nothing on the badge, because it is always ready.

Two things happen on a tap

A phone reads your broadcast profile

Any standard NFC reader, such as the built-in reader of Android or iOS, sees your broadcast profile. The default profile is the badge’s own documentation page, https://badge.team/docs/badges/bornhack-2026/. You can replace it with your own vanity URL, a vCard or another record. See Set your own broadcast data. A tap with the reader of the operating system only reads the profile. It is harmless.

BadgeCtl runs a station command

A phone with the BadgeCtl companion app and the correct event key sends signed commands to the badge. The event stations use these commands, and they improve the statistics of your BornPet:

CommandEffect on your BornPet
more foodSets hunger to 0
more drugsSets sick to 0
more inspirationSets drained to 0
sleep like a bearSets tired to 0

A short message on the badge confirms the result. Each command has a cooldown of 5 minutes. A second tap in that period does nothing.

Station commands need an active game. Select a pet first. The egg countdown counts as an active game. If your pet left, start a new egg. The badge ignores a tap when no pet is active.

Tokens

Tokens from station taps and from other badges go to the Tokens screen in the carousel. The badge collects many tokens and keeps them until the next start. The list is a record of the stations and the badges you tapped during the camp.

When a person pushes a token: to your badge, the badge shows it for about 10 seconds. The badge then broadcasts your own profile again. A pushed token cannot replace your profile.

Set your own broadcast data

The default documentation URL is not permanent. The badge can broadcast any record you like. Use an NFC writer app on your phone, such as NFC Tools, and write to the back of the badge:

  • Vanity URL. Write a URL or URI record, for example annejan.com. A Text record set:https://your.link also works, for writer apps that write text only.
  • vCard. Write a Contact or vCard record. Phones that tap your badge then get your contact card.
  • Wi-Fi, or any other record. The badge serves it without a change.

The rule is simple. The badge keeps what you write, and the record survives a restart. The one exception is a token: record, which goes to your Tokens screen instead. Keep the record short. The limit is about 127 bytes, which is enough for a URL or a small vCard. The badge also cuts a long URL in the set: text form to about 118 characters after the scheme. For a longer URL, write a plain URI record, or use a link shortener.

NFC has no erase function that returns the factory default. An empty write, or the “format tag” function of a writer app, does not restore the built-in documentation URL. It keeps your current profile, or it stores the empty record. To change the broadcast, write the new record over the old one.

This function is unauthenticated. Any person who touches your badge with a writer app can change the record. The badge is in your pocket, so control physical access to it.

What the reader side needs

The badge is always ready. The reader needs two things:

  • The BadgeCtl app.
  • The correct Ed25519 private key in the app. BornHack staff hold this key for the official stations.

Other NFC reader apps cannot send these commands, because they do not have the key. They see only the public URL.

Running your own station

You can run your own station. Build the badge firmware again with your own Ed25519 public key. Then sign the commands with your private key in your reader app. The protocol specification, the wire format and a signing recipe in Kotlin, Python and Rust are in the firmware’s NFC_README.md.

How it works (hardware)

The nRF52840 includes an NFC tag PHY. It drives a coil on the PCB of about 2.8 µH, tuned with capacitors to 13.56 MHz. The PHY supports tag mode only, not reader mode. See the Hardware page.

5 - Clock, alarm & calendar

Three apps use the watch slots of the carousel: Clock, Alarm and Calendar. You open the Alarm from the Clock.

Clock

The Clock has two watch faces, digital and analog. A small bell icon in the header appears when an alarm is on.

To open it: press Left or Right until the badge shows the Clock screen.

KeyAction
Up / DownChanges between the digital face and the analog face
Execute / FireOpens the alarm editor (slot 0)
Left / RightGoes to the next or the previous carousel screen

Setting the time

The badge has no backup battery for its real-time clock. The clock therefore returns to None at every start, and it shows “Clock not set” until you set it. You can set it in two ways:

  • With the MeshCore app over Bluetooth. The phone sends its time. This is the easy method.
  • With a mesh time advert. Stay near a synchronized LoRa repeater, and the badge takes the time over the air. The badge accepts a time over the air only from a trusted source. That is a repeater or companion advert with a verified signature, or a channel that you hold the key for. Other badges near you cannot set your clock.

Set the timezone once, under Main → Settings → Timezone. The badge keeps that setting through a restart. The default is +2, which is CEST for BornHack.

Alarm

Press Execute / Fire on the Clock screen to open the alarm editor.

KeyAction
Up / DownMoves between the fields: Hour, Minute, Days, Tone, Enabled
Execute / FireEnters or leaves the edit mode of a field
CancelReturns to the watch face

The Days field steps through Daily, Weekdays, Weekends, None and Custom. The Tone field has ten built-in tunes: Beep, Imperial March, Rickroll, Pink Panther, Sandstorm, Startup, Trololo, Daisy Bell, Nokia and Samsung.

At the alarm time, the buzzer plays the selected tone up to five times, with 8 seconds between them. Any button press stops the alarm. If you do nothing, the alarm stops after about 32 seconds.

Calendar

The Calendar is a month grid with a timeline for each day. It shows the iCalendar events you imported.

To open it: press Left or Right to the Calendar screen, to the right of the Clock. The grid appears without a cursor. Press Execute / Fire to enter the active mode.

Active mode:

KeyAction
Up / DownMoves the cursor 7 days, one week
Left / RightMoves the cursor 1 day
Execute / FireOpens the timeline of the day
CancelReturns to the passive view

Day detail (timeline):

KeyAction
Up / DownMoves 1 hour
Left / RightMoves a long event title horizontally
Execute / FireShows the full day list, with all events
CancelReturns to the month view

Loading events

The badge imports events at the start, from a file with the name ALARMS.ICS in the root of the USB drive:

  1. Connect the USB-C cable to your computer.
  2. Open the drive with the name CYBR<4 hex>.
  3. Copy your .ics file to the root, with the name ALARMS.ICS.
  4. Eject the drive.
  5. Restart the badge. Slide the ON/OFF switch at the top left of the front off, then back on.

You can use the official BornHack program .ics from https://bornhack.dk/.

Import limits & quirks

The parser is minimal on purpose. If events are absent or wrong, one of these limits is usually the cause:

  • File size: 16 KiB maximum. The parser cuts the rest of the file, in the middle of an event, without a message. A full conference program is larger than this. Cut it first with the firmware’s scripts/strip_ics.py. That script removes DESCRIPTION, UID and similar fields, and it accepts --from, --to and --max to select a range.
  • 31 events maximum. The import stops at the limit without a message. Later events in the file never appear.
  • No recurrence. The parser ignores RRULE. A repeated event imports as its first occurrence only. Export an expanded ICS file with one entry for each occurrence. The BornHack program is already expanded.
  • No all-day events. The parser drops a DTSTART with a date and no time, without a message. Give the event a real start time.
  • ASCII only. The parser removes non-ASCII characters from titles. It does not transliterate them, so Æ, accented letters and emoji disappear.
  • Timezones. The parser shifts only timestamps with the Z suffix, which are UTC, to local time. It always uses the built-in default of UTC+2, which is correct for BornHack, because the import runs before the badge applies your saved timezone. The parser takes floating times and TZID= times without a change. Export in UTC if you are not sure.
  • An event that fired disappears from the Calendar until the next start. Imported events are single-shot alarms. After one fires, the grid and the day view no longer show it. A restart imports everything again.
  • Changes take effect at the start only. Replace ALARMS.ICS, eject the drive correctly, then power cycle the badge.

6 - Games

The Game screen runs BornPets, a virtual pet in the style of the Tamagotchi of the 1990s. It also holds a set of mini-games. You start the mini-games from the pet’s Play menu.

BornPets

Hatch a snail or a cat. Then keep the pet fed, healthy, rested and entertained.

Hatching

The first time you open the Game screen, the badge shows the hatchery. Press Execute / Fire to start. Select a pet from the roster. The built-in pets are Bartholomeus, Cat and Slug. The egg then hatches in about one minute. After the hatching, give your pet a name of up to 12 characters, with the keyboard on the screen. The badge keeps the pet through a restart. You can rename the roster, and you can add your own pets. See Custom pet roster below.

Stats

Your pet has stats that increase with time. A high value is bad for the pet. Watch these stats:

StatFix withNotes
HungerFeedThe pet gets hungry, and the other stats get worse
TiredRest or sleepUse Hibernate for a long sleep
DrainedPlay, or a mini-gameThe pet has no inspiration. Play also clears “miserable”
SickHealUse this when the sick icon appears
MiserablePlayThe other stats then get worse faster, so correct this early

The stats interact. When several stats are bad, the pet becomes miserable faster, and a miserable pet makes every other stat worse. Stay ahead of this spiral.

Controls

KeyAction
Up / DownChanges between the top row (the actions) and the bottom icon row
Left / RightMoves along the current row
Execute / FireActivates the selected icon
CancelGoes back

Hibernate

Before you store the badge for more than a few hours, open the action menu and select Hibernate. The stats then freeze until you wake the pet. If you store the badge without hibernation, the stats continue to decay, and the pet can starve before you find the badge again.

Game modes

There are two difficulty settings, under Main → Bornagotchi → Mode:

  • Classic. The original balance of the badge.
  • Casual. About half the decay speed, and more relief for each action. Use this if you do not want to care for the pet often.

The badge keeps the setting in flash. A * next to the mode name means that the change waits. Restart the badge to apply it.

Turning the pet off

You can remove the pet. Main → Bornagotchi → Disable Game hides the whole Game screen from the carousel. The badge keeps this setting through a restart, and the label changes to Enable Game. While the game is off, an NFC station tap can no longer open the pet. Select Enable Game to get the pet screen back.

Mini-games

Open the Play menu in the bottom row of BornPets and select a game. Each win lowers the drained stat, and it does not raise hunger. The games are therefore free entertainment. Cancel always leaves a mini-game.

GameGoal
Tic-Tac-ToeDraw against the computer, or win (Normal or Impossible difficulty)
Lights OutSwitch a 5×5 grid until every light is off
NimForce the computer to take the last stick
MazeReach any border exit of an 18×18 maze
Black HoleBeat the sum of the AI on a pyramid of 21 cells
Triple BornA merge game in the style of Triple Town, on a 6×6 board
BornJeweledAn accessible match-3 game with a limit of 30 moves

In a game, the joystick moves the cursor. Execute / Fire places or selects. Cancel returns to the Play menu.

Make your own pet

You can build a companion that is not a snail or a cat. The CyberÆgg Pet Maker is a sprite and animation editor for BornPets, and it runs in the browser:

scene.rs/pets/

With the Pet Maker you can:

  • Start from a preset (Bartholomeus, Cat or Slug), or from a Blank canvas.
  • Draw each animation frame with the badge’s palette: black, red, white and transparent. White is the e-paper background, and transparent means that the badge draws nothing.
  • Preview the animation states, for example Idle, with controls for the speed and the onion skin.
  • Change the game balance (BORNPETS.CFG), which sets how fast the stats decay and recover.
  • Select Download ZIP to get all the files for the badge.

Installing a custom pet

The badge shows a USB drive when you connect USB-C. See Getting started. Unpack the export of the Pet Maker. Copy the sprite files to the root of the CYBR<4 hex> drive, together with PETS.CFG and, if you made one, BORNPETS.CFG. Then restart the badge.

Hand-editing sprites (GIMP etc.)

The badge accepts one specific PCX format: 2 bits per pixel, one plane, RLE. Editors such as GIMP export a PCX with 16 or 256 colors instead, and the badge skips those files without a message. The firmware’s scripts/ folder has helpers for this problem:

  • fix_badge_pcx.py writes a PCX with the wrong depth, for example a 4 bpp or 8 bpp GIMP export, back to the badge’s 2 bpp format. It keeps the dimensions.
  • png_to_badge_pcx.py converts a PNG directly to a 152×152 badge PCX.
  • check_badge_pcx.py checks a file before you copy it. You then find a bad export on your computer, not on the badge.

Custom pet roster: PETS.CFG

PETS.CFG is the pet roster in the “Choose your Pet” screen. It is a plain-text file with one PREFIX=NAME on each line. You can edit it without a new firmware:

# --- current pets (rename if you like) ---
0=Bartholomeus
1=Cat
2=Slug

# --- add your own (needs 05xxxx.PCX / 06xxxx.PCX sprites) ---
5=Dragon
6=Ghost
  • PREFIX is the sprite-prefix byte of the pet, in decimal:
    • 0, 1, 2 are the built-in pets. A line for one of these renames it.
    • 3, 4 are reserved for sponsors and menu icons, and the badge ignores them.
    • 5 to 7 are your own pets.
  • NAME is up to 16 ASCII characters.

The sprites of a pet are the PPAAFF.PCX files on the badge. PP is the prefix, AA is the animation part, and FF is the frame. The firmware counts the PCX files that are present, for example 050100 to 050104 for five idle frames. There is therefore no fixed frame count and no header to maintain. Export the sprites at the prefix of the pet from the Pet Maker, and copy them to the drive next to PETS.CFG. The firmware ignores lines that start with #, and reserved or damaged lines.

Custom balance: BORNPETS.CFG

To change only the difficulty, and not the sprites, copy a plain-text BORNPETS.CFG file to the root of the badge’s USB drive. Write one KEY=VALUE on each line:

# speed up hunger decay, slow down the drained stat
HUNGER_RATE=4
DRAINED_INTERVAL=180

Eject the drive and restart the badge. While a configuration is active, a small * appears after the name of the pet. To return to a preset, delete the file and restart the badge. The Pet Maker can write this file for you. The firmware’s USER_GAMES.md documents every key and its range.

Three points are important:

  • Changes take effect at the start only. Eject the drive correctly, so that the computer writes the data. Then power cycle the badge. If no * follows the name of the pet, the badge applied no override.
  • The parser is silent. It skips an unknown key. It also drops the whole line for a value that is not a plain whole number, so no units, no decimals and no minus sign. It shows no error on the screen.
  • The documented “reasonable range” is advice, not a limit. The firmware limits a value only to the range of the integer type. HUNGER_RATE=1000 really fills hunger about 300 times faster, and your pet starves before you disconnect the cable. If a large value damaged your pet, delete the file and restart the badge to return to the preset.

7 - Hardware

The Cyber Ægg is a low-power LoRa badge around a Nordic nRF52840 microcontroller. The design goal is one battery charge for the full week of BornHack. The hardware and the firmware therefore keep the power consumption low.

The full KiCad design is open source, at Ranzbak/bornhack2026-hardware.

Overview

ComponentPartInterface
MicrocontrollerNordic nRF52840
Display1.54 inch black, red and white e-paper, 152 × 152, SSD1675 / SSD1675B controllerSPI
LoRa radioSemtech SX1262SPI
Bluetooth Low EnergyBuilt-in radio of the nRF52840
NFCNFC tag PHY of the nRF52840, with a coil on the PCB
Input5-way joystick, Execute and Cancel buttonsGPIO
FeedbackRGB LED, piezo buzzerGPIO / PWM
PowerLi-ion battery, USB-C for power and data, ON/OFF slide switch

Display

The badge has a 1.54 inch tri-color e-paper display in black, red and white. The resolution is 152 × 152 pixels, and an SSD1675 or SSD1675B controller drives it. E-paper stays readable in bright sunlight at the camp, and it uses no power to hold an image. Both properties help the battery goal of one week.

The panel uses the waveform LUT in its own OTP memory by default. Advanced users can replace that waveform with a calibrated one, for example for a faster refresh. Copy a LUT.CFG file to the USB drive. See the firmware’s LUT.md. You can make a waveform with the ssd1675-calibration tool. If you hold Fire during the start, the badge always uses the safe built-in waveform.

Manual input

The Tamagotchi egg toy of the 1990s inspired the Cyber Ægg, so the buttons have the same names:

ButtonFunction
SelectMoves through the menu options
ExecuteStarts the option under the cursor
CancelCancels the current operation

The Select button is a 5-way joystick with a press action, which makes the navigation easier.

Bluetooth

The nRF52840 provides Bluetooth Low Energy directly. It works in the 2.4 GHz band, and it uses an antenna on the PCB. That antenna comes from a Texas Instruments reference design in the standard KiCad 9 library. For the details, see the TI application note SWRA228.

LoRa

A dedicated Semtech SX1262 radio provides the long-range connection. The matching and balun circuit follows the Semtech application note AN1200.54. The LoRa antenna is a Texas Instruments design, documented in SWRA416. On the network side the badge speaks MeshCore, so it joins the camp mesh immediately.

NFC

The nRF52840 includes an NFC PHY. It drives a resonant circuit: a coil on the PCB of about 2.8 µH, with tuning capacitors. This tank circuit is matched to 13.56 MHz. The nRF52840 supports tag functionality only, not reader mode. The firmware uses the tag for location games and station taps.

Expansion connector

The board has an I²C expansion connector of the QWIIC type. We made a design mistake: the 3.3 V and the GND signals are swapped. Before you connect a QWIIC peripheral, make a corrected cable and use that.

The connector on the badge should have been compatible with QWIIC. QWIIC is a standard I²C connector, used by many SparkFun breakout boards and by boards from other suppliers. The board has two 10 kΩ pull-up resistors. You can also enable the internal pull-ups of the nRF52840 when the bus capacitance is high.

The firmware can also drive an optional Nicolai-Electronics I²C keyboard on this bus, for names and mesh messages. Connect the keyboard with the corrected cable, and the text entry uses the physical keys. Shift and Alt are one-shot toggles, and the alt-symbol layer matches the silkscreen. Without a keyboard, the badge uses the joystick picker on the screen automatically.

Power

A LiPo battery powers the badge, and USB-C charges it. An ON/OFF slide switch at the top left of the front disconnects the battery power. Slide the switch off and back on to power cycle the badge. The battery keeps the badge running when USB is disconnected, so this switch is the only way to restart it. Hold Execute while you switch the badge on to enter DFU mode for a firmware write.

8 - FAQ & troubleshooting

These are the answers to the questions people ask most often about the Cyber Ægg. If no answer helps, report a bug. The bottom of this page tells you where.

General

The badge does not wake up, and the display stays empty. Connect USB-C. If the LED does not blink, hold Execute while you connect the cable, and write the firmware again with dfu-util. See Getting started → Firmware update.

After a firmware write or a factory reset, the badge shows the factory test, then a “ready to ship” screen, and it stops there. This is correct behavior. After a self-test with all tests passed, the badge sets its pass flag, draws the ship screen and stops. The green LED pulses. Power cycle the badge once more. The second start skips the test and runs the application.

The badge shows “Battery voltage critical” at the start, and it goes no further. The cell measured less than 3.0 V at the start, so the firmware stops to protect the cell. The hardware controls the charge, and it continues. Leave USB connected for some time, then power cycle the badge by hand. The badge does not restart itself from this screen.

The clock resets at every start. This is expected, because the badge has no RTC with a backup battery. Pair the badge over Bluetooth with the MeshCore app once per start, or stay near a synchronized mesh repeater. The badge accepts a time over the air only from a trusted source. That is a repeater or companion advert with a verified signature, or a channel that you hold the key for. Other badges near you cannot set your clock. A pairing with a phone always sets it.

Bluetooth is not visible, and I cannot pair. Make sure that Bluetooth is on, under Main → Settings → Bluetooth. The badge keeps this setting through a restart, so set it to BLE: ON.

My alarm did not sound. The clock is not set for this start, and an alarm sounds only when the time is known. If the clock is set, look at the Days field of the alarm. None never sounds. A Weekdays, Weekends or Custom mask sounds on the selected days only. The bell in the header appears for every alarm that is on, whatever its day mask is.

No mesh peers appear. Walk around. The LoRa range changes with the terrain and with the orientation of the antenna. Also check your LoRa preset, under Main → Settings → LoRa Radio. It must be the same as on the rest of the local mesh. See Mesh.

I formatted the badge’s USB drive, and all files are gone. Do not format the drive. The badge understands only its own FAT12 layout. If the start finds another layout, such as exFAT, NTFS or unusual sector sizes, the badge formats the whole partition again and erases every file. It gives no message. To remove files, delete them in the normal way. If the badge already formatted the drive, copy your .PCX, .ICS and .CFG files back and restart it.

The charge symbol disappeared while USB is connected. The charge is complete. The symbol comes back when the cell drains. The battery icon can also be up to a minute behind, because the badge measures the battery only every 60 seconds.

The red LED flashes often, most of all on the BornPet screen. The LED gives one short flash every time the e-paper repaints. Most screens are static, so the flash is rare there. The pet has a slow idle animation, so the badge repaints, and flashes, every few seconds. The frames look almost equal, but each one is a repaint. This is not a fault, and it is usually not an incoming message. The Ignore blink setting, under MeshCore, mutes the flash for incoming mesh messages only. It does not stop the flash for a repaint. To stop the flash, stay on a static screen.

Which screen gives the longest battery life? The e-paper uses almost no power to hold an image. The power goes into the repaints. The most economical screens therefore never repaint by themselves: My QR, Name, Tokens and Calendar stay idle until you press a button. Main and Watch repaint once a minute for the clock. BornPet repaints every few seconds for its animation, and it uses the most power. To make a charge last, stay on My QR or on Name. Both are idle and do not flash. My QR is also useful, because other people can scan you into the mesh.

Display (e-paper)

Red appears only sometimes, for example after a change of screen. This is correct behavior. On one screen the badge uses fast black and white refreshes, which do not repaint the red plane. Red repaints on a full refresh, at a change of screen or at regular intervals. Red is not off. It refreshes less often than black and white.

The whole screen is inverted, and it stays inverted. A full refresh cycles the whole panel for a short time. If that cycle stops early, the image can stay inverted. Go to another screen and back to force a clean redraw. A permanent inversion on the red-capable “B” panels was a firmware bug, so make sure that you use the current firmware.

There is no red, or the screen is washed out, after I added a custom LUT. Your LUT.CFG is a fast waveform without red. Delete LUT.CFG from the badge’s USB drive. You can also hold Fire (the joystick press) during the start, which forces the built-in tri-color waveform for that start. See Hardware → Display.

The white LED blinks, and the badge does not finish the start, after I copied a LUT.CFG. The badge rejects a damaged LUT.CFG, or one for a different panel, automatically. If the screen is unreadable, hold Fire during the start to force the safe built-in waveform. Then delete the file, or correct it.

The badge starts correctly, but it ignores my LUT.CFG. The badge rejects the file without a message. The usual causes are a wrong variant letter for your panel, or the wrong key from the ssd1675-calibration tool. The badge wants the flat band_lut hex field, not stage_luts. A wrong hex length is also a cause, because each LUT value must be exactly 214 hex characters. The file size is not a limit. The badge streams the file from flash, so a full 16-band export of about 3.7 KB, with all comments, loads correctly. If you hold Fire at the start, the badge uses the built-in waveform for that start. The firmware’s LUT.md has the details.

Mesh / Bluetooth

The MeshCore app reports “connected”, but nothing works. I cannot set the clock, I see no contacts, and messages do not go out. The pairing is stale. The phone removed the bond, because you deleted it in the Bluetooth settings or changed phones, and the badge still has it. The badge therefore rejects every command as unauthenticated. To correct this, select Main → Settings → Bluetooth → Clear pairings. That erases all bonds and restarts the badge. Then pair again. The badge holds a maximum of 4 bonds. A fifth phone does not stay, and the badge gives no message.

The Channel screen shows “BLE client connected”, and the buttons do nothing. This is correct behavior. While the phone app is connected, the channel browser on the badge locks, and only Left, Right and Cancel work. Close the app or disconnect it, and the screen unlocks immediately.

My PMs and the peers I heard are gone after a restart. The PM inbox and the list of recently-heard peers are in RAM only. The badge keeps saved contacts. When you meet a person you want to message later, open their entry and save them before you switch the badge off.

NFC

My vanity URL or vCard does not stay. Write it with an NFC writer app, as a normal URL/URI, vCard or Wi-Fi record. Every record you write becomes your broadcast profile and survives a restart. The one exception is a token: record. See NFC & tokens → Set your own broadcast data.

A token I received disappeared, or a URL I tapped came back. A token: write is temporary on purpose. The token goes to the Tokens screen, where the badge keeps it until the next restart. The broadcast returns to your own profile after about 10 seconds. A pushed token cannot replace your profile.

A station tap did nothing. There was no message, and the pet did not change. Station commands work only with an active game. Select a pet first, and note that the egg countdown counts as active. If your pet left, start a new egg. Station commands also come through the signed BadgeCtl reader. If you write the phrase, for example more food, as a plain text record with a general NFC app, it does not feed your pet. It becomes your broadcast profile, and your badge then gives “more food” to every phone that taps it. Write a new URL or vCard to correct this.

Game / BornPets

The pet area shows “No sprites on flash”. The scan at the start found no .PCX files. This happens after a factory reset, after a format of the drive, or after a firmware write without the asset set. Copy the sprite .PCX files back to the CYBR<hex> drive and restart the badge.

A sprite I made has the wrong colors, or it does not appear. The badge needs one specific PCX format: 2 bits per pixel, one plane, RLE. The palette order is fixed: 0 = black, 1 = red, 2 = white, 3 = transparent. The badge ignores the palette in the file. It skips a normal 256-color or 24-bit export without a message. The Pet Maker and the firmware’s asset tool write the correct format. If you edited a sprite by hand in GIMP, write it again with the firmware’s scripts/fix_badge_pcx.py, or check it first with check_badge_pcx.py. See Games → Hand-editing sprites.

BORNPETS.CFG, or a change of mode, has no effect. Both take effect at the start only. Eject the drive correctly, so that the computer writes the file, and power cycle the badge. If no * follows the name of the pet, the badge applied no override. See Games.

Where to file bugs

If you find a fault, open an issue on the firmware repository:

https://codeberg.org/Ranzbak/bornhack-firmware-2026/issues

9 - Flash your badge

This page writes firmware to your badge. You need no toolchain, no drivers and no command line. The browser talks to the badge over USB and writes the image. It can install the badge’s asset files in the same session.

This browser cannot flash. WebUSB is required, and only Chromium-family browsers ship it (Chrome, Edge, Brave, Opera, Arc). Firefox and Safari do not. You can still flash from a terminal with dfu-util.

1 Pick your badge

BornHack 2026 — Cyber Ægg Badge documentation →

2 Put the badge in DFU mode, then connect

disconnected
Product
Serial
VID:PID
Mode

3 Choose the firmware

4 Flash

idle
Do not unplug the badge while it is writing. The bootloader writes straight to the application partition — there is no backup slot — so an interrupted flash leaves the badge in DFU mode until you flash it again. That is recoverable; just re-run the flash.

5 Add the asset files

Firmware alone leaves the badge without artwork. In DFU mode it also appears as a small USB drive named CYBR followed by four hex digits; the sprites, sponsor slides and the event programme belong in the root of that drive. Each firmware image has its own set — the one below follows your choice in step 3.

Copy them straight to the drive

Pick the CYBR… drive when your browser asks, and the files are written for you — no download, no unzipping.

idle
This browser has no File System Access API, so it cannot write to the drive directly. Download the zip below instead.

Or download the zip

Unpack it into the root of the badge drive, not into a subfolder. Eject the drive when you are done, then power-cycle the badge.

5 Add the data it needs

Diagnostics

If something goes wrong

The device chooser is empty. The badge is not in DFU mode. Put it in the bootloader first. On the Cyber Ægg, slide the ON/OFF switch at the top left of the front off, then back on, while you hold Execute. The LED then blinks red. Click Connect again. The battery keeps the badge running, so a disconnection of USB does not restart it.

The page connects, but it reports “application firmware (CDC)”. The cause is the same. You reached the running firmware, not the bootloader. Power cycle the badge into DFU mode.

“Failed to open the device” on Linux. A udev rule is missing. The badge’s own repository has one. Install the rule, connect the badge again, and retry.

The write stopped in the middle. Nothing is damaged. The bootloader writes directly to the application partition, so an interrupted write leaves the badge in DFU mode. Connect again and write again.

Checksum mismatch. The download did not match the published hash, and the page refused it before it wrote anything. Load the page again and retry. If the error continues, report it.

The badge reports “No sprites on flash”. The firmware is installed, but the asset files are not. Do step 5 above, then power cycle the badge.

I flashed DOOM, and nothing happens or it asks for game data. The firmware is only half of DOOM. DOOM does not use the USB drive. Its game data goes into the badge’s QSPI flash over a serial connection. Upload a WAD before you play.

Sprites are missing or wrong after a change of edition. Each firmware image has its own asset set, and the Community Edition draws many more sprites than the standard image. Install the assets of the image you wrote. Step 5 follows your choice in step 3 automatically.

No drive appears for the assets. The badge shows its USB drive only in DFU mode, the mode you write from. If you already power cycled into the new firmware, enter DFU mode again.

The copy finished, but the badge looks empty. Eject the drive in your file manager before you disconnect it. Until you do that, your operating system can still hold the data in its cache.

Flashing without a browser

Every image on this page is a plain .bin file for the application partition, so dfu-util takes it directly:

dfu-util -d 1915:521f -D cyber-aegg.bin

Adding a badge or a firmware image

data/firmwares.toml in the website repository drives the flasher. Put the image in static/firmware/<badge-id>/, record it in that file with its sha256sum, and it appears here. There is no backend to deploy.

Asset payloads work in the same way. Build the archive with flat, deflate-compressed entries. Put it in static/assets/<badge-id>/, and point the badge’s assets entry at it:

cd assets/to-badge && zip -rX -9 cyber-aegg-assets.zip .

Two constraints are important:

  • This site must serve the images. A cross-origin download needs CORS headers, and the release assets of Codeberg and GitHub do not send them.
  • Application images only. A combined image that holds the bootloader (cyber-aegg-full.bin on the Cyber Ægg) starts at address 0x00000000, and it is for SWD or J-Link recovery. DFU writes the application partition, so a combined image here would damage badges.

10 - DOOM

The badge runs DOOM. E1M1, Knee-Deep in the Dead, appears on the badge’s e-paper panel at a few frames per second, in four levels of gray. The piezo buzzer plays the music of the level.

The result is a slideshow, and you can play it.

How to install it

DOOM needs two steps, because the game data is much too large for the badge’s USB drive. The firmware goes into the badge’s program memory over USB. The game data goes into the separate 2 MB QSPI flash chip over a serial connection.

  1. Write the DOOM firmware on the Flash page, as you write any other image.
  2. Power cycle the badge.
  3. Upload a WAD with the loader below.

Upload a WAD

This browser cannot upload. Web Serial is required, and only Chromium-family browsers ship it (Chrome, Edge, Brave, Opera, Arc). Firefox and Safari do not. You can still send the blob from a terminal with a YMODEM tool such as sb or lrzsz.

1 Choose a WAD

No WAD chosen yet.

2 Put the badge in upload mode

Reset the badge while holding Cancel. It does this on its own too, whenever no WAD has been uploaded yet. The screen says it is waiting for a transfer.

disconnected

Pick the badge's serial port when the browser asks — on Linux it is usually /dev/ttyACM0, on macOS a usbmodem entry, on Windows a COM port.

3 Upload

idle

Nearly two megabytes over a serial line takes a few minutes. Leave the tab in the foreground and do not unplug the badge.

Diagnostics

Controls

InputAction
JoystickMove, strafe and turn
Joystick press (Fire)Shoot
ExecuteUse, and open doors
CancelChange weapon. Held during the start, it enters WAD upload mode
Execute + CancelTurns the music on or off

If something goes wrong

The browser shows no serial port. The badge shows its serial console only in WAD upload mode. Reset the badge while you hold Cancel, then try again.

“Timed out waiting for the badge to ask for data”. The badge is connected, but it is not in upload mode, so it asks for no transfer. Reset the badge while you hold Cancel.

The upload stops in the middle. Nothing is damaged. The badge has no usable WAD, and it returns to upload mode. Connect again and send the file again.

DOOM starts, but it reports missing game data. The firmware is installed and the WAD is not. You get this state after every firmware write. Upload the WAD above.

Firefox and Safari show no Connect button. Neither browser has Web Serial. Use a Chromium-family browser, or send the blob from a terminal with a YMODEM tool:

sb --ymodem-1k e1m1.blob < /dev/ttyACM0 > /dev/ttyACM0

About the game data

Only one level fits. The blob above is about 1.8 MiB, which is about 92% of the badge’s QSPI flash. The asset pipeline in the cyberaegg-doom repository builds it from the freely distributable shareware doom1.wad, and it cuts and compresses the data. DOOM and its game data stay the property of id Software.

To build your own blob from a copy of doom1.wad that you have:

bash tools/build_assets.sh    # doom1.wad -> build/e1m1.blob

Then select that file in the loader instead of the prepared one.

Source

The port is at rarenerd/cyberaegg-doom. It is a fork of next-hack/nRF52840Doom, which comes from prBoom and GBADoom. The fork fits the engine to the badge’s e-paper display, its buttons and its power budget. The DOOM engine is under the GPL.

11 - CircuitPython

The Cyber Ægg has a CircuitPython build. It turns the badge into a device you program in Python, with no toolchain. Flash it once. The badge then mounts as a CIRCUITPY drive. Put a code.py file on the drive, and the badge runs it when you save.

Ordinary CircuitPython modules reach the display, the buttons, the joystick, the LEDs, the buzzer and the battery. The same is true for the LoRa radio, NFC, Bluetooth Low Energy and deep sleep.

Install it

  1. Flash the CircuitPython image from the Flash page.
  2. Reset the badge with no button held.
  3. The badge appears as a CIRCUITPY drive. It also gives a serial console for the REPL: /dev/ttyACM0 on Linux, a usbmodem device on macOS, a COM port on Windows.
  4. Copy the libraries and the examples to the drive, as below.

Install the libraries and examples

The examples do not run without the support libraries. An import of cyberaegg_epd or the LoRa driver fails until the libraries are in CIRCUITPY/lib. Install them first. The same archive holds the examples. They go to CIRCUITPY/examples, so you can open one and save it as code.py.

Install the libraries and examples

The e-paper and LoRa drivers and adafruit_display_text for lib, plus the fourteen examples. They go in lib and examples folders in the root of the CIRCUITPY drive, which this creates for you.

idle
This browser has no File System Access API, so it cannot write to the drive directly. Download the zip instead and unpack it into the root of the drive — it already contains the lib and examples folders.

Pick the CIRCUITPY drive when the browser asks — not the CYBR… drive, which is the badge's own storage and only appears in DFU mode.

Diagnostics

Your first program

After you install the libraries, save this file as CIRCUITPY/code.py:

import cyberaegg_epd

display = cyberaegg_epd.get_display()

get_display() returns a standard displayio.EPaperDisplay. Build a displayio group as usual. Assign the group to display.root_group, then refresh the display.

The example epd_hello.py draws a bordered white field with one black square and one red square. The example hwtest.py tests the LED, the buzzer, the charger, the battery, I²C and all the buttons.

The install above puts every example in CIRCUITPY/examples. Copy one to code.py, and the badge runs it when you save. You can also read the examples online and the libraries, or paste an example into the console below.

The firmware includes terminalio and fontio, so adafruit_display_text can draw text labels. That library is in the repository’s lib/ folder.

The serial console

CircuitPython prints print() output and tracebacks to a USB serial console. It also gives a REPL there. The console tells you why a program did not run. Errors never appear on the display.

You can open the console here, without a terminal program:

This browser has no serial support. Web Serial is required, and only Chromium-family browsers ship it (Chrome, Edge, Brave, Opera, Arc). Firefox and Safari do not. Use a terminal program instead — screen /dev/ttyACM0 115200, picocom, or PuTTY on Windows.
disconnected

Click the console to type into it. Ctrl-C stops the running program and drops you at the >>> prompt; Ctrl-D restarts code.py from the top. Ctrl-V pastes into the console once it has focus, and the Paste button does the same without needing focus — handy for pasting an example straight from this page. Multi-line code goes in through the REPL's paste mode, so its indentation survives and it runs as one block.

Drag the bottom-right corner to resize the console, or use Fullscreen for the whole screen (Esc leaves it).

Ctrl-C stops the running program and gives you the >>> prompt. At that prompt you can test the hardware one line at a time. Ctrl-D starts code.py again from the top. This is the quickest way to run a program again after an edit.

The console is only available while CircuitPython is installed. The firmware gives the console, not the bootloader.

Work with the e-paper display

E-paper does not behave like a normal screen. Most first-time surprises come from this.

The badge shows the last image it drew. E-ink holds its image with no power. An EPaperDisplay object does not change the glass when you create it. To start from a clean panel, use this code:

import cyberaegg_epd

cyberaegg_epd.clear()          # white panel, one full refresh

If your program clears the screen and then stops, call displayio.release_displays() before it stops. If you do not, CircuitPython draws its own start logo over the clean panel.

Refreshes are slow, and each one wears the panel. A full tri-color refresh takes about twenty seconds. Obey these rules:

  • Use one refresh for each start. Draw the final image with the white background in the same frame, instead of one refresh to clear and a second refresh to draw.
  • Do not refresh more often than every 180 seconds.
  • Use full refreshes only. This panel does not do partial updates.
  • If the badge stays unused for a long time, leave the panel white. This prevents image retention.

A refresh does not block the program. display.refresh() returns immediately, and the panel continues to update for about six seconds. Read display.busy if you must wait. You can also call cyberaegg_epd.refresh(display). That function waits for the minimum interval of the panel, then blocks until the refresh ends.

LoRa

The repository’s lib/ folder has a driver for the SX1262 radio. The driver uses MeshCore-compatible EU settings: 869.618 MHz, SF8, 62.5 kHz bandwidth, coding rate 4/5, sync word 0x1424, no TCXO.

The driver and the SX126x files it needs are part of the library set that you installed above. You copy no more files:

import cyberaegg_lora

radio = cyberaegg_lora.LoRa()
radio.send("hello")
data, err = radio.receive(timeout_ms=5000)

examples/lora_tx.py sends a counter. examples/lora_rx.py receives, and prints the size and the signal strength of each packet. The receiver works against a second badge that runs lora_tx.py. It also works against live MeshCore traffic. lora_dashboard.py shows live packet statistics on the e-paper display.

NFC

The firmware includes NFC, so you copy nothing into lib/. The badge serves a read-only tag. A phone that touches the badge opens a URL. See examples/nfc_tag.py and the repository’s docs/NFC.md.

Bluetooth

The badge advertises and accepts Bluetooth Low Energy connections from CircuitPython, through the standard _bleio API. It uses the factory address from FICR.DEVICEADDR. The normal Rust firmware advertises with the same address.

import _bleio

adapter = _bleio.adapter
print(adapter.address)

# Flags: LE General Discoverable, BR/EDR not supported, then the complete name.
advertisement = bytes((2, 0x01, 0x06)) + bytes((10, 0x09)) + b"CyberAegg"
adapter.start_advertising(advertisement, scan_response=None, connectable=True,
                          anonymous=False, timeout=0, interval=0.1,
                          tx_power=0, directed_to=None)

A phone scanner then shows the badge. On Linux, use this command:

bluetoothctl --timeout 20 scan le

examples/ble_advertise.py does the same, and advertises again after a central disconnects. Connections and GATT also work. examples/ble_uart.py serves a Nordic UART Service. Any BLE terminal app, such as nRF Connect or Adafruit Bluefruit Connect, connects to CyberAegg. The text you send arrives on the badge’s serial console.

Talk to the badge from a phone

Three examples use that same UART service, so they need no library and no app of your own.

examples/ble_remote.py controls the badge. Send a color name to set the LED, or beep for the buzzer. The badge reports each button press back to the phone. examples/ble_repl.py gives you a Python prompt. Send a line, and the badge runs it and returns the result. Names stay for the whole session, so you build up state line by line. This is not the CircuitPython REPL. The REPL of the supervisor cannot run over Bluetooth on this firmware, because it starts before the MicroPython heap exists.

examples/ble_telemetry.py needs no connection at all. It puts the battery voltage, the buttons and the uptime in the advertising packet, and it refreshes that packet every two seconds. Any scanner reads the values. Advertising is the most dependable part of Bluetooth here. Nothing holds the single connection slot, so many badges report at the same time, and any number of phones read them. This is the right shape for a field full of badges.

Find and connect to other badges

The badge also scans, which is the observer role. It connects out, which is the central role. It reads what the other device offers, which is the GATT client. Two badges therefore find each other and inspect each other.

examples/ble_scan.py lists what the badge hears, and it decodes the beacon of a badge that runs ble_telemetry.py. It scans and advertises at the same time, so two badges watch each other with no connection between them. examples/ble_central.py connects to a second badge that runs examples/ble_uart.py. The link comes up in about 0.2 seconds, and both badges report the same state. examples/ble_explore.py connects and then lists every service and characteristic of the other device. It works against any device, not only against a badge.

Scanning uses the multirole controller library, because the peripheral library has no scanning. That library costs about 31 KB more flash.

Four limits are important:

  • The badge does not read a remote value. It finds the services and the characteristics of a device it connects to. Characteristic.value on a remote characteristic is not implemented. The badge therefore knows what a device offers, and not yet what it holds.
  • The badge does not find a remote descriptor, so it cannot subscribe to a notification from another device.
  • Legacy advertising only. The advertisement must fit in 31 bytes.
  • Bluetooth does not work together with the display. A full tri-color refresh keeps the panel busy for tens of seconds. The background work of displayio competes with the Bluetooth poll, and a connection does not survive a redraw. Keep Bluetooth and the display in separate programs.

Bluetooth starts when a program first imports _bleio, and then stays on. This uses battery power.

The MeshCore companion app cannot talk to the badge while CircuitPython is installed. MeshCore is part of the normal badge firmware, and this image replaces that firmware.

Deep sleep

The alarm module works, with pin wake-up and time wake-up. A battery-powered program can sleep between refreshes instead of a busy loop:

import alarm
import time

alarm.exit_and_deep_sleep_until_alarms(
    alarm.time.TimeAlarm(monotonic_time=time.monotonic() + 300)
)

E-paper keeps its image with no power. The display stays readable during the sleep.

If something goes wrong

No CIRCUITPY drive appears. The badge is still in the bootloader, or the flashing tool rebooted it instead of a reset. Reset the badge with no button held.

My code did not run. CircuitPython runs code.py from the root of the drive. Check the file name. Open the serial console. The console prints syntax errors and tracebacks. The display does not show them.

The display shows the CircuitPython logo over my drawing. Your program cleared the panel and then stopped. Call displayio.release_displays() before the program stops, or keep the program running.

The screen did not change. The firmware limits the refresh rate. It ignores a request that comes too soon after the last refresh. Wait for the interval, or use cyberaegg_epd.refresh(display). That function does the wait for you.

A Bluetooth connection times out, but scanning still works. Check the other computer or phone first. A connected Bluetooth headset takes almost all of the radio time there, and connection attempts then fail while scans still succeed. Disconnect the headset and try again.

Source

The firmware, the libraries and the examples are at rarenerd/cyberaegg-circuitpython. Go directly to the examples or the libraries. The image on the Flash page is that repository’s prebuilt binary, byte for byte. Its checksum matches the published SHA256SUMS file. The repository’s docs/BUILDING.md has the build instructions.

CircuitPython is MIT, and the badge port is Apache-2.0. The Bluetooth build also links Nordic’s SoftDevice Controller and MPSL under LicenseRef-Nordic-5-Clause. That license allows redistribution, but only for use on Nordic silicon. The badge’s nRF52840 is Nordic silicon.