Giter Site home page Giter Site logo

neurobionics / opensourceleg Goto Github PK

View Code? Open in Web Editor NEW
20.0 2.0 19.0 29.51 MB

An open-source software library for numerical computation, data acquisition, and control of lower-limb robotic prostheses.

Home Page: https://opensourceleg.org

License: GNU Lesser General Public License v2.1

Makefile 1.07% Dockerfile 0.23% Python 98.53% C++ 0.17%
control-systems prostheses prosthetic-limbs prosthetics python python3 robotics

opensourceleg's Introduction

opensourceleg

Build status Documentation Status Python Version Dependencies Status

Code style: black Security: bandit Pre-commit License Coverage Report

An open-source software library for numerical computation, data acquisition,
and control of lower-limb robotic prostheses.


Installation

The easiest and quickest way to install the opensourceleg library is via pip:

pip install opensourceleg

If you plan on installing the opensourceleg library on a Raspberry Pi, we recommend using opensourcelegpi tool, which is a cloud-based CI tool used to build an up-to-date OS for a Raspberry Pi that can be used headless/GUI-less to control autonomous/remote robotic systems. This tool bundles the opensourceleg library and its dependencies into a single OS image, which can be flashed onto a microSD card and used to boot a Raspberry Pi. For more information, click here.

Developing

To modify, develop, or contribute to the opensourceleg library, we encourage you to install Poetry, which is a python packaging and dependency management tool. Once you have Poetry installed on your local machine, you can clone the repository and install the opensourceleg library by running the following commands:

git clone https://github.com/neurobionics/opensourceleg.git
cd opensourceleg

poetry install
poetry shell

Documentation

You can find tutorials and API documentation at opensourceleg.readthedocs.io.

License

The opensourceleg library is licensed under the terms of the LGPL-v2.1 license. This license grants users a number of freedoms:

  • You are free to use the opensourceleg library for any purpose.
  • You are free to modify the opensourceleg library to suit your needs.
  • You can study how the opensourceleg library works and change it.
  • You can distribute modified versions of the opensourceleg library.

The GPL license ensures that all these freedoms are protected, now and in the future, requiring everyone to share their modifications when they also share the library in public.

Contributing

Contributions are welcome, and they are greatly appreciated! For more details, read our contribution guidelines.

opensourceleg's People

Contributors

dependabot[bot] avatar imsenthur avatar jderosia avatar tkevinbest avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

opensourceleg's Issues

Add user input waits before homing and loadcell zero

๐Ÿš€ Feature Request

Right now, the osl.home() routine starts moving the joints right away. I think we should require a button press before this happens for safety. Same thing for the loadcell zeroing. If you don't know it is about to happen, you might not be ready for it.

The solution is pretty simple:
input('\nPress enter to begin homing:')

Incorrect conversion for velocity units in actpack

๐Ÿ› Bug Report

The conversions for the motor velocity have the wrong units. Dephy provides velocity in deg/s not motor counts/s.

The correct code is

    def motor_velocity(self) -> float:
        if self._data is not None:
            return int(self._data.mot_vel) * RAD_PER_DEG
        else:
            return 0.0

Similar issues may be present in the other conversions. I did not yet check them.

Add joint name in debug log

Right now, the debug logs are named with the actpack id. It would be easier to use if they were instead named with their corresponding joint name.

Add support for "Offline Mode"

๐Ÿš€ Feature Request

An "offline mode" that allows scripts to execute even if no actpacks are connected. Basically any call to actuator-y methods would return default values and any writes to actuator-y methods would be ignored.

This provides a way to test code and check for syntax or other errors before going to the leg. It might also allow for testing of code without being connected to a pi, such as on WSL or other linux distro.

Unecessary conversion functions in Joints.py

What is the purpose of the methods convert_to_joint_impedance, convert_to_motor_impedance, and convert_to_pid_impedance? They seem to be doing a combination of converting between real units and dephy's arbitrary units. This seems like duplicate functionality that could be replicated with other existing methods. Unless we have a clear use case for them, I think they should be removed.

Logger class has extra csv rows on windows

The CSV writer on windows does automatic new lines, resulting in alternating blank lines in the logs. This can be fixed with the following change:
self._file = open(file_path + ".csv", "w", newline='')

Strain amp dev branch necessary?

Is the strain amp dev branch stale? Let's delete it if no one is using it anymore. I think everything in it was merged into main.

Tutorial for using the logger

Right now, we don't have any documentation on logging. A quick tutorial about the logger and best practices would be good to have.

Docs not building

Need to add pip install sphinx-rtd-theme to poetry dependencies to build docs.

Check for actpack thermal trip

๐Ÿš€ Feature Request

Right now, the actpack fails silently when its internal thermal limit trips. This can be really annoying and confusing.
You can monitor for this:

# Check for thermal fault, bit 2 of the execute status byte
if self.act_pack.status_ex & 0b00000010 == 0b00000010:
    raise RuntimeError("Actpack Thermal Limit Tripped")

Since the actpack errors anyway, this will at least give the user some info. We should add this directly to the update routine.

Rationale for separate `joints.py` file?

Is there a reason to have Joints.py as a separate file instead of just including the joints class inside osl.py? I feel like anything we can do to cut down on files is a good thing.

Is there a use case for manually instantiating a joint? I see where one might want to manually instantiate an actuator, but it seems like you would always just do the osl object and only add one joint if you only wanted to make a joint.

User supplied ports not working for add joints constructor

๐Ÿ› Bug Report

If I pass a port to the addjoint method (knee then ankle), there's something that doesn't work with port_a.

elif "ankle" in name.lower():
            self._has_ankle = True

            if self.has_knee:
                port = port_a

port_a in the above code is undefined if user supplied the port.

Softstart support?

๐Ÿš€ Feature Request

I've used a "soft start" before to make it so torque is applied gradually when the motors first power up. Should we make this a built in part of the repo? I'm thinking something like

osl = OpenSourceLeg(frequency=..., ..., use_soft_start = True, soft_start_time = 1.5)

and then in the write to joints functions, we would just scale by a saturating ramp from 0 to 1 over the soft_start_time. We would scale either the current commands, voltage commands, the stiffness and damping, or the P and D gains depending on mode.

๐Ÿ”ˆ Motivation

Comfort, lack of scariness. Participants usually don't like it when high-jerk torque profiles are applied.
If you're starting in impedance or position control and you're way away from the desired position, you'll get a very snappy torque right at startup.

Units in thermal model incorrect

๐Ÿ› Bug Report

The input to the thermal model should be in Q axis amps, but it is currently given in mA.

Code sample

Can fix it by using the units package:

def update(self) -> None:
        if self.is_streaming:
            self._data = self.read()
            self._thermal_model.T_c = self.case_temperature
            self._thermal_scale = self._thermal_model.update_and_get_scale(
                dt=(1 / self._frequency),
                motor_current=units.convert_from_default(self.motor_current, units.current.A),
            )

Container names in log file

Container names aren't written in the log file. If you write the same fields for two containers (such as recording the same thing from both joints), the log doesn't say which one is which. Maybe we concatenate the container name in the header row?

Logger no longer works with dictionaries

It is helpful to allow the logger to work with dictionaries as well as objects. This lets you use the locals() function to get a dictionary of all local values.

This version of the logger allows it:

def update(self, dataContainersIn):
        if self.save:
            row = []
            for (varGroup, container) in zip(self.varNames, dataContainersIn):
                if type(container) is dict:
                    for var in varGroup:
                        row.append(container.get(var))
                else:
                    for var in varGroup:
                        row.append(getattr(container, var))
            self.csv_writer.writerow(row)

I wanted to use this syntax and was unable to: osl.log.add_attributes(locals(), ["active_test_conditions"])

Add type checking to logger

Errors in the logger are very hard to debug. You need to supply names very specifically or you get weird errors.

This syntax worsk:
osl.log.add_attributes(torqueSensor, ["torque"])
but this would fail with a weird error:
osl.log.add_attributes(torqueSensor, "torque")

A check in the add_attributes method would be very helpful!

Move values in `constants.py` pertaining to actpacks to the actuators file.

Many of the constants in constants.py are specific to the actpack and thus should live within the actuators.py file within the DephyActpack class. One could imagine adding support for another actuator, in which case having these constants contained within their proper container would be important.

Similarly, there are items in control.py that are ActPack specific that should also be moved.

Impedance settings in joints.py have mixed real and non-dim inputs

๐Ÿš€ Feature Request

The set_joint_impedance method and others require both the output impedance and the current control gains to be specified. The output impedance is in real units whereas the current control gains are in arbitrary dephy units. I think these commands should be separated to maintain the hardware abstraction functionality.

I think we should move setting any current control gains to its own method and if the user wants to change those, they need to call that method. Setting the joint impedance should be independent of the current control gains. Under the hood (because I know Flexsea requires us to send both impedance and current control gains together), we should keep track of the latest current control gains and just handle adding those in for the user.

Custom container names and array handling for logger

๐Ÿš€ Feature Request

It would be nice to have the option to set the container name for the data log in cases where the __repr__ field doesn't exist (or you just don't like what it is by default). For example, I envision something along the lines of:

log.add_attributes(lord_IMU.imu_data, ["angle_x", "angle_y", "angle_z"], custom_container_name = "Lord_IMU")

If you don't specify custom_container_name, you just use the existing functionality.

Likewise, the logger doesn't handle arrays right now. It'd be great if it were smart enough to take an array and give each index its own column. For example, in the following code the acceleration field is an array of length 3.

log.add_attributes(cheap_IMU, ["acceleration"])

It would be great if the resulting log could be:
cheap_IMU_acceleration_0 | cheap_IMU_acceleration_1 | cheap_IMU_acceleration_2

Actpack Offline Mode Failing for Thermal Limit

๐Ÿ› Bug Report

When running in offline mode, the loop fails after 3 iterations because the spoofed case temperature reading violates the maximum. The culprit is the new read() method that just increments all data by 15.

๐Ÿ”ฌ How To Reproduce

Instantiate an OSL object in offline mode and run the loop a few times. You'll get this error: WARNING: [KNEE] Thermal limit 80 reached. Stopping motor.

Code sample: Culprit:

    def read(self):
        self._data.batt_volt += 15
        self._data.batt_curr += 15
        self._data.mot_volt += 15
        self._data.mot_cur += 15
        self._data.mot_ang += 15
        self._data.ank_ang += 15
        self._data.mot_vel += 15
        self._data.mot_acc += 15
        self._data.ank_vel += 15
        self._data.temperature += 15
        self._data.genvar_0 += 15
        self._data.genvar_1 += 15
        self._data.genvar_2 += 15
        self._data.genvar_3 += 15
        self._data.genvar_4 += 15
        self._data.genvar_5 += 15
        self._data.accelx += 15
        self._data.accely += 15
        self._data.accelz += 15
        self._data.gyrox += 15
        self._data.gyroy += 15
        self._data.gyroz += 15

Environment

  • OS: [WSL2]

Rename master to main

Naming the main branch master is a little outdated. Might be good to update it to main instead.

Support for SEAs

Note - none of the angle calculation code would support SEAs right now. We might want to consider renaming things. For example joint.output_position is calculated just via motor angle with gear ratio. If there's an SEA in the mix, that might be misleading. I'm not sure the best way to handle this, but we can talk about it. Just wanted to get it our radar.

Unused methods in StrainAmp

The strain_data_to_wrench and similar methods in the strain amp class are unused and are effectively duplicated in the load cell class. Is there a reason for this? Let's eliminate one of the two so we have single points of maintenance.

Error for same port names when in offline mode.

I get this error when running in offline mode:
WARNING: [OSL] Knee and Ankle joints cant have the same port. Please specify a different port for the ankle joint.

I created the joints using:

osl.add_joint('knee', gear_ratio=9*83/18, port=None, offline_mode=True)
osl.add_joint('ankle',gear_ratio=9*83/18, port=None, offline_mode=True)

I think supplying None to both joints should be allowed in this context.

Position Control Tutorial

๐Ÿ› Bug Report

When I run the code in the Position Control Tutorial, nothing happens to the motor (it doesn't change position).

๐Ÿ”ฌ How To Reproduce

Steps to reproduce the behavior:

  1. Type out the code in the tutorial word for word and run on hardware.

Environment

  • OS:
    Windows SSH'd into an RPi (Linux)
  • Python version, get it with:
    3.9.2

๐Ÿ“ˆ Expected behavior

I expect the motor to move to the desired setpoint position

๐Ÿ“Ž Additional context

This is solved by placing osl.update() in the line before osl.knee.set_motor_position(). Also, the set_point description says that it's in 'motor ticks' but I believe it's actually the designated position units.

General check of documentation for typos and consistency

The docs need checked for typos, as I've seen a few. We should also make sure we're consistent about formatting across documents. For example, sometimes we say opensourceleg and others we say opensourceleg when referring to the library.

Improved commenting at the top of parent files containing instructions for use.

๐Ÿš€ Feature Request

It would be nice to add a comment block at the top of each file (such as state_machine.py) that describes how the modules should be used. For example, right now the State class is the first thing someone sees when they open the file which should never really be used on its own (unless I'm missing something). Basically I think we need something in each file that says how to use the components contained within that file in the intended way.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.