Rust SDK Potential?

Hi there, I have a preorder for a Pebble Round 2 coming in July and I’m excited to see what can be done with it. I have a lot of experience with Rust and it seems like the right kind of language for something like this, and I was wondering if anyone else is planning on writing apps in Rust and/or working on an SDK. I’ve looked through discord a bit and seen a few “rewrite it in rust” jokes but so far haven’t found anything substantial aside from this project which was created and last updated 7 years ago in 2019.

Doing some research it looks like pebble was discontinued about 10 years ago so I guess it makes sense that it would’ve mostly missed the whole wave of Rust which only went 1.0 a year earlier. In that time the Rust embedded ecosystem has developed a lot and I’m curious to see what kind of Developer Experience could be had these days.

Are there any Rust embedded experts (or enthusiastic novices) in here, is there anyone else interested in developing Pebble apps with Rust?

I did a beginner’s tutorial in Rust recently (so I’m a novice) and intend to continue learning Rust. A Rust SDK for the Pebble would be great for learning :slight_smile:

But I’m unfortunately not able to help with working on an SDK.

I made a pair of (pretty bad and incomplete) Rust libraries back then and am in the process of fixing and updating them since I received my Time 2 this week. I’d like to make them a bit nicer than the C API and properly document it though, so I’ll have to poke someone about valid-and/or-safe parameters and return values, which could take a little while.

(I also got the Python patch upstreamed into the SDK a few months ago, so it won’t fail to build from the object file count anymore.)

I think the old tutorial will still work, but it would be cool if we could just cargo pebble build or cargo pebble run without too much setup, like you can do towards 3DS. (Ideally somewhat library agnostic.)
If I’d tackle that, I’d only do so after completing the bindings and a minimal safe wrapper around them, though.

I think a (for Rust somewhat unusual) key aspect here is to aim for very little or no added binary size with any platform abstractions. I’d say I’m pretty decent at that at this point, but I’m definitely open to suggestions and contributions.

I’ve also got something in the works, based on bindgen. I think in a couple days it’ll be in a shareable state.

Currently I have windows, layers (text, custom, and bitmap), and timers working on my PT2 through safe wrappers.

On the user side, the code looks like:

    let mut window = Window::new().unwrap();
    window.set_background_color(GCOLOR_BLUE_MOON);

    let mut bitmap_layer = BitmapLayer::new(GRect::new(40, 8, 16, 16)).unwrap();
    window.add_child(&mut bitmap_layer);

    let hero_sheet = Bitmap::from_resource(1 /* gotta improve resource keys */).unwrap();
    let hero_frames = [
        hero_sheet.extract(GRect::new(0, 16, 16, 16)).unwrap(),
        hero_sheet.extract(GRect::new(16, 16, 16, 16)).unwrap(),
        hero_sheet.extract(GRect::new(32, 16, 16, 16)).unwrap(),
        hero_sheet.extract(GRect::new(48, 16, 16, 16)).unwrap(),
    ];
    bitmap_layer.set_bitmap(&hero_frames[0]);

    let mut count = 0;
    Timer::repeat(Duration::from_millis(50), move || {
        count = (count + 1) % hero_frames.len();
        bitmap_layer.set_bitmap(&hero_frames[count]);
        true
    });

    window.show();
    APP.event_loop();

Yes, there is some overhead with reference counting, but I think the ergonomics are worth it. Without the very permissive use of static in c, getting the lifetimes to work out was beyond me.

@Tamschi is the equivalent possible in pebble-skip yet? What would the code look like?

Here’s what I’ve got so far: GitHub - cmbartschat/pebble-rust-2026 · GitHub

@cmb excellent work! I used your library to build a watchface, and as you might have seen, I submitted a PR with various improvements. But the available functionality is already great!

I also don’t like the current build setup, with a carefully-crafted wscript that every user has to copy and tons of specific RUSTFLAGS. So I also made cargo-pebble which automates all of that away and means you don’t have to have neither wscript nor cargo/config.toml. (It’s also super configurable!) It should also work with other libraries as long as they only require cargo buildto produce a static library.

I’m looking forward to feedback (from everyone) on these changes and on the tool, let me know if it works for your project!

@Tamschi your cargo pebble build vision should already work :slight_smile: I didn’t add a run command because the build tells you where the .pbw file is generated, and you can very easily run any standard pebble install <path/to/file.pbw> command after that. (In theory one could hack together something with the custom run command that Cargo supports, so that it instead calls pebble install, but the build command itself is not overridable…)

I also need help with figuring out why specifying -Ctarget-cpu=cortex-XXX or -Ctarget-feature hard-crashes the OS (even when making sure that the FPU is not enabled, e.g. -Ctarget-feature=-fpregs). I’ve tried a few combinations and none work on any of the 3/4 different cores (Cortex-M3, M4 and M33). To be honest I’m out of my depth with ARM embedded, I haven’t ever worked with that much :3

Taking a look, thanks!

Just tested out `cargo-repebble`. Some thoughts:

1. It works!!! That’s amazing.

2. Aplite has lower size limits it seems, even the relatively simple GitHub - cmbartschat/pebble-64cores · GitHub exceeds it. I can try to investigate pebble-rust-2026 exports that can be shrunk.

3. Would be nice if we don’t need to specify the path on the output of `cargo pebble build`. Does `pebble install` just choose the first `build/*.pbw` it sees? I ran a wscript-based build, then deleted wscript, and `pebble install` still found it. Maybe copying the pbw from `target/` to `build/` is all we need.

4. `touch_service_is_enabled` and others are not defined when compiling for e.g. diorite, so GitHub - cmbartschat/pebble-rust-demo · GitHub does not compile for all platforms. I guess that makes sense. If you don’t use the features, they get stripped out of the object file, so you can build backwards-compatible apps by targeting the lowest common denominator, but can’t do any progressive enhancement it seems. In C, there are #defines for these features, but we need to translate those to a form we can guard with `#[cfg()]`. Not really sure the best way to do that.

Thank you for the patches, suggestions, and kicking off multi-platform support!

1. It works!!! That’s amazing.

To Just Work™ was the whole point :slight_smile: I tested it on the Demo project as well as my own, which is largely based on the demo project.

I can try to investigate pebble-rust-2026 exports that can be shrunk.

I investigated this a bunch, some major offenders are the global callback handlers for some reason, as well as parts of the layer handling (on stable, 1K in a few functions). Not really things that can be removed, and I didn’t see how to improve the code itself. May be related to complex Vec operations in a few cases (e.g. swap_remove). Most of these functions shrunk by almost 2x in “nightly” mode (with build_std, using optimize_for_size for the standard library, and many other unstable size optimization flags).

A major issue is actually the panic handler, since thanks to retrieving the panic message, it will pull in the entire formatting infrastructure, which adds at least 2-3K on Aplite. Using -Cpanic=immediate-abort (nightly-only), or disabling the Debug machinery both get rid of this, but maybe we can make this configurable from the outside too? So the panic handler is an almost-noop (maybe a single cheap log_c_str("Panicked!") call) if the user chooses to enable a certain feature flag.

3. Would be nice if we don’t need to specify the path on the output of `cargo pebble build`.

To be fair I didn’t investigate this a lot, so I’ll see how pebble actually detects this. A major goal with cargo-pebble was to avoid having all three target, build, and node_modules directories, so it would be unfortunate if we had to add back the build directory, but I’ll consider it if it improves the DX.

As far as I know the build directory is fully configurable from wscript, so not sure how this works in practice?

4. `touch_service_is_enabled` and others are not defined when compiling for e.g. diorite

I have a few thoughts for how to solve this situation. First, it’s important to note that within pebble-rust-2026, it’s probably pretty hard to detect which build we’re running under, even with bindgen. This is complicated by the fact that currently up to 4 platforms share one Rust target and therefore one build.rs/bindgen invocation.

  • Add feature flags for each platform, which configures various internals of pebble-rust-2026, and makes only the appropriate functionality available. This usually means the user has to have the same feature flags on their own project and pass them through, so they can do #[cfg] in their own code. This would also mean that cargo pebble would have to enable the appropriate feature flags, which is easy enough to handle in the custom wscript. (This is actually a main reason I wanted to have a “common” wscript, so that we as tool authors can make improvements which all users will automatically benefit from once they update!) FYI this approach has precedent in how esp-rs, the officially supported Rust SDK for ESP32, handles it, see e.g. here
  • Add macros à la cfg_select! which would only generate the code for the appropriate platform. This would allow the user to avoid having lots of feature flags, but also require cfg options (either features, or even better a raw option like pebble_platform) on pebble-rust-2026, set from build.rs based on bindgen information (? not sure if that’s possible) or other environment information. I’m not 100% sure how to thread the platform information through the entire stack (pebble build → wscript → cargo → rustc → build.rs → bindgen → proc-macro) in this case, and it would also require non-cargo-pebble users to possibly do extra work or use more advanced wscript configuration, similar to what cargo-pebble uses. This really depends on whether you want pebble-rust-2026 to keep working “easily” with custom build configurations, or make cargo-pebble a hard dependency. I don’t have an opinion on this, I’m usually for more flexibility when possible.
  • Do both :3 (macros for selection, but also feature flags for pebble-rust-2026 which can be inherited by the user if desired)

In any case I think this will require splitting the Rust build from a maximum of 3 currently to one build per platform. Especially once the new build directory layout is finished, they should all run in parallel properly, so I don’t think this will impact cold compile times all that much (hot compile times are decently fast anyways, I currently get 6 seconds of total build time which includes several slow npm invocations and whatever waf and pebble-tool are doing).


I would be interested in combining everyone’s Rust-Pebble efforts! I’ve reserved pebble-rust on Codeberg, in case you’re open to transferring the pebble-rust-2026 repos (including the demo repo) over there. I would also transfer the cargo-pebble repo there, and accept other people’s Rust projects if they’re intended for a general audience.

Also, I’ll be investigating the broken fmt!(), and maybe look into whether ufmt!() works instead.

Edit: Formatting with the Rust formatting infrastructure does actually work, it just uses a lot of memory, so it may simply OOM in most practical applications. ufmt is semi-broken and quite outdated, I won’t put any effort in adding special support for it. I’ll do some experiments for integrating defmt, which will indeed require a new pebble install replacement command :slight_smile:

Edit: Using defmt is impossible because it requires sending null bytes, which are not allowed in the logs. ufmt should work for basic use cases but for now there’s not really a good solution.

In regards to autodetection of the PBW from pebble-tool: It does indeed simply check build/<folder name>.pbw! I’ll add an opt-out feature to create this file from cargo-pebble.

Edit: done, available from the main branch, I’ll publish v0.2 later on the weekend if no other bugs or features come up.

Edit: Added the ability to pass linker flags to the actual ld invocation within the Waf build, which was intended for defmt, but is probably a useful feature regardless.