The size of the type name isn't correlative, but the nesting depth of types roughly corresponds with how many function bodies are going to be generated. In the GUI library, the HStack/VStack types are type-parameterized by their children, which results in a new copy of the layout function for each combination of children types.
A large part of this project was my personal learning. Ultimately I didn't improve the functionality of the device by a large margin; instead I learned a lot about how these devices are built. If I'd let Claude write replacent firmware it would've all been for naught.
Apologies for hijacking this comment, but if you have any interest in doing this sort of work full-time I may have a job for you. This project is awesome.
This is definitely something to be aware of. Installing this firmware definitely invalidates the StVO (though I can always flash the original firmware back). Personally I am morally sound with replacing the firmware of the display unit as the critical functionality is still handled by the motor controller (for example, the brake lever sensors are wired to the motor controller directly, and the display must report the throttle position constantly to not trigger a shutdown); the display unit would need to contain intentionally malicious code to cause problems.
With a SWD probe on the cracked open display initially, and this was necessary while I was still writing/porting the peripheral drivers. Once the firmware was stable (and after I'd asserted that it wasn't possible to accidentally brick the scooter) I moved to testing on the real scooter where the only feedback is if things work or not.
Yep, this is the case. Nine frames are transmitted with the first frame containing a sequence number and the first five bytes, followed by seven frames containing just data, and a final frame containing the last three bytes of data, and a two byte CRC.
Heya, I tried using slint first, but slint needs even more flash, and an allocator on top. Just adding slint (with an empty UI) exhausted my binary size limit.
I've got a happy run G300 pro that has a controller I've been considering digging into. It doesn't seem tuned to the motor. This is great inspiration to start digging in. It actually seems a bit clever to use the USB pins as a CAN bus
I've used https://aisler.net/en (based in Aachen) plenty of times. I like their quality and I was able to pull off some stupidly toleranced layouts. (This is as a hobbyist building some one-off circuit boards). I haven't used their assembly service.
> But it turns out there's all sorts of delightful data structures that you can only express with unsafe in Rust. Intrusive doubly linked lists are the coolest thing ever.
I don't mean this to be a gotcha, but I think it's important to say that we can have these in rust too! The only difference is that the first thing we have to talk about is you the user can go about using an intrusive collection correctly. I love the cordyceps crate for this. If you want to be able to use your type as a linked list node, you must implement a Linked trait, the documentation of which clearly explains how to use it safely: https://docs.rs/cordyceps/latest/cordyceps/trait.Linked.html
I think this crate description encapsulates what's difficult about unsafe rust, which is how unergonomic pointers are. Like why do I need to use `addr_of_mut!`? I'm sure there's a good reason, as Rust tends to think these problems through, but it's very unintuitive compared to returning `&mut`. I feel like I'm juggling way more concepts, which to be fair helps with safety, but it can obscure the algorithm itself.
Does cordyceps have a derive macro? I can imagine that helps a lot with correct implemention, though when it comes to linked lists I can see people wanting to do it themselves.
> I'm sure there's a good reason, as Rust tends to think these problems through, but it's very unintuitive compared to returning `&mut`.
The addr_of_mut docs [1] give a pretty decent explanation of its reason for existence; in short, it lets you get a pointer to something without needing to create potentially-invalid intermediate references. It (and &raw) probably aren't going to be needed if all you need is a &mut, though.
> Does cordyceps have a derive macro?
Doesn't appear to from a quick glance, and given what's in the safety section of the docs [2] it'd probably need to be marked unsafe [3]. Unsafe attributes are new to Rust 2024, though, so that might be a bit new.
That's true but misleading. I'm pretty sure the question was why a normal reference is bad. You don't need `addr_of_mut!()`, but you do need `&raw mut`.
> I'm pretty sure the question was why a normal reference is bad.
Oh, completely missed that interpretation. I think in that case it comes down to references being incompatible with the desired semantics for an intrusive list - & doesn't work if you want to modify what you're pointing to, and &mut doesn't work since you may want multiple things to be able to point to and modify whatever you're pointing to.
Rust allows you to use a literal iirc like r#raw, which helps with migration. This isn't too different from Zig's @"var" for example, which I'm glad modern languages have an escape hatch for naming things that don't parse normally.
> what's difficult about unsafe rust, which is how unergonomic pointers are. Like why do I need to use `addr_of_mut!`?
Things are slowly getting better here! '&raw'/'&raw mut' reference operators were stabilized a couple years ago.
Another ergonomic improvement in the pipeline is a better way to access fields behind pointers, something like C's -> operator. This is taking some time to design because there are things other than raw pointers that would benefit from a generalized field projection mechanism, like Pin, NonNull, and (potentially user-defined) smart pointer types.
> Like why do I need to use `addr_of_mut!`? I'm sure there's a good reason, as Rust tends to think these problems through, but it's very unintuitive compared to returning `&mut`.
That documentation appears to be out of date. The `addr_of_mut` macro was elevated to a first-class language feature, `&raw mut` (there's also a corresponding `&raw` operator). These two operators differ from the usual `&mut` and `&` in that they create raw pointers rather than references; prior to the introduction of this feature (or the aforementioned macros that served as precursors) to create a raw pointer you might need to have done a cast like `&foo as *const` in order to create a raw pointer by casting from a reference, but this could have safety consequences if the temporary reference was to an invalid object. Therefore `&raw` and `&raw mut` were introduced to create raw pointers directly without introducing an intermediate reference, which was arguably the most subtle footgun in unsafe Rust for a few years, and it's nice that it's now addressed.
reply