Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c717d52
Refactors to have different models
HanaeRateau Mar 16, 2026
ed00b19
Renaming and adds scraper and dynamixel_configs
HanaeRateau Mar 16, 2026
b904d88
First working draft of refactor and generalization
HanaeRateau Mar 17, 2026
2b8ebaf
Adds function to lists all models in catalog and makes unwrap_dicts p…
HanaeRateau Mar 17, 2026
7ee7628
Adds multiturn test and fixes tests
HanaeRateau Mar 17, 2026
df1125d
Adds scraping workflow
HanaeRateau Mar 17, 2026
219ad83
Renames scraping workflow step
HanaeRateau Mar 17, 2026
5b6bc08
Removes scraping unecessary trigger
HanaeRateau Mar 17, 2026
148347c
Adds baud rate to MotorConfig
HanaeRateau Mar 18, 2026
36b7792
Updates doc with baud_rate param
HanaeRateau Mar 18, 2026
8a1c19e
Adds currents and torque access and changes MotorConfig to take the p…
HanaeRateau Mar 19, 2026
a6cc4be
Fixes tests missing baud rate for creation
HanaeRateau Mar 19, 2026
f550a85
Replace delete old release action with gh command
HanaeRateau Mar 24, 2026
5d033c8
Make it so we can init with a list of dicts
HanaeRateau Mar 24, 2026
ec42ed0
Fixes release deletion process in workflow
HanaeRateau Mar 24, 2026
ff6de44
Add checkout step to release workflow
HanaeRateau Mar 24, 2026
734a672
Cleaning and adds PWM-related methods
HanaeRateau Mar 25, 2026
1ef236c
Update release.yml
HanaeRateau Mar 25, 2026
30c7a34
Update release.yml trying to use gh CLI tool
HanaeRateau Mar 25, 2026
e91d91a
Fixes loading of catalog
HanaeRateau Mar 25, 2026
01aa2bb
Adds torque estimation capabilities with scraped torque points in cat…
HanaeRateau Mar 27, 2026
79937bb
Forgotten renaming of extraction torque script
HanaeRateau Mar 27, 2026
90ac327
Adds per-motor Operating Mode and adds methods to enable other operat…
HanaeRateau Mar 27, 2026
a0f30ca
Renames unwrap_dict to be protected and adds condition if torque_poly…
HanaeRateau Mar 30, 2026
223be3c
Integrates emergency_stop and atexit functions
HanaeRateau Mar 30, 2026
71e2d8d
Adds velocity p and i gain, fixes locked mutex
HanaeRateau Apr 8, 2026
f3a02cb
Updates README with an example of inheritance
HanaeRateau Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ jobs:
packages: write

steps:
- uses: actions/checkout@v4

- name: Download the artifacts
uses: actions/download-artifact@v4
with:
Expand All @@ -74,23 +76,23 @@ jobs:
$releaseDate = Get-Date -Format "yyyy-MM-dd (at HH:mm:ss UTC)"
echo "RELEASE_DATE=$releaseDate" | Out-File -FilePath $env:GITHUB_ENV -Append

- name: Delete old release
uses: dev-drprasad/delete-tag-and-release@v1.0.1
with:
tag_name: release-${{ github.ref_name }}
github_token: ${{ secrets.GITHUB_TOKEN }}
delete_release: true
- name: Delete release and tag
shell: bash
run: |
gh release delete release-${{ github.ref_name }} --cleanup-tag --yes || true
gh api --method DELETE repos/${{ github.repository }}/git/refs/tags/release-${{ github.ref_name }} || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create Release
if: success() || failure()
uses: softprops/action-gh-release@v2.2.2
with:
name: ${{ github.ref_name }}
tag_name: release-${{ github.ref_name }}
fail_on_unmatched_files: false
draft: false
body: |
Last updated on ${{ env.RELEASE_DATE }}.
files: |
artifacts/dist/*
shell: bash
run: |
gh release create release-${{ github.ref_name }} \
--title "${{ github.ref_name }}" \
--notes "Last updated on ${{ env.RELEASE_DATE }}." \
--target ${{ github.sha }} \
artifacts/dist/* \
artifacts/dynamixelmotors-api.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27 changes: 27 additions & 0 deletions .github/workflows/scrapeDynamixel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Scrape Dynamixel Motors ⛏

on:
workflow_dispatch:

jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Scraping Dynamixel website
run: |
python scraper.py

- name: Commit motors data
shell: bash
run: |
git config user.name "github-actions"
git config user.email "actions@github.com"
git add dynamixelmotorsapi/dynamixel_configs.json
git diff --staged --quiet || git commit -m "chore: refresh motors"
git push
142 changes: 118 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,55 +13,149 @@ python -m pip install git+https://github.com/SofaComplianceRobotics/DynamixelMot
The Dynamixel Motors API provides the `DynamixelMotors` class, which can be used to control the Motors. The API provides methods for controlling the robot's motors.
You can look at the [motors_example.py](motors_example.py) file for a simple example of how to use the API to control the motors of the motors.

You have several ways to create a `DynamixelMotors` object:
- by using the `from_dicts` method, which takes a list of dictionaries or one dictionnary describing the motors like above
- by using a json file containing the same list of dictionaries, and use the `from_json` method
- by inheriting from `DynamixelMotors` class. This is the recommended way if you need to extend the capabilities of your Dynamixel motors set (e.g. you want to add specific methods or attributes)

Simple example thaht sets the angles of the motors to 0 radians, waits for 1 second, and then prints the status of the robot:

Simple example thaht sets the angles of 4 *XM430-W210* motors to 0 radians, waits for 1 second, and then prints the status of the robot:
```python
# Standard creation example

import time
from dynamixelmotorsapi import DynamixelMotors

# Define your own class of motors that inherits DynamixelMotors class
class MyDynamixelMotors(DynamixelMotors):
_length_to_rad = 1.0 / 20.0 # 1/radius of the pulley
_rad_to_pulse = 4096 / (2 * pi) # the resolution of the Dynamixel xm430 w210
_pulse_center= 2048
_max_vel = 1000 # *0.01 rev/min
motors_description = [
{
"id": [0, 1, 2, 3],
"model": "XM430-W210",
"pulley_radius": 20, # radius of the pulley in mm
"pulse_center": 2048,
"max_vel": 1000,
"baud_rate": 57600
}
]

robot_motors = DynamixelMotors.from_dicts(motors_description)

if robot_motors.open():

robot_motors.printStatus()
initial_pos_pulse = [0] * len(DXL_IDs)
robot_motors.angles = initial_pos_pulse
time.sleep(1)
robot_motors.printStatus()
robot_motors.close()

```
This is an example of creation using inheritance. Note that you keep the list of dictionnaries notation you use in the previous example.
```python
# Inheritance creation example

import time
from dynamixelmotorsapi import DynamixelMotors

class MyRobot(DynamixelMotors):
def __init__(self):
super().__init__() # Check if all parameters have been set
super().__init__([{
"id": [0, 1, 2, 3],
"model": "XM430-W210",
"pulley_radius": 20,
"pulse_center": 2048,
"max_vel": 1000,
"baud_rate": 1000000
}])

self.foo_bar = "foo"

robot_motors = MyDynamixelMotors()
def toggleAttribute(self):
self.foo_bar = "foo"if self.foo_bar=="bar" else "foo"

myrobot = MyRobot()

if robot_motors.open():

print("Foo Bar attribute: ", myrobot.foo_bar)
robot_motors.printStatus()
initial_pos_pulse = [0] * len(DXL_IDs)
robot_motors.angles = initial_pos_pulse
time.sleep(1)
robot_motors.printStatus()
print("Foo Bar attribute: ", myrobot.foo_bar)
robot_motors.close()

```

By default, there are four motors with IDS 0, 1, 2, and 3. You can change this in the [_dynamixelmotorsparameters.py](dynamixelmotorsapi/_dynamixelmotorsparameters.py) at line 21:

``` python
21 | DXL_IDs = (0, 1, 2, 3)
```
The catalog of Dynamixel motors has been compiled into the file [dynamixel_configs.json](dynamixelmotorsapi\dynamixel_configs.json).
They've been extracted from the Dynamixel website.

To change the motors parameters based on your motors, like the addresses and values, you can also change them into the file [_dynamixelmotorsparameters.py](dynamixelmotorsapi/_dynamixelmotorsparameters.py) if needed.
Except for the `id` parameter, which must be a list of unique IDs for each motor, the other parameters can be shared among motors of the same model or can also be lists of values for each motor, as long as they are consistent with the number of motors described in the `id` parameter.

Several syntaxes are possible for the motor description dictionaries, as long as they contain the required information: id, model, pulley_radius (mm), pulse_center, max_vel (rev/min).

The parameters already include some Dynamixel motor series so you can easily switch by commenting and uncommenting the right line:
```python
4 | #***** (Use only one definition at a time) *****
5 | MY_DXL = 'X_SERIES' # X330 (5.0 V recommended), X430, X540, 2X430
6 | # MY_DXL = 'MX_SERIES' # MX series with 2.0 firmware update.
7 | # MY_DXL = 'PRO_SERIES' # H54, H42, M54, M42, L54, L42
8 | # MY_DXL = 'PRO_A_SERIES' # PRO series with (A) firmware update.
9 | # MY_DXL = 'P_SERIES' # PH54, PH42, PM54
10 | # MY_DXL = 'XL320' # [WARNING] Operating Voltage : 7.4V
# Example of a motor description dictionary for 4 XM430-W210 motors, note that the IDs must be unique for each motor, but the other parameters can be shared among motors of the same model.
{
"id": [0, 1, 2, 3],
"model": "XM430-W210",
"pulley_radius": 20,
"pulse_center": 2048,
"max_vel": 1000,
"baud_rate": 57600
}

# Equivalent to the above, but with the parameters as lists of values for each motor, which can be useful if you have motors of different models or with different configurations.
[
{
"id": [0, 1, 2, 3],
"model": ["XM430-W210"]*4,
"pulley_radius": [20]*4,
"pulse_center": [2048, 2048, 2048, 2048],
"max_vel": [1000]*4,
"baud_rate": [57600]*4
}
]

# Equivalent to the above, but with each motor described as a separate dictionary in the list, which can be useful if you want to have more flexibility in the configuration of each motor.
[
{
"id": 0,
"model": "XM430-W210",
"pulley_radius": 20,
"pulse_center": 2048,
"max_vel": 1000,
"baud_rate": 57600
},
{
"id": 1,
"model": "XM430-W210",
"pulley_radius": 20,
"pulse_center": 2048,
"max_vel": 1000,
"baud_rate": 57600
},
{
"id": 2,
"model": "XM430-W210",
"pulley_radius": 20,
"pulse_center": 2048,
"max_vel": 1000,
"baud_rate": 57600
},
{
"id": 3,
"model": "XM430-W210",
"pulley_radius": 20,
"pulse_center": 2048,
"max_vel": 1000,
"baud_rate": 57600
}
]
```

⚠️ For now, the API assumes that all the motors are the same

You can also create a `DynamixelMotors` object by directly passing a list of `MotorConfig` objects to the constructor.

## For Developers
The documentation is generated using [pydoc-markdown](https://pypi.org/project/pydoc-markdown/). To generate the documentation, you need to install `pydoc-markdown`:
Expand Down
Loading
Loading