← Talking to a DUPLO train

Talking to a DUPLO train

experiment

How I drive the 2025 LEGO DUPLO train (10427) from a Linux laptop over Bluetooth, and what I learned on the way.

Open questions

What I haven't figured out yet, roughly ordered by value for effort.

1. Finish the tile colour codes

When the train reads an action tile, the TAG mode reports a code. I know 2 of the 7 codes. My earlier guesses for the rest fell apart when two tiles turned out to have been swapped partway through testing.

TAG code Colour Confidence
0 nothing read yet certain
1 yellow confirmed directly
37 white likely
21, 119, 191, 402 ? unknown

Don't guess from the order of a sequence; that is what went wrong. Identify each code against a single tile you can see:

./duplo next --timeout 0

next creeps forward until exactly one new tile is read, then stops and prints its code. Put the train before a tile, note the colour, run it, write down the code. Six runs. Rearranging the track in between can't spoil the result, because nothing depends on order.

Also worth checking: are all six LEGO tile colours in the firmware's list, and what is reported for a colour that isn't a valid tile? Tiles are read optically (coloured paper works), so there may be many more than six codes.

2. Why are some tiles missed?

Sometimes the train drives over a tile without reading it. Ruled out so far: speed (18–30%), direction (both), and the sensor going "deaf" while the hub plays the previous tile's action (approaching the same tile after a different neighbour didn't help).

Still worth trying:

  • The sensor's height above that particular tile
  • A dirty, faded or slightly different-shade tile. Swap in a known-good tile of the same colour and see whether the miss follows the tile or the position.
  • delta 0 on TAG across the miss, to see whether the sensor reports the code briefly (and the stored value is the problem) or nothing arrives at all
  • Whether misses happen while the hub is still reacting to the previous tile. Measure it rather than guess: subscribe to EVENTS and TAG together and look at the order of messages.

3. Can tile actions be turned off?

Right now I overrule the tiles by sending my motor speed 20 times a second. It works, but it's brute force: a write every 50 ms, and the hub still briefly fights back.

Places a real on/off switch might be hiding:

  • Port 52 DEBUG (mode 2, 4×int32): never read or written
  • Port 52 EVENTS as an output: its mask says it accepts output, but I never tried writing structured values to it
  • Hub property IDs beyond the standard set
  • Port 50 mode 1 SPEED instead of mode 0 POWER: possibly the hub decides differently which command wins

A warning: writing to undocumented diagnostic ports on a toy is how you "brick" it (break the firmware for good). The firmware update service (OAD) is right there.

4. Can it play sounds?

I never managed to play a sound on purpose. There is no speaker port like the 2018 train's (0x2a type), and TAG, the obvious candidate, rejects writes with 0x06 invalid use for every index from 0 to 20.

Sounds did play during testing, but those were the hub reacting to tiles on its own, not anything I triggered.

Still open: can the speaker be reached over LWP3 (the LEGO Bluetooth protocol) on this hub at all, or is it internal only? If TAG accepted writes with the right parameters, that would be the way in. The 0x06 error means the command was understood but the parameters were wrong, which isn't quite the same as "read-only". Worth trying: other startup_completion values, mode 1 or higher on that port (the hub only declares mode 0, so probably not), and 1-byte instead of 2-byte payloads.

5. Unexplored modes

Each port has one or more modes (kinds of reading or output). These are unexplored:

Port Mode Status
50 1 SPEED never used; may differ from POWER in which command wins or how speed ramps
52 0 VERS read once: 0x10010004 ×3 then 0x00310031; not decoded
52 2 DEBUG never read
53 1 VLT S never read; probably averages voltage over a different time window than VLT L
54 1 COUNT never read; int32 but with a declared range of only −8..8, which is strange for a counter. A working odometer would be useful if that range is wrong.
54 2 VELO never read; signed RPM ±400

COUNT is the interesting one: a real odometer would tell you where the train is on the loop, instead of working it out from timing.

6. Combined mode

Only one mode per port can be active at a time, so you have to choose between the speedometer's SPEED, COUNT and VELO. LWP3 has a message for reading several at once (Port Input Format Setup, CombinedMode, 0x42), but it only works on ports that say they support combining. This hub's capability flags are known to be wrong, so it's worth simply trying rather than trusting them.

7. Does EVENTS report anything else?

Every EVENTS message I saw had 0xa001 as its first word and 100 + motor speed as its second. If other event IDs exist, some might be tile events, which would be a cleaner signal than the stored TAG value.

8. Keeping the hub awake

The hub powers off after about 30 s standing still; sending commands doesn't help. Is there a keep-alive: a hub property, a regular read, or a zero-power motor command that counts as activity? Without one, you can't leave it running to monitor it passively.

9. The 2018 train is untested

All the 2018 train support in the tool (ports 0x00/0x01/0x11, sound and LED commands) comes from community sources and has never been tested on real hardware by me. It's plausible, not verified.

10. Split messages

The tool assumes each LWP3 message arrives in one Bluetooth notification, which held for everything this train sent. A client for hubs with more ports should rejoin split messages using the leading length byte.