├── .gitignore
├── LICENSE
├── assets
├── demo_gx11.gif
├── ex12_urdf.png
└── gx11_urdf.png
├── demo_ex12.py
├── demo_gx11.py
├── libgex
├── config.py
├── dynamixel_sdk
│ ├── __init__.py
│ ├── group_bulk_read.py
│ ├── group_bulk_write.py
│ ├── group_sync_read.py
│ ├── group_sync_write.py
│ ├── packet_handler.py
│ ├── port_handler.py
│ ├── protocol1_packet_handler.py
│ ├── protocol2_packet_handler.py
│ └── robotis_def.py
├── ex12
│ ├── kinematics.py
│ ├── meshes
│ │ ├── Link1.STL
│ │ ├── Link10.STL
│ │ ├── Link11.STL
│ │ ├── Link12.STL
│ │ ├── Link13.STL
│ │ ├── Link14.STL
│ │ ├── Link15.STL
│ │ ├── Link2.STL
│ │ ├── Link3.STL
│ │ ├── Link4.STL
│ │ ├── Link5.STL
│ │ ├── Link6.STL
│ │ ├── Link7.STL
│ │ ├── Link8.STL
│ │ ├── Link9.STL
│ │ └── base_link.STL
│ ├── test_ik.py
│ └── urdf
│ │ └── ex12.urdf
├── gx11
│ ├── kinematics.py
│ ├── meshes
│ │ ├── Link1.STL
│ │ ├── Link10.STL
│ │ ├── Link11.STL
│ │ ├── Link12.STL
│ │ ├── Link13.STL
│ │ ├── Link14.STL
│ │ ├── Link2.STL
│ │ ├── Link3.STL
│ │ ├── Link4.STL
│ │ ├── Link5.STL
│ │ ├── Link6.STL
│ │ ├── Link7.STL
│ │ ├── Link8.STL
│ │ ├── Link9.STL
│ │ └── base_link.STL
│ ├── test_ik.py
│ └── urdf
│ │ ├── finger1.urdf
│ │ └── gx11.urdf
├── libex12.py
├── libgx11.py
├── motor.py
└── utils.py
├── readme.md
└── zero_hand.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # UV
98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | #uv.lock
102 |
103 | # poetry
104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105 | # This is especially recommended for binary packages to ensure reproducibility, and is more
106 | # commonly ignored for libraries.
107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108 | #poetry.lock
109 |
110 | # pdm
111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112 | #pdm.lock
113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114 | # in version control.
115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116 | .pdm.toml
117 | .pdm-python
118 | .pdm-build/
119 |
120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121 | __pypackages__/
122 |
123 | # Celery stuff
124 | celerybeat-schedule
125 | celerybeat.pid
126 |
127 | # SageMath parsed files
128 | *.sage.py
129 |
130 | # Environments
131 | .env
132 | .venv
133 | env/
134 | venv/
135 | ENV/
136 | env.bak/
137 | venv.bak/
138 |
139 | # Spyder project settings
140 | .spyderproject
141 | .spyproject
142 |
143 | # Rope project settings
144 | .ropeproject
145 |
146 | # mkdocs documentation
147 | /site
148 |
149 | # mypy
150 | .mypy_cache/
151 | .dmypy.json
152 | dmypy.json
153 |
154 | # Pyre type checker
155 | .pyre/
156 |
157 | # pytype static type analyzer
158 | .pytype/
159 |
160 | # Cython debug symbols
161 | cython_debug/
162 |
163 | # PyCharm
164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166 | # and can be added to the global gitignore or merged into this file. For a more nuclear
167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168 | #.idea/
169 |
170 | # Ruff stuff:
171 | .ruff_cache/
172 |
173 | # PyPI configuration file
174 | .pypirc
175 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Democratizing Dexterous
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/assets/demo_gx11.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/assets/demo_gx11.gif
--------------------------------------------------------------------------------
/assets/ex12_urdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/assets/ex12_urdf.png
--------------------------------------------------------------------------------
/assets/gx11_urdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/assets/gx11_urdf.png
--------------------------------------------------------------------------------
/demo_ex12.py:
--------------------------------------------------------------------------------
1 | from libgex.libex12 import Glove
2 | from libgex.utils import search_ports
3 | import time
4 |
5 |
6 | port = search_ports()[0]
7 |
8 | glove = Glove('/dev/ttyACM1') # COM* for Windows, ttyACM* or ttyUSB* for Linux
9 | glove.connect(init=False) # do not torque on glove yet.
10 |
11 | print(glove.fk_finger1()) # get the thumb tip xyz position in base_link frame (bottom of the palm), unit m
12 |
--------------------------------------------------------------------------------
/demo_gx11.py:
--------------------------------------------------------------------------------
1 | from libgex.libgx11 import Hand
2 | import time
3 |
4 | hand = Hand(port='/dev/ttyACM0') # COM* for Windows, ttyACM* or ttyUSB* for Linux
5 | hand.connect(goal_pwm=600) # goal_pwm changes the speed, max 855
6 |
7 | hand.home() # home the hand
8 |
9 | time.sleep(1)
10 |
11 | for i in range(10):
12 | # finger1 vs finger2, thumb vs index
13 | finger1_xyz = [0.04, 0.08, 0.12]
14 | finger2_xyz = [0.04, 0.086, 0.12]
15 |
16 | # use inverse kinmetics
17 | hand.setj_finger1(hand.ik_finger1(finger1_xyz))
18 | hand.setj_finger2(hand.ik_finger2(finger2_xyz))
19 |
20 | time.sleep(2)
21 | hand.home()
22 | time.sleep(2)
23 |
24 | # finger1 vs finger3, thumb vs middle
25 | finger1_xyz = [0.01, 0.08, 0.12]
26 | finger3_xyz = [0.01, 0.086, 0.12]
27 |
28 | hand.setj_finger1(hand.ik_finger1(finger1_xyz))
29 | hand.setj_finger3(hand.ik_finger3(finger3_xyz))
30 |
31 | time.sleep(2)
32 | hand.home()
33 | time.sleep(2)
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/libgex/config.py:
--------------------------------------------------------------------------------
1 | PROTOCOL_VERSION = 2
2 | BAUDRATE = 1000000 # 1M bps
--------------------------------------------------------------------------------
/libgex/dynamixel_sdk/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | ################################################################################
5 | # Copyright 2017 ROBOTIS CO., LTD.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ################################################################################
19 |
20 | # Author: Ryu Woon Jung (Leon)
21 |
22 | from .port_handler import *
23 | from .packet_handler import *
24 | from .group_sync_read import *
25 | from .group_sync_write import *
26 | from .group_bulk_read import *
27 | from .group_bulk_write import *
28 |
--------------------------------------------------------------------------------
/libgex/dynamixel_sdk/group_bulk_read.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | ################################################################################
5 | # Copyright 2017 ROBOTIS CO., LTD.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ################################################################################
19 |
20 | # Author: Ryu Woon Jung (Leon)
21 |
22 | from .robotis_def import *
23 |
24 | PARAM_NUM_DATA = 0
25 | PARAM_NUM_ADDRESS = 1
26 | PARAM_NUM_LENGTH = 2
27 |
28 |
29 | class GroupBulkRead:
30 | def __init__(self, port, ph):
31 | self.port = port
32 | self.ph = ph
33 |
34 | self.last_result = False
35 | self.is_param_changed = False
36 | self.param = []
37 | self.data_dict = {}
38 |
39 | self.clearParam()
40 |
41 | def makeParam(self):
42 | if not self.data_dict:
43 | return
44 |
45 | self.param = []
46 |
47 | for dxl_id in self.data_dict:
48 | if self.ph.getProtocolVersion() == 1.0:
49 | self.param.append(self.data_dict[dxl_id][2]) # LEN
50 | self.param.append(dxl_id) # ID
51 | self.param.append(self.data_dict[dxl_id][1]) # ADDR
52 | else:
53 | self.param.append(dxl_id) # ID
54 | self.param.append(DXL_LOBYTE(self.data_dict[dxl_id][1])) # ADDR_L
55 | self.param.append(DXL_HIBYTE(self.data_dict[dxl_id][1])) # ADDR_H
56 | self.param.append(DXL_LOBYTE(self.data_dict[dxl_id][2])) # LEN_L
57 | self.param.append(DXL_HIBYTE(self.data_dict[dxl_id][2])) # LEN_H
58 |
59 | def addParam(self, dxl_id, start_address, data_length):
60 | if dxl_id in self.data_dict: # dxl_id already exist
61 | return False
62 |
63 | data = [] # [0] * data_length
64 | self.data_dict[dxl_id] = [data, start_address, data_length]
65 |
66 | self.is_param_changed = True
67 | return True
68 |
69 | def removeParam(self, dxl_id):
70 | if dxl_id not in self.data_dict: # NOT exist
71 | return
72 |
73 | del self.data_dict[dxl_id]
74 |
75 | self.is_param_changed = True
76 |
77 | def clearParam(self):
78 | self.data_dict.clear()
79 | return
80 |
81 | def txPacket(self):
82 | if len(self.data_dict.keys()) == 0:
83 | return COMM_NOT_AVAILABLE
84 |
85 | if self.is_param_changed is True or not self.param:
86 | self.makeParam()
87 |
88 | if self.ph.getProtocolVersion() == 1.0:
89 | return self.ph.bulkReadTx(self.port, self.param, len(self.data_dict.keys()) * 3)
90 | else:
91 | return self.ph.bulkReadTx(self.port, self.param, len(self.data_dict.keys()) * 5)
92 |
93 | def rxPacket(self):
94 | self.last_result = False
95 |
96 | result = COMM_RX_FAIL
97 |
98 | if len(self.data_dict.keys()) == 0:
99 | return COMM_NOT_AVAILABLE
100 |
101 | for dxl_id in self.data_dict:
102 | self.data_dict[dxl_id][PARAM_NUM_DATA], result, _ = self.ph.readRx(self.port, dxl_id,
103 | self.data_dict[dxl_id][PARAM_NUM_LENGTH])
104 | if result != COMM_SUCCESS:
105 | return result
106 |
107 | if result == COMM_SUCCESS:
108 | self.last_result = True
109 |
110 | return result
111 |
112 | def txRxPacket(self):
113 | result = self.txPacket()
114 | if result != COMM_SUCCESS:
115 | return result
116 |
117 | return self.rxPacket()
118 |
119 | def isAvailable(self, dxl_id, address, data_length):
120 | if self.last_result is False or dxl_id not in self.data_dict:
121 | return False
122 |
123 | start_addr = self.data_dict[dxl_id][PARAM_NUM_ADDRESS]
124 |
125 | if (address < start_addr) or (start_addr + self.data_dict[dxl_id][PARAM_NUM_LENGTH] - data_length < address):
126 | return False
127 |
128 | return True
129 |
130 | def getData(self, dxl_id, address, data_length):
131 | if not self.isAvailable(dxl_id, address, data_length):
132 | return 0
133 |
134 | start_addr = self.data_dict[dxl_id][PARAM_NUM_ADDRESS]
135 |
136 | if data_length == 1:
137 | return self.data_dict[dxl_id][PARAM_NUM_DATA][address - start_addr]
138 | elif data_length == 2:
139 | return DXL_MAKEWORD(self.data_dict[dxl_id][PARAM_NUM_DATA][address - start_addr],
140 | self.data_dict[dxl_id][PARAM_NUM_DATA][address - start_addr + 1])
141 | elif data_length == 4:
142 | return DXL_MAKEDWORD(DXL_MAKEWORD(self.data_dict[dxl_id][PARAM_NUM_DATA][address - start_addr + 0],
143 | self.data_dict[dxl_id][PARAM_NUM_DATA][address - start_addr + 1]),
144 | DXL_MAKEWORD(self.data_dict[dxl_id][PARAM_NUM_DATA][address - start_addr + 2],
145 | self.data_dict[dxl_id][PARAM_NUM_DATA][address - start_addr + 3]))
146 | else:
147 | return 0
148 |
--------------------------------------------------------------------------------
/libgex/dynamixel_sdk/group_bulk_write.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | ################################################################################
5 | # Copyright 2017 ROBOTIS CO., LTD.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ################################################################################
19 |
20 | # Author: Ryu Woon Jung (Leon)
21 |
22 | from .robotis_def import *
23 |
24 |
25 | class GroupBulkWrite:
26 | def __init__(self, port, ph):
27 | self.port = port
28 | self.ph = ph
29 |
30 | self.is_param_changed = False
31 | self.param = []
32 | self.data_list = {}
33 |
34 | self.clearParam()
35 |
36 | def makeParam(self):
37 | if self.ph.getProtocolVersion() == 1.0 or not self.data_list:
38 | return
39 |
40 | self.param = []
41 |
42 | for dxl_id in self.data_list:
43 | if not self.data_list[dxl_id]:
44 | return
45 |
46 | self.param.append(dxl_id)
47 | self.param.append(DXL_LOBYTE(self.data_list[dxl_id][1]))
48 | self.param.append(DXL_HIBYTE(self.data_list[dxl_id][1]))
49 | self.param.append(DXL_LOBYTE(self.data_list[dxl_id][2]))
50 | self.param.append(DXL_HIBYTE(self.data_list[dxl_id][2]))
51 |
52 | self.param.extend(self.data_list[dxl_id][0])
53 |
54 | def addParam(self, dxl_id, start_address, data_length, data):
55 | if self.ph.getProtocolVersion() == 1.0:
56 | return False
57 |
58 | if dxl_id in self.data_list: # dxl_id already exist
59 | return False
60 |
61 | if len(data) > data_length: # input data is longer than set
62 | return False
63 |
64 | self.data_list[dxl_id] = [data, start_address, data_length]
65 |
66 | self.is_param_changed = True
67 | return True
68 |
69 | def removeParam(self, dxl_id):
70 | if self.ph.getProtocolVersion() == 1.0:
71 | return
72 |
73 | if dxl_id not in self.data_list: # NOT exist
74 | return
75 |
76 | del self.data_list[dxl_id]
77 |
78 | self.is_param_changed = True
79 |
80 | def changeParam(self, dxl_id, start_address, data_length, data):
81 | if self.ph.getProtocolVersion() == 1.0:
82 | return False
83 |
84 | if dxl_id not in self.data_list: # NOT exist
85 | return False
86 |
87 | if len(data) > data_length: # input data is longer than set
88 | return False
89 |
90 | self.data_list[dxl_id] = [data, start_address, data_length]
91 |
92 | self.is_param_changed = True
93 | return True
94 |
95 | def clearParam(self):
96 | if self.ph.getProtocolVersion() == 1.0:
97 | return
98 |
99 | self.data_list.clear()
100 | return
101 |
102 | def txPacket(self):
103 | if self.ph.getProtocolVersion() == 1.0 or len(self.data_list.keys()) == 0:
104 | return COMM_NOT_AVAILABLE
105 |
106 | if self.is_param_changed is True or len(self.param) == 0:
107 | self.makeParam()
108 |
109 | return self.ph.bulkWriteTxOnly(self.port, self.param, len(self.param))
110 |
--------------------------------------------------------------------------------
/libgex/dynamixel_sdk/group_sync_read.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | ################################################################################
5 | # Copyright 2017 ROBOTIS CO., LTD.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ################################################################################
19 |
20 | # Author: Ryu Woon Jung (Leon)
21 |
22 | from .robotis_def import *
23 |
24 |
25 | class GroupSyncRead:
26 | def __init__(self, port, ph, start_address, data_length):
27 | self.port = port
28 | self.ph = ph
29 | self.start_address = start_address
30 | self.data_length = data_length
31 |
32 | self.last_result = False
33 | self.is_param_changed = False
34 | self.param = []
35 | self.data_dict = {}
36 |
37 | self.clearParam()
38 |
39 | def makeParam(self):
40 | if self.ph.getProtocolVersion() == 1.0:
41 | return
42 |
43 | if not self.data_dict: # len(self.data_dict.keys()) == 0:
44 | return
45 |
46 | self.param = []
47 |
48 | for dxl_id in self.data_dict:
49 | self.param.append(dxl_id)
50 |
51 | def addParam(self, dxl_id):
52 | if self.ph.getProtocolVersion() == 1.0:
53 | return False
54 |
55 | if dxl_id in self.data_dict: # dxl_id already exist
56 | return False
57 |
58 | self.data_dict[dxl_id] = [] # [0] * self.data_length
59 |
60 | self.is_param_changed = True
61 | return True
62 |
63 | def removeParam(self, dxl_id):
64 | if self.ph.getProtocolVersion() == 1.0:
65 | return
66 |
67 | if dxl_id not in self.data_dict: # NOT exist
68 | return
69 |
70 | del self.data_dict[dxl_id]
71 |
72 | self.is_param_changed = True
73 |
74 | def clearParam(self):
75 | if self.ph.getProtocolVersion() == 1.0:
76 | return
77 |
78 | self.data_dict.clear()
79 |
80 | def txPacket(self):
81 | if self.ph.getProtocolVersion() == 1.0 or len(self.data_dict.keys()) == 0:
82 | return COMM_NOT_AVAILABLE
83 |
84 | if self.is_param_changed is True or not self.param:
85 | self.makeParam()
86 |
87 | return self.ph.syncReadTx(self.port, self.start_address, self.data_length, self.param,
88 | len(self.data_dict.keys()) * 1)
89 |
90 | def rxPacket(self):
91 | self.last_result = False
92 |
93 | if self.ph.getProtocolVersion() == 1.0:
94 | return COMM_NOT_AVAILABLE
95 |
96 | result = COMM_RX_FAIL
97 |
98 | if len(self.data_dict.keys()) == 0:
99 | return COMM_NOT_AVAILABLE
100 |
101 | for dxl_id in self.data_dict:
102 | self.data_dict[dxl_id], result, _ = self.ph.readRx(self.port, dxl_id, self.data_length)
103 | if result != COMM_SUCCESS:
104 | return result
105 |
106 | if result == COMM_SUCCESS:
107 | self.last_result = True
108 |
109 | return result
110 |
111 | def txRxPacket(self):
112 | if self.ph.getProtocolVersion() == 1.0:
113 | return COMM_NOT_AVAILABLE
114 |
115 | result = self.txPacket()
116 | if result != COMM_SUCCESS:
117 | return result
118 |
119 | return self.rxPacket()
120 |
121 | def isAvailable(self, dxl_id, address, data_length):
122 | if self.ph.getProtocolVersion() == 1.0 or self.last_result is False or dxl_id not in self.data_dict:
123 | return False
124 |
125 | if (address < self.start_address) or (self.start_address + self.data_length - data_length < address):
126 | return False
127 |
128 | return True
129 |
130 | def getData(self, dxl_id, address, data_length):
131 | if not self.isAvailable(dxl_id, address, data_length):
132 | return 0
133 |
134 | if data_length == 1:
135 | return self.data_dict[dxl_id][address - self.start_address]
136 | elif data_length == 2:
137 | return DXL_MAKEWORD(self.data_dict[dxl_id][address - self.start_address],
138 | self.data_dict[dxl_id][address - self.start_address + 1])
139 | elif data_length == 4:
140 | return DXL_MAKEDWORD(DXL_MAKEWORD(self.data_dict[dxl_id][address - self.start_address + 0],
141 | self.data_dict[dxl_id][address - self.start_address + 1]),
142 | DXL_MAKEWORD(self.data_dict[dxl_id][address - self.start_address + 2],
143 | self.data_dict[dxl_id][address - self.start_address + 3]))
144 | else:
145 | return 0
146 |
--------------------------------------------------------------------------------
/libgex/dynamixel_sdk/group_sync_write.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | ################################################################################
5 | # Copyright 2017 ROBOTIS CO., LTD.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ################################################################################
19 |
20 | # Author: Ryu Woon Jung (Leon)
21 |
22 | from .robotis_def import *
23 |
24 |
25 | class GroupSyncWrite:
26 | def __init__(self, port, ph, start_address, data_length):
27 | self.port = port
28 | self.ph = ph
29 | self.start_address = start_address
30 | self.data_length = data_length
31 |
32 | self.is_param_changed = False
33 | self.param = []
34 | self.data_dict = {}
35 |
36 | self.clearParam()
37 |
38 | def makeParam(self):
39 | if not self.data_dict:
40 | return
41 |
42 | self.param = []
43 |
44 | for dxl_id in self.data_dict:
45 | if not self.data_dict[dxl_id]:
46 | return
47 |
48 | self.param.append(dxl_id)
49 | self.param.extend(self.data_dict[dxl_id])
50 |
51 | def addParam(self, dxl_id, data):
52 | if dxl_id in self.data_dict: # dxl_id already exist
53 | return False
54 |
55 | if len(data) > self.data_length: # input data is longer than set
56 | return False
57 |
58 | self.data_dict[dxl_id] = data
59 |
60 | self.is_param_changed = True
61 | return True
62 |
63 | def removeParam(self, dxl_id):
64 | if dxl_id not in self.data_dict: # NOT exist
65 | return
66 |
67 | del self.data_dict[dxl_id]
68 |
69 | self.is_param_changed = True
70 |
71 | def changeParam(self, dxl_id, data):
72 | if dxl_id not in self.data_dict: # NOT exist
73 | return False
74 |
75 | if len(data) > self.data_length: # input data is longer than set
76 | return False
77 |
78 | self.data_dict[dxl_id] = data
79 |
80 | self.is_param_changed = True
81 | return True
82 |
83 | def clearParam(self):
84 | self.data_dict.clear()
85 |
86 | def txPacket(self):
87 | if len(self.data_dict.keys()) == 0:
88 | return COMM_NOT_AVAILABLE
89 |
90 | if self.is_param_changed is True or not self.param:
91 | self.makeParam()
92 |
93 | return self.ph.syncWriteTxOnly(self.port, self.start_address, self.data_length, self.param,
94 | len(self.data_dict.keys()) * (1 + self.data_length))
95 |
--------------------------------------------------------------------------------
/libgex/dynamixel_sdk/packet_handler.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | ################################################################################
5 | # Copyright 2017 ROBOTIS CO., LTD.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ################################################################################
19 |
20 | # Author: Ryu Woon Jung (Leon)
21 |
22 | from .protocol1_packet_handler import *
23 | from .protocol2_packet_handler import *
24 |
25 |
26 | def PacketHandler(protocol_version):
27 | # FIXME: float or int-to-float comparison can generate weird behaviour
28 | if protocol_version == 1.0:
29 | return Protocol1PacketHandler()
30 | elif protocol_version == 2.0:
31 | return Protocol2PacketHandler()
32 | else:
33 | return Protocol2PacketHandler()
34 |
--------------------------------------------------------------------------------
/libgex/dynamixel_sdk/port_handler.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | ################################################################################
5 | # Copyright 2017 ROBOTIS CO., LTD.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ################################################################################
19 |
20 | # Author: Ryu Woon Jung (Leon)
21 |
22 | import time
23 | import serial
24 | import sys
25 | import platform
26 |
27 | LATENCY_TIMER = 16
28 | DEFAULT_BAUDRATE = 1000000
29 |
30 |
31 | class PortHandler(object):
32 | def __init__(self, port_name):
33 | self.is_open = False
34 | self.baudrate = DEFAULT_BAUDRATE
35 | self.packet_start_time = 0.0
36 | self.packet_timeout = 0.0
37 | self.tx_time_per_byte = 0.0
38 |
39 | self.is_using = False
40 | self.port_name = port_name
41 | self.ser = None
42 |
43 | def openPort(self):
44 | return self.setBaudRate(self.baudrate)
45 |
46 | def closePort(self):
47 | self.ser.close()
48 | self.is_open = False
49 |
50 | def clearPort(self):
51 | self.ser.flush()
52 |
53 | def setPortName(self, port_name):
54 | self.port_name = port_name
55 |
56 | def getPortName(self):
57 | return self.port_name
58 |
59 | def setBaudRate(self, baudrate):
60 | baud = self.getCFlagBaud(baudrate)
61 |
62 | if baud <= 0:
63 | # self.setupPort(38400)
64 | # self.baudrate = baudrate
65 | return False # TODO: setCustomBaudrate(baudrate)
66 | else:
67 | self.baudrate = baudrate
68 | return self.setupPort(baud)
69 |
70 | def getBaudRate(self):
71 | return self.baudrate
72 |
73 | def getBytesAvailable(self):
74 | return self.ser.in_waiting
75 |
76 | def readPort(self, length):
77 | if (sys.version_info > (3, 0)):
78 | return self.ser.read(length)
79 | else:
80 | return [ord(ch) for ch in self.ser.read(length)]
81 |
82 | def writePort(self, packet):
83 | return self.ser.write(packet)
84 |
85 | def setPacketTimeout(self, packet_length):
86 | self.packet_start_time = self.getCurrentTime()
87 | self.packet_timeout = (self.tx_time_per_byte * packet_length) + (LATENCY_TIMER * 2.0) + 2.0
88 |
89 | def setPacketTimeoutMillis(self, msec):
90 | self.packet_start_time = self.getCurrentTime()
91 | self.packet_timeout = msec
92 |
93 | def isPacketTimeout(self):
94 | if self.getTimeSinceStart() > self.packet_timeout:
95 | self.packet_timeout = 0
96 | return True
97 |
98 | return False
99 |
100 | def getCurrentTime(self):
101 | return round(time.time() * 1000000000) / 1000000.0
102 |
103 | def getTimeSinceStart(self):
104 | time_since = self.getCurrentTime() - self.packet_start_time
105 | if time_since < 0.0:
106 | self.packet_start_time = self.getCurrentTime()
107 |
108 | return time_since
109 |
110 | def setupPort(self, cflag_baud):
111 | if self.is_open:
112 | self.closePort()
113 |
114 | self.ser = serial.Serial(
115 | port=self.port_name,
116 | baudrate=self.baudrate,
117 | # parity = serial.PARITY_ODD,
118 | # stopbits = serial.STOPBITS_TWO,
119 | bytesize=serial.EIGHTBITS,
120 | timeout=0
121 | )
122 |
123 | self.is_open = True
124 |
125 | self.ser.reset_input_buffer()
126 |
127 | self.tx_time_per_byte = (1000.0 / self.baudrate) * 10.0
128 |
129 | return True
130 |
131 | def getCFlagBaud(self, baudrate):
132 | if baudrate in [9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000,
133 | 2000000, 2500000, 3000000, 3500000, 4000000]:
134 | return baudrate
135 | else:
136 | return -1
137 |
--------------------------------------------------------------------------------
/libgex/dynamixel_sdk/protocol1_packet_handler.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | ################################################################################
5 | # Copyright 2017 ROBOTIS CO., LTD.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ################################################################################
19 |
20 | # Author: Ryu Woon Jung (Leon)
21 |
22 | from .robotis_def import *
23 |
24 | TXPACKET_MAX_LEN = 250
25 | RXPACKET_MAX_LEN = 250
26 |
27 | # for Protocol 1.0 Packet
28 | PKT_HEADER0 = 0
29 | PKT_HEADER1 = 1
30 | PKT_ID = 2
31 | PKT_LENGTH = 3
32 | PKT_INSTRUCTION = 4
33 | PKT_ERROR = 4
34 | PKT_PARAMETER0 = 5
35 |
36 | # Protocol 1.0 Error bit
37 | ERRBIT_VOLTAGE = 1 # Supplied voltage is out of the range (operating volatage set in the control table)
38 | ERRBIT_ANGLE = 2 # Goal position is written out of the range (from CW angle limit to CCW angle limit)
39 | ERRBIT_OVERHEAT = 4 # Temperature is out of the range (operating temperature set in the control table)
40 | ERRBIT_RANGE = 8 # Command(setting value) is out of the range for use.
41 | ERRBIT_CHECKSUM = 16 # Instruction packet checksum is incorrect.
42 | ERRBIT_OVERLOAD = 32 # The current load cannot be controlled by the set torque.
43 | ERRBIT_INSTRUCTION = 64 # Undefined instruction or delivering the action command without the reg_write command.
44 |
45 |
46 | class Protocol1PacketHandler(object):
47 | def getProtocolVersion(self):
48 | return 1.0
49 |
50 | def getTxRxResult(self, result):
51 | if result == COMM_SUCCESS:
52 | return "[TxRxResult] Communication success!"
53 | elif result == COMM_PORT_BUSY:
54 | return "[TxRxResult] Port is in use!"
55 | elif result == COMM_TX_FAIL:
56 | return "[TxRxResult] Failed transmit instruction packet!"
57 | elif result == COMM_RX_FAIL:
58 | return "[TxRxResult] Failed get status packet from device!"
59 | elif result == COMM_TX_ERROR:
60 | return "[TxRxResult] Incorrect instruction packet!"
61 | elif result == COMM_RX_WAITING:
62 | return "[TxRxResult] Now receiving status packet!"
63 | elif result == COMM_RX_TIMEOUT:
64 | return "[TxRxResult] There is no status packet!"
65 | elif result == COMM_RX_CORRUPT:
66 | return "[TxRxResult] Incorrect status packet!"
67 | elif result == COMM_NOT_AVAILABLE:
68 | return "[TxRxResult] Protocol does not support this function!"
69 | else:
70 | return ""
71 |
72 | def getRxPacketError(self, error):
73 | if error & ERRBIT_VOLTAGE:
74 | return "[RxPacketError] Input voltage error!"
75 |
76 | if error & ERRBIT_ANGLE:
77 | return "[RxPacketError] Angle limit error!"
78 |
79 | if error & ERRBIT_OVERHEAT:
80 | return "[RxPacketError] Overheat error!"
81 |
82 | if error & ERRBIT_RANGE:
83 | return "[RxPacketError] Out of range error!"
84 |
85 | if error & ERRBIT_CHECKSUM:
86 | return "[RxPacketError] Checksum error!"
87 |
88 | if error & ERRBIT_OVERLOAD:
89 | return "[RxPacketError] Overload error!"
90 |
91 | if error & ERRBIT_INSTRUCTION:
92 | return "[RxPacketError] Instruction code error!"
93 |
94 | return ""
95 |
96 | def txPacket(self, port, txpacket):
97 | checksum = 0
98 | total_packet_length = txpacket[PKT_LENGTH] + 4 # 4: HEADER0 HEADER1 ID LENGTH
99 |
100 | if port.is_using:
101 | return COMM_PORT_BUSY
102 | port.is_using = True
103 |
104 | # check max packet length
105 | if total_packet_length > TXPACKET_MAX_LEN:
106 | port.is_using = False
107 | return COMM_TX_ERROR
108 |
109 | # make packet header
110 | txpacket[PKT_HEADER0] = 0xFF
111 | txpacket[PKT_HEADER1] = 0xFF
112 |
113 | # add a checksum to the packet
114 | for idx in range(2, total_packet_length - 1): # except header, checksum
115 | checksum += txpacket[idx]
116 |
117 | txpacket[total_packet_length - 1] = ~checksum & 0xFF
118 |
119 | #print "[TxPacket] %r" % txpacket
120 |
121 | # tx packet
122 | port.clearPort()
123 | written_packet_length = port.writePort(txpacket)
124 | if total_packet_length != written_packet_length:
125 | port.is_using = False
126 | return COMM_TX_FAIL
127 |
128 | return COMM_SUCCESS
129 |
130 | def rxPacket(self, port):
131 | rxpacket = []
132 |
133 | result = COMM_TX_FAIL
134 | checksum = 0
135 | rx_length = 0
136 | wait_length = 6 # minimum length (HEADER0 HEADER1 ID LENGTH ERROR CHKSUM)
137 |
138 | while True:
139 | rxpacket.extend(port.readPort(wait_length - rx_length))
140 | rx_length = len(rxpacket)
141 | if rx_length >= wait_length:
142 | # find packet header
143 | for idx in range(0, (rx_length - 1)):
144 | if (rxpacket[idx] == 0xFF) and (rxpacket[idx + 1] == 0xFF):
145 | break
146 |
147 | if idx == 0: # found at the beginning of the packet
148 | if (rxpacket[PKT_ID] > 0xFD) or (rxpacket[PKT_LENGTH] > RXPACKET_MAX_LEN) or (
149 | rxpacket[PKT_ERROR] > 0x7F):
150 | # unavailable ID or unavailable Length or unavailable Error
151 | # remove the first byte in the packet
152 | del rxpacket[0]
153 | rx_length -= 1
154 | continue
155 |
156 | # re-calculate the exact length of the rx packet
157 | if wait_length != (rxpacket[PKT_LENGTH] + PKT_LENGTH + 1):
158 | wait_length = rxpacket[PKT_LENGTH] + PKT_LENGTH + 1
159 | continue
160 |
161 | if rx_length < wait_length:
162 | # check timeout
163 | if port.isPacketTimeout():
164 | if rx_length == 0:
165 | result = COMM_RX_TIMEOUT
166 | else:
167 | result = COMM_RX_CORRUPT
168 | break
169 | else:
170 | continue
171 |
172 | # calculate checksum
173 | for i in range(2, wait_length - 1): # except header, checksum
174 | checksum += rxpacket[i]
175 | checksum = ~checksum & 0xFF
176 |
177 | # verify checksum
178 | if rxpacket[wait_length - 1] == checksum:
179 | result = COMM_SUCCESS
180 | else:
181 | result = COMM_RX_CORRUPT
182 | break
183 |
184 | else:
185 | # remove unnecessary packets
186 | del rxpacket[0: idx]
187 | rx_length -= idx
188 |
189 | else:
190 | # check timeout
191 | if port.isPacketTimeout():
192 | if rx_length == 0:
193 | result = COMM_RX_TIMEOUT
194 | else:
195 | result = COMM_RX_CORRUPT
196 | break
197 |
198 | port.is_using = False
199 |
200 | #print "[RxPacket] %r" % rxpacket
201 |
202 | return rxpacket, result
203 |
204 | # NOT for BulkRead
205 | def txRxPacket(self, port, txpacket):
206 | rxpacket = None
207 | error = 0
208 |
209 | # tx packet
210 | result = self.txPacket(port, txpacket)
211 | if result != COMM_SUCCESS:
212 | return rxpacket, result, error
213 |
214 | # (Instruction == BulkRead) == this function is not available.
215 | if txpacket[PKT_INSTRUCTION] == INST_BULK_READ:
216 | result = COMM_NOT_AVAILABLE
217 |
218 | # (ID == Broadcast ID) == no need to wait for status packet or not available
219 | if (txpacket[PKT_ID] == BROADCAST_ID):
220 | port.is_using = False
221 | return rxpacket, result, error
222 |
223 | # set packet timeout
224 | if txpacket[PKT_INSTRUCTION] == INST_READ:
225 | port.setPacketTimeout(txpacket[PKT_PARAMETER0 + 1] + 6)
226 | else:
227 | port.setPacketTimeout(6) # HEADER0 HEADER1 ID LENGTH ERROR CHECKSUM
228 |
229 | # rx packet
230 | while True:
231 | rxpacket, result = self.rxPacket(port)
232 | if result != COMM_SUCCESS or txpacket[PKT_ID] == rxpacket[PKT_ID]:
233 | break
234 |
235 | if result == COMM_SUCCESS and txpacket[PKT_ID] == rxpacket[PKT_ID]:
236 | error = rxpacket[PKT_ERROR]
237 |
238 | return rxpacket, result, error
239 |
240 | def ping(self, port, dxl_id):
241 | model_number = 0
242 | error = 0
243 |
244 | txpacket = [0] * 6
245 |
246 | if dxl_id >= BROADCAST_ID:
247 | return model_number, COMM_NOT_AVAILABLE, error
248 |
249 | txpacket[PKT_ID] = dxl_id
250 | txpacket[PKT_LENGTH] = 2
251 | txpacket[PKT_INSTRUCTION] = INST_PING
252 |
253 | rxpacket, result, error = self.txRxPacket(port, txpacket)
254 |
255 | if result == COMM_SUCCESS:
256 | data_read, result, error = self.readTxRx(port, dxl_id, 0, 2) # Address 0 : Model Number
257 | if result == COMM_SUCCESS:
258 | model_number = DXL_MAKEWORD(data_read[0], data_read[1])
259 |
260 | return model_number, result, error
261 |
262 | def broadcastPing(self, port):
263 | data_list = None
264 | return data_list, COMM_NOT_AVAILABLE
265 |
266 | def action(self, port, dxl_id):
267 | txpacket = [0] * 6
268 |
269 | txpacket[PKT_ID] = dxl_id
270 | txpacket[PKT_LENGTH] = 2
271 | txpacket[PKT_INSTRUCTION] = INST_ACTION
272 |
273 | _, result, _ = self.txRxPacket(port, txpacket)
274 |
275 | return result
276 |
277 | def reboot(self, port, dxl_id):
278 | return COMM_NOT_AVAILABLE, 0
279 |
280 | def factoryReset(self, port, dxl_id):
281 | txpacket = [0] * 6
282 |
283 | txpacket[PKT_ID] = dxl_id
284 | txpacket[PKT_LENGTH] = 2
285 | txpacket[PKT_INSTRUCTION] = INST_FACTORY_RESET
286 |
287 | _, result, error = self.txRxPacket(port, txpacket)
288 |
289 | return result, error
290 |
291 | def readTx(self, port, dxl_id, address, length):
292 |
293 | txpacket = [0] * 8
294 |
295 | if dxl_id >= BROADCAST_ID:
296 | return COMM_NOT_AVAILABLE
297 |
298 | txpacket[PKT_ID] = dxl_id
299 | txpacket[PKT_LENGTH] = 4
300 | txpacket[PKT_INSTRUCTION] = INST_READ
301 | txpacket[PKT_PARAMETER0 + 0] = address
302 | txpacket[PKT_PARAMETER0 + 1] = length
303 |
304 | result = self.txPacket(port, txpacket)
305 |
306 | # set packet timeout
307 | if result == COMM_SUCCESS:
308 | port.setPacketTimeout(length + 6)
309 |
310 | return result
311 |
312 | def readRx(self, port, dxl_id, length):
313 | result = COMM_TX_FAIL
314 | error = 0
315 |
316 | rxpacket = None
317 | data = []
318 |
319 | while True:
320 | rxpacket, result = self.rxPacket(port)
321 |
322 | if result != COMM_SUCCESS or rxpacket[PKT_ID] == dxl_id:
323 | break
324 |
325 | if result == COMM_SUCCESS and rxpacket[PKT_ID] == dxl_id:
326 | error = rxpacket[PKT_ERROR]
327 |
328 | data.extend(rxpacket[PKT_PARAMETER0: PKT_PARAMETER0 + length])
329 |
330 | return data, result, error
331 |
332 | def readTxRx(self, port, dxl_id, address, length):
333 | txpacket = [0] * 8
334 | data = []
335 |
336 | if dxl_id >= BROADCAST_ID:
337 | return data, COMM_NOT_AVAILABLE, 0
338 |
339 | txpacket[PKT_ID] = dxl_id
340 | txpacket[PKT_LENGTH] = 4
341 | txpacket[PKT_INSTRUCTION] = INST_READ
342 | txpacket[PKT_PARAMETER0 + 0] = address
343 | txpacket[PKT_PARAMETER0 + 1] = length
344 |
345 | rxpacket, result, error = self.txRxPacket(port, txpacket)
346 | if result == COMM_SUCCESS:
347 | error = rxpacket[PKT_ERROR]
348 |
349 | data.extend(rxpacket[PKT_PARAMETER0: PKT_PARAMETER0 + length])
350 |
351 | return data, result, error
352 |
353 | def read1ByteTx(self, port, dxl_id, address):
354 | return self.readTx(port, dxl_id, address, 1)
355 |
356 | def read1ByteRx(self, port, dxl_id):
357 | data, result, error = self.readRx(port, dxl_id, 1)
358 | data_read = data[0] if (result == COMM_SUCCESS) else 0
359 | return data_read, result, error
360 |
361 | def read1ByteTxRx(self, port, dxl_id, address):
362 | data, result, error = self.readTxRx(port, dxl_id, address, 1)
363 | data_read = data[0] if (result == COMM_SUCCESS) else 0
364 | return data_read, result, error
365 |
366 | def read2ByteTx(self, port, dxl_id, address):
367 | return self.readTx(port, dxl_id, address, 2)
368 |
369 | def read2ByteRx(self, port, dxl_id):
370 | data, result, error = self.readRx(port, dxl_id, 2)
371 | data_read = DXL_MAKEWORD(data[0], data[1]) if (result == COMM_SUCCESS) else 0
372 | return data_read, result, error
373 |
374 | def read2ByteTxRx(self, port, dxl_id, address):
375 | data, result, error = self.readTxRx(port, dxl_id, address, 2)
376 | data_read = DXL_MAKEWORD(data[0], data[1]) if (result == COMM_SUCCESS) else 0
377 | return data_read, result, error
378 |
379 | def read4ByteTx(self, port, dxl_id, address):
380 | return self.readTx(port, dxl_id, address, 4)
381 |
382 | def read4ByteRx(self, port, dxl_id):
383 | data, result, error = self.readRx(port, dxl_id, 4)
384 | data_read = DXL_MAKEDWORD(DXL_MAKEWORD(data[0], data[1]),
385 | DXL_MAKEWORD(data[2], data[3])) if (result == COMM_SUCCESS) else 0
386 | return data_read, result, error
387 |
388 | def read4ByteTxRx(self, port, dxl_id, address):
389 | data, result, error = self.readTxRx(port, dxl_id, address, 4)
390 | data_read = DXL_MAKEDWORD(DXL_MAKEWORD(data[0], data[1]),
391 | DXL_MAKEWORD(data[2], data[3])) if (result == COMM_SUCCESS) else 0
392 | return data_read, result, error
393 |
394 | def writeTxOnly(self, port, dxl_id, address, length, data):
395 | txpacket = [0] * (length + 7)
396 |
397 | txpacket[PKT_ID] = dxl_id
398 | txpacket[PKT_LENGTH] = length + 3
399 | txpacket[PKT_INSTRUCTION] = INST_WRITE
400 | txpacket[PKT_PARAMETER0] = address
401 |
402 | txpacket[PKT_PARAMETER0 + 1: PKT_PARAMETER0 + 1 + length] = data[0: length]
403 |
404 | result = self.txPacket(port, txpacket)
405 | port.is_using = False
406 |
407 | return result
408 |
409 | def writeTxRx(self, port, dxl_id, address, length, data):
410 | txpacket = [0] * (length + 7)
411 |
412 | txpacket[PKT_ID] = dxl_id
413 | txpacket[PKT_LENGTH] = length + 3
414 | txpacket[PKT_INSTRUCTION] = INST_WRITE
415 | txpacket[PKT_PARAMETER0] = address
416 |
417 | txpacket[PKT_PARAMETER0 + 1: PKT_PARAMETER0 + 1 + length] = data[0: length]
418 | rxpacket, result, error = self.txRxPacket(port, txpacket)
419 |
420 | return result, error
421 |
422 | def write1ByteTxOnly(self, port, dxl_id, address, data):
423 | data_write = [data]
424 | return self.writeTxOnly(port, dxl_id, address, 1, data_write)
425 |
426 | def write1ByteTxRx(self, port, dxl_id, address, data):
427 | data_write = [data]
428 | return self.writeTxRx(port, dxl_id, address, 1, data_write)
429 |
430 | def write2ByteTxOnly(self, port, dxl_id, address, data):
431 | data_write = [DXL_LOBYTE(data), DXL_HIBYTE(data)]
432 | return self.writeTxOnly(port, dxl_id, address, 2, data_write)
433 |
434 | def write2ByteTxRx(self, port, dxl_id, address, data):
435 | data_write = [DXL_LOBYTE(data), DXL_HIBYTE(data)]
436 | return self.writeTxRx(port, dxl_id, address, 2, data_write)
437 |
438 | def write4ByteTxOnly(self, port, dxl_id, address, data):
439 | data_write = [DXL_LOBYTE(DXL_LOWORD(data)),
440 | DXL_HIBYTE(DXL_LOWORD(data)),
441 | DXL_LOBYTE(DXL_HIWORD(data)),
442 | DXL_HIBYTE(DXL_HIWORD(data))]
443 | return self.writeTxOnly(port, dxl_id, address, 4, data_write)
444 |
445 | def write4ByteTxRx(self, port, dxl_id, address, data):
446 | data_write = [DXL_LOBYTE(DXL_LOWORD(data)),
447 | DXL_HIBYTE(DXL_LOWORD(data)),
448 | DXL_LOBYTE(DXL_HIWORD(data)),
449 | DXL_HIBYTE(DXL_HIWORD(data))]
450 | return self.writeTxRx(port, dxl_id, address, 4, data_write)
451 |
452 | def regWriteTxOnly(self, port, dxl_id, address, length, data):
453 | txpacket = [0] * (length + 7)
454 |
455 | txpacket[PKT_ID] = dxl_id
456 | txpacket[PKT_LENGTH] = length + 3
457 | txpacket[PKT_INSTRUCTION] = INST_REG_WRITE
458 | txpacket[PKT_PARAMETER0] = address
459 |
460 | txpacket[PKT_PARAMETER0 + 1: PKT_PARAMETER0 + 1 + length] = data[0: length]
461 |
462 | result = self.txPacket(port, txpacket)
463 | port.is_using = False
464 |
465 | return result
466 |
467 | def regWriteTxRx(self, port, dxl_id, address, length, data):
468 | txpacket = [0] * (length + 7)
469 |
470 | txpacket[PKT_ID] = dxl_id
471 | txpacket[PKT_LENGTH] = length + 3
472 | txpacket[PKT_INSTRUCTION] = INST_REG_WRITE
473 | txpacket[PKT_PARAMETER0] = address
474 |
475 | txpacket[PKT_PARAMETER0 + 1: PKT_PARAMETER0 + 1 + length] = data[0: length]
476 |
477 | _, result, error = self.txRxPacket(port, txpacket)
478 |
479 | return result, error
480 |
481 | def syncReadTx(self, port, start_address, data_length, param, param_length):
482 | return COMM_NOT_AVAILABLE
483 |
484 | def syncWriteTxOnly(self, port, start_address, data_length, param, param_length):
485 | txpacket = [0] * (param_length + 8)
486 | # 8: HEADER0 HEADER1 ID LEN INST START_ADDR DATA_LEN ... CHKSUM
487 |
488 | txpacket[PKT_ID] = BROADCAST_ID
489 | txpacket[PKT_LENGTH] = param_length + 4 # 4: INST START_ADDR DATA_LEN ... CHKSUM
490 | txpacket[PKT_INSTRUCTION] = INST_SYNC_WRITE
491 | txpacket[PKT_PARAMETER0 + 0] = start_address
492 | txpacket[PKT_PARAMETER0 + 1] = data_length
493 |
494 | txpacket[PKT_PARAMETER0 + 2: PKT_PARAMETER0 + 2 + param_length] = param[0: param_length]
495 |
496 | _, result, _ = self.txRxPacket(port, txpacket)
497 |
498 | return result
499 |
500 | def bulkReadTx(self, port, param, param_length):
501 | txpacket = [0] * (param_length + 7)
502 | # 7: HEADER0 HEADER1 ID LEN INST 0x00 ... CHKSUM
503 |
504 | txpacket[PKT_ID] = BROADCAST_ID
505 | txpacket[PKT_LENGTH] = param_length + 3 # 3: INST 0x00 ... CHKSUM
506 | txpacket[PKT_INSTRUCTION] = INST_BULK_READ
507 | txpacket[PKT_PARAMETER0 + 0] = 0x00
508 |
509 | txpacket[PKT_PARAMETER0 + 1: PKT_PARAMETER0 + 1 + param_length] = param[0: param_length]
510 |
511 | result = self.txPacket(port, txpacket)
512 | if result == COMM_SUCCESS:
513 | wait_length = 0
514 | i = 0
515 | while i < param_length:
516 | wait_length += param[i] + 7
517 | i += 3
518 | port.setPacketTimeout(wait_length)
519 |
520 | return result
521 |
522 | def bulkWriteTxOnly(self, port, param, param_length):
523 | return COMM_NOT_AVAILABLE
524 |
--------------------------------------------------------------------------------
/libgex/dynamixel_sdk/protocol2_packet_handler.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | ################################################################################
5 | # Copyright 2017 ROBOTIS CO., LTD.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ################################################################################
19 |
20 | # Author: Ryu Woon Jung (Leon)
21 |
22 | from .robotis_def import *
23 |
24 | TXPACKET_MAX_LEN = 1 * 1024
25 | RXPACKET_MAX_LEN = 1 * 1024
26 |
27 | # for Protocol 2.0 Packet
28 | PKT_HEADER0 = 0
29 | PKT_HEADER1 = 1
30 | PKT_HEADER2 = 2
31 | PKT_RESERVED = 3
32 | PKT_ID = 4
33 | PKT_LENGTH_L = 5
34 | PKT_LENGTH_H = 6
35 | PKT_INSTRUCTION = 7
36 | PKT_ERROR = 8
37 | PKT_PARAMETER0 = 8
38 |
39 | # Protocol 2.0 Error bit
40 | ERRNUM_RESULT_FAIL = 1 # Failed to process the instruction packet.
41 | ERRNUM_INSTRUCTION = 2 # Instruction error
42 | ERRNUM_CRC = 3 # CRC check error
43 | ERRNUM_DATA_RANGE = 4 # Data range error
44 | ERRNUM_DATA_LENGTH = 5 # Data length error
45 | ERRNUM_DATA_LIMIT = 6 # Data limit error
46 | ERRNUM_ACCESS = 7 # Access error
47 |
48 | ERRBIT_ALERT = 128 # When the device has a problem, this bit is set to 1. Check "Device Status Check" value.
49 |
50 |
51 | class Protocol2PacketHandler(object):
52 | def getProtocolVersion(self):
53 | return 2.0
54 |
55 | def getTxRxResult(self, result):
56 | if result == COMM_SUCCESS:
57 | return "[TxRxResult] Communication success!"
58 | elif result == COMM_PORT_BUSY:
59 | return "[TxRxResult] Port is in use!"
60 | elif result == COMM_TX_FAIL:
61 | return "[TxRxResult] Failed transmit instruction packet!"
62 | elif result == COMM_RX_FAIL:
63 | return "[TxRxResult] Failed get status packet from device!"
64 | elif result == COMM_TX_ERROR:
65 | return "[TxRxResult] Incorrect instruction packet!"
66 | elif result == COMM_RX_WAITING:
67 | return "[TxRxResult] Now receiving status packet!"
68 | elif result == COMM_RX_TIMEOUT:
69 | return "[TxRxResult] There is no status packet!"
70 | elif result == COMM_RX_CORRUPT:
71 | return "[TxRxResult] Incorrect status packet!"
72 | elif result == COMM_NOT_AVAILABLE:
73 | return "[TxRxResult] Protocol does not support this function!"
74 | else:
75 | return ""
76 |
77 | def getRxPacketError(self, error):
78 | if error & ERRBIT_ALERT:
79 | return "[RxPacketError] Hardware error occurred. Check the error at Control Table (Hardware Error Status)!"
80 |
81 | not_alert_error = error & ~ERRBIT_ALERT
82 | if not_alert_error == 0:
83 | return ""
84 | elif not_alert_error == ERRNUM_RESULT_FAIL:
85 | return "[RxPacketError] Failed to process the instruction packet!"
86 |
87 | elif not_alert_error == ERRNUM_INSTRUCTION:
88 | return "[RxPacketError] Undefined instruction or incorrect instruction!"
89 |
90 | elif not_alert_error == ERRNUM_CRC:
91 | return "[RxPacketError] CRC doesn't match!"
92 |
93 | elif not_alert_error == ERRNUM_DATA_RANGE:
94 | return "[RxPacketError] The data value is out of range!"
95 |
96 | elif not_alert_error == ERRNUM_DATA_LENGTH:
97 | return "[RxPacketError] The data length does not match as expected!"
98 |
99 | elif not_alert_error == ERRNUM_DATA_LIMIT:
100 | return "[RxPacketError] The data value exceeds the limit value!"
101 |
102 | elif not_alert_error == ERRNUM_ACCESS:
103 | return "[RxPacketError] Writing or Reading is not available to target address!"
104 |
105 | else:
106 | return "[RxPacketError] Unknown error code!"
107 |
108 | def updateCRC(self, crc_accum, data_blk_ptr, data_blk_size):
109 | crc_table = [0x0000,
110 | 0x8005, 0x800F, 0x000A, 0x801B, 0x001E, 0x0014, 0x8011,
111 | 0x8033, 0x0036, 0x003C, 0x8039, 0x0028, 0x802D, 0x8027,
112 | 0x0022, 0x8063, 0x0066, 0x006C, 0x8069, 0x0078, 0x807D,
113 | 0x8077, 0x0072, 0x0050, 0x8055, 0x805F, 0x005A, 0x804B,
114 | 0x004E, 0x0044, 0x8041, 0x80C3, 0x00C6, 0x00CC, 0x80C9,
115 | 0x00D8, 0x80DD, 0x80D7, 0x00D2, 0x00F0, 0x80F5, 0x80FF,
116 | 0x00FA, 0x80EB, 0x00EE, 0x00E4, 0x80E1, 0x00A0, 0x80A5,
117 | 0x80AF, 0x00AA, 0x80BB, 0x00BE, 0x00B4, 0x80B1, 0x8093,
118 | 0x0096, 0x009C, 0x8099, 0x0088, 0x808D, 0x8087, 0x0082,
119 | 0x8183, 0x0186, 0x018C, 0x8189, 0x0198, 0x819D, 0x8197,
120 | 0x0192, 0x01B0, 0x81B5, 0x81BF, 0x01BA, 0x81AB, 0x01AE,
121 | 0x01A4, 0x81A1, 0x01E0, 0x81E5, 0x81EF, 0x01EA, 0x81FB,
122 | 0x01FE, 0x01F4, 0x81F1, 0x81D3, 0x01D6, 0x01DC, 0x81D9,
123 | 0x01C8, 0x81CD, 0x81C7, 0x01C2, 0x0140, 0x8145, 0x814F,
124 | 0x014A, 0x815B, 0x015E, 0x0154, 0x8151, 0x8173, 0x0176,
125 | 0x017C, 0x8179, 0x0168, 0x816D, 0x8167, 0x0162, 0x8123,
126 | 0x0126, 0x012C, 0x8129, 0x0138, 0x813D, 0x8137, 0x0132,
127 | 0x0110, 0x8115, 0x811F, 0x011A, 0x810B, 0x010E, 0x0104,
128 | 0x8101, 0x8303, 0x0306, 0x030C, 0x8309, 0x0318, 0x831D,
129 | 0x8317, 0x0312, 0x0330, 0x8335, 0x833F, 0x033A, 0x832B,
130 | 0x032E, 0x0324, 0x8321, 0x0360, 0x8365, 0x836F, 0x036A,
131 | 0x837B, 0x037E, 0x0374, 0x8371, 0x8353, 0x0356, 0x035C,
132 | 0x8359, 0x0348, 0x834D, 0x8347, 0x0342, 0x03C0, 0x83C5,
133 | 0x83CF, 0x03CA, 0x83DB, 0x03DE, 0x03D4, 0x83D1, 0x83F3,
134 | 0x03F6, 0x03FC, 0x83F9, 0x03E8, 0x83ED, 0x83E7, 0x03E2,
135 | 0x83A3, 0x03A6, 0x03AC, 0x83A9, 0x03B8, 0x83BD, 0x83B7,
136 | 0x03B2, 0x0390, 0x8395, 0x839F, 0x039A, 0x838B, 0x038E,
137 | 0x0384, 0x8381, 0x0280, 0x8285, 0x828F, 0x028A, 0x829B,
138 | 0x029E, 0x0294, 0x8291, 0x82B3, 0x02B6, 0x02BC, 0x82B9,
139 | 0x02A8, 0x82AD, 0x82A7, 0x02A2, 0x82E3, 0x02E6, 0x02EC,
140 | 0x82E9, 0x02F8, 0x82FD, 0x82F7, 0x02F2, 0x02D0, 0x82D5,
141 | 0x82DF, 0x02DA, 0x82CB, 0x02CE, 0x02C4, 0x82C1, 0x8243,
142 | 0x0246, 0x024C, 0x8249, 0x0258, 0x825D, 0x8257, 0x0252,
143 | 0x0270, 0x8275, 0x827F, 0x027A, 0x826B, 0x026E, 0x0264,
144 | 0x8261, 0x0220, 0x8225, 0x822F, 0x022A, 0x823B, 0x023E,
145 | 0x0234, 0x8231, 0x8213, 0x0216, 0x021C, 0x8219, 0x0208,
146 | 0x820D, 0x8207, 0x0202]
147 |
148 | for j in range(0, data_blk_size):
149 | i = ((crc_accum >> 8) ^ data_blk_ptr[j]) & 0xFF
150 | crc_accum = ((crc_accum << 8) ^ crc_table[i]) & 0xFFFF
151 |
152 | return crc_accum
153 |
154 | def addStuffing(self, packet):
155 | packet_length_in = DXL_MAKEWORD(packet[PKT_LENGTH_L], packet[PKT_LENGTH_H])
156 | packet_length_out = packet_length_in
157 |
158 | temp = [0] * TXPACKET_MAX_LEN
159 |
160 | # FF FF FD XX ID LEN_L LEN_H
161 | temp[PKT_HEADER0: PKT_HEADER0 + PKT_LENGTH_H + 1] = packet[PKT_HEADER0: PKT_HEADER0 + PKT_LENGTH_H + 1]
162 |
163 | index = PKT_INSTRUCTION
164 |
165 | for i in range(0, packet_length_in - 2): # except CRC
166 | temp[index] = packet[i + PKT_INSTRUCTION]
167 | index = index + 1
168 | if packet[i + PKT_INSTRUCTION] == 0xFD \
169 | and packet[i + PKT_INSTRUCTION - 1] == 0xFF \
170 | and packet[i + PKT_INSTRUCTION - 2] == 0xFF:
171 | # FF FF FD
172 | temp[index] = 0xFD
173 | index = index + 1
174 | packet_length_out = packet_length_out + 1
175 |
176 | temp[index] = packet[PKT_INSTRUCTION + packet_length_in - 2]
177 | temp[index + 1] = packet[PKT_INSTRUCTION + packet_length_in - 1]
178 | index = index + 2
179 |
180 | if packet_length_in != packet_length_out:
181 | packet = [0] * index
182 |
183 | packet[0: index] = temp[0: index]
184 |
185 | packet[PKT_LENGTH_L] = DXL_LOBYTE(packet_length_out)
186 | packet[PKT_LENGTH_H] = DXL_HIBYTE(packet_length_out)
187 |
188 | return packet
189 |
190 | def removeStuffing(self, packet):
191 | packet_length_in = DXL_MAKEWORD(packet[PKT_LENGTH_L], packet[PKT_LENGTH_H])
192 | packet_length_out = packet_length_in
193 |
194 | index = PKT_INSTRUCTION
195 | for i in range(0, (packet_length_in - 2)): # except CRC
196 | if (packet[i + PKT_INSTRUCTION] == 0xFD) and (packet[i + PKT_INSTRUCTION + 1] == 0xFD) and (
197 | packet[i + PKT_INSTRUCTION - 1] == 0xFF) and (packet[i + PKT_INSTRUCTION - 2] == 0xFF):
198 | # FF FF FD FD
199 | packet_length_out = packet_length_out - 1
200 | else:
201 | packet[index] = packet[i + PKT_INSTRUCTION]
202 | index += 1
203 |
204 | packet[index] = packet[PKT_INSTRUCTION + packet_length_in - 2]
205 | packet[index + 1] = packet[PKT_INSTRUCTION + packet_length_in - 1]
206 |
207 | packet[PKT_LENGTH_L] = DXL_LOBYTE(packet_length_out)
208 | packet[PKT_LENGTH_H] = DXL_HIBYTE(packet_length_out)
209 |
210 | return packet
211 |
212 | def txPacket(self, port, txpacket):
213 | if port.is_using:
214 | return COMM_PORT_BUSY
215 | port.is_using = True
216 |
217 | # byte stuffing for header
218 | self.addStuffing(txpacket)
219 |
220 | # check max packet length
221 | total_packet_length = DXL_MAKEWORD(txpacket[PKT_LENGTH_L], txpacket[PKT_LENGTH_H]) + 7
222 | # 7: HEADER0 HEADER1 HEADER2 RESERVED ID LENGTH_L LENGTH_H
223 |
224 | if total_packet_length > TXPACKET_MAX_LEN:
225 | port.is_using = False
226 | return COMM_TX_ERROR
227 |
228 | # make packet header
229 | txpacket[PKT_HEADER0] = 0xFF
230 | txpacket[PKT_HEADER1] = 0xFF
231 | txpacket[PKT_HEADER2] = 0xFD
232 | txpacket[PKT_RESERVED] = 0x00
233 |
234 | # add CRC16
235 | crc = self.updateCRC(0, txpacket, total_packet_length - 2) # 2: CRC16
236 |
237 | txpacket[total_packet_length - 2] = DXL_LOBYTE(crc)
238 | txpacket[total_packet_length - 1] = DXL_HIBYTE(crc)
239 |
240 | # tx packet
241 | port.clearPort()
242 | written_packet_length = port.writePort(txpacket)
243 | if total_packet_length != written_packet_length:
244 | port.is_using = False
245 | return COMM_TX_FAIL
246 |
247 | return COMM_SUCCESS
248 |
249 | def rxPacket(self, port):
250 | rxpacket = []
251 |
252 | result = COMM_TX_FAIL
253 | rx_length = 0
254 | wait_length = 11 # minimum length (HEADER0 HEADER1 HEADER2 RESERVED ID LENGTH_L LENGTH_H INST ERROR CRC16_L CRC16_H)
255 |
256 | while True:
257 | rxpacket.extend(port.readPort(wait_length - rx_length))
258 | rx_length = len(rxpacket)
259 | if rx_length >= wait_length:
260 | # find packet header
261 | for idx in range(0, (rx_length - 3)):
262 | if (rxpacket[idx] == 0xFF) and (rxpacket[idx + 1] == 0xFF) and (rxpacket[idx + 2] == 0xFD) and (
263 | rxpacket[idx + 3] != 0xFD):
264 | break
265 |
266 | if idx == 0:
267 | if (rxpacket[PKT_RESERVED] != 0x00) or (rxpacket[PKT_ID] > 0xFC) or (
268 | DXL_MAKEWORD(rxpacket[PKT_LENGTH_L], rxpacket[PKT_LENGTH_H]) > RXPACKET_MAX_LEN) or (
269 | rxpacket[PKT_INSTRUCTION] != 0x55):
270 | # remove the first byte in the packet
271 | del rxpacket[0]
272 | rx_length -= 1
273 | continue
274 |
275 | if wait_length != (DXL_MAKEWORD(rxpacket[PKT_LENGTH_L], rxpacket[PKT_LENGTH_H]) + PKT_LENGTH_H + 1):
276 | wait_length = DXL_MAKEWORD(rxpacket[PKT_LENGTH_L], rxpacket[PKT_LENGTH_H]) + PKT_LENGTH_H + 1
277 | continue
278 |
279 | if rx_length < wait_length:
280 | if port.isPacketTimeout():
281 | if rx_length == 0:
282 | result = COMM_RX_TIMEOUT
283 | else:
284 | result = COMM_RX_CORRUPT
285 | break
286 | else:
287 | continue
288 |
289 | crc = DXL_MAKEWORD(rxpacket[wait_length - 2], rxpacket[wait_length - 1])
290 |
291 | if self.updateCRC(0, rxpacket, wait_length - 2) == crc:
292 | result = COMM_SUCCESS
293 | else:
294 | result = COMM_RX_CORRUPT
295 | break
296 |
297 | else:
298 | # remove unnecessary packets
299 | del rxpacket[0: idx]
300 | rx_length -= idx
301 |
302 | else:
303 | if port.isPacketTimeout():
304 | if rx_length == 0:
305 | result = COMM_RX_TIMEOUT
306 | else:
307 | result = COMM_RX_CORRUPT
308 | break
309 |
310 | port.is_using = False
311 |
312 | if result == COMM_SUCCESS:
313 | rxpacket = self.removeStuffing(rxpacket)
314 |
315 | return rxpacket, result
316 |
317 | # NOT for BulkRead / SyncRead instruction
318 | def txRxPacket(self, port, txpacket):
319 | rxpacket = None
320 | error = 0
321 |
322 | # tx packet
323 | result = self.txPacket(port, txpacket)
324 | if result != COMM_SUCCESS:
325 | return rxpacket, result, error
326 |
327 | # (Instruction == BulkRead or SyncRead) == this function is not available.
328 | if txpacket[PKT_INSTRUCTION] == INST_BULK_READ or txpacket[PKT_INSTRUCTION] == INST_SYNC_READ:
329 | result = COMM_NOT_AVAILABLE
330 |
331 | # (ID == Broadcast ID) == no need to wait for status packet or not available.
332 | # (Instruction == action) == no need to wait for status packet
333 | if txpacket[PKT_ID] == BROADCAST_ID or txpacket[PKT_INSTRUCTION] == INST_ACTION:
334 | port.is_using = False
335 | return rxpacket, result, error
336 |
337 | # set packet timeout
338 | if txpacket[PKT_INSTRUCTION] == INST_READ:
339 | port.setPacketTimeout(DXL_MAKEWORD(txpacket[PKT_PARAMETER0 + 2], txpacket[PKT_PARAMETER0 + 3]) + 11)
340 | else:
341 | port.setPacketTimeout(11)
342 | # HEADER0 HEADER1 HEADER2 RESERVED ID LENGTH_L LENGTH_H INST ERROR CRC16_L CRC16_H
343 |
344 | # rx packet
345 | while True:
346 | rxpacket, result = self.rxPacket(port)
347 | if result != COMM_SUCCESS or txpacket[PKT_ID] == rxpacket[PKT_ID]:
348 | break
349 |
350 | if result == COMM_SUCCESS and txpacket[PKT_ID] == rxpacket[PKT_ID]:
351 | error = rxpacket[PKT_ERROR]
352 |
353 | return rxpacket, result, error
354 |
355 | def ping(self, port, dxl_id):
356 | model_number = 0
357 | error = 0
358 |
359 | txpacket = [0] * 10
360 |
361 | if dxl_id >= BROADCAST_ID:
362 | return model_number, COMM_NOT_AVAILABLE, error
363 |
364 | txpacket[PKT_ID] = dxl_id
365 | txpacket[PKT_LENGTH_L] = 3
366 | txpacket[PKT_LENGTH_H] = 0
367 | txpacket[PKT_INSTRUCTION] = INST_PING
368 |
369 | rxpacket, result, error = self.txRxPacket(port, txpacket)
370 | if result == COMM_SUCCESS:
371 | model_number = DXL_MAKEWORD(rxpacket[PKT_PARAMETER0 + 1], rxpacket[PKT_PARAMETER0 + 2])
372 |
373 | return model_number, result, error
374 |
375 | def broadcastPing(self, port):
376 | data_list = {}
377 |
378 | STATUS_LENGTH = 14
379 |
380 | rx_length = 0
381 | wait_length = STATUS_LENGTH * MAX_ID
382 |
383 | txpacket = [0] * 10
384 | rxpacket = []
385 |
386 | tx_time_per_byte = (1000.0 / port.getBaudRate()) *10.0;
387 |
388 | txpacket[PKT_ID] = BROADCAST_ID
389 | txpacket[PKT_LENGTH_L] = 3
390 | txpacket[PKT_LENGTH_H] = 0
391 | txpacket[PKT_INSTRUCTION] = INST_PING
392 |
393 | result = self.txPacket(port, txpacket)
394 | if result != COMM_SUCCESS:
395 | port.is_using = False
396 | return data_list, result
397 |
398 | # set rx timeout
399 | #port.setPacketTimeout(wait_length * 1)
400 | port.setPacketTimeoutMillis((wait_length * tx_time_per_byte) + (3.0 * MAX_ID) + 16.0);
401 |
402 | while True:
403 | rxpacket += port.readPort(wait_length - rx_length)
404 | rx_length = len(rxpacket)
405 |
406 | if port.isPacketTimeout(): # or rx_length >= wait_length
407 | break
408 |
409 | port.is_using = False
410 |
411 | if rx_length == 0:
412 | return data_list, COMM_RX_TIMEOUT
413 |
414 | while True:
415 | if rx_length < STATUS_LENGTH:
416 | return data_list, COMM_RX_CORRUPT
417 |
418 | # find packet header
419 | for idx in range(0, rx_length - 2):
420 | if rxpacket[idx] == 0xFF and rxpacket[idx + 1] == 0xFF and rxpacket[idx + 2] == 0xFD:
421 | break
422 |
423 | if idx == 0: # found at the beginning of the packet
424 | # verify CRC16
425 | crc = DXL_MAKEWORD(rxpacket[STATUS_LENGTH - 2], rxpacket[STATUS_LENGTH - 1])
426 |
427 | if self.updateCRC(0, rxpacket, STATUS_LENGTH - 2) == crc:
428 | result = COMM_SUCCESS
429 |
430 | data_list[rxpacket[PKT_ID]] = [
431 | DXL_MAKEWORD(rxpacket[PKT_PARAMETER0 + 1], rxpacket[PKT_PARAMETER0 + 2]),
432 | rxpacket[PKT_PARAMETER0 + 3]]
433 |
434 | del rxpacket[0: STATUS_LENGTH]
435 | rx_length = rx_length - STATUS_LENGTH
436 |
437 | if rx_length == 0:
438 | return data_list, result
439 |
440 | else:
441 | result = COMM_RX_CORRUPT
442 |
443 | # remove header (0xFF 0xFF 0xFD)
444 | del rxpacket[0: 3]
445 | rx_length = rx_length - 3
446 |
447 | else:
448 | # remove unnecessary packets
449 | del rxpacket[0: idx]
450 | rx_length = rx_length - idx
451 |
452 | # FIXME: unreachable code
453 | return data_list, result
454 |
455 | def action(self, port, dxl_id):
456 | txpacket = [0] * 10
457 |
458 | txpacket[PKT_ID] = dxl_id
459 | txpacket[PKT_LENGTH_L] = 3
460 | txpacket[PKT_LENGTH_H] = 0
461 | txpacket[PKT_INSTRUCTION] = INST_ACTION
462 |
463 | _, result, _ = self.txRxPacket(port, txpacket)
464 | return result
465 |
466 | def reboot(self, port, dxl_id):
467 | txpacket = [0] * 10
468 |
469 | txpacket[PKT_ID] = dxl_id
470 | txpacket[PKT_LENGTH_L] = 3
471 | txpacket[PKT_LENGTH_H] = 0
472 | txpacket[PKT_INSTRUCTION] = INST_REBOOT
473 |
474 | _, result, error = self.txRxPacket(port, txpacket)
475 | return result, error
476 |
477 | def clearMultiTurn(self, port, dxl_id):
478 | txpacket = [0] * 15
479 |
480 | txpacket[PKT_ID] = dxl_id
481 | txpacket[PKT_LENGTH_L] = 8
482 | txpacket[PKT_LENGTH_H] = 0
483 | txpacket[PKT_INSTRUCTION] = INST_CLEAR
484 | txpacket[PKT_PARAMETER0 + 0] = 0x01
485 | txpacket[PKT_PARAMETER0 + 1] = 0x44
486 | txpacket[PKT_PARAMETER0 + 2] = 0x58
487 | txpacket[PKT_PARAMETER0 + 3] = 0x4C
488 | txpacket[PKT_PARAMETER0 + 4] = 0x22
489 |
490 | _, result, error = self.txRxPacket(port, txpacket)
491 | return result, error
492 |
493 | def factoryReset(self, port, dxl_id, option):
494 | txpacket = [0] * 11
495 |
496 | txpacket[PKT_ID] = dxl_id
497 | txpacket[PKT_LENGTH_L] = 4
498 | txpacket[PKT_LENGTH_H] = 0
499 | txpacket[PKT_INSTRUCTION] = INST_FACTORY_RESET
500 | txpacket[PKT_PARAMETER0] = option
501 |
502 | _, result, error = self.txRxPacket(port, txpacket)
503 | return result, error
504 |
505 | def readTx(self, port, dxl_id, address, length):
506 | txpacket = [0] * 14
507 |
508 | if dxl_id >= BROADCAST_ID:
509 | return COMM_NOT_AVAILABLE
510 |
511 | txpacket[PKT_ID] = dxl_id
512 | txpacket[PKT_LENGTH_L] = 7
513 | txpacket[PKT_LENGTH_H] = 0
514 | txpacket[PKT_INSTRUCTION] = INST_READ
515 | txpacket[PKT_PARAMETER0 + 0] = DXL_LOBYTE(address)
516 | txpacket[PKT_PARAMETER0 + 1] = DXL_HIBYTE(address)
517 | txpacket[PKT_PARAMETER0 + 2] = DXL_LOBYTE(length)
518 | txpacket[PKT_PARAMETER0 + 3] = DXL_HIBYTE(length)
519 |
520 | result = self.txPacket(port, txpacket)
521 |
522 | # set packet timeout
523 | if result == COMM_SUCCESS:
524 | port.setPacketTimeout(length + 11)
525 |
526 | return result
527 |
528 | def readRx(self, port, dxl_id, length):
529 | result = COMM_TX_FAIL
530 | error = 0
531 |
532 | rxpacket = None
533 | data = []
534 |
535 | while True:
536 | rxpacket, result = self.rxPacket(port)
537 |
538 | if result != COMM_SUCCESS or rxpacket[PKT_ID] == dxl_id:
539 | break
540 |
541 | if result == COMM_SUCCESS and rxpacket[PKT_ID] == dxl_id:
542 | error = rxpacket[PKT_ERROR]
543 |
544 | data.extend(rxpacket[PKT_PARAMETER0 + 1: PKT_PARAMETER0 + 1 + length])
545 |
546 | return data, result, error
547 |
548 | def readTxRx(self, port, dxl_id, address, length):
549 | error = 0
550 |
551 | txpacket = [0] * 14
552 | data = []
553 |
554 | if dxl_id >= BROADCAST_ID:
555 | return data, COMM_NOT_AVAILABLE, error
556 |
557 | txpacket[PKT_ID] = dxl_id
558 | txpacket[PKT_LENGTH_L] = 7
559 | txpacket[PKT_LENGTH_H] = 0
560 | txpacket[PKT_INSTRUCTION] = INST_READ
561 | txpacket[PKT_PARAMETER0 + 0] = DXL_LOBYTE(address)
562 | txpacket[PKT_PARAMETER0 + 1] = DXL_HIBYTE(address)
563 | txpacket[PKT_PARAMETER0 + 2] = DXL_LOBYTE(length)
564 | txpacket[PKT_PARAMETER0 + 3] = DXL_HIBYTE(length)
565 |
566 | rxpacket, result, error = self.txRxPacket(port, txpacket)
567 | if result == COMM_SUCCESS:
568 | error = rxpacket[PKT_ERROR]
569 |
570 | data.extend(rxpacket[PKT_PARAMETER0 + 1: PKT_PARAMETER0 + 1 + length])
571 |
572 | return data, result, error
573 |
574 | def read1ByteTx(self, port, dxl_id, address):
575 | return self.readTx(port, dxl_id, address, 1)
576 |
577 | def read1ByteRx(self, port, dxl_id):
578 | data, result, error = self.readRx(port, dxl_id, 1)
579 | data_read = data[0] if (result == COMM_SUCCESS) else 0
580 | return data_read, result, error
581 |
582 | def read1ByteTxRx(self, port, dxl_id, address):
583 | data, result, error = self.readTxRx(port, dxl_id, address, 1)
584 | data_read = data[0] if (result == COMM_SUCCESS) else 0
585 | return data_read, result, error
586 |
587 | def read2ByteTx(self, port, dxl_id, address):
588 | return self.readTx(port, dxl_id, address, 2)
589 |
590 | def read2ByteRx(self, port, dxl_id):
591 | data, result, error = self.readRx(port, dxl_id, 2)
592 | data_read = DXL_MAKEWORD(data[0], data[1]) if (result == COMM_SUCCESS) else 0
593 | return data_read, result, error
594 |
595 | def read2ByteTxRx(self, port, dxl_id, address):
596 | data, result, error = self.readTxRx(port, dxl_id, address, 2)
597 | data_read = DXL_MAKEWORD(data[0], data[1]) if (result == COMM_SUCCESS) else 0
598 | return data_read, result, error
599 |
600 | def read4ByteTx(self, port, dxl_id, address):
601 | return self.readTx(port, dxl_id, address, 4)
602 |
603 | def read4ByteRx(self, port, dxl_id):
604 | data, result, error = self.readRx(port, dxl_id, 4)
605 | data_read = DXL_MAKEDWORD(DXL_MAKEWORD(data[0], data[1]),
606 | DXL_MAKEWORD(data[2], data[3])) if (result == COMM_SUCCESS) else 0
607 | return data_read, result, error
608 |
609 | def read4ByteTxRx(self, port, dxl_id, address):
610 | data, result, error = self.readTxRx(port, dxl_id, address, 4)
611 | data_read = DXL_MAKEDWORD(DXL_MAKEWORD(data[0], data[1]),
612 | DXL_MAKEWORD(data[2], data[3])) if (result == COMM_SUCCESS) else 0
613 | return data_read, result, error
614 |
615 | def writeTxOnly(self, port, dxl_id, address, length, data):
616 | txpacket = [0] * (length + 12)
617 |
618 | txpacket[PKT_ID] = dxl_id
619 | txpacket[PKT_LENGTH_L] = DXL_LOBYTE(length + 5)
620 | txpacket[PKT_LENGTH_H] = DXL_HIBYTE(length + 5)
621 | txpacket[PKT_INSTRUCTION] = INST_WRITE
622 | txpacket[PKT_PARAMETER0 + 0] = DXL_LOBYTE(address)
623 | txpacket[PKT_PARAMETER0 + 1] = DXL_HIBYTE(address)
624 |
625 | txpacket[PKT_PARAMETER0 + 2: PKT_PARAMETER0 + 2 + length] = data[0: length]
626 |
627 | result = self.txPacket(port, txpacket)
628 | port.is_using = False
629 |
630 | return result
631 |
632 | def writeTxRx(self, port, dxl_id, address, length, data):
633 | txpacket = [0] * (length + 12)
634 |
635 | txpacket[PKT_ID] = dxl_id
636 | txpacket[PKT_LENGTH_L] = DXL_LOBYTE(length + 5)
637 | txpacket[PKT_LENGTH_H] = DXL_HIBYTE(length + 5)
638 | txpacket[PKT_INSTRUCTION] = INST_WRITE
639 | txpacket[PKT_PARAMETER0 + 0] = DXL_LOBYTE(address)
640 | txpacket[PKT_PARAMETER0 + 1] = DXL_HIBYTE(address)
641 |
642 | txpacket[PKT_PARAMETER0 + 2: PKT_PARAMETER0 + 2 + length] = data[0: length]
643 | rxpacket, result, error = self.txRxPacket(port, txpacket)
644 |
645 | return result, error
646 |
647 | def write1ByteTxOnly(self, port, dxl_id, address, data):
648 | data_write = [data]
649 | return self.writeTxOnly(port, dxl_id, address, 1, data_write)
650 |
651 | def write1ByteTxRx(self, port, dxl_id, address, data):
652 | data_write = [data]
653 | return self.writeTxRx(port, dxl_id, address, 1, data_write)
654 |
655 | def write2ByteTxOnly(self, port, dxl_id, address, data):
656 | data_write = [DXL_LOBYTE(data), DXL_HIBYTE(data)]
657 | return self.writeTxOnly(port, dxl_id, address, 2, data_write)
658 |
659 | def write2ByteTxRx(self, port, dxl_id, address, data):
660 | data_write = [DXL_LOBYTE(data), DXL_HIBYTE(data)]
661 | return self.writeTxRx(port, dxl_id, address, 2, data_write)
662 |
663 | def write4ByteTxOnly(self, port, dxl_id, address, data):
664 | data_write = [DXL_LOBYTE(DXL_LOWORD(data)),
665 | DXL_HIBYTE(DXL_LOWORD(data)),
666 | DXL_LOBYTE(DXL_HIWORD(data)),
667 | DXL_HIBYTE(DXL_HIWORD(data))]
668 | return self.writeTxOnly(port, dxl_id, address, 4, data_write)
669 |
670 | def write4ByteTxRx(self, port, dxl_id, address, data):
671 | data_write = [DXL_LOBYTE(DXL_LOWORD(data)),
672 | DXL_HIBYTE(DXL_LOWORD(data)),
673 | DXL_LOBYTE(DXL_HIWORD(data)),
674 | DXL_HIBYTE(DXL_HIWORD(data))]
675 | return self.writeTxRx(port, dxl_id, address, 4, data_write)
676 |
677 | def regWriteTxOnly(self, port, dxl_id, address, length, data):
678 | txpacket = [0] * (length + 12)
679 |
680 | txpacket[PKT_ID] = dxl_id
681 | txpacket[PKT_LENGTH_L] = DXL_LOBYTE(length + 5)
682 | txpacket[PKT_LENGTH_H] = DXL_HIBYTE(length + 5)
683 | txpacket[PKT_INSTRUCTION] = INST_REG_WRITE
684 | txpacket[PKT_PARAMETER0 + 0] = DXL_LOBYTE(address)
685 | txpacket[PKT_PARAMETER0 + 1] = DXL_HIBYTE(address)
686 |
687 | txpacket[PKT_PARAMETER0 + 2: PKT_PARAMETER0 + 2 + length] = data[0: length]
688 |
689 | result = self.txPacket(port, txpacket)
690 | port.is_using = False
691 |
692 | return result
693 |
694 | def regWriteTxRx(self, port, dxl_id, address, length, data):
695 | txpacket = [0] * (length + 12)
696 |
697 | txpacket[PKT_ID] = dxl_id
698 | txpacket[PKT_LENGTH_L] = DXL_LOBYTE(length + 5)
699 | txpacket[PKT_LENGTH_H] = DXL_HIBYTE(length + 5)
700 | txpacket[PKT_INSTRUCTION] = INST_REG_WRITE
701 | txpacket[PKT_PARAMETER0 + 0] = DXL_LOBYTE(address)
702 | txpacket[PKT_PARAMETER0 + 1] = DXL_HIBYTE(address)
703 |
704 | txpacket[PKT_PARAMETER0 + 2: PKT_PARAMETER0 + 2 + length] = data[0: length]
705 |
706 | _, result, error = self.txRxPacket(port, txpacket)
707 |
708 | return result, error
709 |
710 | def syncReadTx(self, port, start_address, data_length, param, param_length):
711 | txpacket = [0] * (param_length + 14)
712 | # 14: HEADER0 HEADER1 HEADER2 RESERVED ID LEN_L LEN_H INST START_ADDR_L START_ADDR_H DATA_LEN_L DATA_LEN_H CRC16_L CRC16_H
713 |
714 | txpacket[PKT_ID] = BROADCAST_ID
715 | txpacket[PKT_LENGTH_L] = DXL_LOBYTE(
716 | param_length + 7) # 7: INST START_ADDR_L START_ADDR_H DATA_LEN_L DATA_LEN_H CRC16_L CRC16_H
717 | txpacket[PKT_LENGTH_H] = DXL_HIBYTE(
718 | param_length + 7) # 7: INST START_ADDR_L START_ADDR_H DATA_LEN_L DATA_LEN_H CRC16_L CRC16_H
719 | txpacket[PKT_INSTRUCTION] = INST_SYNC_READ
720 | txpacket[PKT_PARAMETER0 + 0] = DXL_LOBYTE(start_address)
721 | txpacket[PKT_PARAMETER0 + 1] = DXL_HIBYTE(start_address)
722 | txpacket[PKT_PARAMETER0 + 2] = DXL_LOBYTE(data_length)
723 | txpacket[PKT_PARAMETER0 + 3] = DXL_HIBYTE(data_length)
724 |
725 | txpacket[PKT_PARAMETER0 + 4: PKT_PARAMETER0 + 4 + param_length] = param[0: param_length]
726 |
727 | result = self.txPacket(port, txpacket)
728 | if result == COMM_SUCCESS:
729 | port.setPacketTimeout((11 + data_length) * param_length)
730 |
731 | return result
732 |
733 | def syncWriteTxOnly(self, port, start_address, data_length, param, param_length):
734 | txpacket = [0] * (param_length + 14)
735 | # 14: HEADER0 HEADER1 HEADER2 RESERVED ID LEN_L LEN_H INST START_ADDR_L START_ADDR_H DATA_LEN_L DATA_LEN_H CRC16_L CRC16_H
736 |
737 | txpacket[PKT_ID] = BROADCAST_ID
738 | txpacket[PKT_LENGTH_L] = DXL_LOBYTE(
739 | param_length + 7) # 7: INST START_ADDR_L START_ADDR_H DATA_LEN_L DATA_LEN_H CRC16_L CRC16_H
740 | txpacket[PKT_LENGTH_H] = DXL_HIBYTE(
741 | param_length + 7) # 7: INST START_ADDR_L START_ADDR_H DATA_LEN_L DATA_LEN_H CRC16_L CRC16_H
742 | txpacket[PKT_INSTRUCTION] = INST_SYNC_WRITE
743 | txpacket[PKT_PARAMETER0 + 0] = DXL_LOBYTE(start_address)
744 | txpacket[PKT_PARAMETER0 + 1] = DXL_HIBYTE(start_address)
745 | txpacket[PKT_PARAMETER0 + 2] = DXL_LOBYTE(data_length)
746 | txpacket[PKT_PARAMETER0 + 3] = DXL_HIBYTE(data_length)
747 |
748 | txpacket[PKT_PARAMETER0 + 4: PKT_PARAMETER0 + 4 + param_length] = param[0: param_length]
749 |
750 | _, result, _ = self.txRxPacket(port, txpacket)
751 |
752 | return result
753 |
754 | def bulkReadTx(self, port, param, param_length):
755 | txpacket = [0] * (param_length + 10)
756 | # 10: HEADER0 HEADER1 HEADER2 RESERVED ID LEN_L LEN_H INST CRC16_L CRC16_H
757 |
758 | txpacket[PKT_ID] = BROADCAST_ID
759 | txpacket[PKT_LENGTH_L] = DXL_LOBYTE(param_length + 3) # 3: INST CRC16_L CRC16_H
760 | txpacket[PKT_LENGTH_H] = DXL_HIBYTE(param_length + 3) # 3: INST CRC16_L CRC16_H
761 | txpacket[PKT_INSTRUCTION] = INST_BULK_READ
762 |
763 | txpacket[PKT_PARAMETER0: PKT_PARAMETER0 + param_length] = param[0: param_length]
764 |
765 | result = self.txPacket(port, txpacket)
766 | if result == COMM_SUCCESS:
767 | wait_length = 0
768 | i = 0
769 | while i < param_length:
770 | wait_length += DXL_MAKEWORD(param[i + 3], param[i + 4]) + 10
771 | i += 5
772 | port.setPacketTimeout(wait_length)
773 |
774 | return result
775 |
776 | def bulkWriteTxOnly(self, port, param, param_length):
777 | txpacket = [0] * (param_length + 10)
778 | # 10: HEADER0 HEADER1 HEADER2 RESERVED ID LEN_L LEN_H INST CRC16_L CRC16_H
779 |
780 | txpacket[PKT_ID] = BROADCAST_ID
781 | txpacket[PKT_LENGTH_L] = DXL_LOBYTE(param_length + 3) # 3: INST CRC16_L CRC16_H
782 | txpacket[PKT_LENGTH_H] = DXL_HIBYTE(param_length + 3) # 3: INST CRC16_L CRC16_H
783 | txpacket[PKT_INSTRUCTION] = INST_BULK_WRITE
784 |
785 | txpacket[PKT_PARAMETER0: PKT_PARAMETER0 + param_length] = param[0: param_length]
786 |
787 | _, result, _ = self.txRxPacket(port, txpacket)
788 |
789 | return result
790 |
--------------------------------------------------------------------------------
/libgex/dynamixel_sdk/robotis_def.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | ################################################################################
5 | # Copyright 2017 ROBOTIS CO., LTD.
6 | #
7 | # Licensed under the Apache License, Version 2.0 (the "License");
8 | # you may not use this file except in compliance with the License.
9 | # You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing, software
14 | # distributed under the License is distributed on an "AS IS" BASIS,
15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 | # See the License for the specific language governing permissions and
17 | # limitations under the License.
18 | ################################################################################
19 |
20 | # Author: Ryu Woon Jung (Leon)
21 |
22 | BROADCAST_ID = 0xFE # 254
23 | MAX_ID = 0xFC # 252
24 |
25 | # Instruction for DXL Protocol
26 | INST_PING = 1
27 | INST_READ = 2
28 | INST_WRITE = 3
29 | INST_REG_WRITE = 4
30 | INST_ACTION = 5
31 | INST_FACTORY_RESET = 6
32 | INST_CLEAR = 16
33 | INST_SYNC_WRITE = 131 # 0x83
34 | INST_BULK_READ = 146 # 0x92
35 | # --- Only for 2.0 ---
36 | INST_REBOOT = 8
37 | INST_STATUS = 85 # 0x55
38 | INST_SYNC_READ = 130 # 0x82
39 | INST_BULK_WRITE = 147 # 0x93
40 |
41 | # Communication Result
42 | COMM_SUCCESS = 0 # tx or rx packet communication success
43 | COMM_PORT_BUSY = -1000 # Port is busy (in use)
44 | COMM_TX_FAIL = -1001 # Failed transmit instruction packet
45 | COMM_RX_FAIL = -1002 # Failed get status packet
46 | COMM_TX_ERROR = -2000 # Incorrect instruction packet
47 | COMM_RX_WAITING = -3000 # Now recieving status packet
48 | COMM_RX_TIMEOUT = -3001 # There is no status packet
49 | COMM_RX_CORRUPT = -3002 # Incorrect status packet
50 | COMM_NOT_AVAILABLE = -9000 #
51 |
52 |
53 | # Macro for Control Table Value
54 | def DXL_MAKEWORD(a, b):
55 | return (a & 0xFF) | ((b & 0xFF) << 8)
56 |
57 |
58 | def DXL_MAKEDWORD(a, b):
59 | return (a & 0xFFFF) | (b & 0xFFFF) << 16
60 |
61 |
62 | def DXL_LOWORD(l):
63 | return l & 0xFFFF
64 |
65 |
66 | def DXL_HIWORD(l):
67 | return (l >> 16) & 0xFFFF
68 |
69 |
70 | def DXL_LOBYTE(w):
71 | return w & 0xFF
72 |
73 |
74 | def DXL_HIBYTE(w):
75 | return (w >> 8) & 0xFF
76 |
--------------------------------------------------------------------------------
/libgex/ex12/kinematics.py:
--------------------------------------------------------------------------------
1 | import pybullet as p
2 | import os.path as osp
3 | import numpy as np
4 | abs_path = osp.dirname(osp.abspath(__file__))
5 |
6 |
7 |
8 | class KinEX12:
9 | def __init__(self) -> None:
10 | self.name = 'EX12'
11 | p.connect(p.DIRECT)
12 | self.offset_z = 3
13 |
14 | self.bullet_hand = p.loadURDF(osp.join(abs_path, "urdf/ex12.urdf"), useFixedBase=True, basePosition=[0, 0, self.offset_z])
15 | for i in range(20):
16 | p.stepSimulation()
17 |
18 | self.thumb_link_id = 4
19 | self.thumb_joint_ids = [0, 1, 2, 3]
20 |
21 | self.index_link_id = 9
22 | self.index_joint_ids = [5, 6, 7, 8]
23 |
24 | self.middle_link_id = 14
25 | self.middle_joint_ids = [10, 11, 12, 13]
26 |
27 | self.direction_finger1 = [1, -1, -1, -1]
28 | self.direction_finger2 = [-1, 1, 1, 1]
29 | self.direction_finger3 = [-1, -1, -1, -1]
30 |
31 | def fk_finger1(self, q=[0]*4):
32 | """
33 | finger1 正运动学,3自由度
34 | """
35 | # 匹配关节方向,degree to rad
36 | q = [q_*d*np.pi/180 for q_, d in zip(q, self.direction_finger1)]
37 |
38 |
39 | for i, joint_position in zip(self.thumb_joint_ids, q):
40 | p.setJointMotorControl2(self.bullet_hand, i, p.POSITION_CONTROL, joint_position)
41 |
42 | for i in range(20):
43 | p.stepSimulation()
44 |
45 | ee_pos = p.getLinkState(self.bullet_hand, self.thumb_link_id, computeForwardKinematics=1)[4]
46 |
47 | ee_pos = list(ee_pos)
48 | ee_pos[2] -= self.offset_z
49 |
50 | return ee_pos
51 |
52 | def ik_finger1(self, xyz):
53 | # 计算逆运动学
54 | joint_positions = p.calculateInverseKinematics(self.bullet_hand, self.thumb_link_id, xyz, maxNumIterations=300, residualThreshold=1e-4)
55 |
56 | q = joint_positions[:4]
57 | # 匹配关节方向,rad to degree
58 | q = [q_*d*180/np.pi for q_, d in zip(q, self.direction_finger1)]
59 |
60 | return q
61 |
62 |
63 | def fk_finger2(self, q=[0]*4):
64 | """
65 | finger2 正运动学,4自由度
66 | """
67 | # 匹配关节方向,degree to rad
68 | q = [q_*d*np.pi/180 for q_, d in zip(q, self.direction_finger2)]
69 |
70 |
71 | for i, joint_position in zip(self.index_joint_ids, q):
72 | p.setJointMotorControl2(self.bullet_hand, i, p.POSITION_CONTROL, joint_position)
73 |
74 | for i in range(20):
75 | p.stepSimulation()
76 |
77 | ee_pos = p.getLinkState(self.bullet_hand, self.index_link_id, computeForwardKinematics=1)[4]
78 |
79 | ee_pos = list(ee_pos)
80 | ee_pos[2] -= self.offset_z
81 |
82 | return ee_pos
83 |
84 | def ik_finger2(self, xyz):
85 | # 计算逆运动学
86 | joint_positions = p.calculateInverseKinematics(self.bullet_hand, self.index_link_id, xyz, maxNumIterations=300, residualThreshold=1e-4)
87 |
88 | q = joint_positions[4:8]
89 |
90 | q = [q_*d*180/np.pi for q_, d in zip(q, self.direction_finger2)]
91 |
92 | return q
93 |
94 | def fk_finger3(self, q=[0]*4):
95 | """
96 | finger3 正运动学,4自由度
97 | """
98 | # 匹配关节方向,degree to rad
99 | q = [q_*d*np.pi/180 for q_, d in zip(q, self.direction_finger3)]
100 |
101 |
102 | for i, joint_position in zip(self.middle_joint_ids, q):
103 | p.setJointMotorControl2(self.bullet_hand, i, p.POSITION_CONTROL, joint_position)
104 |
105 | for i in range(20):
106 | p.stepSimulation()
107 |
108 | ee_pos = p.getLinkState(self.bullet_hand, self.middle_link_id, computeForwardKinematics=1)[4]
109 |
110 | ee_pos = list(ee_pos)
111 | ee_pos[2] -= self.offset_z
112 |
113 | return ee_pos
114 |
115 | def ik_finger3(self, xyz):
116 | # 计算逆运动学
117 | joint_positions = p.calculateInverseKinematics(self.bullet_hand, self.middle_link_id, xyz, maxNumIterations=300, residualThreshold=1e-4)
118 |
119 | q = joint_positions[8:12]
120 | # 匹配关节方向,rad to degree
121 | q = [q_*d*180/np.pi for q_, d in zip(q, self.direction_finger3)]
122 |
123 | return q
124 |
125 | if __name__ == "__main__":
126 | kin = KinGX11()
127 |
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link1.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link1.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link10.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link10.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link11.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link11.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link12.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link12.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link13.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link13.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link14.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link14.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link15.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link15.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link2.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link2.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link3.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link3.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link4.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link4.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link5.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link5.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link6.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link6.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link7.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link7.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link8.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link8.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/Link9.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/Link9.STL
--------------------------------------------------------------------------------
/libgex/ex12/meshes/base_link.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/ex12/meshes/base_link.STL
--------------------------------------------------------------------------------
/libgex/ex12/test_ik.py:
--------------------------------------------------------------------------------
1 | import pybullet as p
2 | import pybullet_data
3 |
4 | import time
5 |
6 |
7 | p.connect(p.GUI)
8 | p.setAdditionalSearchPath(pybullet_data.getDataPath()) #optionally
9 |
10 | # p.loadURDF("plane.urdf")
11 |
12 | # 取消碰撞检测
13 | hand = p.loadURDF("urdf/ex12.urdf", useFixedBase=True, basePosition=[0, 0, 0])
14 | p.setGravity(0, 0, -9.8)
15 |
16 |
17 | valid_joints = []
18 | for i in range(p.getNumJoints(hand)):
19 | info = p.getJointInfo(hand, i)
20 | print(info[1])
21 | if info[2] == p.JOINT_REVOLUTE:
22 |
23 | valid_joints.append(i)
24 |
25 |
26 | print(valid_joints)
27 | # 增加三个xyz的拖动条
28 |
29 | tx = p.addUserDebugParameter(f"TX", 0, 0.3, 0.02)
30 | ty = p.addUserDebugParameter(f"TY", 0, 0.3, 0.06)
31 | tz = p.addUserDebugParameter(f"TZ", 0., 0.6, 0.1)
32 |
33 | sliders = [tx, ty, tz]
34 |
35 | # 逆运动学
36 | while True:
37 | xyz = [p.readUserDebugParameter(i) for i in sliders]
38 |
39 | # 计算逆运动学
40 | joint_positions = p.calculateInverseKinematics(hand, 14, xyz, maxNumIterations=300, residualThreshold=1e-4)
41 |
42 | joint_positions = [0]*12
43 |
44 | # print(len(joint_positions))
45 | # 设置关节位置
46 | for i, joint_position in zip(valid_joints, joint_positions):
47 | p.setJointMotorControl2(hand, i, p.POSITION_CONTROL, joint_position)
48 |
49 | p.stepSimulation()
50 |
51 | xyz_now5 = p.getLinkState(hand, 14, computeForwardKinematics=1)[4]
52 | # xyz_now5 = p.getLinkState(hand, 3, computeForwardKinematics=1)[0]
53 |
54 | print(f'target: {xyz[0]:.3f}, {xyz[1]:.3f}, {xyz[2]:.3f}, now5: {xyz_now5[0]:.3f}, {xyz_now5[1]:.3f}, {xyz_now5[2]:.3f}')
55 |
56 | time.sleep(1./240.)
57 | # while True:
58 | # p.stepSimulation()
59 | # time.sleep(1./240.)
60 |
61 |
--------------------------------------------------------------------------------
/libgex/ex12/urdf/ex12.urdf:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 |
9 |
10 |
13 |
15 |
22 |
23 |
24 |
27 |
28 |
30 |
31 |
33 |
35 |
36 |
37 |
38 |
41 |
42 |
44 |
45 |
46 |
47 |
49 |
50 |
53 |
55 |
62 |
63 |
64 |
67 |
68 |
70 |
71 |
73 |
75 |
76 |
77 |
78 |
81 |
82 |
84 |
85 |
86 |
87 |
90 |
93 |
95 |
97 |
99 |
104 |
105 |
107 |
108 |
111 |
113 |
120 |
121 |
122 |
125 |
126 |
128 |
129 |
131 |
133 |
134 |
135 |
136 |
139 |
140 |
142 |
143 |
144 |
145 |
148 |
151 |
153 |
155 |
157 |
162 |
163 |
165 |
166 |
169 |
171 |
178 |
179 |
180 |
183 |
184 |
186 |
187 |
189 |
191 |
192 |
193 |
194 |
197 |
198 |
200 |
201 |
202 |
203 |
206 |
209 |
211 |
213 |
215 |
220 |
221 |
223 |
224 |
227 |
229 |
236 |
237 |
238 |
241 |
242 |
244 |
245 |
247 |
249 |
250 |
251 |
252 |
255 |
256 |
258 |
259 |
260 |
261 |
264 |
267 |
269 |
271 |
273 |
278 |
279 |
281 |
282 |
285 |
287 |
294 |
295 |
296 |
299 |
300 |
302 |
303 |
305 |
307 |
308 |
309 |
310 |
313 |
314 |
316 |
317 |
318 |
319 |
322 |
325 |
327 |
329 |
331 |
332 |
334 |
335 |
338 |
340 |
347 |
348 |
349 |
352 |
353 |
355 |
356 |
358 |
360 |
361 |
362 |
363 |
366 |
367 |
369 |
370 |
371 |
372 |
375 |
378 |
380 |
382 |
384 |
389 |
390 |
392 |
393 |
396 |
398 |
405 |
406 |
407 |
410 |
411 |
413 |
414 |
416 |
418 |
419 |
420 |
421 |
424 |
425 |
427 |
428 |
429 |
430 |
433 |
436 |
438 |
440 |
442 |
447 |
448 |
450 |
451 |
454 |
456 |
463 |
464 |
465 |
468 |
469 |
471 |
472 |
474 |
476 |
477 |
478 |
479 |
482 |
483 |
485 |
486 |
487 |
488 |
491 |
494 |
496 |
498 |
500 |
505 |
506 |
508 |
509 |
512 |
514 |
521 |
522 |
523 |
526 |
527 |
529 |
530 |
532 |
534 |
535 |
536 |
537 |
540 |
541 |
543 |
544 |
545 |
546 |
549 |
552 |
554 |
556 |
558 |
563 |
564 |
566 |
567 |
570 |
572 |
579 |
580 |
581 |
584 |
585 |
587 |
588 |
590 |
592 |
593 |
594 |
595 |
598 |
599 |
601 |
602 |
603 |
604 |
607 |
610 |
612 |
614 |
616 |
617 |
619 |
620 |
623 |
625 |
632 |
633 |
634 |
637 |
638 |
640 |
641 |
643 |
645 |
646 |
647 |
648 |
651 |
652 |
654 |
655 |
656 |
657 |
660 |
663 |
665 |
667 |
669 |
674 |
675 |
677 |
678 |
681 |
683 |
690 |
691 |
692 |
695 |
696 |
698 |
699 |
701 |
703 |
704 |
705 |
706 |
709 |
710 |
712 |
713 |
714 |
715 |
718 |
721 |
723 |
725 |
727 |
732 |
733 |
735 |
736 |
739 |
741 |
748 |
749 |
750 |
753 |
754 |
756 |
757 |
759 |
761 |
762 |
763 |
764 |
767 |
768 |
770 |
771 |
772 |
773 |
776 |
779 |
781 |
783 |
785 |
790 |
791 |
793 |
794 |
797 |
799 |
806 |
807 |
808 |
811 |
812 |
814 |
815 |
817 |
819 |
820 |
821 |
822 |
825 |
826 |
828 |
829 |
830 |
831 |
834 |
837 |
839 |
841 |
843 |
848 |
849 |
851 |
852 |
855 |
857 |
864 |
865 |
866 |
869 |
870 |
872 |
873 |
875 |
877 |
878 |
879 |
880 |
883 |
884 |
886 |
887 |
888 |
889 |
892 |
895 |
897 |
899 |
901 |
902 |
--------------------------------------------------------------------------------
/libgex/gx11/kinematics.py:
--------------------------------------------------------------------------------
1 | import pybullet as p
2 | import os.path as osp
3 | import numpy as np
4 | abs_path = osp.dirname(osp.abspath(__file__))
5 |
6 |
7 |
8 | class KinGX11:
9 | def __init__(self) -> None:
10 | self.name = 'GX11'
11 | p.connect(p.DIRECT)
12 |
13 | self.bullet_hand = p.loadURDF(osp.join(abs_path, "urdf/gx11.urdf"), useFixedBase=True, basePosition=[0, 0, 0])
14 | for i in range(20):
15 | p.stepSimulation()
16 |
17 | self.thumb_link_id = 3
18 | self.thumb_joint_ids = [0, 1, 2]
19 |
20 | self.index_link_id = 8
21 | self.index_joint_ids = [4, 5, 6, 7]
22 |
23 | self.middle_link_id = 13
24 | self.middle_joint_ids = [9, 10, 11, 12]
25 |
26 | self.direction_finger1 = [1, 1, 1]
27 | self.direction_finger2 = [1, -1, -1, -1]
28 | self.direction_finger3 = [-1, -1, 1, 1]
29 |
30 | def fk_finger1(self, q=[0]*3):
31 | """
32 | finger1 正运动学,3自由度
33 | """
34 | # 匹配关节方向,degree to rad
35 | q = [q_*d*np.pi/180 for q_, d in zip(q, self.direction_finger1)]
36 |
37 | for i, joint_position in zip(self.thumb_joint_ids, q):
38 | p.setJointMotorControl2(self.bullet_hand, i, p.POSITION_CONTROL, joint_position)
39 |
40 | for i in range(20):
41 | p.stepSimulation()
42 |
43 | ee_pos = p.getLinkState(self.bullet_hand, self.thumb_link_id, computeForwardKinematics=1)[4]
44 |
45 | return ee_pos
46 |
47 | def ik_finger1(self, xyz):
48 | # 计算逆运动学
49 | joint_positions = p.calculateInverseKinematics(self.bullet_hand, self.thumb_link_id, xyz, maxNumIterations=300, residualThreshold=1e-4)
50 |
51 | q = joint_positions[:3]
52 | # 匹配关节方向,rad to degree
53 | q = [q_*d*180/np.pi for q_, d in zip(q, self.direction_finger1)]
54 |
55 | return q
56 |
57 |
58 | def fk_finger2(self, q=[0]*4):
59 | """
60 | finger2 正运动学,4自由度
61 | """
62 | # 匹配关节方向,degree to rad
63 | q = [q_*d*np.pi/180 for q_, d in zip(q, self.direction_finger2)]
64 |
65 | for i, joint_position in zip(self.index_joint_ids, q):
66 | p.setJointMotorControl2(self.bullet_hand, i, p.POSITION_CONTROL, joint_position)
67 |
68 | for i in range(20):
69 | p.stepSimulation()
70 |
71 | ee_pos = p.getLinkState(self.bullet_hand, self.index_link_id, computeForwardKinematics=1)[4]
72 |
73 | return ee_pos
74 |
75 | def ik_finger2(self, xyz):
76 | # 计算逆运动学
77 | joint_positions = p.calculateInverseKinematics(self.bullet_hand, self.index_link_id, xyz, maxNumIterations=300, residualThreshold=1e-4)
78 |
79 | q = joint_positions[3:7]
80 |
81 | q = [q_*d*180/np.pi for q_, d in zip(q, self.direction_finger2)]
82 |
83 | return q
84 |
85 | def fk_finger3(self, q=[0]*4):
86 | """
87 | finger3 正运动学,4自由度
88 | """
89 | # 匹配关节方向,degree to rad
90 | q = [q_*d*np.pi/180 for q_, d in zip(q, self.direction_finger3)]
91 |
92 | for i, joint_position in zip(self.middle_joint_ids, q):
93 | p.setJointMotorControl2(self.bullet_hand, i, p.POSITION_CONTROL, joint_position)
94 |
95 | for i in range(20):
96 | p.stepSimulation()
97 |
98 | ee_pos = p.getLinkState(self.bullet_hand, self.middle_link_id, computeForwardKinematics=1)[4]
99 |
100 | return ee_pos
101 |
102 | def ik_finger3(self, xyz):
103 | # 计算逆运动学
104 | joint_positions = p.calculateInverseKinematics(self.bullet_hand, self.middle_link_id, xyz, maxNumIterations=300, residualThreshold=1e-4)
105 |
106 | q = joint_positions[7:11]
107 | # 匹配关节方向,rad to degree
108 | q = [q_*d*180/np.pi for q_, d in zip(q, self.direction_finger3)]
109 |
110 | return q
111 |
112 | if __name__ == "__main__":
113 | kin = KinGX11()
114 |
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link1.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link1.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link10.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link10.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link11.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link11.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link12.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link12.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link13.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link13.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link14.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link14.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link2.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link2.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link3.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link3.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link4.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link4.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link5.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link5.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link6.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link6.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link7.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link7.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link8.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link8.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/Link9.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/Link9.STL
--------------------------------------------------------------------------------
/libgex/gx11/meshes/base_link.STL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Democratizing-Dexterous/libgex/1edde3c08b44b2952b5643b2b171a0958ab21988/libgex/gx11/meshes/base_link.STL
--------------------------------------------------------------------------------
/libgex/gx11/test_ik.py:
--------------------------------------------------------------------------------
1 | import pybullet as p
2 | import pybullet_data
3 |
4 | import time
5 |
6 |
7 | p.connect(p.GUI)
8 | p.setAdditionalSearchPath(pybullet_data.getDataPath()) #optionally
9 |
10 | # p.loadURDF("plane.urdf")
11 |
12 | hand = p.loadURDF("urdf/gx11.urdf", useFixedBase=True, basePosition=[0, 0, 0])
13 | p.setGravity(0, 0, -9.8)
14 |
15 |
16 | valid_joints = []
17 | for i in range(p.getNumJoints(hand)):
18 | info = p.getJointInfo(hand, i)
19 | print(info[1])
20 | if info[2] == p.JOINT_REVOLUTE:
21 |
22 | valid_joints.append(i)
23 |
24 |
25 | # 增加三个xyz的拖动条
26 |
27 | tx = p.addUserDebugParameter(f"TX", 0, 0.3, 0.03)
28 | ty = p.addUserDebugParameter(f"TY", 0, 0.3, 0.06)
29 | tz = p.addUserDebugParameter(f"TZ", 0., 0.6, 0.1)
30 |
31 | sliders = [tx, ty, tz]
32 |
33 | # 逆运动学
34 | while True:
35 | xyz = [p.readUserDebugParameter(i) for i in sliders]
36 |
37 | # 计算逆运动学
38 | joint_positions = p.calculateInverseKinematics(hand, 3, xyz, maxNumIterations=300, residualThreshold=1e-4)
39 |
40 | # print(len(joint_positions))
41 | # 设置关节位置
42 | for i, joint_position in zip(valid_joints, joint_positions):
43 | p.setJointMotorControl2(hand, i, p.POSITION_CONTROL, joint_position)
44 |
45 | p.stepSimulation()
46 |
47 | xyz_now5 = p.getLinkState(hand, 3, computeForwardKinematics=1)[4]
48 | # xyz_now1 = p.getLinkState(hand, 3, computeForwardKinematics=1)[0]
49 |
50 | # print(f'target: {xyz[0]:.3f}, {xyz[1]:.3f}, {xyz[2]:.3f}, now5: {xyz_now5[0]:.3f}, {xyz_now5[1]:.3f}, {xyz_now5[2]:.3f}')
51 |
52 | time.sleep(1./240.)
53 | # while True:
54 | # p.stepSimulation()
55 | # time.sleep(1./240.)
56 |
57 |
--------------------------------------------------------------------------------
/libgex/gx11/urdf/finger1.urdf:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 |
9 |
10 |
13 |
15 |
22 |
23 |
24 |
27 |
28 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
42 |
43 |
44 |
45 |
47 |
48 |
51 |
53 |
60 |
61 |
62 |
65 |
66 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
77 |
78 |
80 |
81 |
82 |
83 |
86 |
89 |
91 |
93 |
95 |
100 |
101 |
103 |
104 |
107 |
109 |
116 |
117 |
118 |
121 |
122 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
133 |
134 |
136 |
137 |
138 |
139 |
142 |
145 |
147 |
149 |
151 |
156 |
157 |
159 |
160 |
163 |
165 |
172 |
173 |
174 |
177 |
178 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
189 |
190 |
192 |
193 |
194 |
195 |
198 |
201 |
203 |
205 |
207 |
212 |
213 |
215 |
216 |
219 |
221 |
228 |
229 |
230 |
233 |
234 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
245 |
246 |
248 |
249 |
250 |
251 |
254 |
257 |
259 |
261 |
263 |
268 |
269 |
270 |
--------------------------------------------------------------------------------
/libgex/gx11/urdf/gx11.urdf:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 |
9 |
10 |
13 |
15 |
22 |
23 |
24 |
27 |
28 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
42 |
43 |
44 |
45 |
47 |
48 |
51 |
53 |
60 |
61 |
62 |
65 |
66 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
77 |
78 |
80 |
81 |
82 |
83 |
86 |
89 |
91 |
93 |
95 |
100 |
101 |
103 |
104 |
107 |
109 |
116 |
117 |
118 |
121 |
122 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
133 |
134 |
136 |
137 |
138 |
139 |
142 |
145 |
147 |
149 |
151 |
156 |
157 |
159 |
160 |
163 |
165 |
172 |
173 |
174 |
177 |
178 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
189 |
190 |
192 |
193 |
194 |
195 |
198 |
201 |
203 |
205 |
207 |
212 |
213 |
215 |
216 |
219 |
221 |
228 |
229 |
230 |
233 |
234 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
245 |
246 |
248 |
249 |
250 |
251 |
254 |
257 |
259 |
261 |
263 |
268 |
269 |
271 |
272 |
275 |
277 |
284 |
285 |
286 |
289 |
290 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
301 |
302 |
304 |
305 |
306 |
307 |
310 |
313 |
315 |
317 |
319 |
324 |
325 |
327 |
328 |
331 |
333 |
340 |
341 |
342 |
345 |
346 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
357 |
358 |
360 |
361 |
362 |
363 |
366 |
369 |
371 |
373 |
375 |
380 |
381 |
383 |
384 |
387 |
389 |
396 |
397 |
398 |
401 |
402 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
413 |
414 |
416 |
417 |
418 |
419 |
422 |
425 |
427 |
429 |
431 |
436 |
437 |
439 |
440 |
443 |
445 |
452 |
453 |
454 |
457 |
458 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
469 |
470 |
472 |
473 |
474 |
475 |
478 |
481 |
483 |
485 |
487 |
492 |
493 |
495 |
496 |
499 |
501 |
508 |
509 |
510 |
513 |
514 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
525 |
526 |
528 |
529 |
530 |
531 |
534 |
537 |
539 |
541 |
543 |
544 |
546 |
547 |
550 |
552 |
559 |
560 |
561 |
564 |
565 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
576 |
577 |
579 |
580 |
581 |
582 |
585 |
588 |
590 |
592 |
594 |
599 |
600 |
602 |
603 |
606 |
608 |
615 |
616 |
617 |
620 |
621 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
632 |
633 |
635 |
636 |
637 |
638 |
641 |
644 |
646 |
648 |
650 |
655 |
656 |
658 |
659 |
662 |
664 |
671 |
672 |
673 |
676 |
677 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
688 |
689 |
691 |
692 |
693 |
694 |
697 |
700 |
702 |
704 |
706 |
711 |
712 |
714 |
715 |
718 |
720 |
727 |
728 |
729 |
732 |
733 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
744 |
745 |
747 |
748 |
749 |
750 |
753 |
756 |
758 |
760 |
762 |
767 |
768 |
770 |
771 |
774 |
776 |
783 |
784 |
785 |
788 |
789 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
800 |
801 |
803 |
804 |
805 |
806 |
809 |
812 |
814 |
816 |
818 |
819 |
--------------------------------------------------------------------------------
/libgex/libex12.py:
--------------------------------------------------------------------------------
1 | import time
2 | from .dynamixel_sdk import PortHandler, PacketHandler
3 | from .motor import Motor
4 | import sys
5 | import numpy as np
6 | from .config import BAUDRATE, PROTOCOL_VERSION
7 | from .ex12 import kinematics
8 |
9 | class Glove:
10 | def __init__(self, port) -> None:
11 | self.is_connected = False
12 | self.port = port
13 | self.name = 'EX12'
14 | self.kin = kinematics.KinEX12()
15 |
16 | def connect(self, goal_pwm=300, init=True):
17 | """
18 | 连接Hand,并且使能每个电机为默认的力控位置模式
19 | """
20 |
21 | portHandler = PortHandler(self.port)
22 | packetHandler = PacketHandler(PROTOCOL_VERSION)
23 |
24 | if portHandler.openPort() and portHandler.setBaudRate(BAUDRATE):
25 | print(f'Open {self.port} Success...')
26 | self.is_connected = True
27 | else:
28 | print(f'Failed...')
29 | self.is_connected = False
30 | sys.exit(0)
31 |
32 | self.portHandler = portHandler
33 | self.packetHandler = packetHandler
34 |
35 | self.motors = [Motor(i+1, portHandler, packetHandler) for i in range(12)]
36 |
37 | if init:
38 | for m in self.motors:
39 | m.init_config(goal_pwm=goal_pwm)
40 |
41 | print(f'{self.name} init done...')
42 |
43 |
44 | def off(self):
45 | """
46 | 失能所有电机
47 | """
48 | for m in self.motors:
49 | m.torq_off()
50 |
51 | def on(self):
52 | """
53 | 使能所有电机
54 | """
55 | for m in self.motors:
56 | m.torq_on()
57 |
58 |
59 | def home(self):
60 | """
61 | GX11会到原点
62 | """
63 | motors = self.motors
64 | for m in motors:
65 | m.set_pos(0)
66 | time.sleep(1)
67 |
68 | def getj(self):
69 | """
70 | 获取GX11关节角度,单位度
71 | """
72 | js = [m.get_pos() for m in self.motors]
73 |
74 | js_rect = []
75 | for j in js:
76 | if j > 180:
77 | j -= 360
78 | if j < -180:
79 | j += 360
80 | js_rect.append(j)
81 |
82 |
83 | return js_rect
84 |
85 | def setj(self, js):
86 | """
87 | 设置GX11关节角度,单位度
88 | """
89 | for m, j in zip(self.motors, js):
90 | m.set_pos(j)
91 |
92 | def getj_finger1(self):
93 | """
94 | 获取GX11指1关节角度,单位度
95 | """
96 | js = [m.get_pos() for m in self.motors[0:4]]
97 | js_rect = []
98 | for j in js:
99 | if j > 180:
100 | j -= 360
101 | if j < -180:
102 | j += 360
103 | js_rect.append(j)
104 |
105 |
106 | return js_rect
107 |
108 | def setj_finger1(self, js):
109 | """
110 | 设置GX11指1关节角度,单位度
111 | """
112 | for m, j in zip(self.motors[0:4], js):
113 | m.set_pos(j)
114 |
115 | def getj_finger2(self):
116 | """
117 | 获取GX11指2关节角度,单位度
118 | """
119 | js = [m.get_pos() for m in self.motors[4:8]]
120 | js_rect = []
121 | for j in js:
122 | if j > 180:
123 | j -= 360
124 | if j < -180:
125 | j += 360
126 | js_rect.append(j)
127 |
128 |
129 | return js_rect
130 |
131 | def setj_finger2(self, js):
132 | """
133 | 设置GX11指2关节角度,单位度
134 | """
135 | for m, j in zip(self.motors[4:8], js):
136 | m.set_pos(j)
137 |
138 | def getj_finger3(self):
139 | """
140 | 获取GX11指3关节角度,单位度
141 | """
142 | js = [m.get_pos() for m in self.motors[8:12]]
143 | js_rect = []
144 | for j in js:
145 | if j > 180:
146 | j -= 360
147 | if j < -180:
148 | j += 360
149 | js_rect.append(j)
150 |
151 |
152 | return js_rect
153 |
154 | def setj_finger3(self, js):
155 | """
156 | 设置GX11指3关节角度,单位度
157 | """
158 | for m, j in zip(self.motors[8:12], js):
159 | m.set_pos(j)
160 |
161 | def set_zero_whole_hand(self):
162 | """
163 | GX11手掌设置编码器以当前位置归0,慎用
164 | """
165 | for m in self.motors[4:12]:
166 | m.set_zero()
167 |
168 |
169 | def fk_finger1(self):
170 | """获取finger1的正运动学,单位m"""
171 | xyz = self.kin.fk_finger1(self.getj_finger1())
172 |
173 | return xyz
174 |
175 | def ik_finger1(self, xyz):
176 | """获取finger1的逆运动学,单位m"""
177 | q = self.kin.ik_finger1(xyz)
178 |
179 | return q
180 |
181 |
182 | def fk_finger2(self):
183 | """获取finger2的正运动学,单位m"""
184 | xyz = self.kin.fk_finger2(self.getj_finger2())
185 |
186 | return xyz
187 |
188 | def ik_finger2(self, xyz):
189 | """获取finger2的逆运动学,单位m"""
190 | q = self.kin.ik_finger2(xyz)
191 |
192 | return q
193 |
194 | def fk_finger3(self):
195 | """获取finger3的正运动学,单位m"""
196 | xyz = self.kin.fk_finger3(self.getj_finger3())
197 |
198 | return xyz
199 |
200 | def ik_finger3(self, xyz):
201 | """获取finger3的逆运动学,单位m"""
202 | q = self.kin.ik_finger3(xyz)
203 |
204 | return q
--------------------------------------------------------------------------------
/libgex/libgx11.py:
--------------------------------------------------------------------------------
1 | import time
2 | from .dynamixel_sdk import PortHandler, PacketHandler
3 | from .motor import Motor
4 | import sys
5 | import numpy as np
6 | from .config import BAUDRATE, PROTOCOL_VERSION
7 | from .gx11 import kinematics
8 |
9 | class Hand:
10 | def __init__(self, port) -> None:
11 | self.is_connected = False
12 | self.port = port
13 | self.name = 'GX11'
14 | self.kin = kinematics.KinGX11()
15 |
16 | def connect(self, goal_pwm=300):
17 | """
18 | 连接Hand,并且使能每个电机为默认的力控位置模式
19 | """
20 |
21 | portHandler = PortHandler(self.port)
22 | packetHandler = PacketHandler(PROTOCOL_VERSION)
23 |
24 | if portHandler.openPort() and portHandler.setBaudRate(BAUDRATE):
25 | print(f'Open {self.port} Success...')
26 | self.is_connected = True
27 | else:
28 | print(f'Failed...')
29 | self.is_connected = False
30 | sys.exit(0)
31 |
32 | self.portHandler = portHandler
33 | self.packetHandler = packetHandler
34 |
35 | self.motors = [Motor(i+1, portHandler, packetHandler) for i in range(11)]
36 |
37 | for m in self.motors:
38 | m.init_config(goal_pwm=goal_pwm)
39 |
40 | print(f'{self.name} init done...')
41 |
42 | def off(self):
43 | """
44 | 失能所有电机
45 | """
46 | for m in self.motors:
47 | m.torq_off()
48 |
49 | def on(self):
50 | """
51 | 使能所有电机
52 | """
53 | for m in self.motors:
54 | m.torq_on()
55 |
56 |
57 | def home(self):
58 | """
59 | GX11会到原点
60 | """
61 | motors = self.motors
62 | for m in motors:
63 | m.set_pos(0)
64 | time.sleep(1)
65 |
66 | def getj(self):
67 | """
68 | 获取GX11关节角度,单位度
69 | """
70 | js = [m.get_pos() for m in self.motors]
71 | return js
72 |
73 | def setj(self, js):
74 | """
75 | 设置GX11关节角度,单位度
76 | """
77 | for m, j in zip(self.motors, js):
78 | m.set_pos(j)
79 |
80 | def getj_finger1(self):
81 | """
82 | 获取GX11指1关节角度,单位度
83 | """
84 | js = [m.get_pos() for m in self.motors[0:3]]
85 | return js
86 |
87 | def setj_finger1(self, js):
88 | """
89 | 设置GX11指1关节角度,单位度
90 | """
91 | for m, j in zip(self.motors[0:3], js):
92 | m.set_pos(j)
93 |
94 | def getj_finger2(self):
95 | """
96 | 获取GX11指2关节角度,单位度
97 | """
98 | js = [m.get_pos() for m in self.motors[3:7]]
99 | return js
100 |
101 | def setj_finger2(self, js):
102 | """
103 | 设置GX11指2关节角度,单位度
104 | """
105 | for m, j in zip(self.motors[3:7], js):
106 | m.set_pos(j)
107 |
108 | def getj_finger3(self):
109 | """
110 | 获取GX11指3关节角度,单位度
111 | """
112 | js = [m.get_pos() for m in self.motors[7:11]]
113 | return js
114 |
115 | def setj_finger3(self, js):
116 | """
117 | 设置GX11指3关节角度,单位度
118 | """
119 | for m, j in zip(self.motors[7:11], js):
120 | m.set_pos(j)
121 |
122 | def set_zero_whole_hand(self):
123 | """
124 | GX11手掌设置编码器以当前位置归0,慎用
125 | """
126 | for m in self.motors:
127 | m.set_zero()
128 |
129 |
130 | def fk_finger1(self):
131 | """获取finger1的正运动学,单位m"""
132 | xyz = self.kin.fk_finger1(self.getj_finger1())
133 |
134 | return xyz
135 |
136 | def ik_finger1(self, xyz):
137 | """获取finger1的逆运动学,单位m"""
138 | q = self.kin.ik_finger1(xyz)
139 |
140 | return q
141 |
142 |
143 | def fk_finger2(self):
144 | """获取finger2的正运动学,单位m"""
145 | xyz = self.kin.fk_finger2(self.getj_finger2())
146 |
147 | return xyz
148 |
149 | def ik_finger2(self, xyz):
150 | """获取finger2的逆运动学,单位m"""
151 | q = self.kin.ik_finger2(xyz)
152 |
153 | return q
154 |
155 | def fk_finger3(self):
156 | """获取finger3的正运动学,单位m"""
157 | xyz = self.kin.fk_finger3(self.getj_finger3())
158 |
159 | return xyz
160 |
161 | def ik_finger3(self, xyz):
162 | """获取finger3的逆运动学,单位m"""
163 | q = self.kin.ik_finger3(xyz)
164 |
165 | return q
--------------------------------------------------------------------------------
/libgex/motor.py:
--------------------------------------------------------------------------------
1 | from .dynamixel_sdk import *
2 | import os
3 | import time
4 | import numpy as np
5 |
6 | class Motor:
7 | def __init__(self, id, portHandler, packetHandler) -> None:
8 | self.id = id
9 | self.unit_scale = 0.087891 # degree
10 | self.port_handler = portHandler
11 | self.packet_handler = packetHandler
12 |
13 | self.addr_torq_enable = 64
14 | self.addr_led = 65
15 | self.addr_present_pos = 132
16 | self.addr_present_current = 126
17 | self.addr_goal_pos = 116
18 | self.addr_curr_limit = 38
19 | self.addr_pos_p = 84
20 | self.addr_pos_d = 80
21 | self.addr_vel_limit = 44
22 |
23 | self.addr_home_off = 20
24 | self.addr_goal_curr = 102
25 | self.addr_goal_pwm = 100
26 |
27 | # 运行模式
28 | self.addr_operating_mode = 11
29 | self.curr_operating_mode = 0 # 电流模式
30 | self.vel_operating_mode = 1
31 | self.pos_operating_mode = 3
32 | self.extpos_operating_mode = 4
33 | self.currpos_operating_mode = 5 # 推荐使用,位置力控模式
34 | self.pwm_operating_mode = 16
35 |
36 | self.curr_limit = 1200
37 | self.curr_max = 1200
38 |
39 | def led_on(self):
40 | """
41 | 开启LED
42 | """
43 | self.packet_handler.write1ByteTxRx(self.port_handler, self.id, self.addr_led, 1)
44 |
45 | def led_off(self):
46 | """
47 | 关闭LED
48 | """
49 | self.packet_handler.write1ByteTxRx(self.port_handler, self.id, self.addr_led, 0)
50 |
51 | def torq_on(self):
52 | """
53 | 打开电机使能
54 | """
55 | self.packet_handler.write1ByteTxRx(self.port_handler, self.id, self.addr_torq_enable, 1)
56 |
57 | def torq_off(self):
58 | """
59 | 关闭电机使能
60 | """
61 | self.packet_handler.write1ByteTxRx(self.port_handler, self.id, self.addr_torq_enable, 0)
62 |
63 | def get_pos(self):
64 | """
65 | 获取电机位置,单位度
66 | """
67 | pos = self.packet_handler.read4ByteTxRx(self.port_handler, self.id, self.addr_present_pos)
68 | pos = pos[0]
69 | if pos > 2**31:
70 | pos = pos - 2**32
71 | else:
72 | pos = pos
73 | return pos*self.unit_scale
74 |
75 | def set_zero(self):
76 | """
77 | 设置电机归0
78 | """
79 | self.packet_handler.write4ByteTxRx(self.port_handler, self.id, self.addr_home_off, 0)
80 | pos = self.get_pos()
81 | self.packet_handler.write4ByteTxRx(self.port_handler, self.id, self.addr_home_off, -int(pos/self.unit_scale))
82 |
83 | def set_curr_limit(self, curr=800):
84 | # 设置最大电流,单位mA
85 | self.packet_handler.write2ByteTxRx(self.port_handler, self.id, self.addr_curr_limit, curr)
86 | if curr > self.curr_max:
87 | curr = self.curr_max
88 | self.curr_limit = curr
89 |
90 | def pos_force_mode(self, goal_current=600, goal_pwm=200):
91 | """
92 | 设置力控位置模式
93 | """
94 | self.torq_off()
95 | # 力控位置模式
96 | self.packet_handler.write1ByteTxRx(self.port_handler, self.id, self.addr_operating_mode, self.currpos_operating_mode)
97 |
98 | # https://emanual.robotis.com/docs/en/dxl/x/xl330-m288/
99 | if goal_current > self.curr_limit:
100 | goal_current = self.curr_limit
101 | # 设置goal current
102 | self.packet_handler.write2ByteTxRx(self.port_handler, self.id, self.addr_goal_curr, goal_current)
103 |
104 | if goal_pwm > 885:
105 | goal_pwm = 885
106 |
107 | # 设置goal pwm
108 | self.packet_handler.write2ByteTxRx(self.port_handler, self.id, self.addr_goal_pwm, goal_pwm) # 885 Max
109 | self.torq_on()
110 |
111 | def force_mode(self):
112 | """
113 | 设置电流模式
114 | """
115 | self.torq_off()
116 | # 力控模式
117 | self.packet_handler.write1ByteTxRx(self.port_handler, self.id, self.addr_operating_mode, self.curr_operating_mode)
118 |
119 |
120 | def init_config(self, curr_limit=1500, goal_current=1000, goal_pwm=800):
121 | """
122 | 电机初始配置,LED闪烁,设置为力控位置模式,并使能电机,goal_current影响力的大小,goal_pwm影响速度
123 | """
124 | for i in range(2):
125 | self.led_on()
126 | time.sleep(0.1)
127 | self.led_off()
128 | time.sleep(0.1)
129 | self.set_curr_limit(curr_limit)
130 | self.pos_force_mode(goal_current, goal_pwm)
131 |
132 |
133 | def set_pos(self, pos):
134 | """
135 | 设置目标角度,单位度
136 | """
137 | self.packet_handler.write4ByteTxRx(self.port_handler, self.id, self.addr_goal_pos, int(pos/self.unit_scale))
138 |
139 | def set_curr(self, cur):
140 | """
141 | 设置目标电流,单位mA
142 | """
143 | self.packet_handler.write2ByteTxRx(self.port_handler, self.id, self.addr_goal_curr, cur)
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/libgex/utils.py:
--------------------------------------------------------------------------------
1 | import serial
2 | import serial.tools
3 | import serial.tools.list_ports
4 |
5 | def search_ports():
6 | """
7 | search for serial ports, COM* for Windows, ttyACM* or ttyUSB* for Linux
8 | """
9 | ports = serial.tools.list_ports.comports()
10 |
11 | if len(ports) == 0:
12 | print('No COM ports found!')
13 | return None
14 |
15 | for p in ports:
16 | print(p.device, p.serial_number)
17 |
18 | return ports
19 |
20 |
21 | if __name__ == "__main__":
22 | ports = search_ports()
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## Updates
2 | * 2025-06-06: Our paper is released in [arxiv](https://arxiv.org/pdf/2506.04982). You can cite by:
3 | ```bibtex
4 | @misc{dong2025gexdemocratizingdexterityfullyactuated,
5 | title={GEX: Democratizing Dexterity with Fully-Actuated Dexterous Hand and Exoskeleton Glove},
6 | author={Yunlong Dong and Xing Liu and Jun Wan and Zelin Deng},
7 | year={2025},
8 | eprint={2506.04982},
9 | archivePrefix={arXiv},
10 | primaryClass={cs.RO},
11 | url={https://arxiv.org/abs/2506.04982},
12 | }
13 | ```
14 |
15 | ## Python Library to Control Dexterous Hand GX11 and Exoskeleton Glove EX12
16 | Libgex uses dynamixel SDK to control the motors in Python via OpenRB-150 control board through TTL at 1M bps. And libgex uses PyBullet to calculate the forward kinematics and inverse kinematics of the GX11 and EX12.
17 |
18 | ### Python Dependencies
19 | ```bash
20 | pip install pyserial
21 | pip install pybullet
22 | ```
23 |
24 | ### GX11 and EX12 URDF
25 | Drag folder `libgex/gx11` or `libgex/ex11` into http://urdf.robotsfan.com/ to visualize the URDF.
26 |
27 | GX11 has 3 joints in finger1(thumb), 4 joints in finger2(index), 4 joints in finger3(middle).
28 |
29 |
30 |
31 | Or use https://github.com/openrr/urdf-viz to visualize the URDF and display the link name and axes.
32 |
33 | EX12 has 4 joints in finger1(thumb), 4 joints in finger2(index), 4 joints in finger3(middle).
34 |
35 | The 4 joints design in thumb enable the exoskeleton glove to be better attached to the human thumb tip.
36 |
37 |
38 |
39 | ### Control GX11
40 |
41 | ```bash
42 | python demo_gx11.py
43 | ```
44 |
45 |
46 |
47 | ### Control EX12
48 | ```bash
49 | python demo_ex12.py
50 | ```
51 |
52 | ### Code Example
53 | ```bash
54 | from libgex.libgx11 import Hand
55 | import time
56 |
57 | hand = Hand(port='/dev/ttyACM0') # COM* for Windows, ttyACM* or ttyUSB* for Linux, if in Linux, check the port number with `ls /dev/tty*`, then run `sudo chmod 777 /dev/ttyACM*`
58 | hand.connect(goal_pwm=600) # will torque on all the motors, goal_pwm changes the speed, max 855
59 |
60 | hand.home() # home the hand
61 |
62 | hand.motors[0].setj(90) # unit degree
63 |
64 | print(hand.getj()) # print all the joints angles in degree
65 | ```
66 |
67 | ```bash
68 | from libgex.libex12 import Glove
69 | from libgex.utils import search_ports
70 | import time
71 |
72 |
73 | port = search_ports()[0]
74 |
75 | glove = Glove('/dev/ttyACM1') # COM* for Windows, ttyACM* or ttyUSB* for Linux, if in Linux, check the port number with `ls /dev/tty*`, then run `sudo chmod 777 /dev/ttyACM*`
76 | glove.connect(init=False) # do not torque on glove yet.
77 |
78 | print(glove.fk_finger1()) # get the thumb tip xyz position in base_link frame (bottom of the palm), unit m
79 |
80 | ```
81 |
82 | All the contorl commands are in `libgex/libgx11.py` and `libgex/libex12.py`
83 |
84 |
85 | ### Hardware
86 |
87 | #### EX12
88 |
89 | https://github.com/Democratizing-Dexterous/ExoskeletonGloveEX12
90 |
91 | #### GX11
92 |
93 | https://github.com/Democratizing-Dexterous/DexterousHandGX11
94 |
95 |
96 | ### Acknowledgement
97 |
98 | https://github.com/ROBOTIS-GIT/DynamixelSDK
99 |
100 | Leap Hand
101 |
--------------------------------------------------------------------------------
/zero_hand.py:
--------------------------------------------------------------------------------
1 | from libgex.libgx11 import Hand
2 | from libgex.utils import search_ports
3 | import time
4 |
5 |
6 |
7 | hand = Hand(port="/dev/ttyACM0") # COM* for Windows, ttyACM* or ttyUSB* for Linux
8 | hand.connect()
9 |
10 | print(hand.getj())
11 | # set hand zero, call off() first
12 | hand.off()
13 | hand.set_zero_whole_hand()
14 | print(hand.getj())
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------