A first for 2026: on Saturday I visited a pond with ducks in it. Ride
out to Dunmow, in which we find a large pond, an island with a
duckhouse, some ducks, a small child (not pictured) feeding them, and
some willow trees. It's set in a dip in the land, so the grassy banks
slope downwards towards the water. Top marks for duckpond.
Two points deducted for the journey there, though. Joining the dual
carriageway, I identified a gap in lane 1, matched my speed to the
traffic speed, did a shoulder check as I crossed the line from the
slip road into lane 1, and when I looked ahead again there was a car
alongside me in the same lane I was in. So, wearing my surprised face,
I dropped back behind him. Reviewing the camera footage later it seems
that he had moved from lane 2 to lane 1 as I was moving from the slip
road into lane 1.
Accepting that these things happen and considering what we can do
to avoid it, the obvious strategy is to not be doing a shoulder
check at that time.
... which brings me neatly onto the second topic of this post: I did
an ERS day
on Wednesday and I have the certificate to prove it. It was billed as
an introduction to advanced riding, so we touched on topics like limit
points and
IPSGA
which I knew about in theory but don't always practise in
practice. Most of the day was spent riding - a mixture of some very
fun contry lanes, a bit of dual carriageway and some villages. I did a
couple of overtakes (no, not through the villages) that I don't
think I'd have gone for if I were on my own.
Most of my takeaways are riffs on the general theme of "information
gathering" (as the IAM would no doubt describe it):
Avid readers (both of them) will remember that last year I did a
TfL 1:1 training session in which I
was told I wasn't doing enough shoulder checks. Well, I started
doing more shoulder checks after that and ... now I'm doing them too
much. As in the dual carriageway not-actually-an-incident above: no
point in looking backwards for danger when I already know the lane
is clear and the most likely source of danger is ahead. (To tie this
into the theme: don't look round if there's no information to be had
by doing so)
seeing the shape of the road ahead by looking at where trees/hedges
are, or the lines between telegraph poles. Even just, sometimes,
looking sideways across the fields to see the road past the next
bend and the the oncoming traffic. I've watched videos where riders
say they're doing this but it didn't "click" until I saw it in real
life. Thinking back, I had the same experience with limit points.
commentary riding: a few times I missed opportunities basically
because I came out of one hazard (e.g. a roundabout) looking at the
back of the car in front instead of already planning for the next
hazard (a slow lorry on the exit, or a queue of traffic). I've
tried commentary riding before and I found that my brain can't form
words fast enough to explain everything, and what comes out of my
mouth is "that thing is coming out of the thing over there, oh look,
no not you, ok, let's take that, no". But the
instructor took the lead along one leg of the ride and
commentaried(sic) it and it was noticeable that I was faster and
smoother listening to his commentary than I would have been without
it, so definitely worth persisting.
(I have an idea that this is related to the cognitive psychology
concept of
chunking: by
articulating what's happening and what we'll do about it, it
encourages the brain to form associations, and so allows operating
on more information because individual units are combined. Caution:
I have entirely no evidence to support this hypothesis but it does
sound beguilingly plausible, which is very often the case for things that aren't
true)
positive feedback on machine control and "making progress", which
was good to hear
According to my trip counter it was a 150 mile day (including about 50
miles to get to the start point and home from the finish) and I was
feeling pretty bushed afterwards, as my usual ride is 2-3 hours and
about a third of that distance.
Returning to the original topic: I don't think there's much else worth
saying about the ride to Dunmow, except to admit that the flying duck photo was
a fluke. Lots of traffic meant not many opportunities to get up to
speed, but it was warm and sunny which kind of made up for that.
First (and only, so far) ride this year in mesh jacket, summer gloves
and Bowtex leggings. On Wednesday I had a 7:30am start and was was
back in full leathers.
My quest to visit all the duck ponds in Essex and Hertforshire has
involved a lot of looking at Google Streetview, and when I saw this
one near Epping Green it
looked a bit overgrown and unloved but I figured I'd stop by anyway on
the way to pick up new brake pads in Harlow.
Just like the previous pond, there were no ducks - I may have to
broaden my selection criteria - but despite that, I am feeling
tremendously pleased about having stopped there anyway. It's
obviously seen a bit of maintenance since the Google car went past,
and was so much lovelier to visit in person than I was expecting. It's
sited in a small grassy area with some trees around and it's just
... sort of heartwarming. Probably helped that the sun was out.
The pub is across the road and has been closed since 2019, which is a
bit of a shame as it would be a lovely place to sit outside with a
drink and look across the road. As far as I can tell from the
internet there are/were plans to reopen it as a community pub but I found no news on that more recent than Dec 2024.
From Epping Green to Harlow via a tedious litany of suburban
roundabouts, which would have been less faff if my satnav wasn't
giving me the silent treatment. Collect brake pads from Sportbikeshop,
then a mildly circuitous and much more rural route home via Old
Harlow, Matching Tye, Matching Green, Moreton, North Weald Bassett,
Epping etc. There's a very nice stretch of road between Harlow and
Matching: bendy enough to be fun but open enough that you can see
further than one bend ahead.
I have booked an
ERS which
is happening in about ten days and I hope will be
fun/interesting/informative. I'm not going to be at all defensive
about having my riding criticised, no, definitely not.
I've pivoted twice on Eculocate since the previous blog post: first a
minor change of direction and then followed by a much bigger one.
I'll start with the bigger one: I've stopped working on it. This is
because I am 95% sure it won't actually work with my motorbike. What's
my basis for this?
consider: "gear indicators" for motorbikes with k-line diagnostics plug into the
ECU diagnotsic port (in fact, the honda k-line protocol was pretty
much reverse-engineered using one of them). The gear indicators for
bikes of the same age as
mine
tee into the speed sensor connector and also tap the rpm input to the
ECU. Why would they do this if they could just use the diagnostic
port?
if you have a fancy workshop diagnostic
tool,
the appropriate cable to use it with a 2001 CBR600F is described as
a blink code
cable -
which rather suggests there isn't any more data provided by the ECU
than ... blink codes
I don't know definitively that it won't work. I do know that if it
doesn't work and it releases any kind of magic smoke, replacing the
ECU is going to cost a pretty huge amount of money. So, Im just not
going to.
That's the major pivot.
The minor pivot, which preceded it, was when I realised that I can't
use the Python Bleak
library on my phone with Termux, because its Android
support
uses a different Python-for-Android backend than the Termux
linux-userland-for-android. Argh. So, I decided to stop trying to do
Android, and instead dusted off
Biscuit which is my previously
abandoned project to make a bike computer using a Motorola G4 Play
phone (it has a removable battery and is old enough to be reasonably
well supported on mainlin Linux) running Mobile
Nixos. So I
added an eculocate
module
and ported the eculocate gui
client
from termux:gui to Gtk and then I thought I'd better check a few things and then
I had the major pivot. So, that's the chronology.
What now?
I've spent a few days bringing the stalled Mobile Nixos device back
up to date: current nixpkgs, rebase against current mobile-nixos
version, upgrade to later kernel, etc. Will have to figure out what
else is outstanding before I can reasonably ask for a review/merge.
and some time on the "maps" app in biscuit, mostly just to make the
scrolling less lurchy as the gps moves around
and some time on the quite unrelated task of bringing Liminix back
up to date so it can be built using a current Nixpkgs version rather than the last release but one.
The plan currently is to get Biscuit into some kind of shape that
I can dogfood it:
at minimum, show a gpx route overlaid on the map
extra credit, add kind of turn-by-turn navigation/rerouting if the
rider should stray from the gpx path
given that (last time I tried it) the phone wouldn't acquire a GPS
lock in less than ten minutes, there may still be a role for reusing
(parts of) eculocate to provide external GPS and maybe some other
sensors like IMU - perhaps even buy one of those
tap-the-speed-sensor gear indicators for its wiring and expose the
bike engine speed. Project GXXR has
lots of interesting stuff on this subject.
I said in the previous entry that "pairing is not actually going to
help much anyway", with the implication that I was done thinking about
BLE. Turns out I was not done thinking about BLE. Notwithstanding that
the scope for anyone to maliciously connect the device to the wrong
wifi is pretty limited, the attack I hadn't considered is that an
attacker might be listening to us as we send the credentials for the
right wifi network. Which could be bad.
It continues to be true, though, that "pairing is not actually going
to help much anyway": although "Just Works" pairing is encypted
against passive eavesdropping, an active MITM will still see
everything we send, and can be put together for the price of two
bluetooth-capable MCUs. So, instead of relying on the transport we're
adding encryption/authentication of our own.
getrandom
A lot of Rust crypto stuff depends on the getrandom
crate that provides an interface
to the OS's randomness source.
getrandom doesn't have a supported target for esp32 no_std, meaning it
won't build without a custom randomness source. Further, the
mechanisom for adding a custom source has changed between getrandom
0.2 and 0.3. If you follow the current getrandom docs for adding the
esp32 hardware random source, and then cargo add some crate that
transitively adds a rand_core dependency, you may find as I did that
your crate depends on the older version: you now have two versions
of getrandom in your project and one of them still doesn't work. I
don't have nubers for how likely this is but I've hit it twice with
different randomly(sic) selected libraries.
The 0.2.17 docs
explain how to do custom implementations for the version of getrandom that
everyone's actually using, I can't now remember where I found the source for
making it work with esp_hal, but the tl;dr is I did this.
salt and shake
NaCl/libsodium seems a defensible choice of library that avoids the
"don't roll your own crypto" injunction. I picked
crypto_box
from nacl-compat on the Rust side, and expected PyNaCl's
nacl.public.Box to work with it. It does, but ...
when you encrypt with python, you get back a result which is 40
bytes longer than the input plaintext, because it "stores
authentication information and the nonce alongside [the ciphertext]"
when you decrypt with Rust, it expects the nonce and the ciphertext
to be passed as separate arguments to decrypt
the nonce is 24 bytes, not 40 bytes, just in case you were thinking
that explains the difference
It's simple once you know what's going on, but it would
be a lot simpler to work out what's going on if the docs were a bit
more precise than just saying "alongside". For posterity, the first 24
bytes are nonce, and the rest is a concatenation of the ciphertext and
a "16 byte authenticator", which is also described as a "MAC tag". The
Rust-side decrypt method expects the nonce as its first argument and
the combined ciphertext+tag as the second, so although I didn't find
out which of those two comes first, blessedly, we don't need to know.
"alongside". Bah.
(It took longer to work this out than it should have, because in the
process of sending it across Bluetooth I managed to append a chunk of
NUL bytes to the ciphertext, which not surprisingly caused it to
fail to decrypt no matter how I carved it up.)
there may be TrouBLE ahead
It wouldn't feel real if I hadn't had to spend some time after that
wrestling with bluetooth again.
This time: when you write a characteristic value that exceeds the MTU,
instead of writing it all in one go it uses a sequence of PrepareWrite
messages to send each chunk and then an ExecuteWrite to stitch it
together. TrouBLE (the rust bluetooth stack) handles this insofar as
it processes these messages and updates the value in the server, but it means you don't get a
GattEvent::Write
event that you can use to perform any other action (in our case,
save the data to nvs and and reboot).
Either you can match
GattEvent::Other
and then do arcane things to find out if it was an ExecuteWrite and
was for the appropriate attribute handle, or you can do what I did and add an
extra "I'm done now" step to the provisioning process where it writes
a boolean when it's finished.
More eculocate: I spent the past couple of weeks first on rearranging
the client code to be a bit less awful - now there's a
command line client as well as an android/termux GUI one, and they share code - and then on adding an
affordance for configuring the wifi network that doesn't require
hardcoding it at build time.
This is kind of a distraction from plugging it into an actual
motorbike, because I could have just hardcoded the wifi details for
tethering to my phone, but once it's attached to the bike then I can
only flash it OTA and I don't want to be messing around tethering my
development machine to my phone just so it can be on the same network
as the bike.
In January I said:
There's a convention for provisioning wifi on these devices
which involves using a mobile phone app to connect to it using BLE
then sending the ssid/password of the chosen access point. In fact
there's even a prebuilt Android app which we can use and an esp32
arduino library which we can't (because we have elected to make our
lives difficult and use Rust instead). But I am led to believe that
"rewrite everything in Rust" is idiomatic for Rust programmers anyway.
I haven't done this yet.
And ... I still haven't, or not as such, because change of
plan. Because I would have to write the device-side code myself
anyway (because Rust), and I would want to write the phone-side code
instead of using their app (because I'd like to have one app to do
everything instead of separate apps for provisioning and for
recording), then I have free rein on the protocol, and the Espressif
protocol
seems ... quite involved?
(What I didn't learn until checking references for writing this blog
post which has already taken way too long it is already Tuesday is
that the Espressif protocol I linked there no longer exists
except in git history, and appears to have been replaced by a different
one called
Blufi
so I am feeling even less bad about swerving reimplementing it)
Background, if you're lucky enough to not know much about Bluetooth
Low Energy: the usual interaction model is "GATT", which can be
thought of as a network-based key/value store. A device defines
"characteristics" and "attributes" and allows read/write access to
them. If you're working "with the grain" of the design, you use this
to model some kind of object/record/aggregate/entity -
or, alternatively, you could just have attributes for rx and tx
and send streams of data over them in any format you like.
The Espressif protocol - as was - defines a protobuf-based protocol
that runs over both BLE and also WiFi with SoftAP, whereas if we're
restricting ourselves to BLE we can actually leverage GATT instead of
using it to emulate a stream interface.
So what have we done instead? We have four characteristics (with
hindsight maybe this should have been four attritbutes of one
characteristic). eculocate scans for wifi networks it can see, then
it sets the attribute max_index to the number ot visible
networks. The client then loops throuh 1..max_index writing to the
attribute current_index, which causes eculocate to update
current_network to contain the ssid of the nth scanned network. When
the client finds a network it likes, it writes the corresponding
password to secret and eculocate saves the ssid/secret into
flash. The loop feels a bit weird, but there isn't any way - at least,
I can't see any way - to specify that an attribute is array-valued.
To provide security for the precious wifi credentials we simply enable
pairing, which causes the two ends to encrypt with AES-CCM.
Sounds simple, and it probably would be except that USB bluetooth
hardware is a market for
lemons (I
originally said something much much ruder there) and the degree of
care and research needed to get one that works properly in Linux far
exceeds the degree of care and research I exerted. The first dongle
i bought, which identifies as 33fa:0010 and is said to be based on a
"Barrot" chipset, is a buggy pile of shite which locks up and resets
randomly, even when using a kernel that contains
e7d1cad6545c,
meaning that every test of the code had to be preceded by removing and
reloading the module and thewn it would work about 70% of the time
provided I didn't try to pair, which caused it to time out consistently.
Then I dug out a Raspberry Pi Zero W from the junk box, but the
builtin bluetooth on that only supports 4.1, and the TrouBLE stack
won't pair with anything older than 4.2
(I have since received an Edup B3536 dongle which is based on the
Realtek chipset and said to be a whole lot better - but it only
arrived after I started this blog post and I haven't tried it in anger
yet)
It turns out that with the current hardware configuration pairing is
not actually going to help much anyway. The device has no
keyboard or display (thus no way to display or confirm a pairing pin),
so is restricted to "just works" pairing, which offers no protection
against MITM at the time of pairing. Since all we're using Bluetooth
for is entering the wifi details, and the BLE service is shut down
once it successfully connects to wifi, there is no point setting up a
secure connection "for later" as there is no "later".
eculocate goes into "wifi chooser" mode when there's no saved network
or when it can't connect to the saved network, so a black hat (or
black balaclava) could attack it by separating your motorbike from
your phone so the phone is out of range, then turn the ignition on,
then configuring a different wifi network. It won't help them a lot as
they're still lacking the ed25119 key that controls TCP
connections. And at this point if they're sitting on your motorbike
and the ignition is on, maybe you have bigger problems ... perhaps we
could regard the bike's ignition key as a hardware token.
The proper way to do pairing and have it actually be useful would be
to add an NFC reader/writer and use OOB authentication
but I am very much feeling like I wil save that for another day.