Project tutorial

Autonomous Delivery System © GPL3+

An Autonomous Delivery System using the DonkeyCar which allows you to send or receive physical items, in a number of different scenarios.

  • 9,875 views
  • 1 comment
  • 56 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)
Hy gluegun
Hot glue gun (generic)

Apps and online services

About this project

As the title says, my project is about an autonomous delivery system, not yet intelligent enough to deliver stuff across cities, but one which 'should' at least be able to deliver small objects from one place to another in your house.

I will be using the open source DonkeyCar platform, which is powered by the Raspberry Pi and Pi Camera to create an autonomous vehicle.

When I heard of the DonkeyCar for the first time, I was surprised why an autonomous vehicle project would be named DonkeyCar out of all things, but when I got to use one for myself from Hackster's ARM contest, I got the answer to this question. Don't get me wrong on this; it is an amazing project but still in it's very early stages, so I would advise you at the start to not have very high expectations and to think of it realistically. It has a great, growing community and their Slack channel is also very informative. If you get stuck anywhere or even if you have just gotten to know this, I would recommend you to join that.

You can find more details about all the parts needed to build the DonkeyCar, and also where you can find a complete kit from this page.

With that being said, let's move on to the project itself.

How the DonkeyCar works

You must know by now, that the DonkeyCar uses the Raspberry Pi as the brain for the project, it runs the Donkey library and you will have to first assemble the DonkeyCar using this guide here. When assembled, you will need to install the software for it on the Raspberry Pi, as well as on another host PC using this guide here. The host PC is needed because the Raspberry Pi is not powerful enough to train an autopilot.

Note: I simply used the pre-built disk image for the Raspberry Pi which you can get from here.

Then you will need to calibrate your DonkeyCar's steering and throttle so as to make it work consistently. To do that, you need to find optimal PWM values for throttle and steering, and this process is described here. After calibration, you should test your DonkeyCar and try to get used to driving it. I recommend using a physical joystick rather than the web controller. I used a PS4 joystick through the browser and it worked without any issues.

Once you are confident enough to drive, you need to start to record data (without any mistakes as it would affect the autopilot). And once you get data recorded, (it is stored in the tubs folder in the mycar directory on the Raspberry Pi), you will need to transfer it back to your host PC to train.

Note: I am using Windows, so I had to use Cygwin with rsync and ssh installed. If you use Linux, you should have these installed depending on the distro being used.

The training is done using Tensorflow and Keras, and once you do it, you will get an autopilot file which you will need to transfer back to the Rapberry Pi. You can use this autopilot file to drive in two modes; one which the DonkeyCar controls the steering only and throttle is controlled by the user (called Local Angle), and the other in which the DonkeyCar controls both steering and throttle (called Local Pilot). Everything about training an autopilot is described here.

If you want to build the Autonomous Delivery System, I would expect you to be familiar with driving your DonkeyCar and also training an autopilot and using it, as I won't be going through those details. Everything about that is well documented and you can find support in the DonkeyCar Slack discussion if there is anything you need help on.

The way the Donkey library works is that you can add your own sensors and actuators by making custom 'parts'. More details on adding your own parts can be found on these excellent YouTube videos here and here.

Setting up the DonkeyCar

You should build your car from the instructions at this page, to the point where you are able to drive, and to train and run an autopilot on your car. Once you do that, follow on with the instructions below.

There were a few issues I got into when installing the software, but googling your problem will give you an idea of how to solve it, so I won't be going into those details. If you have anything, feel free to comment.

Note: I will be using Windows on my host PC for running the DonkeyCar software.

A pinout of the Raspberry Pi for your reference:

This is my assembled DonkeyCar:

After assembly, move on to driving and training an autopilot and once you are familiar with those, read on.

How the autonomous delivery system works

The main idea of the autonomous delivery system is to have a robot which can transport physical objects from one place to the other. There are many potential uses for such a system; in general it would be detecting when something has arrived, and taking it where it is needed autonomously. Some specific applications I could think of are these:

  • Transporting something as a letter or small parcel from one place in your house (let's say from a letter box) to you in the house.
  • In a workplace or office, with many people working together and the need to transport physical documents from one station to the other.
  • In a environment such as a hospital where samples need to be transported.

The autonomous delivery system should be able to work for the above scenario with a few minor changes.

But for this documentation, I will chose the first application specified, that is transporting something small such as a letter or small parcel (I assume it's a letter from now on) from one place in your house (from example from a letter box - to be called the origin from now on) to you in your house. This autonomous delivery system will be designed using the DonkeyCar platform, with a custom part. To know more about DonkeyCar parts, look here. For doing this, the DonkeyCar needs to be able to do the following:

  • At first, the DonkeyCar should be waiting at the origin.
  • It should detect (using a sensor) when a letter is placed in a container (on the DonkeyCar).
  • Once detected, it should start moving on a specified track (all the rooms in your house)
  • Optionally, it could also alert you using sound that a letter has arrived.
  • Once you notice the letter and pick it up from the container on the Donkey Car, the sensor should detect that there is nothing to deliver at the moment.
  • Therefore, it should reach the origin and stop there, waiting for another letter, and the cycle continues.

And now for the technical details on how the DonkeyCar is able to do the above tasks:

  • I chose Arduino MKR1000 connected to the Raspberry Pi 3 on the DonkeyCar. This Arduino MKR1000 will have an ultrasonic sensor (HC-SR04) and a bluetoooth module (HC-05) attached to it, and it will read data off the sensors, process it, and send to the Raspberry Pi.
  • The ultrasonic sensor is placed on the container and used to detect presence of a letter in the container.
  • The DonkeyCar needs to detect if it is at the origin or not. To do that, I will use a bluetooth beacon, simply because they are short range and cheap to buy.
  • The DonkeyCar needs to be trained in your house on an imaginary track from the place you set as the origin, to the wherever you want it to go; all the rooms in the house. This is done using Tensorflow and Keras and the process is described here.
  • And optionally, you could also connect a speaker to the Raspberry Pi, to give your DonkeyCar a voice and alert you when a letter has arrived.

Note: Because I don't have another bluetooth module at hand, I am forced to re-purpose the only thing which I can at the moment; the Sensor Hub Nano, as a bluetooth beacon. I know it is foolish to use it this way, but I had used it in a previous project and the setup was working perfectly, therefore I had to test it out. You can meanwhile configure two bluetooth modules to auto-connect and use them for the same purpose (Read on and you will understand!).

These are the things required to make the project (other than the DonkeyCar, of course):

This should be enough description of the idea behind the autonomous delivery system, and it could get you thinking on how to use it in the different applications given above. Now to move on in more detail on each part.

How the RPi is connected to the Arduino MKR1000:

This is pretty simple, there is a USB cable connecting the Raspberry Pi to the MKR1000, no need to use any GPIO pins.

This is a pinout of the MKR1000:

Communication is done through the serial port, by the following code snippet on the Arduino MKR1000:

 message = Serial.read();
 if (message == '1') {
  Serial.println(isLetterDetected);
 }

And this python code on the Raspberry Pi:

ser = serial.Serial('/dev/ttyACM0', 9600)
ser.write(b"1")
if(ser.in_waiting >0):
    line = ser.readline()

How the letter detection works:

The HC-SR04 ultrasonic sensor is connected to the Arduino MKR1000, and it gives the distance in centimeters.

The ultrasonic sensor should be placed in a container in such a way that there is a noticeable difference in the distance it gives when the container is empty, and when a letter is placed. So you will need to play with this a bit to get something correct.

How the bluetooth beacon works:

The HC-05 bluetooth module is also connected to the MKR1000, and they have a state pin, which goes HIGH when a bluetooth connection with another device is established and LOW when not connected. I will use digitalRead() with the MKR1000 on this pin to detect if the DonkeyCar is at the origin or not.

As for the beacon (the bluetooth device which connects with the HC-05 module), it could theoretically be any bluetooth device, but because I do not have another module at the moment, I will need to temporarily use Infineon's Sensor Hub Nano, which I used in a previous project to automatically connect with the HC-05 bluetooth module. You can find the details of that here.

So to summarize, the HC-05 is wired to the MKR1000, and configured to automatically connect to the Sensor Hub Nano if it is in range, and the MKR1000 can detect whether or not the HC-05 is connected to anything or not (using the state pin as an indicator). It will send this information to the Raspberry Pi which can then finally detect if it is at the origin or not.

I think this should explain many of your questions of how the autonomous delivery system works. Now we move on to the actual build and modification of the DonkeyCar as an autonomous delivery system.

Configuring the bluetooth module

The first thing you will need to do is to get the MAC address of your Sensor Hub Nano Evaluation Kit. There will be many different ways to do that, but I will tell how I did it.

Pair the Sensor Hub Nano with your smartphone:

Download Infineon's Sensor Hub Nano Evaluation app (for Android) from here and switch on your Sensor Hub Nano. Open the app, and it will display the Sensor Hub Nano as "IFX_NANOHUB", with the MAC address below it.

Note this down as you will need it later.

Note: You would be better off un-pairing the Sensor Hub Nano from your smartphone if you aren't using it now because if your phone is nearby with bluetooth on and the Sensor Hub Nano paired, the phone automatically connects with it. And when you set up the HC-05 and try to get it to pair with the Nano Hub, it simply won't connect.

Now to get the HC-05 in AT mode so that it can be configured.

AT mode allows us to configure settings of the HC-05 bluetooth module; set the baud rate or set whether to connect as a slave or master device and more.

First, upload the "AT Commands" sketch to the Arduino MKR1000. This will allow us to give commands to the Bluetooth module in AT mode, through the Arduino MKR1000.

// Original sketch from Martyn Currey's blog here:
// http://www.martyncurrey.com/arduino-with-hc-05-bluetooth-module-at-mode/
//
// Sketch modified by me to work with Arduino MKR1000!
//
// Basic Bluetooth sketch
// Connect the HC-05 module and communicate using the serial monitor
//
// The HC-05 defaults to commincation mode when first powered on.
// Needs to be placed in to AT mode
// After a factory reset the default baud rate for communication mode is 38400
char c = ' ';
void setup() {
 // start the serial communication with the host computer
 Serial.begin(9600);
 Serial.println("Arduino with HC-05 is ready");
 // start communication with the HC-05 using 38400
 Serial1.begin(38400);
 Serial.println("Serial1 started at 38400");
}
void loop() {
 // Keep reading from HC-05 and send to Arduino Serial Monitor
 if (Serial1.available())
 {
   c = Serial1.read();
   Serial.write(c);
 }
 // Keep reading from Arduino Serial Monitor and send to HC-05
 if (Serial.available())
 {
   c =  Serial.read();
   // mirror the commands back to the serial monitor
   // makes it easy to follow the commands
   Serial.write(c);
   Serial1.write(c);
 }
}

Then wire up only the bluetooth module to the Arduino MKR1000 following the diagram.

Note: It would be a good idea to wire it all up first on a breadboard, and proceed to proper wiring once you’ve set it up properly.

If you try to turn on the Sensor Hub Nano and the HC-05, you will see that they do not connect automatically at this time. This is what you would see:

To change the HC-05 settings, you will need to get your bluetooth module in AT mode. The method for doing this depends upon which breakout board you have and so you may have to do it differently. If you have a module that is different than the one I have, head over to Martyn Currey’s blog here where you can find detailed information about how to get the HC-05 bluetooth modules in AT mode. If you’re stuck, google your problem or comment and I’ll try to help.

My bluetooth module has the button switch, so I have to do the following steps to get it in AT command mode (Don't forget to upload the AT command code to the Arduino):

  • Disconnect the module’s power. (TX and RX lines are still connected!)
  • Press and hold the button switch on the module closed
  • Apply power while still holding down the button switch
  • When the LED comes on, release the switch

A video showing how to get the HC-05 in AT mode:

Once in AT mode you will notice a considerable difference in the pattern of LED blinks on the HC-05. In communication mode, the LED blinks quickly, about 5 times a second while in AT mode the LED blinks once every couple of seconds.

Then, open the serial monitor, set the baud rate to 9600 and select “Both NL & CR”.

Note: You will need to set it to newline and carriage return, or AT commands don’t work.

Type “AT” in the serial monitor and you should receive an “OK”. If you do, then you can proceed further and give the commands as I did.

Basically, we need to change these settings in AT mode:

  • Delete all currently paired devices
  • Get it to connect only to a specified Bluetooth MAC address
  • Set bluetooth connection mode to ‘Master’
  • Specify the MAC address we need it to connect to
  • Set the baud rate to 115200, stop bit to be 2 bits and even parity

The above instructions were given so that you can use them even if you have another Bluetooth module, by referring to the commands and what they do. But now I will list the commands I gave for the HC-05 to pair with the Sensor Hub Nano.

  • AT+RMAAD
  • AT+CMODE=0
  • AT+ROLE=1
  • AT+BIND=1234, 56, abcdef (Replace with the MAC address of the Sensor Hub Nano)
  • AT+UART=115200, 0, 0

Here is a log of my AT commands for your reference:

You should now un-plug the Arduino to turn off the Bluetooth module. This will get it back in communication mode.

Note: If you mess anything up in the HC-05 settings, it would be a good idea to reset the module to the default settings and start from scratch with the command: AT+ORGL

Testing the connection:

Now, you will need to test if the last step was successful; you can do that by turning on the Sensor Hub Nano. The blue LED will blink very slowly, once every couple of seconds. Then, plug in your Arduino to your PC, and note the change in the LED blinks on both the HC-05 and Sensor Hub Nano.

Look at the blinking now and compare it with the blinking before:

There is a noticeable difference, and you should get to know that both the modules are connected.

Note: If you have paired your smartphone with the Sensor Hub Nano before, you may have to un-pair it otherwise it would cause connection problems. It can only connect to one device at a time.

If so, you have configured the HC-05 successfully, and you can move on the setting up the ultrasonic sensor

Setting up the ultrasonic sensor

After you have configured the bluetooth module, you will need to connect that along with the ultrasonic sensor to the Arduino MKR1000. Follow this diagram to do that (Leave the HC-05 connected):

An image of the MKR1000 with the ultrasonic sensor and bluetooth module connected:

Now upload the following code:

int trigPin = 7;    // Trigger
int echoPin = 6;    // Echo
long duration, cm;
const int numReadings = 10;
int readings[numReadings];      // the readings from the analog input
int readIndex = 0;              // the index of the current reading
int total = 0;                  // the running total
int averagecm = 0;
void setup() {
 //Serial Port begin
 Serial.begin(9600);
 //Define inputs and outputs
 pinMode(trigPin, OUTPUT);
 pinMode(echoPin, INPUT);
 // initialize all the readings to 0:
 for (int thisReading = 0; thisReading < numReadings; thisReading++) {
   readings[thisReading] = 0;
 }
}
void loop() {
 // The sensor is triggered by a HIGH pulse of 10 or more microseconds.
 // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
 digitalWrite(trigPin, LOW);
 delayMicroseconds(5);
 digitalWrite(trigPin, HIGH);
 delayMicroseconds(10);
 digitalWrite(trigPin, LOW);
 // Read the signal from the sensor: a HIGH pulse whose
 // duration is the time (in microseconds) from the sending
 // of the ping to the reception of its echo off of an object.
 pinMode(echoPin, INPUT);
 duration = pulseIn(echoPin, HIGH);
 // Convert the time into a distance
 cm = (duration / 2) / 29.1;   // Divide by 29.1 or multiply by 0.0343
 // subtract the last reading:
 total = total - readings[readIndex];
 // read from the sensor:
 readings[readIndex] = cm;
 // add the reading to the total:
 total = total + readings[readIndex];
 // advance to the next position in the array:
 readIndex = readIndex + 1;
 // if we're at the end of the array...
 if (readIndex >= numReadings) {
   // ...wrap around to the beginning:
   readIndex = 0;
 }
 // calculate the average:
 averagecm = total / numReadings;
 // send it to the computer as ASCII digits
 Serial.println(averagecm);
 delay(100);
} 

And open the serial monitor. You should see output like this:

This is a simple code to find the output of the ultrasonic sensor in centimeters, and then find an average (because as you will notice, the sensor readings can be erratic).

Now you need to find a container which will be placed on the DonkeyCar and in which letters can be placed. I am using this:

As I said before, the ultrasonic sensor should be placed the container in such a way that there is a noticeable difference in the output it gives when the container is empty, and when a letter is placed in it. So after making holes for the ultrasonic sensor and placing it in:

And how it looks like finally:

The output without a letter in the container is sometimes 13cm and sometimes 14cm, and when a letter is placed the output is between 6cm and 12cm. Once you find good values for the minimum and maximum distance, just note them down we will need them later.

This is my average sensor output when the container is empty:

And this when it has a letter:

Therefore I chose 12 as the maximum range and 6 as the minimum.

Check everything and once it is working as intended, we move on to the fun part of modifying the DonkeyCar.

Modifying the DonkeyCar

Now, it is time to place the MKR1000 attached with the modules on the DonkeyCar. I would just say use your imagination and wire it up however you like. This is what I did, feel free to do it any way you prefer.

I placed the MKR1000 at the back on this piece of cardboard taped to the DonkeyCar:

The letter container and ultrasonic sensor combination on top:

The bluetooth module taped to the side:

And finally attached the powerbank below the letter container with cable ties:

And at the end it looks like this:

Once done, we need to move on the software part.

Uploading and testing code on the MKR1000:

First of all, download the attached "MKR1000 Ultrasonic Sensor and Beacon" code and replace the minimum and maximum values you got earlier like this:

Upload the code, open the serial monitor and send "1". Check the output. It should be like this:

We have finished the MKR1000 part now, disconnect it and move on.

Making a custom DonkeyCar part:

We need to create a new part to work with the DonkeyCar library, which will allow the Raspberry Pi to retrieve data from the MKR1000 and decide the autonomous delivery system's behavior based on that data.

To access your Raspberry Pi remotely, you will need an SSH client (I am using PuTTY), and to know the IP address of your Raspberry Pi (simply get that from your router’s settings).

First of all, you need to know the serial port the MKR1000 will connect to on the Raspberry Pi. Without plugging in the Arduino MKR1000, type this and hit enter on the Raspberry Pi:

ls /dev/tty* 

It should show all the available serial ports, and now plug in the MKR1000 to the Raspberry Pi, type the same command and hit enter. The new port is the MKR1000's serial port, and you should note that down.

Create a file called MKR1000.py with the following contents but replace the serial port with the one you found earlier.

Place it in the ‘mycar’ directory (you can also put it in the DonkeyCar directory, but I just prefer it here).

Now you will need to replace your manage.py file in the 'mycar' directory with the modified one I have attached. You could use rsync for that, just change the directories (remember to delete the original manage.py too!):

rsync -r ~/manage.py pi@<your_ip_address>:~/mycar/manage.py

Now go the modified mycar directory and do:

python manage.py drive

You should see this output.

Both numbers can be either 0 or 1, and the first number shows if a letter is detected in the container on the DonkeyCar, while the second shows if bluetooth is connected or not (or in other words, if the DonkeyCar is at the origin).

Put a letter in the container, or turn on the Sensor Hub Nano and put it in range with the DonkeyCar and see the values change.

Now that you got the output correctly, you should move on to training an autopilot.

Training an autopilot:

Decide upon a track in your house, keeping in mind the origin (the place where the DonkeyCar will get the letters) and the track should be where you want the car to go. You will need to place the Sensor Hub Nano (what we configured the bluetooth module to connect to), at the origin, so that when the DonkeyCar can detect whether or not it is at the origin.

Note: We will not be using an open track; not one surrounded by tape or ribbons. There would be no fun in building a track with ribbons, but you could try that way as well. I tried this and got surprisingly good results, with the condition that there is no noticeable change in the environment to the DonkeyCar's camera.

Drive a few laps, and once you're confident, delete the tub folder (to remove all previous data) and then drive carefully without hitting anything.

Collect enough good data and then transfer it to your PC with rsync:

rsync -r pi@<your_pi_ip_address>:~/mycar/data/  ~/mycar/data/

Note: I am using Windows as my host PC, so I use rsync with Cygwin.

Once you get the images on your PC, you need to train an autopilot, using:

python ~/mycar/manage.py train --tub <tub folder names comma separated> --model ./models/mypilot

And then move it back to your Raspberry Pi with:

rsync -r ~/mycar/models/ pi@<your_ip_address>:~/mycar/models/

Connect the MKR1000 to the Raspberry Pi with a USB cable and then run the DonkeyCar software with your autopilot with:

python manage.py drive --model ~/mycar/models/mypilot

It should turn on give you output same as it did before, but this time, we're using an autopilot.

Test the autopilot by going to the web interface on your host PC, and set it to Local Angle and limit the throttle to something less (30% works great for me). Then drive and it should steer automatically. Once the steering is good and it can navigate through the track without hitting, set the throttle mode to constant (the least value possible), and see the car navigate by itself.

Note: Local Pilot doesn't work for me for some reason; the car just sits there without doing anything, but be sure to check it out, maybe it does work for you. It says in the DonkeyCar documentation that as for now, the Local Pilot mode is not very reliable.

Hopefully it should work as intended.

You will notice that the code just keeps on printing out if there is a letter in the container or Bluetooth is connected or not. It will not stop the car based on these conditions. This is due to the simple reason that it has not been programmed to do this yet. If everything until now is working, we move on to personalizing the Autonomous Delivery System.

Personalizing the Autonomous Delivery System:

Your DonkeyCar can now recognize whether it has received a letter or not, and whether it is at the origin or not. But it still needs to know when to stop or when to start.

This is the MKR1000.py file we created earlier.

The code is self explanatory, the Raspberry Pi sends it the character "1", waits for some time for a response from the MKR1000, and then receives and parses the data. But if we want to stop at the origin until a letter is present in the container, we can add:

while((letterDetected == "0") and (bluetoothConnected == "1\r\n")):
    ser.write(b"1")
    time.sleep(0.0625)
    if(ser.in_waiting >0):
        line = ser.readline()
        command = line.decode("utf-8")
        letterDetected,bluetoothConnected = command.split(':')

The while loop will prevent the code from moving forward until a letter is put in the container. I know this is not the best way to do it but I am still figuring out how to use the DonkeyCar library. The documentation does not give information on how to do something like this, or maybe I didn't find it. But this works for the time being.

This will stop the DonkeyCar at the origin if it has no letter until it gets one. It need to be improved a lot but is a good starting point and you can modify it according to your situation.

Feel free to add more sensors and modify the loop to work in this way by simple logical statements.

Testing the Autonomous Delivery System

The following pictures should clearly describe how the Autonomous Delivery System works.

Step 1:

DonkeyCar is at the origin (where the Sensor Hub Nano is placed), waiting for a letter. The letter is about to be placed in the container

Step 2:

DonkeyCar detects the letter and starts to move forward on it's track with the autopilot.

Step 3:

It keeps going on the track, steering and taking turns autonomously, as it was trained.

Step 4:

While the DonkeyCar is going on its track the letter is removed, and the DonkeyCar detects that.

Step 5:

There is no letter in it and the DonkeyCar detects that. But because it is not yet at the origin, it still moves on with the autopilot.

Step 6:

The DonkeyCar continues on its track until the bluetooth module automatically connects with the Sensor Hub Nano and so the DonkeyCar detects whether it is at the origin. Once it reaches the origin, and there is no letter in the container, it will stop automatically, waiting for something to be placed, and this continues on...

Bonus Feature 1: Giving it a voice

Yes, you read it right! Giving your Autonomous Delivery System a voice will be a great and useful feature.

Note: Dexter Industries' website has posted how to make your Raspberry Pi speak in excellent detail here and I am writing this from there. So head over to the link given and read the details from there.

First, we will need a small USB speaker, such as this:

Connect it to the Raspberry Pi's audio and power jack.

Then we need to install eSpeak, an open source speech synthesizer. This can be done using:

sudo apt-get install espeak

After installing, just run:

espeak "My DonkeyCar can speak now!" 2>/dev/null

And you will hear the text. The voice is robotic, but I think it suits the DonkeyCar. This can be used to alert the user, when a letter has arrived or in any case the user needs.

And to use it in a Python program, we need to use a subprocess, and the examples given on Dexter Industries' website explain that, so you should head over there.

To use it with the DonkeyCar, you will need to create a new DonkeyCar part, give it the output from the MKR1000.py part, so that you know whether a letter has arrived or not, and so you can decide when to speak, and you will need to use 'subprocess' to allow you to access it in your DonkeyCar part.

It's not difficult but will take a bit of experimentation, and so I won't go through this now due to lack of time. The python examples on the Dexter Industries' will be your help in doing this.

Bonus Feature 2: Wireless charging

It would be great to have wireless charging on your DonkeyCar. You could just place it on the docking station and it will automatically charge the power bank, with having to fumble with the wires on the DonkeyCar.

This will be done using the FT1235 5W transmitter and an FT1236 5W receiver, developed by Futara Elettronica.

I soldered the receiver and transmitter coils to each of them, and then soldered the pin headers.

Then cut a spare USB micro Type B cable, find the right wires for VCC and Ground using a multimeter and the screenshot from Wikipedia below:

Attach the OUT pin on the receiver to the VCC on the micro-B cable, and GND to ground respectively.

Once done, confirm the wiring again. If you reverse it, it will damage the powerbank.

Then on the transmitter side, get a 5volts 2A power adapter and wire it up correctly to VIN and GNDA.

And now, attach the receiver to the DonkeyCar and plug in the micro-B cable to the powerbank.

Now power on the transmitter with 5v and at least 2A:

And place the car on top of it, for the powerbank to start charging:

Final touches

This completes the documentation, and should set you up on your way to an Autonomous Delivery System.

This was completely a learning experience for me (and a lot of fun). It was the first time I had used a platform such as the DonkeyCar and had an amazing time testing it. As for the Autonomous Delivery System, to get it working as intended, you need to take a lot of things into consideration. The training must be done very carefully and without mistakes, and once done, be careful to not move things in the environment which can confuse the auto-pilot.

Other than that, there was an idea I thought of which would allow the DonkeyCar to stop or start moving as the user needs. The idea was to use visual indicators in the DonkeyCar camera's to allow it to start or stop. Think of it like a traffic signal. When it is red, the DonkeyCar will stop, and as soon as it turn green, it would move on. It could be done by training the auto-pilot in that way. It is something I will try once I get the time to do.

Be sure to leave your comments and thoughts about the project.

Code

MKR1000 Ultrasonic Sensor and BeaconArduino
This is the final code you will need to upload to the MKR1000 before you connect it to the Raspberry Pi. Make sure to replace the minimumValue and maximumValue to what you found!
//Replace these with minimum and values you found
int minimumValue = 6;
int maximumValue = 12;

int trigPin = 7;      //Trigger pin
int echoPin = 6;      //Echo pin
int btStatePin = 9;   //Bluetooth state pin
int isLetterDetected;
long duration, cm;
byte message;

// Define the number of samples to keep track of. The higher the number, the
// more the readings will be smoothed, but the slower the output will respond to
// the input. Using a constant rather than a normal variable lets us use this
// value to determine the size of the readings array.
const int numReadings = 10;

int readings[numReadings];      // the readings from the analog input
int readIndex = 0;              // the index of the current reading
int total = 0;                  // the running total
int average = 0;                // the average

//BTconnected is false when not connected and true when connected
boolean BTconnected = false;

void setup() {
  // initialize serial communication with computer:
  Serial.begin(9600);
  // initialize all the readings to 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }
  //Define inputs and outputs
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(btStatePin, INPUT);
}

void loop() {
  // The sensor is triggered by a HIGH pulse of 10 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  digitalWrite(trigPin, LOW);
  delayMicroseconds(5);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  // Read the signal from the sensor: a HIGH pulse whose
  // duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  pinMode(echoPin, INPUT);
  duration = pulseIn(echoPin, HIGH);

  // Convert the time into a distance
  cm = (duration / 2) / 29.1;   // Divide by 29.1 or multiply by 0.0343

  // subtract the last reading:
  total = total - readings[readIndex];
  // read from the sensor:
  readings[readIndex] = cm;
  // add the reading to the total:
  total = total + readings[readIndex];
  // advance to the next position in the array:
  readIndex = readIndex + 1;

  // if we're at the end of the array...
  if (readIndex >= numReadings) {
    // ...wrap around to the beginning:
    readIndex = 0;
  }

  // calculate the average:
  average = total / numReadings;
  // send it to the computer as ASCII digits

  if (average >= minimumValue && average <= maximumValue) {
    isLetterDetected = 1;
  }
  else {
    isLetterDetected = 0;
  }

  if (digitalRead(btStatePin) == HIGH)  {
    BTconnected = true;
  }
  else {
    BTconnected = false;
  }

  message = Serial.read();
  if (message == '1') {
    Serial.print(isLetterDetected);
    Serial.print(":");
    Serial.println(BTconnected);
  }

  delay(30);
}
Ultrasonic sensor with MKR1000Arduino
This will allow you to test the ultrasonic sensor with the MKR1000. It finds the distance, and prints an average value of the distance in centimeters
int trigPin = 7;    // Trigger
int echoPin = 6;    // Echo
long duration, cm;

const int numReadings = 10;

int readings[numReadings];      // the readings from the analog input
int readIndex = 0;              // the index of the current reading
int total = 0;                  // the running total
int averagecm = 0;

void setup() {
  //Serial Port begin
  Serial.begin(9600);
  //Define inputs and outputs
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  // initialize all the readings to 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }
}

void loop() {
  // The sensor is triggered by a HIGH pulse of 10 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  digitalWrite(trigPin, LOW);
  delayMicroseconds(5);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  // Read the signal from the sensor: a HIGH pulse whose
  // duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  pinMode(echoPin, INPUT);
  duration = pulseIn(echoPin, HIGH);

  // Convert the time into a distance
  cm = (duration / 2) / 29.1;   // Divide by 29.1 or multiply by 0.0343

  // subtract the last reading:
  total = total - readings[readIndex];
  // read from the sensor:
  readings[readIndex] = cm;
  // add the reading to the total:
  total = total + readings[readIndex];
  // advance to the next position in the array:
  readIndex = readIndex + 1;

  // if we're at the end of the array...
  if (readIndex >= numReadings) {
    // ...wrap around to the beginning:
    readIndex = 0;
  }

  // calculate the average:
  averagecm = total / numReadings;
  // send it to the computer as ASCII digits

  Serial.println(averagecm);

  delay(100);
}
Modified manage.pyPython
This is the modified manage.py file. It needs to be replaced with the manage.py file in the mycar directory, and needs to have the same name (manage.py)
#!/usr/bin/env python3
"""
Scripts to drive a donkey 2 car and train a model for it.

Usage:
    manage.py (drive) [--model=<model>] [--js] [--chaos]
    manage.py (train) [--tub=<tub1,tub2,..tubn>]  (--model=<model>) [--base_model=<base_model>] [--no_cache]

Options:
    -h --help        Show this screen.
    --tub TUBPATHS   List of paths to tubs. Comma separated. Use quotes to use wildcards. ie "~/tubs/*"
    --js             Use physical joystick.
    --chaos          Add periodic random steering when manually driving
"""
import os
from docopt import docopt

import donkeycar as dk

#import parts
from donkeycar.parts.camera import PiCamera
from donkeycar.parts.transform import Lambda
from donkeycar.parts.keras import KerasCategorical
from donkeycar.parts.actuator import PCA9685, PWMSteering, PWMThrottle
from donkeycar.parts.datastore import TubGroup, TubWriter
from donkeycar.parts.controller import LocalWebController, JoystickController
from donkeycar.parts.clock import Timestamp
from MKR1000 import MKR1000Serial

def drive(cfg, model_path=None, use_joystick=False, use_chaos=False):
    """
    Construct a working robotic vehicle from many parts.
    Each part runs as a job in the Vehicle loop, calling either
    it's run or run_threaded method depending on the constructor flag `threaded`.
    All parts are updated one after another at the framerate given in
    cfg.DRIVE_LOOP_HZ assuming each part finishes processing in a timely manner.
    Parts may have named outputs and inputs. The framework handles passing named outputs
    to parts requesting the same named input.
    """

    V = dk.vehicle.Vehicle()

    mkr1000 = MKR1000Serial()
    #add the part to read and write to the same channel.
    V.add(mkr1000, inputs=[], outputs=[], threaded=True)

    clock = Timestamp()
    V.add(clock, outputs='timestamp')

    cam = PiCamera(resolution=cfg.CAMERA_RESOLUTION)
    V.add(cam, outputs=['cam/image_array'], threaded=True)

    if use_joystick or cfg.USE_JOYSTICK_AS_DEFAULT:
        ctr = JoystickController(max_throttle=cfg.JOYSTICK_MAX_THROTTLE,
                                 steering_scale=cfg.JOYSTICK_STEERING_SCALE,
                                 auto_record_on_throttle=cfg.AUTO_RECORD_ON_THROTTLE)
    else:
        # This web controller will create a web server that is capable
        # of managing steering, throttle, and modes, and more.
        ctr = LocalWebController(use_chaos=use_chaos)

    V.add(ctr,
          inputs=['cam/image_array'],
          outputs=['user/angle', 'user/throttle', 'user/mode', 'recording'],
          threaded=True)

    # See if we should even run the pilot module.
    # This is only needed because the part run_condition only accepts boolean
    def pilot_condition(mode):
        if mode == 'user':
            return False
        else:
            return True

    pilot_condition_part = Lambda(pilot_condition)
    V.add(pilot_condition_part, inputs=['user/mode'],
                                outputs=['run_pilot'])

    # Run the pilot if the mode is not user.
    kl = KerasCategorical()
    if model_path:
        kl.load(model_path)

    V.add(kl, inputs=['cam/image_array'],
              outputs=['pilot/angle', 'pilot/throttle'],
              run_condition='run_pilot')

    # Choose what inputs should change the car.
    def drive_mode(mode,
                   user_angle, user_throttle,
                   pilot_angle, pilot_throttle):
        if mode == 'user':
            return user_angle, user_throttle

        elif mode == 'local_angle':
            return pilot_angle, user_throttle

        else:
            return pilot_angle, pilot_throttle

    drive_mode_part = Lambda(drive_mode)
    V.add(drive_mode_part,
          inputs=['user/mode', 'user/angle', 'user/throttle',
                  'pilot/angle', 'pilot/throttle'],
          outputs=['angle', 'throttle'])

    steering_controller = PCA9685(cfg.STEERING_CHANNEL)
    steering = PWMSteering(controller=steering_controller,
                           left_pulse=cfg.STEERING_LEFT_PWM,
                           right_pulse=cfg.STEERING_RIGHT_PWM)

    throttle_controller = PCA9685(cfg.THROTTLE_CHANNEL)
    throttle = PWMThrottle(controller=throttle_controller,
                           max_pulse=cfg.THROTTLE_FORWARD_PWM,
                           zero_pulse=cfg.THROTTLE_STOPPED_PWM,
                           min_pulse=cfg.THROTTLE_REVERSE_PWM)

    V.add(steering, inputs=['angle'])
    V.add(throttle, inputs=['throttle'])

    # add tub to save data
    inputs = ['cam/image_array', 'user/angle', 'user/throttle', 'user/mode', 'timestamp']
    types = ['image_array', 'float', 'float',  'str', 'str']

    #multiple tubs
    #th = TubHandler(path=cfg.DATA_PATH)
    #tub = th.new_tub_writer(inputs=inputs, types=types)

    # single tub
    tub = TubWriter(path=cfg.TUB_PATH, inputs=inputs, types=types)
    V.add(tub, inputs=inputs, run_condition='recording')

    V.start(rate_hz=cfg.DRIVE_LOOP_HZ,
            max_loop_count=cfg.MAX_LOOPS)


def train(cfg, tub_names, new_model_path, base_model_path=None ):
    """
    use the specified data in tub_names to train an artifical neural network
    saves the output trained model as model_name
    """
    X_keys = ['cam/image_array']
    y_keys = ['user/angle', 'user/throttle']
    def train_record_transform(record):
        """ convert categorical steering to linear and apply image augmentations """
        record['user/angle'] = dk.util.data.linear_bin(record['user/angle'])
        # TODO add augmentation that doesn't use opencv
        return record

    def val_record_transform(record):
        """ convert categorical steering to linear """
        record['user/angle'] = dk.util.data.linear_bin(record['user/angle'])
        return record

    new_model_path = os.path.expanduser(new_model_path)

    kl = KerasCategorical()
    if base_model_path is not None:
        base_model_path = os.path.expanduser(base_model_path)
        kl.load(base_model_path)

    print('tub_names', tub_names)
    if not tub_names:
        tub_names = os.path.join(cfg.DATA_PATH, '*')
    tubgroup = TubGroup(tub_names)
    train_gen, val_gen = tubgroup.get_train_val_gen(X_keys, y_keys,
                                                    train_record_transform=train_record_transform,
                                                    val_record_transform=val_record_transform,
                                                    batch_size=cfg.BATCH_SIZE,
                                                    train_frac=cfg.TRAIN_TEST_SPLIT)

    total_records = len(tubgroup.df)
    total_train = int(total_records * cfg.TRAIN_TEST_SPLIT)
    total_val = total_records - total_train
    print('train: %d, validation: %d' % (total_train, total_val))
    steps_per_epoch = total_train // cfg.BATCH_SIZE
    print('steps_per_epoch', steps_per_epoch)

    kl.train(train_gen,
             val_gen,
             saved_model_path=new_model_path,
             steps=steps_per_epoch,
             train_split=cfg.TRAIN_TEST_SPLIT)


if __name__ == '__main__':
    args = docopt(__doc__)
    cfg = dk.load_config()

    if args['drive']:
        drive(cfg, model_path = args['--model'], use_joystick=args['--js'], use_chaos=args['--chaos'])

    elif args['train']:
        tub = args['--tub']
        new_model_path = args['--model']
        base_model_path = args['--base_model']
        cache = not args['--no_cache']
        train(cfg, tub, new_model_path, base_model_path)
MKR1000.pyPython
This is the python file for the custom MKR1000 part for the DonkeyCar. You need to edit your MKR1000's serial port in the Raspberry Pi, and place it in the mycar directory.
import serial
import time

class MKR1000Serial:
    def update(self):
        print('Starting MKR1000 serial connection...')
    def run_threaded(self):
        ser = serial.Serial('/dev/ttyACM0', 9600)
        ser.write(b"1")
        time.sleep(0.0625)
        if(ser.in_waiting >0):
            line = ser.readline()
            command = line.decode("utf-8")
            letterDetected,bluetoothConnected = command.split(':')
            print(letterDetected,",",bluetoothConnected)

Schematics

MKR1000 with ultrasonic sensor and bluetooth module
Fritzing diagram of the Arduino MKR1000 with the ultrasonic sensor and bluetooth module. Just wire everything according to this.
Final m2cqmlndj2

Comments

Similar projects you might like

Make an Autonomous "Follow Me" Cooler

Project tutorial by Hacker Shack

  • 94,559 views
  • 189 comments
  • 362 respects

Probability | Autonomous Rover

Project in progress by colepurtzer

  • 6,226 views
  • 19 comments
  • 41 respects

Smart Garbage Monitoring System Using Arduino 101

Project tutorial by Technovation

  • 21,399 views
  • 7 comments
  • 32 respects

Otto DIY+ Arduino Bluetooth Robot Easy to 3D Print

Project tutorial by Team Otto builders

  • 48,258 views
  • 117 comments
  • 162 respects

Arduino Controlled Smart Hydroponic Modular System

Project in progress by Luis Antonio Martin Nuez

  • 9,103 views
  • 2 comments
  • 61 respects

Smart Personal Money Vault Monitoring System Based on IoT

Project tutorial by Salah Uddin

  • 896 views
  • 0 comments
  • 6 respects
Add projectSign up / Login