Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

970 enable rc controller in webui #985

Merged
merged 10 commits into from
Feb 1, 2022
53 changes: 44 additions & 9 deletions donkeycar/parts/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@

class Joystick(object):
'''
An interface to a physical joystick
An interface to a physical joystick.
The joystick holds available buttons
and axis; both their names and values
and can be polled to state changes.
'''
def __init__(self, dev_fn='/dev/input/js0'):
self.axis_states = {}
Expand All @@ -29,6 +32,10 @@ def __init__(self, dev_fn='/dev/input/js0'):


def init(self):
"""
Query available buttons and axes given
a path in the linux device tree.
"""
try:
from fcntl import ioctl
except ModuleNotFoundError:
Expand Down Expand Up @@ -294,7 +301,12 @@ def pulse_width(self, high):
else:
return 0.0

def run(self):
def run(self, mode=None, recording=None):
"""
:param mode: default user/mode
:param recording: default recording mode
"""

i = 0
for channel in self.channels:
# signal is a value in [0, (MAX_OUT-MIN_OUT)]
Expand All @@ -312,13 +324,15 @@ def run(self):
if (self.signals[2] - self.jitter) > 0:
self.mode = 'local'
else:
self.mode = 'user'
# pass though value if provided
self.mode = mode if mode is not None else 'user'

# check throttle channel
if ((self.signals[1] - self.jitter) > 0) and self.RECORD: # is throttle above jitter level? If so, turn on auto-record
is_action = True
else:
is_action = False
# pass through default value
is_action = recording if recording is not None else False
return self.signals[0], self.signals[1], self.mode, is_action

def shutdown(self):
Expand Down Expand Up @@ -803,6 +817,7 @@ def __init__(self, *args, **kwargs):

class JoystickController(object):
'''
Class to map joystick buttons and axes to functions.
JoystickController is a base class. You will not use this class directly,
but instantiate a flavor based on your joystick type. See classes following this.

Expand All @@ -825,9 +840,11 @@ def __init__(self, poll_delay=0.0,
dev_fn='/dev/input/js0',
auto_record_on_throttle=True):

self.img_arr = None
self.angle = 0.0
self.throttle = 0.0
self.mode = 'user'
self.mode_latch = None
self.poll_delay = poll_delay
self.running = True
self.last_throttle_axis_val = 0
Expand Down Expand Up @@ -1073,6 +1090,7 @@ def toggle_mode(self):
self.mode = 'local'
else:
self.mode = 'user'
self.mode_latch = self.mode
print('new mode:', self.mode)


Expand All @@ -1088,9 +1106,25 @@ def chaos_monkey_off(self):
self.chaos_monkey_steering = None


def run_threaded(self, img_arr=None):
def run_threaded(self, img_arr=None, mode=None, recording=None):
"""
:param img_arr: current camera image or None
:param mode: default user/mode
:param recording: default recording mode
"""
self.img_arr = img_arr

#
# enforce defaults if they are not none.
#
if mode is not None:
self.mode = mode
if self.mode_latch is not None:
self.mode = self.mode_latch
self.mode_latch = None
if recording is not None:
self.recording = recording

'''
process E-Stop state machine
'''
Expand Down Expand Up @@ -1118,9 +1152,8 @@ def run_threaded(self, img_arr=None):
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 None, None, None, None
def run(self, img_arr=None, mode=None, recording=None):
return self.run_threaded(img_arr, mode, recording)


def shutdown(self):
Expand All @@ -1131,7 +1164,9 @@ def shutdown(self):

class JoystickCreatorController(JoystickController):
'''
A Controller object helps create a new controller object and mapping
A Controller object helps create a new controller object and mapping.
This is used in management/joystic_creator when mapping
a custom joystick.
'''
def __init__(self, *args, **kwargs):
super(JoystickCreatorController, self).__init__(*args, **kwargs)
Expand Down
26 changes: 23 additions & 3 deletions donkeycar/parts/robohat.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,30 @@ def update(self):
print("MM1: Error reading serial input!")
break

def run(self, img_arr=None):
return self.run_threaded()
def run(self, img_arr=None, mode=None, recording=None):
"""
:param img_arr: current camera image or None
:param mode: default user/mode
:param recording: default recording mode
"""
return self.run_threaded(img_arr, mode, recording)

def run_threaded(self, img_arr=None, mode=None, recording=None):
"""
:param img_arr: current camera image
:param mode: default user/mode
:param recording: default recording mode
"""
self.img_arr = img_arr

#
# enforce defaults if they are not none.
#
if mode is not None:
self.mode = mode
if recording is not None:
self.recording = recording

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


Expand Down
115 changes: 92 additions & 23 deletions donkeycar/parts/web_controller/templates/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ var driveHandler = new function() {
this.load = function() {
driveURL = '/drive'
socket = new WebSocket('ws://' + location.host + '/wsDrive');

socket.onmessage = function (event) {
console.log(event.data);
};

setBindings()

Expand All @@ -52,7 +48,7 @@ var driveHandler = new function() {

var manager = nipplejs.create(joystick_options);
bindNipple(manager)

if(!!navigator.getGamepads){
console.log("Device has gamepad support.")
hasGamepad = true;
Expand All @@ -69,8 +65,49 @@ var driveHandler = new function() {
}
};

//
// Update a state object with the given data.
// This will only update existing fields in
// the state; it will not add new fields that
// may exist in the data but not the state.
//
var updateState = function(state, data) {
let changed = false;
if(typeof data === 'object') {
const keys = Object.keys(data)
keys.forEach(key => {
//
// state must already have the key;
// we are not adding new fields to the state,
// we are only updating existing fields.
//
if(state.hasOwnProperty(key) && state[key] !== data[key]) {
if(typeof state[key] === 'object') {
// recursively update the state's object field
changed = updateState(state[key], data[key]) && changed;
} else {
state[key] = data[key];
changed = true;
}
}
});
}
return changed;
}

var setBindings = function() {
//
// when server sends a message with state changes
// then update our local state and
// if there were any changes then redraw the UI.
//
socket.onmessage = function (event) {
console.log(event.data);
const data = JSON.parse(event.data);
if(updateState(state, data)) {
updateUI();
}
};

$(document).keydown(function(e) {
if(e.which == 32) { toggleBrake() } // 'space' brake
Expand All @@ -79,9 +116,10 @@ var driveHandler = new function() {
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
if(e.which == 65) { updateDriveMode('local') } // 'a' turn on local mode (full _A_uto)
if(e.which == 85) { updateDriveMode('user') } // 'u' turn on manual mode (_U_user)
if(e.which == 83) { updateDriveMode('local_angle') } // 's' turn on local mode (auto _S_teering)
if(e.which == 77) { toggleDriveMode() } // 'm' toggle drive mode (_M_ode)
});

$('#mode_select').on('change', function () {
Expand Down Expand Up @@ -250,17 +288,33 @@ var driveHandler = new function() {
//drawLine(state.tele.user.angle, state.tele.user.throttle)
};

var postDrive = function() {

//Send angle and throttle values
data = JSON.stringify({ 'angle': state.tele.user.angle,
'throttle':state.tele.user.throttle,
'drive_mode':state.driveMode,
'recording': state.recording})
console.log(data)
// $.post(driveURL, data)
socket.send(data)
updateUI()
const ALL_POST_FIELDS = ['angle', 'throttle', 'drive_mode', 'recording'];

//
// Set any changed properties to the server
// via the websocket connection
//
var postDrive = function(fields=[]) {

if(fields.length === 0) {
fields = ALL_POST_FIELDS;
}

let data = {}
fields.forEach(field => {
switch (field) {
case 'angle': data['angle'] = state.tele.user.angle; break;
case 'throttle': data['throttle'] = state.tele.user.throttle; break;
case 'drive_mode': data['drive_mode'] = state.driveMode; break;
case 'recording': data['recording'] = state.recording; break;
default: console.log(`Unexpected post field: '${field}'`); break;
}
});
if(data) {
console.log(`Posting ${data}`);
socket.send(JSON.stringify(data))
updateUI()
}
};

var applyDeadzone = function(number, threshold){
Expand Down Expand Up @@ -405,18 +459,34 @@ var driveHandler = new function() {

var updateDriveMode = function(mode){
state.driveMode = mode;
postDrive()
postDrive(["drive_mode"])
};

var toggleDriveMode = function() {
switch(state.driveMode) {
case "user": {
updateDriveMode("local_angle");
break;
}
case "local_angle": {
updateDriveMode("local");
break;
}
default: {
updateDriveMode("user");
break;
}
}
}

var toggleRecording = function(){
state.recording = !state.recording
postDrive()
postDrive(['recording']);
};

var toggleBrake = function(){
state.brakeOn = !state.brakeOn;
initialGamma = null;


if (state.brakeOn) {
brake();
Expand All @@ -431,7 +501,6 @@ var driveHandler = new function() {
state.driveMode = 'user';
postDrive()


i++
if (i < 5) {
setTimeout(function () {
Expand Down
29 changes: 20 additions & 9 deletions donkeycar/parts/web_controller/templates/vehicle.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<div class="form-inline">
<div class="form-group">
<label class="group-label">
Control Mode
Control Mode Help
<a data-toggle="modal" class="btn btn-primary btn-xs" data-target="#aboutControlModes">
<span class="glyphicon glyphicon-info-sign"></span>
</a>
Expand Down Expand Up @@ -85,18 +85,18 @@
</div>

<form>
<label>Mode &amp; Pilot</label>
<label>Pilot Mode [m]</label>
<div class="form-group">
<select id="mode_select" class="form-control">
<option disabled selected> Select Mode </option>
<option value="user">User (d)</option>
<option value="local">Local Pilot (d)</option>
<option value="local_angle">Local Angle (d)</option>
<option disabled selected> Select Mode [m]</option>
<option value="user">User [u]</option>
<option value="local">Full Auto [a]</option>
<option value="local_angle">Auto Steering [s]</option>
</select>
</div>
<div class="form-group">
<button type="button" id="record_button" class="btn btn-info btn-block">
Start Recording (r)
Start Recording [r]
</button>
</div>
</form>
Expand Down Expand Up @@ -155,8 +155,19 @@ <h4 class="modal-title" id="myModalLabel">About Control Modes</h4>
</p>
<p>
<strong>Device tilt</strong> control is enabled for devices with <a href="https://developer.mozilla.org/en-US/docs/Web/API/Detecting_device_orientation">device orientation sensors</a>, and should work with most modern smartphones. Hold your device in landscape mode, tilt forward/backward for throttle and left/right for steering.
</p>

</p>
<p>
<strong>Pilot Mode</strong> can be toggled or chosen using the keyboard.
<ul>
<li>Toggle Pilot <b>M</b>ode: <code>M</code></li>
<li><b>U</b>ser Mode: <code>U</code></li>
<li>Auto <b>S</b>teering Mode: <code>S</code></li>
<li>Full <b>A</b>uto Mode: <code>A</code></li>
</ul>
</p>
<p>
<strong>Recording</strong> can be toggled using the keyboard.
<ul><li>Toggle <b>R</b>ecording: <code>R</code></li></ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
Expand Down
Loading