Keep an eye on your data at all times with this desktop terminal for PostHog.

- Coming soon: Order a kit with all hardware
- ESP32-S3 Reverse TFT Feather –
Adafruit 5691
– buy: Adafruit, DigiKey, Mouser, Botland (EU), Cool Components (UK), Electromaker, BerryBase (EU) - Optional: PKCell 552035 350mAh 3.7V LiPoly battery – buy: Adafruit, Tinytronics (EU), BerryBase (EU)
- 3D printed enclosure – print: 3mf file
Use PlatformIO to open this project. It works with VSCode and Cursor, streamlining package management, builds and flashing microcontroller boards.
Microcontrollers are a pain. They've got limited memory and, for our purposes here, you've got to write C++ 🫠
But in exchange, our code can touch reality like no other kind of project. Here's what we're dealing with.
If you've ever written mobile code, you'll feel right at home: we can only update the UI via the UI thread, otherwise the board crashes.
We've got two cores and multiple "tasks" assigned between them – task is FreeRTOS-speak for threads:
Core 0 (Protocol CPU) tasks:
- WiFi
- Web portal server
- Insight parsing
- NeoPixel control
Core 1 (Application CPU) tasks:
- LVGL tick (maintains timing, animations, etc for the graphics library)
- UI: screen drawing and input handling
We have to keep this stuff carefully isolated or we're going to crash.
Nothing wrong with a little agent-driven coding. This project has leaned on it plenty.
But beware: the LLM agents are very bad at modeling cross-thread interactions and thread-safe architectures on their own. You'll need to lead them explicitly. The existing architecture seems pretty stable and predictable at this point. Lean on it. If your robot ventures off the trail and takes initiative that breaks these patterns, you'll end up with crashes and your pulls will not be accepted.

If the board isn't responding:
- Hold ▼ (Page down/D0)
- Press Reset
- Release ▼ (Page down/D0)
The board will restart in bootloader mode, where it can be re-flashed using PlatformIO.
- Status card: working
- WiFi provisioning card with QR Code: working
- Friend card to give you (mild) reassurance: working
- Numeric card for Big Number insights: working
- Funnel card: needs a redesign; probably should be horizontal layout instead, won't display more than three steps right now
- Line graph card: broken, not properly scaling larger data sets, probably fine if you have an insight scoped between 7-30 days
- Other insights: not yet supported
EventQueue
is how the project manages communication between tasks and prevents coupling. Events – changes via the web UI, returned requests from the PostHog client – are dispatched out of core 0 to be received by the UI task. Any important data can be safely copied from one context into the other, preventing crashes and other drama.
The UI is a stack of cards. The user navigates between them using built-in buttons (the arrow keys)
CardNavigationStack
manages the UI presentation of these cards, animating transitions.
CardController
manages updates to the stack contents. If an insight is deleted or added via web UI, the controller processes that update reactively.
A basic provisioning and configuration UI is provided. You can access it via a QR code on first launch, and by the IP shown in the status screen once WiFi is configured.
Open html/portal.html
in your browser to preview changes. The contents of html
are inlined into a single file on each build by htmlconvert.py
.
Web portal budget: Right now the portal costs about 18KB. We'll allow up to 100KB. All portal assets must be locally available, since the portal needs to work when the device doesn't have WiFi. If you want to try adding a more complex UI framework than hand-rolled JS and HTML, you're welcome to try as long as its build system is quick and the final static output is under 100KB.
ProvisioningCard
displays a QR code to connect to the device. If WiFi is connected, it displays connection stats.
InsightCard
visualizes PostHog data. Numeric card is working best. The rest need help.
FriendCard
lets Max the hedgehog visit with you and provide encouragement.
InsightParser
ingests PostHog API responses and makes them available to the UI. PostHogClient
constructs requests and dispatches responses.
This project relies on the powerful LVGL project at v9.2.2 for drawing, animation and other UI tasks.
ConfigManager
handles persistent storage and retrieval of credentials and insights. CaptivePortal
provides the web server and interacts with ConfigManager
to read and write to persistent storage.
The following PRs would be interesting, and may earn you a free DeskHog kit:
- Additional insight parsing and visualization
- Flappy hog or other silly game things to do; constraint: you can only use the center button as input
- Support for other boards and displays
- Enhance the web UI and
ConfigManger
to allow re-ordering of insights, set custom titles per-insight - LLM slop mitigation: if you see anything obviously stupid in this code that hasn't yet been caught and cleaned up
- DX improvements around task isolation: if your experience with embedded code says there's a better way to architect this, happy to follow your lead
- Improved C++: not my preferred language, feel free to suggest idiomatic and architectural improvements
- Desk utilities, like a pomodoro timer; also constrained: center button only. Be creative!
- PlatformIO config improvements: flashing builds causes a reboot that seems unnecessary, maybe you know a better configuration
- OTA update mechanisms
PRs over issues, but if you've got any trouble, feel free to open an issue. You can also contact the maintainer, danilo@posthog.com.