-
Notifications
You must be signed in to change notification settings - Fork 10
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
Calzim/arm api python #40
Conversation
python arm api and created new versions of all the arm kit examples to use the arm api
added comments and cleaned up code
have yet to remove old dead code
also cleaned out some dead code and added some comments
FYI: I changed the base branch into which this will be merged, just so we can see the diff between this branch and #39 . |
|
||
|
||
|
||
class Arm(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inherit from object
like above to be consistent. However, I think this is done automatically for Python 3, and since we don't support Python 2 anymore, we can probably just get rid of this explicit inheritance...
def __init__(self, params): | ||
# Create arm from lookup | ||
self.lookup = hebi.Lookup() | ||
sleep(2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't want to do this in the constructor... We should probably expose the Arm API as something like the following:
def create_arm(params, lookup_sleep_amount=2, other_input_parameters...):
lookup = hebi.Lookup()
sleep(lookup_sleep_amount)
group = lookup.get_group_from_names([params.family], params.module_names)
if group is None:
raise RuntimeError("Could not create group") # Probably want to raise exception here on failure IMO?
return Arm(group) # Can add other constructor parameters as needed here too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran into something similar in MATLAB and I'm a big fan of just passing in the group directly. The lookup has so many settings and overloads that it seems easier to just use the existing API rather than replicating it via factory methods.
|
||
# Create robot model | ||
try: | ||
self.model = hebi.robot_model.import_from_hrdf(params.hrdf) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above. This should probably be moved outside of the constructor and into the create_arm
comment I posted above. An Arm shouldn't be ever created with an invalid hrdf file (i.e., we want to prevent "incomplete/invalid" state like this)
self.model = hebi.robot_model.import_from_hrdf(params.hrdf) | ||
except Exception as e: | ||
print('Could not load HRDF: {0}'.format(e)) | ||
sys.exit() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
definitely should remove this exit()
call...
|
||
self.set_goal_first = True | ||
self.at_goal = True | ||
self.goal = "grav comp" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally, we have instance variables starting with an underscore (or two, to make them "private"/mangled) and create getters/setters as needed to modify them. I'd prefer that for this class as well.
def loadGains(self, gains_file): | ||
try: | ||
self.cmd.read_gains(gains_file) | ||
if not self.grp.send_command_with_acknowledgement(self.cmd): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should definitely try sending gains multiple times. This warrants a separate discussion, but it's relevant enough to comment on for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MATLAB just added a generic sendWithRetry
that works for gains and other commands. Maybe that would be useful here as well: https://github.com/HebiRobotics/hebi-matlab-examples/blob/master/include/hebi/HebiUtils.m#L822-L855
|
||
# Updates feedback and generates the basic command for this timestep | ||
def update(self): | ||
self.prev_fbk = list(self.fbk) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a list?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm responsible for this. This clones the IOState. Before this was
self.prev_fbk = self.fbk
I guess the less cryptic way to do this would be
self.prev_list = self.fbk.copy()
This wasn't a thing until Python3.3, so I remembered the list(list) way instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is feedback.copy_from
the behavior you want here?
return | ||
|
||
|
||
def createMotionArray(self, size, prev_cmd, array=None, flow=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably use snek_case
and not camelCase
in this class too, btw...
# Note flow overrides any array passed (used for vel and accel, should never pass flow and positions) | ||
mArray = np.empty((self.grp.size, size+1)) | ||
mArray[:, 0] = prev_cmd | ||
if flow == None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pedantic thing: Apparently it's better practice to do if flow is None:
here instead.
I think the rationale is that None
is a "singleton" of sorts... There is only one None
instance in a Python interpreter. That means that if flow
is equal to None
(literally, if flow is "null"), it's underlying object is the same object id (i.e., both variables point to the same object in memory) as None
, meaning you can use the is
operator.
|
||
|
||
def createGoal(self, position, durration=None, velocity=None, accel=None, flow=None): | ||
# Create trajectory to target position |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for writing up documentation on the parameters here. One thing though:
We should do this whole comment as a multi line comment (like the Ex: ...
below it), meaning this should be replaced with:
def create_goal(params....):
"""
Create trajectory to target position
... Blah Blah ...
......
Ex:
3dof arm with a 2 point trajectory
.....
@@ -30,7 +30,7 @@ | |||
|
|||
# Mobile device setup | |||
phone_family = 'HEBI' | |||
phone_name = "Cal's iPhone" | |||
phone_name = "Mobile IO" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the naming convention is mobileIO
now. Could be wrong though... I always forget myself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's mobileIO
def __init__(self, family, moduleNames, hrdf): | ||
self.family = family | ||
self.hrdf = hrdf | ||
self.moduleNames = moduleNames |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
different casing than other names?
|
||
|
||
# Loads gains from given gains file | ||
def loadGains(self, gains_file): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this function should accept a gains object rather than a filename. There are plenty of cases where we would want to load some gains and modify them before setting (e.g. the impedance control example increases the effort kp by 2x).
That being said, this would be a good candidate for a helper function rather than part of the Arm API.
@@ -193,7 +193,7 @@ def run(): | |||
print_and_cr("When in playback mode, 'b6' resumes training, and 'b1' quits.") | |||
|
|||
|
|||
m = mbio.MobileIO("HEBI", "Phone Name") | |||
m = mbio.MobileIO("HEBI", "Mobile IO") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mobileIO
ported arm api for python and fixed examples to work with it