Experimenting
The train makes experiments hard. It sleeps about a minute after you wake it, powers off after about 30 s standing still, needs a button press to come back, and takes over its motor whenever it reads an action tile. Experiments have to be planned around that.
The limits
| Limit | What it means for you |
|---|---|
| Sleeps about 1 min after waking | Start the command first, so it is already waiting when you press the button |
| Powers off after about 30 s standing still | No experiment can include a long pause |
| Sending commands does not keep it awake | Only movement does |
| Needs a button press to wake | Every failed attempt means walking over to the train |
| The bond goes stale every time it sleeps | Reconnect code must fix this by itself, or you waste button presses |
| Tiles take over the motor | Re-send your speed 20 times a second (20 Hz), or the train is not under your control |
A bond is the saved pairing between two Bluetooth devices; see Connecting.
What worked
Wait for the train instead of scanning for it. With --timeout 0, any
command waits until the train starts advertising (broadcasting that it is
there). Start the command first, press the button second. This beats scanning,
failing and retrying.
Subscribe first, ask questions later. Get sensor data flowing in the first second, then query ports while values are already arriving. Querying every port up front uses up your time.
Cache anything that never changes. The list of ports and modes is the same
for every hub of the same type. The tool saves it in .duplo-cache.json, so
later runs skip about 160 requests and start listening right away.
Use one self-contained command, not an interactive session. ./duplo lap
and ./duplo next connect, run one experiment, report and exit. This was much
more reliable than driving an interactive shell.
Use delta 0 to read the current value, delta 1 to see changes. With
delta 1, a train standing still looks broken, because nothing changes so
nothing is reported. With delta 0 you get the current value at once, followed
by a flood of repeats.
Filter the log with a script, not by eye. At delta 0 a single port sends
thousands of samples. Collapsing runs of identical values
(awk '$1!=p{print; p=$1}') turned 2389 samples into one line.
Use timing as evidence. Tiles one track piece apart give a gap of about 2.2 s at 20% speed. A gap twice as long means a tile was missed, and where the long gap falls tells you which one. This is how I caught two tiles that had been swapped. Timing is geometry: it doesn't care what you believe the layout is.
An interactive session over a FIFO
Worth knowing, though it proved fragile. A FIFO (named pipe) keeps one connection open while you send it commands from separate shell commands:
mkfifo /tmp/in.fifo
( sleep 7200 > /tmp/in.fifo ) & # holds the pipe open so the reader never sees EOF
./duplo shell --timeout 0 < /tmp/in.fifo 2>&1 &
echo "sub 51 0 0" > /tmp/in.fifo # then send commands whenever
The sleep line matters. Without a writer that keeps the pipe open, the first
echo closes it, the shell sees end of input (EOF) and exits.
This saved many button presses while I was exploring. But the log grew to 85 000 lines, and sometimes the session had quietly reconnected and lost its subscriptions. For anything you want to repeat, use a one-shot command.
Mistakes to avoid
pkill -f killed its own shell, twice. A command that contains the pattern
text kills itself, even through a heredoc that only mentions the filename. My
patch was never applied and the next run quietly used old code. Split the
string ("dup""lo.py"), match on a longer path, or find the PID with pgrep
first and kill that.
Stopping a background task left an orphan holding the connection. Stopping
the wrapper shell does not stop the Python process inside it. The leftover
process kept the train connected, while the new session sat there reporting
InProgress and "not connecting". I caused it myself, but it looked exactly
like a hardware fault. Always run pgrep -af duplo before blaming the train.
Redirecting output into a missing folder stopped the task before it started. The error looked like a connection failure. Check that a background task really started.
Driving blind ran the train off the track, more than once, because a 2–3 s burst at 40% goes further than the straight section. With a closed loop (the program steering by sensor feedback) this stops being a problem. Without one, keep bursts short, or push the train by hand.
A 30 s pause powered the train off mid-test. The hub switched itself off,
and the next twelve "steps" went nowhere. They came back as "no change", which
looked like a sensor result. Before trusting a run full of nothing, look for
hub action 0x30 / 0x31 in the log (the hub's warnings that it is switching
off).
Moving in 1 s steps barely moved the train (2–5% measured speed), so my careful step-by-step approach produced no data. Check the speedometer to confirm the train is really moving before you read meaning into silence.
Before you trust a null result
Silence from the train can mean many things. Before concluding "the sensor does not report X", check:
- Is something else holding the connection? (
pgrep -af duplo) - Did the hub send
0x30/0x31(switching off) during the run? - Is the train really moving? (signed
SPEEDon port 54) - Was the subscription accepted, or did it reply with a
0x05generic error? - Is
deltaset so that an unchanged value would never be shown? - Has the track layout changed since the last run? (Mine had.)