-
Notifications
You must be signed in to change notification settings - Fork 0
Donkey
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.
- Download prebuilt zipped disk image
- Insert SD, ensure write enabled
- Open Etcher
- Flash
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>"
}
- Setup venv
sudo apt-get install virtualenv build-essential python3-dev gfortran libhdf5-dev
~ $ virtualenv env -p python3
source env/bin/activate
- Install dependencies
pip install keras==2.0.6
pip install tensorflow==1.3.0
-
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
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
- Functionsdrive
,train
. -
square.py
- Functionsdrive
,train
. Alternate? Web controller? Delete this??? -
time_stack.py
- Functionsdrive
,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
.
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.
- Get IP of host and Pi:
~ $ hostname -I
e.g. Laptop 192.168.2.137 Pi 192.168.2.127
- usr@host ~ $ sudo donkey findcar # Scan for car
- usr@host ~ $ ping x.x.x.x # If you know the IP or resolved host name
If not found, log into the Pi:
- pi@dtwo ~ $ ping google.com # Check for network
- Connect
Normal start
- 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
-
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
-
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)