ESP8266 firmware that gives the TI-84 Plus CE Wi-Fi, HTTP and HTTPS. Flash it onto an ESP8266 board, plug the board into the calculator's USB port, and programs built with tinclib can call REST APIs, fetch web pages and upload data. No board? Run the same firmware as a desktop app and plug the calculator straight into your PC.
- HTTP and HTTPS: GET, HEAD, POST, PUT, PATCH and DELETE, request bodies, response headers, chunked transfer and redirects.
- Verified TLS: certificates are always checked against Mozilla's CA roots, built into the firmware image. TLS 1.2 via BearSSL.
- Wi-Fi profiles: 5 saved networks (hidden ones included), joined strongest-signal first, with an optional firmware-side lock.
- Small and non-blocking: fits in the ESP8266's ~40 KB of free heap and never blocks the main loop, so the calculator always gets a reply in time.
- Calculator-friendly: responses are pulled in pieces as the calculator reads them, with optional transcoding to the CE's character set.
- Runs on a PC too: the same core builds as a Windows, Linux and macOS app, with a packet trace for debugging.
- Tested: native unit tests with the protocol's golden vectors, a fuzzed dispatcher, and an end-to-end test of the PC build.
tinclib-firmware is one part of four:
| Repo | What |
|---|---|
| tinclib | The C library your calculator program links against |
| tinclib-firmware (this one) | Firmware for the ESP8266 board, which does the networking |
| tinclib-config | TINCLIBC, the calculator app for Wi-Fi setup |
| tinclib-protocol | The wire protocol between calculator and board |
TI-84 Plus CE ──USB──> ESP8266 board ──Wi-Fi──> the internet
(tinclib) (this firmware)
This release speaks tinclib-protocol v0.6. See tinclib's README for which boards the calculator's USB driver supports.
Download a build from Releases:
| Target | File | Status |
|---|---|---|
| ESP8266 | tinclib-firmware-esp8266.bin |
protocol 0.6, 5 Wi-Fi slots |
| PC: Windows, Linux, macOS | tinclib-pc-* |
protocol 0.6, uses the PC's network |
| ESP32 | planned |
Flash the ESP8266 build:
pip install esptool
esptool.py --port COM5 write_flash 0x0 tinclib-firmware-esp8266.bin # /dev/ttyUSB0 on LinuxThen set up Wi-Fi from the calculator with TINCLIBC.
git clone --recursive https://github.com/gavinhsmith/tinclib-firmware
pip install platformio
pio test -e native # core tests on the PC
pio run -e esp8266 # ESP8266 firmware
pio run -e esp8266 -t upload
pio run -e pc # PC app, see belowOn Windows, run pio from PowerShell or cmd. MSYS2's gcc fails silently
when it is invoked from Git Bash.
env:pc builds the firmware as a desktop app that stands in for the board. Plug the calculator straight
into the PC; tinclib then runs in USB device mode and shows up as a serial port, and the app answers it
using the PC's own network.
pio run -e pc # -> .pio/build/pc/program(.exe)
.pio/build/pc/program COM7 # Windows; /dev/ttyACM0 on Linux, /dev/cu.usbmodem* on macOSThe app waits for the port to appear and reopens it if the calculator is unplugged, which the calculator
sees like a board reset. The PC can't join another network, so it reports itself as connected, has a
single Wi-Fi slot named "LAN", and keeps it locked (the protocol's Wi-Fi lock): WIFI_SET and
WIFI_FORGET get ERR_LOCKED, and every request goes out through the PC's own networking stack.
Every packet is printed to stdout as it passes, one readable line each (> from the calculator, < the
reply); log lines (request states and errors) go to stderr:
3.102 calc > #2 HELLO v0.6 max_payload=256
3.103 calc < #2 HELLO ok v0.6 max_payload=1024 heap=1048576 wifi_slots=1
3.210 calc > #5 REQ_BEGIN GET http://api.example.com/v1/items?... (headers: 31 bytes, not shown)
3.498 calc < #7 REQ_STATUS BODY http=200 len=812 type=application/json
3.520 calc < #8 BODY_READ @0 200 bytes: "{\"items\":[{\"id\":1,\"name\":\"first\"},{\"id\":2,\"n"...
The trace never shows request header text, request bodies, response header values, URL query strings (they often carry API keys) or Wi-Fi passwords; it shows their lengths instead. Response body data is cut to its first 48 bytes.
To test a real board with the calculator before it has its own power, plug both into the PC (the board is powered by its USB) and bridge the two ports:
.pio/build/pc/program COM7 --bridge COM5 # calculator's port, then the board'sEverything is passed through unchanged, and every packet is traced exactly as above; the board, not the app, answers the calculator. The board port's DTR and RTS are kept off so the dev board's auto-reset circuit doesn't hold the ESP in reset. Either side can be unplugged and comes back on its own.
test/pc/e2e.py tests it end to end (add --bridge to run the same test through bridge mode): a
pseudo-terminal plays the calculator and a local HTTP server the internet (Linux and macOS; CI runs it
on both).
One shared core runs on every target. Each target adds a thin platform layer.
| Path | What |
|---|---|
lib/tinc_core/ |
Target-independent C99 with no Arduino or OS calls: UART framing link, dispatcher, request state machine, HTTP parser, ASCII transcoding, Wi-Fi ranking |
lib/tinc_core/tinc_platform.h |
What every target must provide: clock, heap, UART, non-blocking TCP, Wi-Fi status and slot storage |
lib/tinc_tls/ |
HTTPS for the BearSSL targets (ESP8266 and PC) over the platform's plain TCP |
certs/cacert.pem |
Mozilla's CA roots, turned into a flash-resident table at build time by tools/gen_roots.py |
platforms/<target>/ |
One platform layer per target. It implements tinc_platform.h and calls tinc_link_step() and tinc_poll() from its loop |
external/tinclib-protocol |
Submodule pinned to a protocol tag. lib/tinc_core/proto.c builds it; it is never copied |
test/test_core/ |
Native Unity tests for the core, including the protocol's golden vectors, run against test/fake_platform.h |
test/fuzz/ |
libFuzzer target for the dispatcher |
test/pc/e2e.py |
End-to-end test of the PC target |
- Create
platforms/<target>/implementinglib/tinc_core/tinc_platform.h. Nothing in it may block: DNS, connect, UART writes and Wi-Fi joins must all make progress one loop iteration at a time. - Add
[env:<target>]toplatformio.iniwithbuild_src_filter = +<<target>/>. - Add the target to the
buildmatrix in.github/workflows/ci.yml.
The core and its tests don't change. If a target needs something the
interface doesn't offer, extend tinc_platform.h and the fake together.
- The
esp8266env uses theesp12eboard, which assumes 4 MB of flash. Check the module withesptool.py flash_idbefore the first flash. For a 1 MB module, setboard_build.ldscript = eagle.flash.1m64.ld. - TCP uses lwIP's raw API because
WiFiClient::connect()blocks. The data the server sends is only acknowledged as the CE reads it, so a slow reader slows the download instead of filling RAM. - TLS tops out at 1.2 (BearSSL), so a server that requires TLS 1.3 fails
with
ERR_TLS. The clock comes from SNTP after joining Wi-Fi; HTTPS waits for it. - Wi-Fi lock: build with
-D TINC_WIFI_LOCK=1(e.g.build_flagsinplatformio.ini) to stop the calculator changing the saved profiles. There's no physical switch wired up yet. - Hidden networks (the
HIDDENslot flag) are joined by name after every network the scan did show. - 5 Wi-Fi slots (
-D TINC_SLOT_COUNT=5inplatformio.ini; the core reports it inHELLO). - Profiles saved by the older 3-slot firmware (protocol 0.1/0.2) move into slots 0-2 on the first boot after an upgrade.
- The debug log goes to Serial1 (GPIO2, TX only). The reset reason is logged at boot.
- The ESP8266 SDK doesn't report WPA2-Enterprise in scan results, so those networks can't be flagged as unsupported. Joining them simply fails.
- The
WIFI_SETcomment inprotocol.hsays the ESP joins "the first reachable slot, 0 → wifi_slots-1". This firmware ranks candidates by RSSI instead, as AGENTS.md requires. The protocol.h comment should be fixed upstream. - There is no error code for a storage failure. A failed slot write returns
ERR_NO_MEM.