Self-driving (very small) cars — Part I

Doing A.I. in the wild with Python, Bluetooth cars and Dominic Toretto.

Jacopo Tagliabue
Sep 9, 2018 · 11 min read

A.I. meets the physical world (on a budget)

“I’m one of those boys that appreciates a fine body, regardless of the make” — Dominic Toretto

Autonomous vehicle startups are so hot right now: even without considering the usual suspects (Uber, Waymo, Cruise, etc.), there is a ton of less known and relatively less funded startups attacking the space (there’s even a Udacity course!). The key word here is relatively: it does not matter how you do it, it is just a steep price to pay compared to your standard SaaS company (as cool tools like lidar are very expensive + collecting training/test data is a pain).

Why should these people have all the fun? Isn’t there anything we could do in our scrappy garage to start playing around? As it turns out, a common move in the industry is using simulators, from ad hoc physics engines to off-the-shelf video games (you can also try something in your browser!). However, we tried something else in our weekend hacking: instead of trading the physical world for a simulation of it, we chose to scale down the problem and work with (literally) a toy universe.

The educational (and even research) value of toys has been highlighted before: if you’re old enough to remember the good ol’ days in A.I., you may recall that an expensive Sony dog was heaven for Robocup teams and the like (fun fact: the first program I have ever written ran on a white-and-blue Aibo, which now roams careless and free in the paradise of good robot-doggies). Talking about moving toys specifically, an honorable mention is certainly due to Vehicles, the clever book by Valentino Braitenberg, in which simple vehicles exhibit traits of “synthetic psychology” of increasing complexity.

In this two-parts post, we are going to setup a Python-based development environment to send/receive messages to/from small bluetooth cars on a racing track. Since we are low on money, but not on style, we will get started with the Fast and Furious edition of famous Anki Overdrive racing car set.

If you live your life, like us, a Git commit at a time, follow us in the crazy journey from zero to self-driving (very small) cars.

No Hollywood celebrity has been hurt while writing this post.

DISCLAIMER: this is a fun weekend project and by no means a thought-out research statement or production-ready code; as it turns out, there is a lot about real cars that cannot be reproduced in the chosen environment (more on that later). That said, we got to learn about bluetooth stuff and, more interestingly, how to structure A.I. and learning problems under different constraints than what we are used to in the rarefied world of API development.

Please note that all the code for this post is freely available on Github.

[Originally published at towardsdatascience.com on September 9, 2018.]

The setup

“You just earned yourself a dance with the devil, boy.” — Luke Hobbs

To start playing with Luke and Dom, we just need a race track with bluetooth cars and a computer that can talk to them so that we can teach it how to drive by itself:

  • as anticipated, we chose Anki Overdrive as our small-scale physical universe. Overdrive is advertised as an “intelligent battle racing system”, which is basically a bluetooth version of your old Polistil/Scalextric track. What makes Overdrive nerd-friendly is that the Drive SDK, containing the bluetooth protocol to interact with the cars, was open-sourced (however, figuring out the nuts and bolts of car communication took us a while: see below);
  • as far as “computer with bluetooth capabilities” goes, we chose Python+node to achieve cross-platform compatibility through noble, so pretty much anything should work with minor-to-no modifications. Specifically, we used our Rasp Pi 3B+ for compiling and running the original C library on Linux and our 2017 MacBook Pro for most experiments.

No garage project can be said to be complete without a low-res, grainy picture of the setup — so this is ours (here’s the original):

The difference between man and boy is apparently the price of their toys.

Furious but not so Fast: going for a test drive

First things first, let’s test the setup: before going heads down into Anki Drive protocol, we check that we can read/send messages from/to our bluetooth cars using ready-made libraries. We’ll run two tests: pick the one you like the most for your own setup.

Anki Official C tool on a Raspberry Pi

  • compile and install the sample Anki apps following the instructions here (do NOT forget to install all dependencies first);
  • launch vehicle-scan app to scan for vehicles (write down the id!);
  • launch vehicle-tool to connect to a vehicle using the id fetched above;
  • test a bunch of commands (screen-capture + clumsy video of me driving through the Pi).

Chrome 68+web app+bluetooth API on a 2017 Mac Book Pro

  • point your browser to this awesome demo here;
  • connect and test a bunch of commands (clumsy video of me driving with the browser).

Mission is a GO!

Enabling real-time car communication with Python

“We do what we do best. We improvise, all right?” — Brian O’Conner

Now that we know that everything works, it’s Python time! We could only find one unofficial Python wrapper for the Drive SDK: the code actually looks amazing and we tested successfully example.py on the Raspberry; however, it depends on bluepy, so it just runs on Linux.

The hack-y solution we end up developing is a mash-up of ideas from (a dumbed-down version of) the Python wrapper above, a nice Java wrapper and this Node project:

In particular, all “decisions” will be made in the Python layer, as expected, but Python won’t communicate directly with the cars, as a small node gateway (built with noble) will provide the needed cross-platform abstraction from the bluetooth sensor; internally, Python and node will read/write info leveraging the speed of the socket connection.

If you want to learn more about the wrapper itself, the repo is somewhat self-documenting and the Appendix at the end should give you a good high-level overview. If you just want to start driving, you will need to:

  • write down the bluetooth device id of the target vehicle
  • clone the repo
  • cd into the node folder and download dependencies with npm install
  • cd into the python folder and download dependencies (if any) for your favorite Python env pip install -r requirements.txt
  • fire up the node gateway node node_server.js PORT UUID
  • from your favorite Python env, run python constant_speed_example.py —-car=UUID and see the vehicle moving!

(This is me doing all these steps in one minute, re-using a Python virtualenv for convenience)

We are ready for our first quarter of mile!

Our first quarter of mile

“Ride or die, remember?” — Dominic Toretto

The first thing we’ll do is just running around our oval track at a constant speed of, say, 400 mm/s (I know, that is so slow).

Oval track with piece identifiers (IDs from our kit).

(you can use the constant_speed_example.py in the repo to do the same; track images are taken from here).

Our Python app will start printing out in real time notifications on the car position, such as:

{'location': 17, 'piece': 36, 'offset': 67.5, 'speed': 390, 'self_speed': 400, 'clockwise': 7, 'notification_time': datetime.datetime(2018, 8, 25, 21, 9, 33, 359248), 'is_clockwise': False}{'location': 23, 'piece': 57, 'offset': 67.5, 'speed': 422, 'self_speed': 400, 'clockwise': 71, 'notification_time': datetime.datetime(2018, 8, 25, 21, 9, 32, 229689), 'is_clockwise': True}...

piece and speed are pretty trivial (speed is the speed recorded by the vehicle, self_speed is the “theoretical” speed as set by our command), offset identifies the “lane” (see a visual explanation here), and location is an incremental integer (see a very good visual explanation here).

Can we go FAST now? Sure we can, but before stepping on the gas pedal we need to solve a basic problem: how can we measure the time it takes to finish a lap? As we dream of improving our machine-assisted driving strategy, we need a way to gauge how fast we are lap over lap.

Even if position updates are not that frequent (as we receive a notification 3/4 times per second), we’ll start by simply taking the time between two consecutive passes on the 33 segment (starting line) as the lap time. This is the output of running lap_time_example.py on our oval track, at 400 mm/sec:

time lap in seconds was 9.046201
...
time lap in seconds was 9.029405
...
time lap in seconds was 9.045055
...
time lap in seconds was 9.044495
...

While there are some discrepancies, it does not look that bad (SD 0.007)! As a further test, this is the same program at 800 mm/sec:

time lap in seconds was 4.497926
...
time lap in seconds was 4.502276
...
time lap in seconds was 4.497534
...

Still reasonable! As a first approximation, it is enough.

As we just saw (surprise surprise!), by increasing the speed of the vehicle we are getting better performance (at 800 mm/sec the time is almost exactly 1/2 of what it was at 400). Why don’t we just go faster and faster? Well, it is not that trivial, as our universe is small but still physically realistic. To see what I mean, this is what happens if we initialize the car at 1000 mm/sec:

Oooops

Not even Dominic Toretto could have made that turn (well, maybe he could have)!

Let’s take some tricks from the professional pilot playbook (or just common sense, really): we shall write down a slightly more complex driving policy (see custom_policy_example.py), such as the following:

  • we will increase our speed to 1000 mm/s at the start of each straight segment;
  • we will brake and reduce our speed to 800 mm/s just before the U-turn.
Pretty basic, but we need to start somewhere, don’t we?

Results are much better both quantitatively (faster lap time) and qualitatively: the vehicle behavior is much more realistic now:

We’re drifting baby!

Of course, many other improvements come immediately to mind, such as for example using lanes in a smarter way.

Since the possible optimizations are endless, well, wouldn’t be nice if the computer could figure that out? Yes, it would (that is, in the end, the whole scientific excuse for having so much fun with toy cars).

There are indeed a lot of interesting data questions we could try and ask in our toy universe:

  • how can a vehicle learn the best “driving policy” given a track configuration? Which is really two challenges in one: learning a policy AND generalizing it — wouldn’t it be cool if what it learns in one track could be immediately used in another, unseen one?
  • What will change when we introduce other vehicles into the mix? We could, for example, have them compete against each other, or we could use the second vehicle to learn cooperative behavior (e.g. avoid collision).
  • Can we condition the learning process on variables other than the track configuration? For example, we could imagine abstracting away steering and braking commands from the current library through a Car interface that implements them in different ways. What will change?
  • How smart can the vehicle be with the current sensors? As we saw already with position, sensors are pretty basic and won’t allow a fine-grained control: what can we do to be smarter with what we have (e.g. interpolate position?) and what can we easily add to give us more dimensions to play with (e.g. webcam with object recognition)?
  • …and surely many more we haven’t thought of yet!

The important thing is that we now have a basic, but functional setup to run experiments and collect data from our small cars. In Part II we’ll start putting all this engineering stuff to work and help us design increasingly smarter behavior for our self-driving cars (spoiler alert: we will start by manually driving using the Mac keyboard).

Appendix: repo structure

The code used for this tutorial is open-sourced here under an Apache License. Please note that the current master is released as alpha as it just contains the bare minimum the get things going: no unit tests, no fancy stuff, almost no protection from errors, etc.. We’ll hope to make several improvements to the code base as we progress with our experiments: feedback and contributions are most welcomed!

The project structure is pretty straightforward, with the two main folders containing node and Python code:

node_app
node_socket-app
node_server.js
package-lock.json
python_app
py_overdrive_sdk
py_overdrive.py
track_images/
create_track_image.py
constant_speed_example.py
track_discovery_example.py
custom_policy_example.py
requirements.txt
.gitignore

node_server.js is a straightforward node implementation of a server scanning and chatting with bluetooth devices (inspired by this wrapper). When the server gets started, noble waits for the bluetooth device to be available and start scanning using Anki characteristic UUIDs. The rest of the code is just the server listening for events coming from Python and executing the bluetooth-related logic: “meta-commands” come with the syntax COMMAND|PARAM (e.g. CONNECT|ba0d6a19–1bf6–4681–93da-e9c28f2b6743), while car commands are hex strings and go straight to the vehicles (e.g. 03900101 is what enables the SDK mode). FYI, it’s also the first app I ever wrote in node (so that’s my excuse if it doesn’t work!).

py_overdrive.py contains the Overdrive class encapsulating the vehicle-related logic and the communication from/to the node gateway. We leverage Python threads and queues to handle concurrent processes to write, read and handle location events in a non-blocking way. At initialization time, if a function is provided to the constructor, the system will take custom actions (“a driving policy”) in response to location events (see custom_policy_example.py for a simple use case). track_discovery_example.py and the companion script create_track_image.py will get you started on mapping the different pieces of a track and printing out the layout (p.s.: the code is just a quick and dirty stub that makes many simplifying assumptions).

If you’d like to help out, the repo README contains a work-in-progress list of improvements and experiments to be made. Feel free to reach out for comments, feedbacks and ideas.

See you, space cowboys

Stay tuned, as we will be back soon with Part II. If you have questions, feedback or comments, please share your story with jacopo.tagliabue@tooso.ai.

Acknowledgments

Don’t forget to get the latest from Tooso on Linkedin, Twitter and Instagram.

It would have been a month project, not a weekend one, without Internet and the fantastic people on it sharing their code and ideas (see the links inside the repo for specific credits): as far as copy+paste-ing goes, this project is indeed second to none.

Of course, none of this would be possible without Anki’s vision to democratize robotics. We first discovered Anki through Cozmo, a Wall-E like cute small robot with a fantastic Python SDK (Cozmo possibilities make it an incredible playground for all sorts of weekend projects). While we are of course in no way affiliated with Anki blah blah legal stuff here I can’t be bothered to copy+paste blah blah, we thoroughly enjoy their products.

Originally published at towardsdatascience.com on September 9, 2018.

Tooso

Articles, thoughts and updates from Tooso, a software company pioneering AI-based search

Jacopo Tagliabue

Written by

Co-founder and nerd in chief of Tooso.ai, I failed the Turing Test once, but that was many friends ago.

Tooso

Articles, thoughts and updates from Tooso, a software company pioneering AI-based search

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade