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

Calzim/arm api python #40

Closed
wants to merge 14 commits into from

Conversation

CalZim
Copy link

@CalZim CalZim commented Mar 12, 2020

ported arm api for python and fixed examples to work with it

@rLinks234 rLinks234 self-requested a review March 15, 2020 16:11
@rLinks234 rLinks234 changed the base branch from master to calzim/example-cleanup March 15, 2020 16:49
@rLinks234
Copy link
Contributor

FYI: I changed the base branch into which this will be merged, just so we can see the diff between this branch and #39 .

@iamtesch iamtesch changed the base branch from calzim/example-cleanup to calzim/only_example_cleanup March 20, 2020 14:20



class Arm():
Copy link
Contributor

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)
Copy link
Contributor

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

Copy link

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)
Copy link
Contributor

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()
Copy link
Contributor

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"
Copy link
Contributor

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):
Copy link
Contributor

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.

Copy link

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)
Copy link
Contributor

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?

Copy link
Contributor

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

Copy link
Contributor

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):
Copy link
Contributor

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:
Copy link
Contributor

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
Copy link
Contributor

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"
Copy link
Contributor

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.

Copy link

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
Copy link

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):
Copy link

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")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mobileIO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants