Skip to content

Development#11

Draft
Gadgetoid wants to merge 71 commits intomainfrom
dev-jan-2026
Draft

Development#11
Gadgetoid wants to merge 71 commits intomainfrom
dev-jan-2026

Conversation

@Gadgetoid
Copy link
Member

@Gadgetoid Gadgetoid commented Jan 27, 2026

Changelog

⚠️ You will need to flash the with-filesystem build since there are breaking changes which require the apps to be updated. Back up your filesystem first!

Latest experimental build here: https://github.com/pimoroni/tufty2350/actions/runs/21787998506/artifacts/5419215551

Thonny

The MVP for a working program is either:

def update():
    screen.text("Hello World", 0, 0)
    
run(update)

or - avoiding run:

def update():
    screen.pen = color.black
    screen.clear()
    screen.pen = color.white
    screen.text("Hello World", 0, 0)

while True:
    badge.poll()
    update()
    display.update()

FAT FS corruption issues

After much deliberation I realised we're setting the filesystem label every time the board boots. This means there's a write to the FAT filesystem which could potentially go wrong†. This is now removed, with the label set in the filesystem image instead. Hopefully this leads to a more robust FAT.

In addition to this, the board will save a checksum file and backup of the 16K FAT at the head of the filesystem. If the board fails you may be able to use Thonny and a simple script to recover the last backed up FAT.

TODO: Maybe make this an explicit feature of MSC mode requiring a button combo/long press or something unlikely to happen accidentally.

† - by wrong, I mean that a flash write starts with a block erase and if your board resets between the erase and the new data being written then you have a 4k empty block where there should have been 1/4th of file allocation table.

Picovector

  • jpeg image support

  • loading images from RAM/buffers support image.load(open("image.jpeg", "rb").read())

  • A small bugfix to fix the right-hand edge of some vector shapes being cut off.

  • The dda algorithm ended up being full raycast and was renamed to raycast with dda for a single ray.

  • vspan_tex is now blit_vspan with floating point texture coordinates

io is now badge

io has been - roughly - renamed to badge and buttons are now builtin, this changes io.BUTTON_A in io.pressed to either BUTTON_A in io.pressed() or io.pressed(BUTTON_A)

io.ticks -> badge.ticks
io.ticks_delta -> badge.ticks_delta
badgeware.get_battery_level() -> badge.battery_level()
badgeware.is_charging -> badge.is_charging()

badge has new features:

  • badge.model returns "tufty", "blinky", or "badger" depending on board
  • badge.uid returns the board UID as hexadecimal represented as an ASCII string
  • free() is now builtin

Caselights

The caselights, the four lights illuminating the case, are now accessed through:

badge.set_caselights(v1[, v2, v3, v3])

and:

v1, v2, v3, v4 = badge.get_caselights()

rtc functions now have their own module

Functions related to the RTC have been moved into the builtin rtc. This includes helpers like rtc.set_timer(minutes=N).

Images

🥳 jpeg image support!

Both JPEG and PNG images support downsizing on load.

my_image = image.load(filename, max_width, max_height)

Text Module

text_tokenise() -> text.tokenise()
text_draw() -> text.draw()
scroll_text() -> text.scroll()

Mode now includes VSYNC (Tufty only)

mode() is now badge.mode(), it still supports LORES and HIRES and has an additional VSYNC option to enable VSYNC. Avoiding VSYNC will normally give you a better framerate where your app logic takes over ~8ms, since you wont be missing entire VSYNC periods and having to wait up to 16.6ms. Using VSYNC will fix screen tearing at the cost of raw framerate. Currently display.update() on Tufty blocks for around 7.7ms leaving you only 8ms for logic/drawing.

To enable VSYNC when changing mode use badge.mode(HIRES | VSYNC) or badge.mode(LORES | VSYNC)

Add a mode to return the framebuffer to the user without being cleared.

Allows for a slightly better framerate when the whole screen is overdrawn.
Add builtins for X2, X4 and OFF so we can:

image.antialias = X2
@Gadgetoid Gadgetoid mentioned this pull request Feb 2, 2026
MichaelBell and others added 2 commits February 2, 2026 14:30
Deprecate passing True/False into display.update() to pick pixel
doubled vs full resolution.

Move this to display.fullres(True / False)
MichaelBell and others added 7 commits February 3, 2026 22:54
This violated the cardinal rule: don't write to FAT from the board.

Highly probable source of our corrupt-from-just-resetting issue.

Remove any attempt to recover the FAT during boot and instead emit an
error. This leaves us with room to inspect or attempt to recover the
filesystem rather than the board simply resetting it.
Allow the user to attach Thonny or a serial terminal and try to
diagnose, recover or reformat the filesystem.

Note: _boot_fat.py occurs before USB is enabled.
MichaelBell and others added 22 commits February 7, 2026 22:30
text.scroll broke convention with other drawing methods by accepting
a bg and fb pen, which is confusing terminology we don't use elsewhere.

Remove the pen settings to fix this.

The "continuous" argument also made no sense, and was easily confused
as "keep scrolling forever". Change it to "gap" to be more explicit
that it controls the gap between scrolling text instances, not how
many times the text will scroll.

Finally make the default target *just* screen so that screen.pen will
work, otherwise this gets copied to the implicit window only once on
scroll init and cannot be subsequently changed.
We had lost support for UTF-8 codepoints and by extension everything
from the degrees sign to many accented characters.

Add this support back in and expand MonaSans-Medium.af with the
necessary codepoints for (incomplete) European language support.
Move much of the busywork handled by main into run() and allow it to
accept an app path.

Fix fonts set by import being overriden at runtime (except where an
inline run(update) is used).

Don't clear twice on the first update.

Make sure all cleanup happens on HOME exit, exceptions etc.

NOTE: "on_exit" will fire in all cases, even if the app crashes with
an exception. Care should be taken not to write garbage if app init
has failed.
Return bytes in all cases so we're not mixing units.
badge.default_pen(pen) -> badge.default_pen = pen
badge.default_clear(pen) -> badge.default_clear = pen
w, h = badge.resolution() -> w, h = badge.resolution
badge.set_caselights(c1[, c2, c3, c4]) -> badge.caselights(c1[, c2, c3, c4])
c1, c2, c3, c4 = badge.get_caselights() -> c1, c2, c3, c4 = badge.caselights()
@Gadgetoid Gadgetoid force-pushed the dev-jan-2026 branch 2 times, most recently from 8d7973e to cd2ab9e Compare February 16, 2026 09:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants