Skip to content

Commit

Permalink
Stock additions 074 (#73)
Browse files Browse the repository at this point in the history
* update README.md

* add my files

* interface updates

* add more libaries and camera offset

* add fix for steer unavailable

* add capnp related code

* finish updates to controlsd

* add dynamic gas

* add dynamic follow

* more updates

* more updates

* offroad warning removal

* run all test

* add ui and updated

* use touch_poll already called

* don't pop out the sidebar when touching df button

* use lqr for 17 corolla

* fix

* add origin and branch tags

* don't clean

* camerad zmq_poll, also recover from EAGAIN

* Fix kernel logging in logcatd, fixes commaai#957

* Revert "fix"

This reverts commit 7c9ec3f.

* Revert "don't pop out the sidebar when touching df button"

This reverts commit a0ef34b.

* new test

* add debugger

* fix

* fix

* don't activate sidebar if df button touched

* so this should work

* send df button status

* send df button status

* test

* debug

* debug

* debug

* debug

* test

* test

* test

* test

* ...will this finally work then?

* ...of course it works all of a sudden

* tuning

* fix

* tuning

* tuning

* tuning

* test

* add padding

* padding only for y

* clean up

* move

* add padding to x again

* don't run all

* use cache

* remove

* add scons_cache file to run scons with cache enabled on demand

* quicker

* test

* test

* revert

Co-authored-by: Willem Melching <willem.melching@gmail.com>
  • Loading branch information
sshane and pd0wm authored Mar 18, 2020
1 parent d1ad7f3 commit 509c4f9
Show file tree
Hide file tree
Showing 47 changed files with 1,567 additions and 424 deletions.
Binary file added .media/gifs/op_tune.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .media/photos/df_profiles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
431 changes: 129 additions & 302 deletions README.md

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
Stock Additions 0.7.4 (version 0.2)
========================
* Sidebar will not pop out when you tap the Dynamic Follow profile change button when driving

Version 0.7.4 (2020-03-20)
========================
* New driving model: improved lane changes and lead car detection
Expand Down Expand Up @@ -25,6 +29,16 @@ Version 0.7.2 (2020-02-07)
* Support for 2016, 2017 and 2020 Lexus RX thanks to illumiN8i!
* Support for 2020 Chrysler Pacifica Hybrid thanks to adhintz!

Stock Additions 0.7.1 (version 0.2)
========================
* Recover faster when lead leaves path, or when braking or acceleration is required immediately. Also should speed up the acceleration with auto lane change.
* Add 3 different dynamic follow profiles: `roadtrip`, `relaxed`, and `traffic`. `relaxed` is the current dynamic follow profile. You can also swap profiles without rebooting live by using opEdit. SSH in and: `cd /data/openpilot;python op_edit.py` The parameter to change is `dynamic_follow`
* Fix for steering unavailable when wheel goes over 100 degrees/sec.
* Tuning for dynamic gas
* Accelerate quicker and get closer when you're lane changing
* Higher acceleration, and higher limits for turns
* Automatic updates, EON will reboot by itself as long as it is inactive

Version 0.7.1 (2020-01-20)
========================
* comma two support!
Expand All @@ -34,6 +48,17 @@ Version 0.7.1 (2020-01-20)
* More robust updater thanks to jyoung8607! Requires NEOS update
* Improve low speed ACC tuning

Stock Additions 0.7 (version 0.1)
========================
* Dynamic lane speed is a new feature that reduces your cruising speed if many vehicles around you are significantly slower than you. This works with and without an openpilot-identified lead.
* Dynamic gas tuning. Above 20 mph we take lead velocity and the following distance into account. Possibility of different tuning for different cars in the future. (DYNAMIC GAS NOW ONLY WORKS ON TOYOTA COROLLA AND RAV4 PEDAL)
* Dynamic follow tuning, don't get as close when lead is accelerating.
* Added static_steer_ratio parameter, if True openpilot will use the steer ratio in your interface file. Default is true, false uses the openpilot learned value which can vary through your drives.
* Added ability to live tune parameters with `op_tune.py`. Currently only the camera offset (`camera_offset`) is supported.
* Some Corolla tuning.
* Reduce max acceleration.
* TO NOTE: Dynamic Lane Speed will not work with stopped cars, at any speed. There is also a margin that cars must be traveling within in order to affect your speed. Don't expect anything magical, just minor quality of drive improvements.

Version 0.7 (2019-12-13)
========================
* Move to SCons build system!
Expand Down
1 change: 1 addition & 0 deletions cereal/car.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ struct CarEvent @0x9b1657f34caf3ad3 {
carUnrecognized @66;
radarCommIssue @67;
driverMonitorLowAcc @68;
dfButtonAlert @69;
}
}

Expand Down
11 changes: 11 additions & 0 deletions cereal/log.capnp
Original file line number Diff line number Diff line change
Expand Up @@ -1946,6 +1946,14 @@ struct Sentinel {
type @0 :SentinelType;
}

struct SmiskolData {
mpcTR @0 :Float32;
}

struct DynamicFollowButton {
status @0 :UInt16;
}

struct Event {
# in nanoseconds?
logMonoTime @0 :UInt64;
Expand Down Expand Up @@ -2024,5 +2032,8 @@ struct Event {
dMonitoringState @71: DMonitoringState;
liveLocationKalman @72 :LiveLocationKalman;
sentinel @73 :Sentinel;

smiskolData @74 :SmiskolData;
dynamicFollowButton @75 :DynamicFollowButton;
}
}
3 changes: 3 additions & 0 deletions cereal/service_list.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ carParams: [8071, true, 0.02, 1]
frontFrame: [8072, true, 10.]
dMonitoringState: [8073, true, 5., 1]

smiskolData: [8074, false, 20.]
dynamicFollowButton: [8075, false, 0.]

testModel: [8040, false, 0.]
testLiveLocation: [8045, false, 0.]
testJoystick: [8056, false, 0.]
Expand Down
98 changes: 98 additions & 0 deletions common/data_collector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from common.travis_checker import travis
from selfdrive.swaglog import cloudlog
import threading
import time
import os
from common.op_params import opParams

op_params = opParams()

class DataCollector:
def __init__(self, file_path, keys, write_frequency=60, write_threshold=2):
"""
This class provides an easy way to set up your own custom data collector to gather custom data.
Parameters:
file_path (str): The path you want your custom data to be written to.
keys: (list): A string list containing the names of the values you want to collect.
Your data list needs to be in this order.
write_frequency (int/float): The rate at which to write data in seconds.
write_threshold (int): The length of the data list we need to collect before considering writing.
Example:
data_collector = DataCollector('/data/openpilot/custom_data', ['v_ego', 'a_ego', 'custom_dict'], write_frequency=120)
"""

self.log_data = op_params.get('log_data', False)
self.file_path = file_path
self.keys = keys
self.write_frequency = write_frequency
self.write_threshold = write_threshold
self.data = []
self.last_write_time = time.time()
self.thread_running = False
self.is_initialized = False

def initialize(self): # add keys to top of data file
if not os.path.exists(self.file_path) and not travis:
with open(self.file_path, "w") as f:
f.write('{}\n'.format(self.keys))
self.is_initialized = True

def append(self, sample):
"""
Appends your sample to a central self.data variable that gets written to your specified file path every n seconds.
Parameters:
sample: Can be any type of data. List, dictionary, numbers, strings, etc.
Or a combination: dictionaries, booleans, and floats in a list
Continuing from the example above, we assume that the first value is your velocity, and the second
is your acceleration. IMPORTANT: If your values and keys are not in the same order, you will have trouble figuring
what data is what when you want to process it later.
Example:
data_collector.append([17, 0.5, {'a': 1}])
"""

if self.log_data:
if len(sample) != len(self.keys):
raise Exception("You need the same amount of data as you specified in your keys")
if not self.is_initialized:
self.initialize()
self.data.append(sample)
self.check_if_can_write()

def reset(self, reset_type=None):
if reset_type in ['data', 'all']:
self.data = []
if reset_type in ['time', 'all']:
self.last_write_time = time.time()

def check_if_can_write(self):
"""
You shouldn't ever need to call this. It checks if we should write, then calls a thread to do so
with a copy of the current gathered data. Then it clears the self.data variable so that new data
can be added and it won't be duplicated in the next write.
If the thread is still writing by the time of the next write, which shouldn't ever happen unless
you set a low write frequency, it will skip creating another write thread. If this occurs,
something is wrong with writing.
"""

if (time.time() - self.last_write_time) >= self.write_frequency and len(self.data) >= self.write_threshold and not travis:
if not self.thread_running:
write_thread = threading.Thread(target=self.write, args=(self.data,))
write_thread.daemon = True
write_thread.start()
# self.write(self.data) # non threaded approach
self.reset(reset_type='all')
elif self.write_frequency > 30:
cloudlog.warning('DataCollector write thread is taking a while to write data.')

def write(self, current_data):
"""
Only write data that has been added so far in background. self.data is still being appended to in
foreground so in the next write event, new data will be written. This eliminates lag causing openpilot
critical processes to pause while a lot of data is being written.
"""

self.thread_running = True
with open(self.file_path, "a") as f:
f.write('{}\n'.format('\n'.join(map(str, current_data)))) # json takes twice as long to write
self.reset(reset_type='time')
self.thread_running = False
195 changes: 195 additions & 0 deletions common/op_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#!/usr/bin/env python3
import os
import json
import time
from selfdrive.swaglog import cloudlog
from common.travis_checker import travis


def write_params(params, params_file):
if not travis:
with open(params_file, "w") as f:
json.dump(params, f, indent=2, sort_keys=True)
os.chmod(params_file, 0o764)


def read_params(params_file, default_params):
try:
with open(params_file, "r") as f:
params = json.load(f)
return params, True
except Exception as e:
cloudlog.error(e)
params = default_params
return params, False


class KeyInfo:
has_allowed_types = False
live = False
has_default = False
has_description = False
hidden = False


class opParams:
def __init__(self):
"""
To add your own parameter to opParams in your fork, simply add a new dictionary entry with the name of your parameter and its default value to save to new users' op_params.json file.
The description, allowed_types, and live keys are no longer required but recommended to help users edit their parameters with opEdit correctly.
- The description value will be shown to users when they use opEdit to change the value of the parameter.
- The allowed_types key is used to restrict what kinds of values can be entered with opEdit so that users can't reasonably break the fork with unintended behavior.
Limiting the range of floats or integers is still recommended when `.get`ting the parameter.
When a None value is allowed, use `type(None)` instead of None, as opEdit checks the type against the values in the key with `isinstance()`.
- Finally, the live key tells both opParams and opEdit that it's a live parameter that will change. Therefore, you must place the `op_params.get()` call in the update function so that it can update.
Here's an example of the minimum required dictionary:
self.default_params = {'camera_offset': {'default': 0.06}}
"""

self.default_params = {'camera_offset': {'default': 0.06, 'allowed_types': [float, int], 'description': 'Your camera offset to use in lane_planner.py', 'live': True},
'awareness_factor': {'default': 3.0, 'allowed_types': [float, int], 'description': 'Multiplier for the awareness times', 'live': False},
'lane_hug_direction': {'default': None, 'allowed_types': [type(None), str], 'description': "(None, 'left', 'right'): Direction of your lane hugging, if present. None will disable this modification", 'live': False},
'lane_hug_angle_offset': {'default': 0.0, 'allowed_types': [float, int], 'description': ('This is the angle your wheel reads when driving straight at highway speeds.\n'
'Replaces both offsets from the calibration learner to help fix lane hugging.\n'
'Enter absolute value here, direction is determined by parameter \'lane_hug_direction\''), 'live': True},
'dynamic_follow': {'default': 'relaxed', 'allowed_types': [str], 'description': "Can be: ('traffic', 'relaxed', 'roadtrip'): Left to right increases in following distance.\n"
"All profiles support dynamic follow so you'll get your preferred distance while\n"
"retaining the smoothness and safety of dynamic follow!", 'live': True},
'alca_nudge_required': {'default': True, 'allowed_types': [bool], 'description': ('Whether to wait for applied torque to the wheel (nudge) before making lane changes. '
'If False, lane change will occur IMMEDIATELY after signaling'), 'live': False},
'alca_min_speed': {'default': 25.0, 'allowed_types': [float, int], 'description': 'The minimum speed allowed for an automatic lane change (in MPH)', 'live': False},
'steer_ratio': {'default': None, 'allowed_types': [type(None), float, int], 'description': '(Can be: None, or a float) If you enter None, openpilot will use the learned sR.\n'
'If you use a float/int, openpilot will use that steer ratio instead', 'live': True},
'use_dynamic_lane_speed': {'default': True, 'allowed_types': [bool], 'description': 'Whether you want openpilot to adjust your speed based on surrounding vehicles', 'live': False},
'min_dynamic_lane_speed': {'default': 20.0, 'allowed_types': [float, int], 'description': 'The minimum speed to allow dynamic lane speed to operate (in MPH)', 'live': False},
'upload_on_hotspot': {'default': False, 'allowed_types': [bool], 'description': 'If False, openpilot will not upload driving data while connected to your phone\'s hotspot', 'live': False},
'reset_integral': {'default': False, 'allowed_types': [bool], 'description': 'This resets integral whenever the longitudinal PID error crosses or is zero.\nShould help it recover from overshoot quicker', 'live': False},
'disengage_on_gas': {'default': True, 'allowed_types': [bool], 'description': 'Whether you want openpilot to be disengage on gas input or not. It can cause issues on specific cars'},
'no_ota_updates': {'default': False, 'allowed_types': [bool], 'description': 'Set this to True to disable all automatic updates. Reboot to take effect'},
'dynamic_gas': {'default': True, 'allowed_types': [bool], 'description': 'Whether to use dynamic gas if your car is supported'},

'op_edit_live_mode': {'default': False, 'allowed_types': [bool], 'description': 'This parameter controls which mode opEdit starts in. It should be hidden from the user with the hide key', 'hide': True}}

self.params = {}
self.params_file = "/data/op_params.json"
self.last_read_time = time.time()
self.read_frequency = 5.0 # max frequency to read with self.get(...) (sec)
self.force_update = False # replaces values with default params if True, not just add add missing key/value pairs
self.to_delete = ['dynamic_lane_speed', 'longkiV', 'following_distance', 'static_steer_ratio', 'uniqueID'] # a list of params you want to delete (unused)
self.run_init() # restores, reads, and updates params

def add_default_params(self):
prev_params = dict(self.params)
for key in self.default_params:
if self.force_update:
self.params[key] = self.default_params[key]['default']
elif key not in self.params:
self.params[key] = self.default_params[key]['default']
return prev_params == self.params

def format_default_params(self):
return {key: self.default_params[key]['default'] for key in self.default_params}

def run_init(self): # does first time initializing of default params
if travis:
self.params = self.format_default_params()
return
self.params = self.format_default_params() # in case any file is corrupted
to_write = False
if os.path.isfile(self.params_file):
self.params, read_status = read_params(self.params_file, self.format_default_params())
if read_status:
to_write = not self.add_default_params() # if new default data has been added
if self.delete_old(): # or if old params have been deleted
to_write = True
else: # don't overwrite corrupted params, just print to screen
cloudlog.error("ERROR: Can't read op_params.json file")
else:
to_write = True # user's first time running a fork with op_params, write default params
if to_write:
write_params(self.params, self.params_file)

def delete_old(self):
prev_params = dict(self.params)
for i in self.to_delete:
if i in self.params:
del self.params[i]
return prev_params == self.params

def put(self, key, value):
self.params.update({key: value})
write_params(self.params, self.params_file)

def get(self, key=None, default=None, force_update=False): # can specify a default value if key doesn't exist
self.update_params(key, force_update)
if key is None:
return self.get_all()

if key in self.params:
key_info = self.key_info(key)
if key_info.has_allowed_types:
value = self.params[key]
allowed_types = self.default_params[key]['allowed_types']
if type(value) not in allowed_types:
cloudlog.warning('op_params: User\'s value is not valid!')
if key_info.has_default: # invalid value type, try to use default value
default_value = self.default_params[key]['default']
if type(default_value) in allowed_types: # actually check if the default is valid
# return default value because user's value of key is not in the allowed_types to avoid crashing openpilot
return default_value
else: # else use a standard value based on type (last resort to keep openpilot running if user's value is of invalid type)
return self.value_from_types(allowed_types)
else:
return value # all good, returning user's value
else:
return self.params[key] # no defined allowed types, returning user's value

return default # not in params

def get_all(self): # returns all non-hidden params
return {k: v for k, v in self.params.items() if not self.key_info(k).hidden}

def key_info(self, key):
key_info = KeyInfo()
if key is None:
return key_info
if key in self.default_params:
if 'allowed_types' in self.default_params[key]:
allowed_types = self.default_params[key]['allowed_types']
if isinstance(allowed_types, list) and len(allowed_types) > 0:
key_info.has_allowed_types = True
if 'live' in self.default_params[key]:
key_info.live = self.default_params[key]['live']
if 'default' in self.default_params[key]:
key_info.has_default = True
if 'description' in self.default_params[key]:
key_info.has_description = True
if 'hide' in self.default_params[key]:
key_info.hidden = self.default_params[key]['hide']
return key_info

def value_from_types(self, allowed_types):
if list in allowed_types:
return []
elif float in allowed_types or int in allowed_types:
return 0
elif type(None) in allowed_types:
return None
elif str in allowed_types:
return ''
return None # unknown type

def update_params(self, key, force_update):
if force_update or self.key_info(key).live: # if is a live param, we want to get updates while openpilot is running
if not travis and (time.time() - self.last_read_time >= self.read_frequency or force_update): # make sure we aren't reading file too often
self.params, read_status = read_params(self.params_file, self.format_default_params())
if not read_status:
time.sleep(1/100.)
self.params, _ = read_params(self.params_file, self.format_default_params()) # if the file was being written to, retry once
self.last_read_time = time.time()

def delete(self, key):
if key in self.params:
del self.params[key]
write_params(self.params, self.params_file)
2 changes: 2 additions & 0 deletions common/travis_checker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from common.basedir import BASEDIR
travis = BASEDIR.strip('/').split('/')[0] != 'data'
Loading

0 comments on commit 509c4f9

Please sign in to comment.