Skip to content
MarcusJones edited this page Jun 8, 2018 · 2 revisions

As we have mentioned many times around this wiki, we owe a great debt to the work and help of the donkey car community. Here are some legacy instructions from the donkey repo that will be phased out as the mule gains greater autonomy from its donkey progenitor.

Flash SD

  1. Download prebuilt zipped disk image
  2. Insert SD, ensure write enabled
  3. Open Etcher
  4. Flash

Setup WiFi

Create the wpa_supplicant.conf file in the root of the boot. This file will be moved to /etc/wpa_supplicant/wpa_supplicant.conf on first boot.

country=DE
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="<your network name>"
    psk="<your password>"
}

Host Setup

  1. Setup venv
  • sudo apt-get install virtualenv build-essential python3-dev gfortran libhdf5-dev
  • ~ $ virtualenv env -p python3
  • source env/bin/activate
  1. Install dependencies
  • pip install keras==2.0.6
  • pip install tensorflow==1.3.0

Installation overview

  • The github repo is cloned to local.

  • The repo package is installed pip install setup.py

    • The setup.py script has various options set
    • An egg link is created ./anaconda3/lib/python3.6/site-packages/donkeycar.egg-link, pointing to the local git repo
    • A CLI entry point is created at /bin/donkey = execute_from_command_line();
  • From the donkey CLI, the createcar command is called

  • Clone repo git clone ...

  • Install After setup and pull of the repo, the package is installed;

pip install -e donkeycar

The flag -e: Install a local project in "editable" mode.

Alternatively,

python setup.py install

Does the same, except with no pip environment tracking.

  • Donkey command

When calling 'donkey' at CLI, the /bin/donkey file is called;

#!/home/batman/anaconda3/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'donkeycar','console_scripts','donkey'
__requires__ = 'donkeycar'
import re
import sys
from pkg_resources import load_entry_point

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(
        load_entry_point('donkeycar', 'console_scripts', 'donkey')()
    )

Therefore, the defined mapping donkeycar=donkeycar.management.base:execute_from_command_line is loaded, and the execute_from_command_line() function is called.

  • Donkey createcar

Default setup: donkey createcar --path ~/d2

Other options; * --path path where to create car folder * --template name of car template to use * --overwrite should replace existing files

This command performs the following steps; * Creates base path and folders /models, /data, /logs * Copies the template file (default is donkey2.py) into /manage.py * Copies config_default.py into config.py

Configuration options

A selected subset of configuration options in setup.py:

car_templates=['templates/*']
web_controller_html = package_files('donkeycar/parts/web_controller/templates', 'donkeycar/')
extra_files = car_templates + web_controller_html

setup(name='donkeycar',

    license='MIT',

    entry_points={'console_scripts': ['donkey=donkeycar.management.base:execute_from_command_line',],},

    install_requires=['numpy', 'pillow','docopt','tornado','requests','keras','h5py','python-socketio',
                                    'flask','eventlet','moviepy','pandas',],
    extras_require={'pi': [
                        'picamera',
                        'Adafruit_PCA9685',
                        ]},

    package_data={'donkeycar': extra_files,},

    include_package_data=True,

    packages=find_packages(exclude=(['tests', 'docs', 'site', 'env'])),
)

These files are passed into setup.

Template files are copied into the target directory:

  • config_defaults.py - Settings for i.e. Image res, Loop frequency, Paths, PWM settings
  • donkey2.py - Functions drive, train.
  • square.py - Functions drive, train. Alternate? Web controller? Delete this???
  • time_stack.py - Functions drive, train. Alternate? Delete this???
  • tk1.py - Script to drive a donkey car using a webserver hosted on the vehicle. ?

The following commands are exposed in donkeycar.management.base:execute_from_command_line()

The exposed args are all subclassed from BaseCommand.

Entry points

From SO: The most popular kind of entry point is the "console_script" entry point, which points to a function that you want made available as a command-line tool to whoever installs your package.

In this case, the function execute_from_command_line() is exposed to CLI by calling donkey with a command. (This links into the base.py?).

~$ which donkey
/home/batman/anaconda3/bin/donkey
    commands = {
            'createcar': CreateCar,
            'findcar': FindCar,
            'calibrate': CalibrateCar,
            'tubclean': TubManager,
            'tubhist': ShowHistogram,
            'tubplot': ShowPredictionPlots,
            'tubcheck': TubCheck,
            'makemovie': MakeMovie,
            'sim': Sim,
                }

  • findcar=FindCar No args. Run:
        print('Looking up your computer IP address...')
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(("8.8.8.8",80))
        ip = s.getsockname()[0]
        print('Your IP address: %s ' %s.getsockname()[0])
        s.close()

        print("Finding your car's IP address...")
        cmd = "sudo nmap -sP " + ip + "/24 | awk '/^Nmap/{ip=$NF}/B8:27:EB/{print ip}'"
        print("Your car's ip address is:" )
        os.system(cmd)
  • createcar=CreateCar
        parser = argparse.ArgumentParser(prog='createcar', usage='%(prog)s [options]')
        parser.add_argument('--path', default=None, help='path where to create car folder')
        parser.add_argument('--template', default=None, help='name of car template to use')
        parser.add_argument('--overwrite', action='store_true', help='should replace existing files')
  • NOT EXPOSED: ShowHistogram Produce a histogram of record type frequency in the given tub

  • NOT EXPOSED: ShowPredictionPlots Plot model predictions for angle and throttle against data from tubs.

Drive

  • Get IP of host and Pi:

~ $ hostname -I

e.g. Laptop 192.168.2.137 Pi 192.168.2.127

  1. usr@host ~ $ sudo donkey findcar # Scan for car
  2. usr@host ~ $ ping x.x.x.x # If you know the IP or resolved host name

If not found, log into the Pi:

  1. pi@dtwo ~ $ ping google.com # Check for network
  • Connect

Normal start

  1. Connect to car from host
* Search for the IP
~/git/donkey/donkey findcar # Retruns IP

* Test the IP
ping xxx.xxx.xxx.xxx # Use IP
* Host name is 'dtwo'
ping dtwo # DOES NOT WORK
ping dtwo.local # This works

* SSH into the RPi
ssh pi@dtwo
asdfasdf # Password
  • Start driving
  1. At the RPi SSH CLI

    • Go into d2 directory (where this car template project was created)
    • Run python manage.py drive
    • This starts a web server at :8887 4.
#
cd d2
python manage.py drive
# OR
ddrive # alias = python manage.py drive
jsdrive #alias = python manage.py drive --js

# Check the http server
# <pi ip>:8887/drive

Start recording: CIRCLE

  • Transfer JPG to host laptop
# Transfer from pi to Laptop
rsync -r pi@dtwo:~/d2/data/tub_30_18-03-28 ~/d2/data
rsync -r pi@dtwo:~/d2/data/tub_47_18-04-08 ~/d2/data
rsync -r pi@192.168.2.137:~/d2/data/tub_51_18-04-09 ~/d2/data
rsync -r pi@192.168.2.160:~/d2/data/tub_61_18-04-11 ~/d2/data


# Transfer, verbose:
rsync -rv pi@dtwo:~/d2/data/tub_30_18-03-28 ~/d2/data

# Sync a directory
rsync -r pi@dtwo:~/d2/data/  ~/d2/data/

rsync -opts <sourcedir> <targetdir>
     -v, --verbose               increase verbosity
    -r, --recursive             recurse into directories

  • Train model ACTIVATE the environment!!!
act drive
python ~/d2/manage.py train --tub ~/d2/data/tub_30_18-03-28 --model ~/d2/models/mypilot2
python ~/d2/manage.py train --tub ~/d2/data/tub_47_18-04-08 --model ~/d2/models/mypilot3
python ~/d2/manage.py train --tub ~/d2/data/tub_51_18-04-09 --model ~/d2/models/mypilot51
python ~/d2/manage.py train --tub ~/d2/data/tub_61_18-04-11 --model ~/d2/models/mypilot61

(Syntax)

 python ~/d2/manage.py train
    --tub <tub folder names comma separated>
    --model ./models/mypilot
python ~/d2/manage.py train --tub ./data/tub_9_18-03-25 --model mypilot
  • Transfer model to car rsync -v /d2/models/mypilot3 pi@dtwo:/d2/models/ rsync -v /d2/models/mypilot51 pi@192.168.2.137:/d2/models/ rsync -v /d2/models/mypilot61 pi@192.168.2.160:/d2/models/

Transfering a model BACK from the pi... rsync -v pi@dtwo:~/d2/models/mypilot2 ~/d2/models

Syntax rsync -r /d2/models/ pi@<your_ip_address>:/d2/models/ rsync -r /d2/models/ pi@dtwo:/d2/models/ rsync -r /d2/models/ pi@dtwo:/d2/models/

  • Autonomous drive

Syntax

python ~/d2/manage.py drive --model ~/d2/models/MODELNAME


python manage.py drive --model ~/d2/models/mypilot
python manage.py drive --model ~/d2/models/mypilot2
python ~/d2/manage.py drive --model ~/d2/models/mypilot3
python ~/d2/manage.py drive --model ~/d2/models/mypilot51
python ~/d2/manage.py drive --model ~/d2/models/mypilot61

192.168.2.127:8887

Go to web browser

:8887

Select constant throttle, try 17%

Select local angle

Press i key to start

Start recording

Joystick

  • DS3 Controls * top2 = PS3 dpad up => increase throttle scale * base = PS3 dpad down => decrease throttle scale * base2 = PS3 dpad left => increase steering scale * pinkie = PS3 dpad right => decrease steering scale * trigger = PS3 select => switch modes * top = PS3 start => toggle constant throttle * base5 = PS3 left trigger 1 * base3 = PS3 left trigger 2 * base6 = PS3 right trigger 1 * base4 = PS3 right trigger 2 * thumb2 = PS3 right thumb * thumb = PS3 left thumb * circle = PS3 circrle => toggle recording * triangle = PS3 triangle => increase max throttle * cross = PS3 cross => decrease max throttle

  • Web controller throttle limit Edit the list /donkeycar/parts/web_controller/templates/vehicle.html

# Line 30
          <div class="form-group" style="max-width:30%;">
            <label class="group-label">Max Throttle</label><br/>
            <div class="form-group">
              <select id="max_throttle_select" class="form-control">
                <option disabled selected> Select Max Throttle </option>
                {% for t in [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 100] %}
                  <option value="{{ t / 100.0 }}">{{ t }}%</option>
                {% end %}
              </select>
            </div>
          </div>
  • Data object

/donkeycar/parts/web_controller/web.py

# line 155
def post(self):
        '''
        Receive post requests as user changes the angle
        and throttle of the vehicle on a the index webpage
        '''
        data = tornado.escape.json_decode(self.request.body)
        self.application.angle = data['angle']
        self.application.throttle = data['throttle']
  • Keyboard functions /donkeycar/parts/web_controller/templates/static/main.js

Editing functions made NO CHANGE

// line 69
    var setBindings = function() {

      $(document).keydown(function(e) {
          if(e.which == 32) { toggleBrake() }  // 'space'  brake
          if(e.which == 82) { toggleRecording() }  // 'r'  toggle recording
          if(e.which == 73) { throttleUp() }  // 'i'  throttle up
          if(e.which == 75) { throttleDown() } // 'k'  slow down
          if(e.which == 74) { angleLeft() } // 'j' turn left
          if(e.which == 76) { angleRight() } // 'l' turn right
          if(e.which == 65) { updateDriveMode('auto') } // 'a' turn on auto mode
          if(e.which == 68) { updateDriveMode('user') } // 'd' turn on manual mode
          if(e.which == 83) { updateDriveMode('auto_angle') } // 'a' turn on auto mode
      });

// line 394
    var throttleUp = function(){
      state.tele.user.throttle = limitedThrottle(Math.min(state.tele.user.throttle + .05, 1));
      postDrive()
    };

// line 399
    var throttleDown = function(){
      state.tele.user.throttle = limitedThrottle(Math.max(state.tele.user.throttle - .05, -1));
      postDrive()
    };



  • Joystick controller
class JoystickController(object):
    '''
    Joystick client using access to local physical input
    '''

    def __init__(self, poll_delay=0.0,
                 max_throttle=1.0,
                 steering_axis='x',
                 throttle_axis='rz',
                 steering_scale=1.0,
                 throttle_scale=-1.0,
                 dev_fn='/dev/input/js0',
                 auto_record_on_throttle=True):

        self.angle = 0.0
        self.throttle = 0.0
        self.mode = 'user'
        self.poll_delay = poll_delay
        self.running = True
        self.max_throttle = max_throttle
        self.steering_axis = steering_axis
        self.throttle_axis = throttle_axis
        self.steering_scale = steering_scale
        self.throttle_scale = throttle_scale
        self.recording = False
        self.constant_throttle = False
        self.auto_record_on_throttle = auto_record_on_throttle
        self.dev_fn = dev_fn
        self.js = None

        #We expect that the framework for parts will start a new
        #thread for our update fn. We used to do that and it caused
        #two threads to be polling for js events.

    def on_throttle_changes(self):
        '''
        turn on recording when non zero throttle in the user mode.
        '''
        if self.auto_record_on_throttle:
            self.recording = (self.throttle != 0.0 and self.mode == 'user')

    def init_js(self):
        '''
        attempt to init joystick
        '''
        try:
            self.js = Joystick(self.dev_fn)
            self.js.init()
        except FileNotFoundError:
            print(self.dev_fn, "not found.")
            self.js = None
        return self.js is not None


    def update(self):
        '''
        poll a joystick for input events

        button map name => PS3 button => function
        * top2 = PS3 dpad up => increase throttle scale
        * base = PS3 dpad down => decrease throttle scale
        * base2 = PS3 dpad left => increase steering scale
        * pinkie = PS3 dpad right => decrease steering scale
        * trigger = PS3 select => switch modes
        * top = PS3 start => toggle constant throttle
        * base5 = PS3 left trigger 1
        * base3 = PS3 left trigger 2
        * base6 = PS3 right trigger 1
        * base4 = PS3 right trigger 2
        * thumb2 = PS3 right thumb
        * thumb = PS3 left thumb
        * circle = PS3 circrle => toggle recording
        * triangle = PS3 triangle => increase max throttle
        * cross = PS3 cross => decrease max throttle
        '''

        #wait for joystick to be online
        while self.running and not self.init_js():
            time.sleep(5)

        while self.running:
            button, button_state, axis, axis_val = self.js.poll()

            if axis == self.steering_axis:
                self.angle = self.steering_scale * axis_val
                print("angle", self.angle)

            if axis == self.throttle_axis:
                #this value is often reversed, with positive value when pulling down
                self.throttle = (self.throttle_scale * axis_val * self.max_throttle)
                print("throttle", self.throttle)
                self.on_throttle_changes()

            if button == 'trigger' and button_state == 1:
                '''
                switch modes from:
                user: human controlled steer and throttle
                local_angle: ai steering, human throttle
                local: ai steering, ai throttle
                '''
                if self.mode == 'user':
                    self.mode = 'local_angle'
                elif self.mode == 'local_angle':
                    self.mode = 'local'
                else:
                    self.mode = 'user'
                print('new mode:', self.mode)

            if button == 'circle' and button_state == 1:
                '''
                toggle recording on/off
                '''
                if self.auto_record_on_throttle:
                    print('auto record on throttle is enabled.')
                elif self.recording:
                    self.recording = False
                else:
                    self.recording = True

                print('recording:', self.recording)

            if button == 'triangle' and button_state == 1:
                '''
                increase max throttle setting
                '''
                self.max_throttle = round(min(1.0, self.max_throttle + 0.01), 2)
                if self.constant_throttle:
                    self.throttle = self.max_throttle
                    self.on_throttle_changes()

                print('max_throttle:', self.max_throttle)

            if button == 'cross' and button_state == 1:
                '''
                decrease max throttle setting
                '''
                self.max_throttle = round(max(0.0, self.max_throttle - 0.01), 2)
                if self.constant_throttle:
                    self.throttle = self.max_throttle
                    self.on_throttle_changes()

                print('max_throttle:', self.max_throttle)

            if button == 'base' and button_state == 1:
                '''
                increase throttle scale
                '''
                self.throttle_scale = round(min(0.0, self.throttle_scale + 0.05), 2)
                print('throttle_scale:', self.throttle_scale)

            if button == 'top2' and button_state == 1:
                '''
                decrease throttle scale
                '''
                self.throttle_scale = round(max(-1.0, self.throttle_scale - 0.05), 2)
                print('throttle_scale:', self.throttle_scale)

            if button == 'base2' and button_state == 1:
                '''
                increase steering scale
                '''
                self.steering_scale = round(min(1.0, self.steering_scale + 0.05), 2)
                print('steering_scale:', self.steering_scale)

            if button == 'pinkie' and button_state == 1:
                '''
                decrease steering scale
                '''
                self.steering_scale = round(max(0.0, self.steering_scale - 0.05), 2)
                print('steering_scale:', self.steering_scale)

            if button == 'top' and button_state == 1:
                '''
                toggle constant throttle
                '''
                if self.constant_throttle:
                    self.constant_throttle = False
                    self.throttle = 0
                    self.on_throttle_changes()
                else:
                    self.constant_throttle = True
                    self.throttle = self.max_throttle
                    self.on_throttle_changes()
                print('constant_throttle:', self.constant_throttle)

            time.sleep(self.poll_delay)

    def run_threaded(self, img_arr=None):
        self.img_arr = img_arr
        return self.angle, self.throttle, self.mode, self.recording

    def run(self, img_arr=None):
        raise Exception("We expect for this part to be run with the threaded=True argument.")
        return False

    def shutdown(self):
        self.running = False
        time.sleep(0.5)