├── .dir-locals.el ├── .github └── workflows │ └── pythonpublish.yml ├── .gitignore ├── .ipynb_checkpoints └── Mirobot-PythonSDK测试脚本-checkpoint.ipynb ├── LICENSE ├── Mirobot-PythonSDK测试脚本.ipynb ├── README.md ├── docs ├── Mirobot-Python使用案例 │ ├── Mirobot-Python例程.md │ └── image │ │ └── p2p.png ├── Mirobot-Py项目开发日志 │ └── 开发日志.md ├── Mirobot机械臂校准 │ └── Mirobot机械臂校准.md ├── PythonSDK-API手册(en) │ ├── _config.yml │ ├── index.html │ └── mirobot │ │ ├── base_mirobot.html │ │ ├── base_rover.html │ │ ├── bluetooth_low_energy_interface.html │ │ ├── exceptions.html │ │ ├── extended_dataclasses.html │ │ ├── index.html │ │ ├── mirobot.html │ │ ├── mirobot_status.html │ │ ├── serial_device.html │ │ └── serial_interface.html ├── PythonSDK-安装指南 │ └── PythonSDK-安装指南.md └── build_docs.sh ├── examples ├── air_pump │ └── air_pump.py ├── get_status │ └── get_status.py ├── home │ └── home.py ├── set_joint_angle │ └── set_joint_angle.py ├── set_wrist_pose │ └── set_wrist_pose.py └── src │ └── .ipynb_checkpoints │ └── Mirobot-PythonSDK测试脚本-checkpoint.ipynb ├── images └── Mirobot_Solo_256.jpg ├── mirobot ├── __init__.py ├── base_mirobot.py ├── base_rover.py ├── bluetooth_low_energy_interface.py ├── exceptions.py ├── extended_dataclasses.py ├── mirobot.py ├── mirobot_status.py ├── resources │ ├── __init__.py │ └── reset.xml ├── serial_device.py └── serial_interface.py ├── requirements.txt ├── scripts └── live_docs.sh ├── setup.py └── test.py /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((magit-status-mode . ((magit-todos-exclude-globs . ("docs/*"))))) 5 | -------------------------------------------------------------------------------- /.github/workflows/pythonpublish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | push: 8 | tags: 9 | - '*' 10 | 11 | jobs: 12 | deploy: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: '3.x' 23 | 24 | - name: Install dependencies 25 | run: | 26 | python -m pip install --upgrade pip 27 | pip install setuptools wheel twine 28 | 29 | - name: Build and publish to PyPi 30 | env: 31 | TWINE_USERNAME: __token__ 32 | TWINE_PASSWORD: "${{ secrets.PYPI_TOKEN }}" 33 | run: | 34 | python setup.py sdist bdist_wheel 35 | twine upload dist/* 36 | 37 | - name: Create Github Release 38 | uses: "marvinpinto/action-automatic-releases@latest" 39 | with: 40 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 41 | automatic_release_tag: latest 42 | prerelease: false 43 | files: | 44 | dist/* 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.egg-info/ 3 | build/ 4 | dist/ 5 | -------------------------------------------------------------------------------- /.ipynb_checkpoints/Mirobot-PythonSDK测试脚本-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "scrolled": true 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stderr", 12 | "output_type": "stream", 13 | "text": [ 14 | "[] [DEBUG] Welcome to use mirobot_py (version: kyle2020-0821)\n", 15 | "[] [DEBUG] Attempting to open serial port COM7\n", 16 | "[] [DEBUG] Succeeded in opening serial port COM7\n", 17 | "[COM7] [DEBUG] [RECV] \n", 18 | "[COM7] [DEBUG] [RECV] Grbl 0.9j ['$' for help]\n", 19 | "[COM7] [DEBUG] [RECV] \n", 20 | "[COM7] [DEBUG] [RECV] Qinnew Robot 20200803_TEST_1 based on Grbl 0.9j ['$' for help]\n", 21 | "[COM7] [DEBUG] [RECV] \n", 22 | "[COM7] [DEBUG] [RECV] D1: 127.000\n", 23 | "[COM7] [DEBUG] [RECV] A1: 29.690\n", 24 | "[COM7] [DEBUG] [RECV] A2: 108.000\n", 25 | "[COM7] [DEBUG] [RECV] A3: 20.000\n", 26 | "[COM7] [DEBUG] [RECV] D4: 168.980\n", 27 | "[COM7] [DEBUG] [RECV] L: -24.280\n", 28 | "[COM7] [DEBUG] [RECV] X offset: 0.000\n", 29 | "[COM7] [DEBUG] [RECV] Y offset: 0.000\n", 30 | "[COM7] [DEBUG] [RECV] Z offset: 0.000\n", 31 | "[COM7] [DEBUG] [RECV] X tool frame offset: 0.000\n", 32 | "[COM7] [DEBUG] [RECV] Y tool frame offset: 0.000\n", 33 | "[COM7] [DEBUG] [RECV] Z tool frame offset: 0.000\n", 34 | "[COM7] [DEBUG] [RECV] Line discard number: 0\n", 35 | "[COM7] [DEBUG] [RECV] Axis_7 mode: Rail mode\n", 36 | "[COM7] [DEBUG] [RECV] Initialized Cartesian coordinates and rotation:\n", 37 | "[COM7] [DEBUG] [RECV] X: 198.670\n", 38 | "[COM7] [DEBUG] [RECV] Y: 0.000\n", 39 | "[COM7] [DEBUG] [RECV] Z: 230.720\n", 40 | "[COM7] [DEBUG] [RECV] RX: 0.000\n", 41 | "[COM7] [DEBUG] [RECV] RY: 0.000\n", 42 | "[COM7] [DEBUG] [RECV] RZ: 0.000\n", 43 | "[COM7] [DEBUG] [RECV] Using reset pos!\n", 44 | "[COM7] [DEBUG] [RECV CACHE] \n", 45 | "Free memory: 2036\n", 46 | "\n", 47 | "[COM7] [DEBUG] [SENT] $H\n", 48 | "[COM7] [DEBUG] [RECV] in homeing moving...ok\n" 49 | ] 50 | }, 51 | { 52 | "data": { 53 | "text/plain": [ 54 | "['in homeing moving...ok']" 55 | ] 56 | }, 57 | "execution_count": 1, 58 | "metadata": {}, 59 | "output_type": "execute_result" 60 | } 61 | ], 62 | "source": [ 63 | "from mirobot import Mirobot\n", 64 | "import time\n", 65 | "arm = Mirobot(portname='COM7', debug=True)\n", 66 | "arm.home_simultaneous()" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 2, 72 | "metadata": {}, 73 | "outputs": [ 74 | { 75 | "name": "stderr", 76 | "output_type": "stream", 77 | "text": [ 78 | "[COM7] [DEBUG] [RECV CACHE] \n", 79 | "[COM7] [DEBUG] [SENT] ?\n", 80 | "[COM7] [DEBUG] [RECV] \n", 81 | "[COM7] [DEBUG] [RECV] ok\n" 82 | ] 83 | } 84 | ], 85 | "source": [ 86 | "arm.update_status()" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 12, 92 | "metadata": {}, 93 | "outputs": [ 94 | { 95 | "name": "stderr", 96 | "output_type": "stream", 97 | "text": [ 98 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 99 | "\n", 100 | "[COM7] [DEBUG] [SENT] M20 G90 G0 X198.67 Y20 Z230 F2000\n", 101 | "[COM7] [DEBUG] [RECV] ok\n", 102 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 103 | "\n", 104 | "[COM7] [DEBUG] [SENT] M20 G90 G0 X198.67 Y20 Z150 F2000\n", 105 | "[COM7] [DEBUG] [RECV] ok\n", 106 | "[COM7] [DEBUG] current mirobot state: Run\n", 107 | "[COM7] [DEBUG] current mirobot state: Run\n", 108 | "[COM7] [DEBUG] current mirobot state: Run\n", 109 | "[COM7] [DEBUG] current mirobot state: Run\n", 110 | "[COM7] [DEBUG] current mirobot state: Run\n", 111 | "[COM7] [DEBUG] current mirobot state: Run\n", 112 | "[COM7] [DEBUG] current mirobot state: Run\n", 113 | "[COM7] [DEBUG] current mirobot state: Run\n", 114 | "[COM7] [DEBUG] current mirobot state: Run\n", 115 | "[COM7] [DEBUG] current mirobot state: Run\n", 116 | "[COM7] [DEBUG] current mirobot state: Run\n", 117 | "[COM7] [DEBUG] current mirobot state: Run\n", 118 | "[COM7] [DEBUG] current mirobot state: Idle\n", 119 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 120 | "\n", 121 | "[COM7] [DEBUG] [SENT] M20 G90 G0 X150.0 Y20 Z230 F2000\n", 122 | "[COM7] [DEBUG] [RECV] ok\n", 123 | "[COM7] [DEBUG] current mirobot state: Run\n", 124 | "[COM7] [DEBUG] current mirobot state: Run\n", 125 | "[COM7] [DEBUG] current mirobot state: Run\n", 126 | "[COM7] [DEBUG] current mirobot state: Run\n", 127 | "[COM7] [DEBUG] current mirobot state: Run\n", 128 | "[COM7] [DEBUG] current mirobot state: Run\n", 129 | "[COM7] [DEBUG] current mirobot state: Run\n", 130 | "[COM7] [DEBUG] current mirobot state: Run\n", 131 | "[COM7] [DEBUG] current mirobot state: Run\n", 132 | "[COM7] [DEBUG] current mirobot state: Run\n", 133 | "[COM7] [DEBUG] current mirobot state: Run\n", 134 | "[COM7] [DEBUG] current mirobot state: Run\n", 135 | "[COM7] [DEBUG] current mirobot state: Run\n", 136 | "[COM7] [DEBUG] current mirobot state: Idle\n" 137 | ] 138 | }, 139 | { 140 | "data": { 141 | "text/plain": [ 142 | "['ok']" 143 | ] 144 | }, 145 | "execution_count": 12, 146 | "metadata": {}, 147 | "output_type": "execute_result" 148 | } 149 | ], 150 | "source": [ 151 | "print(\"运动到目标点 A\")\n", 152 | "arm.go_to_cartesian_ptp(200, 20, 230)\n", 153 | "print(f\"当前末端在机械臂坐标系下的位姿 {arm.cartesian}\")\n", 154 | "time.sleep(1)\n", 155 | "print(\"运动到目标点 B\")\n", 156 | "arm.go_to_cartesian_ptp(200, 20, 150)\n", 157 | "print(f\"当前末端在机械臂坐标系下的位姿 {arm.cartesian}\")\n", 158 | "time.sleep(1)\n", 159 | "print(\"运动到目标点 C\")\n", 160 | "arm.go_to_cartesian_ptp(150., -20, 230)\n", 161 | "print(f\"当前末端在机械臂坐标系下的位姿 {arm.cartesian}\")" 162 | ] 163 | }, 164 | { 165 | "cell_type": "code", 166 | "execution_count": null, 167 | "metadata": {}, 168 | "outputs": [], 169 | "source": [] 170 | }, 171 | { 172 | "cell_type": "code", 173 | "execution_count": 13, 174 | "metadata": {}, 175 | "outputs": [ 176 | { 177 | "name": "stderr", 178 | "output_type": "stream", 179 | "text": [ 180 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 181 | "\n", 182 | "[COM7] [DEBUG] [SENT] M20 G90 G0 X150.0 Y-20 Z230 F2000\n", 183 | "[COM7] [DEBUG] [RECV] ok\n", 184 | "[COM7] [DEBUG] current mirobot state: Run\n", 185 | "[COM7] [DEBUG] current mirobot state: Run\n", 186 | "[COM7] [DEBUG] current mirobot state: Run\n", 187 | "[COM7] [DEBUG] current mirobot state: Run\n", 188 | "[COM7] [DEBUG] current mirobot state: Run\n", 189 | "[COM7] [DEBUG] current mirobot state: Run\n", 190 | "[COM7] [DEBUG] current mirobot state: Run\n", 191 | "[COM7] [DEBUG] current mirobot state: Run\n", 192 | "[COM7] [DEBUG] current mirobot state: Idle\n" 193 | ] 194 | }, 195 | { 196 | "data": { 197 | "text/plain": [ 198 | "['ok']" 199 | ] 200 | }, 201 | "execution_count": 13, 202 | "metadata": {}, 203 | "output_type": "execute_result" 204 | } 205 | ], 206 | "source": [] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 10, 211 | "metadata": {}, 212 | "outputs": [ 213 | { 214 | "name": "stderr", 215 | "output_type": "stream", 216 | "text": [ 217 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 218 | "\n", 219 | "[COM7] [DEBUG] [SENT] M20 G90 G0 X198.67 Y20 Z150 F2000\n", 220 | "[COM7] [DEBUG] [RECV] ok\n", 221 | "[COM7] [DEBUG] current mirobot state: Run\n", 222 | "[COM7] [DEBUG] current mirobot state: Run\n", 223 | "[COM7] [DEBUG] current mirobot state: Run\n", 224 | "[COM7] [DEBUG] current mirobot state: Run\n", 225 | "[COM7] [DEBUG] current mirobot state: Run\n", 226 | "[COM7] [DEBUG] current mirobot state: Run\n", 227 | "[COM7] [DEBUG] current mirobot state: Run\n", 228 | "[COM7] [DEBUG] current mirobot state: Run\n", 229 | "[COM7] [DEBUG] current mirobot state: Run\n", 230 | "[COM7] [DEBUG] current mirobot state: Run\n", 231 | "[COM7] [DEBUG] current mirobot state: Run\n", 232 | "[COM7] [DEBUG] current mirobot state: Run\n", 233 | "[COM7] [DEBUG] current mirobot state: Idle\n" 234 | ] 235 | }, 236 | { 237 | "data": { 238 | "text/plain": [ 239 | "['ok']" 240 | ] 241 | }, 242 | "execution_count": 10, 243 | "metadata": {}, 244 | "output_type": "execute_result" 245 | } 246 | ], 247 | "source": [] 248 | }, 249 | { 250 | "cell_type": "code", 251 | "execution_count": null, 252 | "metadata": {}, 253 | "outputs": [], 254 | "source": [] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": 3, 259 | "metadata": {}, 260 | "outputs": [ 261 | { 262 | "data": { 263 | "text/plain": [ 264 | "MirobotStatus(state='Idle', angle=MirobotAngles(a=0.0, b=0.0, c=0.0, x=0.0, y=0.0, z=0.0, d=0.0), cartesian=MirobotCartesians(x=198.67, y=0.0, z=230.72, a=0.0, b=0.0, c=0.0), pump_pwm=0, valve_pwm=0, motion_mode=True)" 265 | ] 266 | }, 267 | "execution_count": 3, 268 | "metadata": {}, 269 | "output_type": "execute_result" 270 | } 271 | ], 272 | "source": [ 273 | "arm.status" 274 | ] 275 | }, 276 | { 277 | "cell_type": "code", 278 | "execution_count": 8, 279 | "metadata": {}, 280 | "outputs": [ 281 | { 282 | "name": "stderr", 283 | "output_type": "stream", 284 | "text": [ 285 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 286 | "\n", 287 | "[COM7] [DEBUG] [SENT] M20 G90 G1 X-3.985 Y241.5 Z190.19 A0 B0 C0 F2000\n", 288 | "[COM7] [DEBUG] [RECV] ok\n", 289 | "[COM7] [DEBUG] current mirobot state: Run\n", 290 | "[COM7] [DEBUG] current mirobot state: Run\n", 291 | "[COM7] [DEBUG] current mirobot state: Run\n", 292 | "[COM7] [DEBUG] current mirobot state: Run\n", 293 | "[COM7] [DEBUG] current mirobot state: Run\n", 294 | "[COM7] [DEBUG] current mirobot state: Run\n", 295 | "[COM7] [DEBUG] current mirobot state: Run\n", 296 | "[COM7] [DEBUG] current mirobot state: Run\n", 297 | "[COM7] [DEBUG] current mirobot state: Run\n", 298 | "[COM7] [DEBUG] current mirobot state: Run\n", 299 | "[COM7] [DEBUG] current mirobot state: Run\n", 300 | "[COM7] [DEBUG] current mirobot state: Run\n", 301 | "[COM7] [DEBUG] current mirobot state: Run\n", 302 | "[COM7] [DEBUG] current mirobot state: Run\n", 303 | "[COM7] [DEBUG] current mirobot state: Run\n", 304 | "[COM7] [DEBUG] current mirobot state: Run\n", 305 | "[COM7] [DEBUG] current mirobot state: Run\n", 306 | "[COM7] [DEBUG] current mirobot state: Run\n", 307 | "[COM7] [DEBUG] current mirobot state: Run\n", 308 | "[COM7] [DEBUG] current mirobot state: Run\n", 309 | "[COM7] [DEBUG] current mirobot state: Run\n", 310 | "[COM7] [DEBUG] current mirobot state: Run\n", 311 | "[COM7] [DEBUG] current mirobot state: Run\n", 312 | "[COM7] [DEBUG] current mirobot state: Run\n", 313 | "[COM7] [DEBUG] current mirobot state: Run\n", 314 | "[COM7] [DEBUG] current mirobot state: Run\n", 315 | "[COM7] [DEBUG] current mirobot state: Run\n", 316 | "[COM7] [DEBUG] current mirobot state: Run\n", 317 | "[COM7] [DEBUG] current mirobot state: Run\n", 318 | "[COM7] [DEBUG] current mirobot state: Run\n", 319 | "[COM7] [DEBUG] current mirobot state: Run\n", 320 | "[COM7] [DEBUG] current mirobot state: Run\n", 321 | "[COM7] [DEBUG] current mirobot state: Run\n", 322 | "[COM7] [DEBUG] current mirobot state: Run\n", 323 | "[COM7] [DEBUG] current mirobot state: Run\n", 324 | "[COM7] [DEBUG] current mirobot state: Run\n", 325 | "[COM7] [DEBUG] current mirobot state: Run\n", 326 | "[COM7] [DEBUG] current mirobot state: Run\n", 327 | "[COM7] [DEBUG] current mirobot state: Run\n", 328 | "[COM7] [DEBUG] current mirobot state: Idle\n" 329 | ] 330 | }, 331 | { 332 | "data": { 333 | "text/plain": [ 334 | "['ok']" 335 | ] 336 | }, 337 | "execution_count": 8, 338 | "metadata": {}, 339 | "output_type": "execute_result" 340 | } 341 | ], 342 | "source": [ 343 | "arm.go_to_cartesian_lin(x=-3.985, y=241.50, z=190.19, a=0, b=0, c=0)" 344 | ] 345 | }, 346 | { 347 | "cell_type": "code", 348 | "execution_count": 11, 349 | "metadata": {}, 350 | "outputs": [ 351 | { 352 | "data": { 353 | "text/plain": [ 354 | "MirobotCartesians(x=-3.985, y=241.503, z=190.191, a=28.916, b=13.226, c=139.337)" 355 | ] 356 | }, 357 | "execution_count": 11, 358 | "metadata": {}, 359 | "output_type": "execute_result" 360 | } 361 | ], 362 | "source": [ 363 | "arm.cartesian" 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": 10, 369 | "metadata": {}, 370 | "outputs": [ 371 | { 372 | "name": "stdout", 373 | "output_type": "stream", 374 | "text": [ 375 | "Help on method go_to_cartesian_lin in module mirobot.mirobot:\n", 376 | "\n", 377 | "go_to_cartesian_lin(x=None, y=None, z=None, a=None, b=None, c=None, speed=None, wait=None) method of mirobot.mirobot.Mirobot instance\n", 378 | " Linear move to a position in cartesian coordinates. (Command: `M20 G90 G1`)\n", 379 | " \n", 380 | " Parameters\n", 381 | " ----------\n", 382 | " x : Union[float, mirobot.mirobot_status.MirobotCartesians]\n", 383 | " (Default value = `None`) If `float`, this represents the X-axis position.\n", 384 | " If of type `mirobot.mirobot_status.MirobotCartesians`, then this will be used for all positional values instead.\n", 385 | " y : float\n", 386 | " (Default value = `None`) Y-axis position.\n", 387 | " z : float\n", 388 | " (Default value = `None`) Z-axis position.\n", 389 | " a : float\n", 390 | " (Default value = `None`) Orientation angle: Roll angle\n", 391 | " b : float\n", 392 | " (Default value = `None`) Orientation angle: Pitch angle\n", 393 | " c : float\n", 394 | " (Default value = `None`) Orientation angle: Yaw angle\n", 395 | " speed : int\n", 396 | " (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s)\n", 397 | " wait : bool\n", 398 | " (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead.\n", 399 | " \n", 400 | " Returns\n", 401 | " -------\n", 402 | " msg : List[str] or bool\n", 403 | " If `wait` is `True`, then return a list of strings which contains message output.\n", 404 | " If `wait` is `False`, then return whether sending the message succeeded.\n", 405 | "\n" 406 | ] 407 | } 408 | ], 409 | "source": [ 410 | "help(arm.go_to_cartesian_lin)" 411 | ] 412 | } 413 | ], 414 | "metadata": { 415 | "kernelspec": { 416 | "display_name": "Python 3", 417 | "language": "python", 418 | "name": "python3" 419 | }, 420 | "language_info": { 421 | "codemirror_mode": { 422 | "name": "ipython", 423 | "version": 3 424 | }, 425 | "file_extension": ".py", 426 | "mimetype": "text/x-python", 427 | "name": "python", 428 | "nbconvert_exporter": "python", 429 | "pygments_lexer": "ipython3", 430 | "version": "3.7.8" 431 | } 432 | }, 433 | "nbformat": 4, 434 | "nbformat_minor": 4 435 | } 436 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sourabh Cheedella 4 | 5 | Copyright (c) 2020 Matthew Wachter 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /Mirobot-PythonSDK测试脚本.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "scrolled": true 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stderr", 12 | "output_type": "stream", 13 | "text": [ 14 | "[] [DEBUG] Welcome to use mirobot_py (version: kyle2020-0821)\n", 15 | "[] [DEBUG] Attempting to open serial port COM7\n", 16 | "[] [DEBUG] Succeeded in opening serial port COM7\n", 17 | "[COM7] [DEBUG] [RECV] \n", 18 | "[COM7] [DEBUG] [RECV] Grbl 0.9j ['$' for help]\n", 19 | "[COM7] [DEBUG] [RECV] \n", 20 | "[COM7] [DEBUG] [RECV] Qinnew Robot 20200803_TEST_1 based on Grbl 0.9j ['$' for help]\n", 21 | "[COM7] [DEBUG] [RECV] \n", 22 | "[COM7] [DEBUG] [RECV] D1: 127.000\n", 23 | "[COM7] [DEBUG] [RECV] A1: 29.690\n", 24 | "[COM7] [DEBUG] [RECV] A2: 108.000\n", 25 | "[COM7] [DEBUG] [RECV] A3: 20.000\n", 26 | "[COM7] [DEBUG] [RECV] D4: 168.980\n", 27 | "[COM7] [DEBUG] [RECV] L: -24.280\n", 28 | "[COM7] [DEBUG] [RECV] X offset: 0.000\n", 29 | "[COM7] [DEBUG] [RECV] Y offset: 0.000\n", 30 | "[COM7] [DEBUG] [RECV] Z offset: 0.000\n", 31 | "[COM7] [DEBUG] [RECV] X tool frame offset: 0.000\n", 32 | "[COM7] [DEBUG] [RECV] Y tool frame offset: 0.000\n", 33 | "[COM7] [DEBUG] [RECV] Z tool frame offset: 0.000\n", 34 | "[COM7] [DEBUG] [RECV] Line discard number: 0\n", 35 | "[COM7] [DEBUG] [RECV] Axis_7 mode: Rail mode\n", 36 | "[COM7] [DEBUG] [RECV] Initialized Cartesian coordinates and rotation:\n", 37 | "[COM7] [DEBUG] [RECV] X: 198.670\n", 38 | "[COM7] [DEBUG] [RECV] Y: 0.000\n", 39 | "[COM7] [DEBUG] [RECV] Z: 230.720\n", 40 | "[COM7] [DEBUG] [RECV] RX: 0.000\n", 41 | "[COM7] [DEBUG] [RECV] RY: 0.000\n", 42 | "[COM7] [DEBUG] [RECV] RZ: 0.000\n", 43 | "[COM7] [DEBUG] [RECV] Using reset pos!\n", 44 | "[COM7] [DEBUG] [RECV CACHE] \n", 45 | "Free memory: 2036\n", 46 | "\n", 47 | "[COM7] [DEBUG] [SENT] $H\n", 48 | "[COM7] [DEBUG] [RECV] in homeing moving...ok\n" 49 | ] 50 | }, 51 | { 52 | "data": { 53 | "text/plain": [ 54 | "['in homeing moving...ok']" 55 | ] 56 | }, 57 | "execution_count": 1, 58 | "metadata": {}, 59 | "output_type": "execute_result" 60 | } 61 | ], 62 | "source": [ 63 | "from mirobot import Mirobot\n", 64 | "import time\n", 65 | "arm = Mirobot(portname='COM7', debug=True)\n", 66 | "arm.home_simultaneous()" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 2, 72 | "metadata": {}, 73 | "outputs": [ 74 | { 75 | "name": "stderr", 76 | "output_type": "stream", 77 | "text": [ 78 | "[COM7] [DEBUG] [RECV CACHE] \n", 79 | "[COM7] [DEBUG] [SENT] ?\n", 80 | "[COM7] [DEBUG] [RECV] \n", 81 | "[COM7] [DEBUG] [RECV] ok\n" 82 | ] 83 | } 84 | ], 85 | "source": [ 86 | "arm.update_status()" 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 16, 92 | "metadata": {}, 93 | "outputs": [ 94 | { 95 | "name": "stderr", 96 | "output_type": "stream", 97 | "text": [ 98 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 99 | "\n", 100 | "[COM7] [DEBUG] [SENT] M20 G90 G0 X200 Y20 Z230 F2000\n", 101 | "[COM7] [DEBUG] [RECV] ok\n" 102 | ] 103 | }, 104 | { 105 | "name": "stdout", 106 | "output_type": "stream", 107 | "text": [ 108 | "运动到目标点 A\n" 109 | ] 110 | }, 111 | { 112 | "name": "stderr", 113 | "output_type": "stream", 114 | "text": [ 115 | "[COM7] [DEBUG] current mirobot state: Run\n", 116 | "[COM7] [DEBUG] current mirobot state: Run\n", 117 | "[COM7] [DEBUG] current mirobot state: Run\n", 118 | "[COM7] [DEBUG] current mirobot state: Run\n", 119 | "[COM7] [DEBUG] current mirobot state: Run\n", 120 | "[COM7] [DEBUG] current mirobot state: Run\n", 121 | "[COM7] [DEBUG] current mirobot state: Run\n", 122 | "[COM7] [DEBUG] current mirobot state: Run\n", 123 | "[COM7] [DEBUG] current mirobot state: Run\n", 124 | "[COM7] [DEBUG] current mirobot state: Run\n", 125 | "[COM7] [DEBUG] current mirobot state: Run\n", 126 | "[COM7] [DEBUG] current mirobot state: Idle\n" 127 | ] 128 | }, 129 | { 130 | "name": "stdout", 131 | "output_type": "stream", 132 | "text": [ 133 | "当前末端在机械臂坐标系下的位姿 MirobotCartesians(x=199.99, y=20.001, z=229.724, a=-0.001, b=0.015, c=0.041)\n" 134 | ] 135 | }, 136 | { 137 | "name": "stderr", 138 | "output_type": "stream", 139 | "text": [ 140 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 141 | "\n", 142 | "[COM7] [DEBUG] [SENT] M20 G90 G0 X200 Y20 Z150 F2000\n", 143 | "[COM7] [DEBUG] [RECV] ok\n", 144 | "[COM7] [DEBUG] current mirobot state: Run\n" 145 | ] 146 | }, 147 | { 148 | "name": "stdout", 149 | "output_type": "stream", 150 | "text": [ 151 | "运动到目标点 B\n" 152 | ] 153 | }, 154 | { 155 | "name": "stderr", 156 | "output_type": "stream", 157 | "text": [ 158 | "[COM7] [DEBUG] current mirobot state: Run\n", 159 | "[COM7] [DEBUG] current mirobot state: Run\n", 160 | "[COM7] [DEBUG] current mirobot state: Run\n", 161 | "[COM7] [DEBUG] current mirobot state: Run\n", 162 | "[COM7] [DEBUG] current mirobot state: Run\n", 163 | "[COM7] [DEBUG] current mirobot state: Run\n", 164 | "[COM7] [DEBUG] current mirobot state: Run\n", 165 | "[COM7] [DEBUG] current mirobot state: Run\n", 166 | "[COM7] [DEBUG] current mirobot state: Run\n", 167 | "[COM7] [DEBUG] current mirobot state: Run\n", 168 | "[COM7] [DEBUG] current mirobot state: Run\n", 169 | "[COM7] [DEBUG] current mirobot state: Idle\n" 170 | ] 171 | }, 172 | { 173 | "name": "stdout", 174 | "output_type": "stream", 175 | "text": [ 176 | "当前末端在机械臂坐标系下的位姿 MirobotCartesians(x=199.987, y=20.001, z=149.696, a=-0.002, b=0.019, c=0.041)\n" 177 | ] 178 | }, 179 | { 180 | "name": "stderr", 181 | "output_type": "stream", 182 | "text": [ 183 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 184 | "\n", 185 | "[COM7] [DEBUG] [SENT] M20 G90 G0 X150.0 Y-20 Z230 F2000\n", 186 | "[COM7] [DEBUG] [RECV] ok\n", 187 | "[COM7] [DEBUG] current mirobot state: Run\n" 188 | ] 189 | }, 190 | { 191 | "name": "stdout", 192 | "output_type": "stream", 193 | "text": [ 194 | "运动到目标点 C\n" 195 | ] 196 | }, 197 | { 198 | "name": "stderr", 199 | "output_type": "stream", 200 | "text": [ 201 | "[COM7] [DEBUG] current mirobot state: Run\n", 202 | "[COM7] [DEBUG] current mirobot state: Run\n", 203 | "[COM7] [DEBUG] current mirobot state: Run\n", 204 | "[COM7] [DEBUG] current mirobot state: Run\n", 205 | "[COM7] [DEBUG] current mirobot state: Run\n", 206 | "[COM7] [DEBUG] current mirobot state: Run\n", 207 | "[COM7] [DEBUG] current mirobot state: Run\n", 208 | "[COM7] [DEBUG] current mirobot state: Run\n", 209 | "[COM7] [DEBUG] current mirobot state: Run\n", 210 | "[COM7] [DEBUG] current mirobot state: Run\n", 211 | "[COM7] [DEBUG] current mirobot state: Run\n", 212 | "[COM7] [DEBUG] current mirobot state: Run\n", 213 | "[COM7] [DEBUG] current mirobot state: Idle\n" 214 | ] 215 | }, 216 | { 217 | "name": "stdout", 218 | "output_type": "stream", 219 | "text": [ 220 | "当前末端在机械臂坐标系下的位姿 MirobotCartesians(x=150.001, y=-20.008, z=229.707, a=-0.0, b=-0.004, c=-0.037)\n" 221 | ] 222 | } 223 | ], 224 | "source": [ 225 | "print(\"运动到目标点 A\")\n", 226 | "arm.go_to_cartesian_ptp(200, 20, 230)\n", 227 | "print(f\"当前末端在机械臂坐标系下的位姿 {arm.cartesian}\")\n", 228 | "time.sleep(1)\n", 229 | "print(\"运动到目标点 B\")\n", 230 | "arm.go_to_cartesian_ptp(200, 20, 150)\n", 231 | "print(f\"当前末端在机械臂坐标系下的位姿 {arm.cartesian}\")\n", 232 | "time.sleep(1)\n", 233 | "print(\"运动到目标点 C\")\n", 234 | "arm.go_to_cartesian_ptp(150., -20, 230)\n", 235 | "print(f\"当前末端在机械臂坐标系下的位姿 {arm.cartesian}\")" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": null, 241 | "metadata": {}, 242 | "outputs": [], 243 | "source": [] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": 13, 248 | "metadata": {}, 249 | "outputs": [ 250 | { 251 | "name": "stderr", 252 | "output_type": "stream", 253 | "text": [ 254 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 255 | "\n", 256 | "[COM7] [DEBUG] [SENT] M20 G90 G0 X150.0 Y-20 Z230 F2000\n", 257 | "[COM7] [DEBUG] [RECV] ok\n", 258 | "[COM7] [DEBUG] current mirobot state: Run\n", 259 | "[COM7] [DEBUG] current mirobot state: Run\n", 260 | "[COM7] [DEBUG] current mirobot state: Run\n", 261 | "[COM7] [DEBUG] current mirobot state: Run\n", 262 | "[COM7] [DEBUG] current mirobot state: Run\n", 263 | "[COM7] [DEBUG] current mirobot state: Run\n", 264 | "[COM7] [DEBUG] current mirobot state: Run\n", 265 | "[COM7] [DEBUG] current mirobot state: Run\n", 266 | "[COM7] [DEBUG] current mirobot state: Idle\n" 267 | ] 268 | }, 269 | { 270 | "data": { 271 | "text/plain": [ 272 | "['ok']" 273 | ] 274 | }, 275 | "execution_count": 13, 276 | "metadata": {}, 277 | "output_type": "execute_result" 278 | } 279 | ], 280 | "source": [] 281 | }, 282 | { 283 | "cell_type": "code", 284 | "execution_count": 10, 285 | "metadata": {}, 286 | "outputs": [ 287 | { 288 | "name": "stderr", 289 | "output_type": "stream", 290 | "text": [ 291 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 292 | "\n", 293 | "[COM7] [DEBUG] [SENT] M20 G90 G0 X198.67 Y20 Z150 F2000\n", 294 | "[COM7] [DEBUG] [RECV] ok\n", 295 | "[COM7] [DEBUG] current mirobot state: Run\n", 296 | "[COM7] [DEBUG] current mirobot state: Run\n", 297 | "[COM7] [DEBUG] current mirobot state: Run\n", 298 | "[COM7] [DEBUG] current mirobot state: Run\n", 299 | "[COM7] [DEBUG] current mirobot state: Run\n", 300 | "[COM7] [DEBUG] current mirobot state: Run\n", 301 | "[COM7] [DEBUG] current mirobot state: Run\n", 302 | "[COM7] [DEBUG] current mirobot state: Run\n", 303 | "[COM7] [DEBUG] current mirobot state: Run\n", 304 | "[COM7] [DEBUG] current mirobot state: Run\n", 305 | "[COM7] [DEBUG] current mirobot state: Run\n", 306 | "[COM7] [DEBUG] current mirobot state: Run\n", 307 | "[COM7] [DEBUG] current mirobot state: Idle\n" 308 | ] 309 | }, 310 | { 311 | "data": { 312 | "text/plain": [ 313 | "['ok']" 314 | ] 315 | }, 316 | "execution_count": 10, 317 | "metadata": {}, 318 | "output_type": "execute_result" 319 | } 320 | ], 321 | "source": [] 322 | }, 323 | { 324 | "cell_type": "code", 325 | "execution_count": null, 326 | "metadata": {}, 327 | "outputs": [], 328 | "source": [] 329 | }, 330 | { 331 | "cell_type": "code", 332 | "execution_count": 3, 333 | "metadata": {}, 334 | "outputs": [ 335 | { 336 | "data": { 337 | "text/plain": [ 338 | "MirobotStatus(state='Idle', angle=MirobotAngles(a=0.0, b=0.0, c=0.0, x=0.0, y=0.0, z=0.0, d=0.0), cartesian=MirobotCartesians(x=198.67, y=0.0, z=230.72, a=0.0, b=0.0, c=0.0), pump_pwm=0, valve_pwm=0, motion_mode=True)" 339 | ] 340 | }, 341 | "execution_count": 3, 342 | "metadata": {}, 343 | "output_type": "execute_result" 344 | } 345 | ], 346 | "source": [ 347 | "arm.status" 348 | ] 349 | }, 350 | { 351 | "cell_type": "code", 352 | "execution_count": 8, 353 | "metadata": {}, 354 | "outputs": [ 355 | { 356 | "name": "stderr", 357 | "output_type": "stream", 358 | "text": [ 359 | "[COM7] [DEBUG] [RECV CACHE] ok\n", 360 | "\n", 361 | "[COM7] [DEBUG] [SENT] M20 G90 G1 X-3.985 Y241.5 Z190.19 A0 B0 C0 F2000\n", 362 | "[COM7] [DEBUG] [RECV] ok\n", 363 | "[COM7] [DEBUG] current mirobot state: Run\n", 364 | "[COM7] [DEBUG] current mirobot state: Run\n", 365 | "[COM7] [DEBUG] current mirobot state: Run\n", 366 | "[COM7] [DEBUG] current mirobot state: Run\n", 367 | "[COM7] [DEBUG] current mirobot state: Run\n", 368 | "[COM7] [DEBUG] current mirobot state: Run\n", 369 | "[COM7] [DEBUG] current mirobot state: Run\n", 370 | "[COM7] [DEBUG] current mirobot state: Run\n", 371 | "[COM7] [DEBUG] current mirobot state: Run\n", 372 | "[COM7] [DEBUG] current mirobot state: Run\n", 373 | "[COM7] [DEBUG] current mirobot state: Run\n", 374 | "[COM7] [DEBUG] current mirobot state: Run\n", 375 | "[COM7] [DEBUG] current mirobot state: Run\n", 376 | "[COM7] [DEBUG] current mirobot state: Run\n", 377 | "[COM7] [DEBUG] current mirobot state: Run\n", 378 | "[COM7] [DEBUG] current mirobot state: Run\n", 379 | "[COM7] [DEBUG] current mirobot state: Run\n", 380 | "[COM7] [DEBUG] current mirobot state: Run\n", 381 | "[COM7] [DEBUG] current mirobot state: Run\n", 382 | "[COM7] [DEBUG] current mirobot state: Run\n", 383 | "[COM7] [DEBUG] current mirobot state: Run\n", 384 | "[COM7] [DEBUG] current mirobot state: Run\n", 385 | "[COM7] [DEBUG] current mirobot state: Run\n", 386 | "[COM7] [DEBUG] current mirobot state: Run\n", 387 | "[COM7] [DEBUG] current mirobot state: Run\n", 388 | "[COM7] [DEBUG] current mirobot state: Run\n", 389 | "[COM7] [DEBUG] current mirobot state: Run\n", 390 | "[COM7] [DEBUG] current mirobot state: Run\n", 391 | "[COM7] [DEBUG] current mirobot state: Run\n", 392 | "[COM7] [DEBUG] current mirobot state: Run\n", 393 | "[COM7] [DEBUG] current mirobot state: Run\n", 394 | "[COM7] [DEBUG] current mirobot state: Run\n", 395 | "[COM7] [DEBUG] current mirobot state: Run\n", 396 | "[COM7] [DEBUG] current mirobot state: Run\n", 397 | "[COM7] [DEBUG] current mirobot state: Run\n", 398 | "[COM7] [DEBUG] current mirobot state: Run\n", 399 | "[COM7] [DEBUG] current mirobot state: Run\n", 400 | "[COM7] [DEBUG] current mirobot state: Run\n", 401 | "[COM7] [DEBUG] current mirobot state: Run\n", 402 | "[COM7] [DEBUG] current mirobot state: Idle\n" 403 | ] 404 | }, 405 | { 406 | "data": { 407 | "text/plain": [ 408 | "['ok']" 409 | ] 410 | }, 411 | "execution_count": 8, 412 | "metadata": {}, 413 | "output_type": "execute_result" 414 | } 415 | ], 416 | "source": [ 417 | "arm.go_to_cartesian_lin(x=-3.985, y=241.50, z=190.19, a=0, b=0, c=0)" 418 | ] 419 | }, 420 | { 421 | "cell_type": "code", 422 | "execution_count": 11, 423 | "metadata": {}, 424 | "outputs": [ 425 | { 426 | "data": { 427 | "text/plain": [ 428 | "MirobotCartesians(x=-3.985, y=241.503, z=190.191, a=28.916, b=13.226, c=139.337)" 429 | ] 430 | }, 431 | "execution_count": 11, 432 | "metadata": {}, 433 | "output_type": "execute_result" 434 | } 435 | ], 436 | "source": [ 437 | "arm.cartesian" 438 | ] 439 | }, 440 | { 441 | "cell_type": "code", 442 | "execution_count": 10, 443 | "metadata": {}, 444 | "outputs": [ 445 | { 446 | "name": "stdout", 447 | "output_type": "stream", 448 | "text": [ 449 | "Help on method go_to_cartesian_lin in module mirobot.mirobot:\n", 450 | "\n", 451 | "go_to_cartesian_lin(x=None, y=None, z=None, a=None, b=None, c=None, speed=None, wait=None) method of mirobot.mirobot.Mirobot instance\n", 452 | " Linear move to a position in cartesian coordinates. (Command: `M20 G90 G1`)\n", 453 | " \n", 454 | " Parameters\n", 455 | " ----------\n", 456 | " x : Union[float, mirobot.mirobot_status.MirobotCartesians]\n", 457 | " (Default value = `None`) If `float`, this represents the X-axis position.\n", 458 | " If of type `mirobot.mirobot_status.MirobotCartesians`, then this will be used for all positional values instead.\n", 459 | " y : float\n", 460 | " (Default value = `None`) Y-axis position.\n", 461 | " z : float\n", 462 | " (Default value = `None`) Z-axis position.\n", 463 | " a : float\n", 464 | " (Default value = `None`) Orientation angle: Roll angle\n", 465 | " b : float\n", 466 | " (Default value = `None`) Orientation angle: Pitch angle\n", 467 | " c : float\n", 468 | " (Default value = `None`) Orientation angle: Yaw angle\n", 469 | " speed : int\n", 470 | " (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s)\n", 471 | " wait : bool\n", 472 | " (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead.\n", 473 | " \n", 474 | " Returns\n", 475 | " -------\n", 476 | " msg : List[str] or bool\n", 477 | " If `wait` is `True`, then return a list of strings which contains message output.\n", 478 | " If `wait` is `False`, then return whether sending the message succeeded.\n", 479 | "\n" 480 | ] 481 | } 482 | ], 483 | "source": [ 484 | "help(arm.go_to_cartesian_lin)" 485 | ] 486 | } 487 | ], 488 | "metadata": { 489 | "kernelspec": { 490 | "display_name": "Python 3", 491 | "language": "python", 492 | "name": "python3" 493 | }, 494 | "language_info": { 495 | "codemirror_mode": { 496 | "name": "ipython", 497 | "version": 3 498 | }, 499 | "file_extension": ".py", 500 | "mimetype": "text/x-python", 501 | "name": "python", 502 | "nbconvert_exporter": "python", 503 | "pygments_lexer": "ipython3", 504 | "version": "3.7.8" 505 | } 506 | }, 507 | "nbformat": 4, 508 | "nbformat_minor": 4 509 | } 510 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mirobot 2 | 3 | ## Description 4 | 5 | `mirobot` is a python module that can be used to control the [WLkata Mirobot](http://www.wlkata.com/site/index.html) 6 | 7 | ![Mirobot](/images/Mirobot_Solo_256.jpg) 8 | 9 | This library uses the G code protocol to communicate with the Mirobot over a serial connection. The official **G code instruction set** and **driver download** for the Mirobot can be found at the [WLkata Download Page](http://www.wlkata.com/site/downloads.html) 10 | 11 | ## Installation 12 | 13 | `mirobot` requires Python >= 3.6. Use `pip3` to install it: 14 | 15 | ```bash 16 | pip3 install mirobot-py 17 | ``` 18 | 19 | Make sure to **not** install the `mirobot` package-- that package is unrelated to this one. 20 | 21 | ## Example Usage 22 | 23 | ```python3 24 | from mirobot import Mirobot 25 | 26 | with Mirobot(portname='COM3', debug=True) as m: 27 | m.home_individual() 28 | 29 | m.go_to_zero() 30 | ``` 31 | 32 | And that's it! Now if you want to save keystrokes, here's a even more minimal version: 33 | 34 | ```python3 35 | from mirobot import Mirobot 36 | 37 | with Mirobot() as m: 38 | m.home_simultaneous() 39 | ``` 40 | 41 | The `Mirobot` class can detect existing open serial ports and "guess" which one to use as the Mirobot. There's no need to specify a portname for most cases! 42 | 43 | ## Documentation 44 | 45 | Many of the functions and structures in this library are documented. The documentation is hosted [here](https://rirze.github.io/mirobot-py/). If anything is unclear in the docs, please open a Github issue. 46 | 47 | ## Differences from source repository 48 | 49 | ### Credits 50 | 51 | Big thanks to Mattew Wachter for laying down the framework for this library-- please check out his links below: 52 | 53 | [Matthew Wachter](https://www.matthewwachter.com) 54 | 55 | [VT Pro Design](https://www.vtprodesign.com) 56 | 57 | ### Reasons to fork (and not merge upstream) 58 | 59 | While based of the same code initially, this repository has developed in a different direction with opinionated views on how one should use a robotics library. Specifically, there is the problem of 'output' when operating a gcode-programmed machine like Mirobot. 60 | 61 | - [Matthew's library](https://github.com/matthewwachter/py-mirobot) takes the traditional approach to recieving output from the robot as they appear. Basically this replicates the live terminal feedback in a client similar to Wlkata's Studio program. The original code has a thread listening the background for new messages and displays them as they appear. 62 | 63 | - This repository intends to take a more programmatic approach to this behavior. Specifically it narrows down the path to responsibility by explicitly pairing each command to its output. In a stream-messages-as-they-come approach to output messaging, it is not clear (or atleast easy) to determine which command failed and how to ensure scripts stop execution at exactly the point of failure (and not after). That is why each instruction in this library has a dedicated output, ensuring success and having its own message output as a return value. This approach is a lot harder to construct and relies on adapting to the idiosyncrasies of gcode and Mirobot programming. 64 | 65 | In the end, while developing this approach to error responsibility, I realized that this would probably not suit everyone's needs-- sometimes people just want a live feed of output. That is why I think Matthew's continued work would be great for the community. I don't want this repository and its beliefs to consume another. I also do not see a way to combine both approaches-- they are inherently incompatible at the core level. 66 | 67 | It is my belief that people who are looking to do significant scripting and logic-testing routines will benefit greatly from this library. People who are looking to use a CLI-friendly framework should instead use Matthew's [`py-mirobot`](https://github.com/matthewwachter/py-mirobot) library. 68 | 69 | ## License 70 | 71 | License: [MIT](https://github.com/rirze/mirobot-py/blob/master/LICENSE) 72 | -------------------------------------------------------------------------------- /docs/Mirobot-Python使用案例/Mirobot-Python例程.md: -------------------------------------------------------------------------------- 1 | # Mirobot-Python例程 2 | 3 | 4 | 5 | ## 机械臂回归机械零点与状态查询 6 | **例程源码** 7 | ```python 8 | ''' 9 | 机械臂回归机械零点与状态查询 10 | ''' 11 | from mirobot import Mirobot 12 | import time 13 | 14 | print("实例化Mirobot机械臂实例") 15 | arm = Mirobot(portname='COM7', debug=False) 16 | 17 | # 机械臂Home 多轴并行 18 | print("机械臂Homing开始") 19 | arm.home_simultaneous(wait=True) 20 | print("机械臂Homing结束") 21 | 22 | # 状态更新与查询 23 | print("更新机械臂状态") 24 | arm.update_status() 25 | print(f"更新后的状态对象: {arm.status}") 26 | print(f"更新后的状态名称: {arm.status.state}") 27 | ``` 28 | 29 | **输出日志** 30 | ``` 31 | 实例化Mirobot机械臂实例 32 | 机械臂Homing开始 33 | 机械臂Homing结束 34 | 更新机械臂状态 35 | 更新后的状态对象: MirobotStatus(state='Idle', angle=MirobotAngles(a=0.0, b=0.0, c=0.0, x=0.0, y=0.0, z=0.0, d=0.0), cartesian=MirobotCartesians(x=198.67, y=0.0, z=230.72, a=0.0, b=0.0, c=0.0), pump_pwm=0, valve_pwm=0, motion_mode=True) 36 | 更新后的状态名称: Idle 37 | ``` 38 | ## 设置关节角度 39 | 40 | **例程源码** 41 | ```python 42 | ''' 43 | 设置机械臂关节的角度, 单位° 44 | ''' 45 | from mirobot import Mirobot 46 | import time 47 | arm = Mirobot(portname='COM7', debug=False) 48 | arm.home_simultaneous() 49 | 50 | # 设置单个关节的角度 51 | print("测试设置单个关节的角度") 52 | arm.set_joint_angle({1:100.0}, wait=True) 53 | print("动作执行完毕") 54 | # 状态查询 55 | print(f"状态查询: {arm.get_status()}") 56 | # 停顿2s 57 | time.sleep(2) 58 | 59 | # 设置多个关节的角度 60 | print("设置多个关节的角度") 61 | target_angles = {1:90.0, 2:30.0, 3:-20.0, 4:10.0, 5:0.0, 6:90.0} 62 | arm.set_joint_angle(target_angles, wait=True) 63 | print("动作执行完毕") 64 | # 状态查询 65 | print(f"状态查询: {arm.get_status()}") 66 | # 停顿2s 67 | time.sleep(2) 68 | 69 | ``` 70 | 71 | **输出日志** 72 | 73 | ``` 74 | 测试设置单个关节的角度 75 | 动作执行完毕 76 | 状态查询: ['', 'ok'] 77 | 设置多个关节的角度 78 | 动作执行完毕 79 | 状态查询: ['', 'ok'] 80 | ``` 81 | 82 | ## 设置机械臂末端的位姿(Point2Point) 83 | 84 | **例程源码** 85 | ```python 86 | ''' 87 | 机械臂腕关节的位置控制, 点控 point to point 88 | ''' 89 | from mirobot import Mirobot 90 | import time 91 | arm = Mirobot(portname='COM7', debug=False) 92 | arm.home_simultaneous() 93 | 94 | print("运动到目标点 A") 95 | arm.set_wrist_pose(200, 20, 230, mode="p2p") 96 | print(f"当前末端在机械臂坐标系下的位姿 {arm.pose}") 97 | time.sleep(1) 98 | 99 | print("运动到目标点 B") 100 | arm.set_wrist_pose(200, 20, 150, mode="linear") 101 | print(f"当前末端在机械臂坐标系下的位姿 {arm.pose}") 102 | time.sleep(1) 103 | 104 | print("运动到目标点 C, 指定末端的姿态角") 105 | arm.set_wrist_pose(150, -20, 230, roll=30.0, pitch=0, yaw=45.0) 106 | print(f"当前末端在机械臂坐标系下的位姿 {arm.pose}") 107 | ``` 108 | 109 | **日志输出** 110 | 111 | ``` 112 | 运动到目标点 A 113 | 当前末端在机械臂坐标系下的位姿 Pose(x=199.99,y=20.001,z=229.724,roll=-0.001,pitch=0.015,yaw=0.041) 114 | 运动到目标点 B 115 | 当前末端在机械臂坐标系下的位姿 Pose(x=199.987,y=20.001,z=149.696,roll=-0.002,pitch=0.019,yaw=0.041) 116 | 运动到目标点 C, 指定末端的姿态角 117 | 当前末端在机械臂坐标系下的位姿 Pose(x=149.858,y=-19.859,z=229.829,roll=44.991,pitch=-0.031,yaw=45.001) 118 | ``` 119 | 120 | 121 | ## 气泵控制 122 | 123 | **例程源码** 124 | ```python 125 | ''' 126 | 气泵控制 127 | ''' 128 | from mirobot import Mirobot 129 | import time 130 | arm = Mirobot(portname='COM7', debug=False) 131 | arm.home_simultaneous() 132 | 133 | # 气泵开启 134 | arm.pump_on() 135 | # 等待5s 136 | time.sleep(5) 137 | # 气泵关闭 138 | arm.pump_off() 139 | ``` 140 | 141 | -------------------------------------------------------------------------------- /docs/Mirobot-Python使用案例/image/p2p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlkata/mirobot-py/28f7efa5fe8ae27740068de2cf0322c3599c3e17/docs/Mirobot-Python使用案例/image/p2p.png -------------------------------------------------------------------------------- /docs/Mirobot-Py项目开发日志/开发日志.md: -------------------------------------------------------------------------------- 1 | # 开发日志 2 | 3 | ## SDK修改日志 4 | * 发送消息前先清空缓冲区 5 | * 修改wait ok的处理逻辑 6 | * 修改wait for idle的逻辑 7 | * 修改程序中断不了的问题 8 | key interrupt 9 | 还有其他类型的中断s 10 | * 解决状态查询存在一定概率失败的情况 11 | 多重复几次 12 | * [TODO]重新设计API 13 | * set_joint_angle(); 带角度偏移量 14 | * [TODO]修改气泵模块的API 15 | True跟False颠倒了 16 | ```python 17 | arm.set_valve(True) 18 | arm.set_air_pump(False) 19 | ``` 20 | * [TODO] 测试笛卡尔坐标系 21 | 22 | **PROBLEM** 23 | * 获取的机械臂位姿`Cartesian `不对,会被阻塞 24 | * 程序阻塞的时候不能中断 -------------------------------------------------------------------------------- /docs/Mirobot机械臂校准/Mirobot机械臂校准.md: -------------------------------------------------------------------------------- 1 | # Mirobot机械臂校准 2 | 3 | 4 | 5 | 6 | 7 | [Mirobot使用教程—Mirobot机械臂的校准](https://www.bilibili.com/video/BV1ra4y1v7ZQ?from=search&seid=13654227412522230733) 8 | 9 | 10 | 11 | 1. 首先需要清零,然后复位 12 | 13 | 2. 设置-> 机械臂校准开始 -> 确定 14 | 3. 调整机械臂到预设位姿 15 | 4. 设置-> 机械臂校准保存 -> 确定 16 | 17 | -------------------------------------------------------------------------------- /docs/PythonSDK-API手册(en)/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /docs/PythonSDK-API手册(en)/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/PythonSDK-API手册(en)/mirobot/base_rover.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mirobot.base_rover API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

Module mirobot.base_rover

21 |
22 |
23 |
24 | 25 | Expand source code 26 | 27 |
import functools
 28 | 
 29 | 
 30 | class BaseRover:
 31 |     def __init__(self, mirobot):
 32 |         "docstring"
 33 |         self._mirobot = mirobot
 34 | 
 35 |     def repeat_decorator(fn):
 36 | 
 37 |         @functools.wraps(fn)
 38 |         def repeat_wrapper(self, *args, **kwargs):
 39 |             args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
 40 |             args_dict = dict(zip(args_names, args))
 41 | 
 42 |             def get_arg(arg_name, default=None):
 43 |                 if arg_name in args_dict:
 44 |                     return args_dict.get(arg_name)
 45 |                 elif arg_name in kwargs:
 46 |                     return kwargs.get(arg_name)
 47 |                 else:
 48 |                     return default
 49 | 
 50 |             repeat = get_arg('repeat', 1)
 51 | 
 52 |             output = []
 53 |             for i in range(repeat):
 54 |                 output.extend(self.fn(*args, **kwargs))
 55 | 
 56 |             return output
 57 | 
 58 |         return repeat_wrapper
 59 | 
 60 |     @repeat_decorator
 61 |     def move_upper_left(self, repeat=1, wait=True):
 62 |         instruction = "W7"
 63 |         return self._mirobot.send_msg(instruction, wait=wait)
 64 | 
 65 |     @repeat_decorator
 66 |     def move_upper_right(self, repeat=1, wait=True):
 67 |         instruction = "W9"
 68 |         return self._mirobot.send_msg(instruction, wait=wait)
 69 | 
 70 |     @repeat_decorator
 71 |     def move_bottom_left(self, repeat=1, wait=True):
 72 |         instruction = "W1"
 73 |         return self._mirobot.send_msg(instruction, wait=wait)
 74 | 
 75 |     @repeat_decorator
 76 |     def move_bottom_right(self, repeat=1, wait=True):
 77 |         instruction = "W3"
 78 |         return self._mirobot.send_msg(instruction, wait=wait)
 79 | 
 80 |     @repeat_decorator
 81 |     def move_forward(self, repeat=1, wait=True):
 82 |         instruction = "W8"
 83 |         return self._mirobot.send_msg(instruction, wait=wait)
 84 | 
 85 |     @repeat_decorator
 86 |     def move_backward(self, repeat=1, wait=True):
 87 |         instruction = "W2"
 88 |         return self._mirobot.send_msg(instruction, wait=wait)
 89 | 
 90 |     @repeat_decorator
 91 |     def move_left(self, repeat=1, wait=True):
 92 |         instruction = "W4"
 93 |         return self._mirobot.send_msg(instruction, wait=wait)
 94 | 
 95 |     @repeat_decorator
 96 |     def move_right(self, repeat=1, wait=True):
 97 |         instruction = "W6"
 98 |         return self._mirobot.send_msg(instruction, wait=wait)
 99 | 
100 |     @repeat_decorator
101 |     def rotate_left(self, repeat=1, wait=True):
102 |         instruction = "W10"
103 |         return self._mirobot.send_msg(instruction, wait=wait)
104 | 
105 |     @repeat_decorator
106 |     def rotate_right(self, repeat=1, wait=True):
107 |         instruction = "W11"
108 |         return self._mirobot.send_msg(instruction, wait=wait)
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |

Classes

119 |
120 |
121 | class BaseRover 122 | (mirobot) 123 |
124 |
125 |

docstring

126 |
127 | 128 | Expand source code 129 | 130 |
class BaseRover:
131 |     def __init__(self, mirobot):
132 |         "docstring"
133 |         self._mirobot = mirobot
134 | 
135 |     def repeat_decorator(fn):
136 | 
137 |         @functools.wraps(fn)
138 |         def repeat_wrapper(self, *args, **kwargs):
139 |             args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
140 |             args_dict = dict(zip(args_names, args))
141 | 
142 |             def get_arg(arg_name, default=None):
143 |                 if arg_name in args_dict:
144 |                     return args_dict.get(arg_name)
145 |                 elif arg_name in kwargs:
146 |                     return kwargs.get(arg_name)
147 |                 else:
148 |                     return default
149 | 
150 |             repeat = get_arg('repeat', 1)
151 | 
152 |             output = []
153 |             for i in range(repeat):
154 |                 output.extend(self.fn(*args, **kwargs))
155 | 
156 |             return output
157 | 
158 |         return repeat_wrapper
159 | 
160 |     @repeat_decorator
161 |     def move_upper_left(self, repeat=1, wait=True):
162 |         instruction = "W7"
163 |         return self._mirobot.send_msg(instruction, wait=wait)
164 | 
165 |     @repeat_decorator
166 |     def move_upper_right(self, repeat=1, wait=True):
167 |         instruction = "W9"
168 |         return self._mirobot.send_msg(instruction, wait=wait)
169 | 
170 |     @repeat_decorator
171 |     def move_bottom_left(self, repeat=1, wait=True):
172 |         instruction = "W1"
173 |         return self._mirobot.send_msg(instruction, wait=wait)
174 | 
175 |     @repeat_decorator
176 |     def move_bottom_right(self, repeat=1, wait=True):
177 |         instruction = "W3"
178 |         return self._mirobot.send_msg(instruction, wait=wait)
179 | 
180 |     @repeat_decorator
181 |     def move_forward(self, repeat=1, wait=True):
182 |         instruction = "W8"
183 |         return self._mirobot.send_msg(instruction, wait=wait)
184 | 
185 |     @repeat_decorator
186 |     def move_backward(self, repeat=1, wait=True):
187 |         instruction = "W2"
188 |         return self._mirobot.send_msg(instruction, wait=wait)
189 | 
190 |     @repeat_decorator
191 |     def move_left(self, repeat=1, wait=True):
192 |         instruction = "W4"
193 |         return self._mirobot.send_msg(instruction, wait=wait)
194 | 
195 |     @repeat_decorator
196 |     def move_right(self, repeat=1, wait=True):
197 |         instruction = "W6"
198 |         return self._mirobot.send_msg(instruction, wait=wait)
199 | 
200 |     @repeat_decorator
201 |     def rotate_left(self, repeat=1, wait=True):
202 |         instruction = "W10"
203 |         return self._mirobot.send_msg(instruction, wait=wait)
204 | 
205 |     @repeat_decorator
206 |     def rotate_right(self, repeat=1, wait=True):
207 |         instruction = "W11"
208 |         return self._mirobot.send_msg(instruction, wait=wait)
209 |
210 |

Methods

211 |
212 |
213 | def move_backward(self, repeat=1, wait=True) 214 |
215 |
216 |
217 |
218 | 219 | Expand source code 220 | 221 |
@repeat_decorator
222 | def move_backward(self, repeat=1, wait=True):
223 |     instruction = "W2"
224 |     return self._mirobot.send_msg(instruction, wait=wait)
225 |
226 |
227 |
228 | def move_bottom_left(self, repeat=1, wait=True) 229 |
230 |
231 |
232 |
233 | 234 | Expand source code 235 | 236 |
@repeat_decorator
237 | def move_bottom_left(self, repeat=1, wait=True):
238 |     instruction = "W1"
239 |     return self._mirobot.send_msg(instruction, wait=wait)
240 |
241 |
242 |
243 | def move_bottom_right(self, repeat=1, wait=True) 244 |
245 |
246 |
247 |
248 | 249 | Expand source code 250 | 251 |
@repeat_decorator
252 | def move_bottom_right(self, repeat=1, wait=True):
253 |     instruction = "W3"
254 |     return self._mirobot.send_msg(instruction, wait=wait)
255 |
256 |
257 |
258 | def move_forward(self, repeat=1, wait=True) 259 |
260 |
261 |
262 |
263 | 264 | Expand source code 265 | 266 |
@repeat_decorator
267 | def move_forward(self, repeat=1, wait=True):
268 |     instruction = "W8"
269 |     return self._mirobot.send_msg(instruction, wait=wait)
270 |
271 |
272 |
273 | def move_left(self, repeat=1, wait=True) 274 |
275 |
276 |
277 |
278 | 279 | Expand source code 280 | 281 |
@repeat_decorator
282 | def move_left(self, repeat=1, wait=True):
283 |     instruction = "W4"
284 |     return self._mirobot.send_msg(instruction, wait=wait)
285 |
286 |
287 |
288 | def move_right(self, repeat=1, wait=True) 289 |
290 |
291 |
292 |
293 | 294 | Expand source code 295 | 296 |
@repeat_decorator
297 | def move_right(self, repeat=1, wait=True):
298 |     instruction = "W6"
299 |     return self._mirobot.send_msg(instruction, wait=wait)
300 |
301 |
302 |
303 | def move_upper_left(self, repeat=1, wait=True) 304 |
305 |
306 |
307 |
308 | 309 | Expand source code 310 | 311 |
@repeat_decorator
312 | def move_upper_left(self, repeat=1, wait=True):
313 |     instruction = "W7"
314 |     return self._mirobot.send_msg(instruction, wait=wait)
315 |
316 |
317 |
318 | def move_upper_right(self, repeat=1, wait=True) 319 |
320 |
321 |
322 |
323 | 324 | Expand source code 325 | 326 |
@repeat_decorator
327 | def move_upper_right(self, repeat=1, wait=True):
328 |     instruction = "W9"
329 |     return self._mirobot.send_msg(instruction, wait=wait)
330 |
331 |
332 |
333 | def repeat_decorator(fn) 334 |
335 |
336 |
337 |
338 | 339 | Expand source code 340 | 341 |
def repeat_decorator(fn):
342 | 
343 |     @functools.wraps(fn)
344 |     def repeat_wrapper(self, *args, **kwargs):
345 |         args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
346 |         args_dict = dict(zip(args_names, args))
347 | 
348 |         def get_arg(arg_name, default=None):
349 |             if arg_name in args_dict:
350 |                 return args_dict.get(arg_name)
351 |             elif arg_name in kwargs:
352 |                 return kwargs.get(arg_name)
353 |             else:
354 |                 return default
355 | 
356 |         repeat = get_arg('repeat', 1)
357 | 
358 |         output = []
359 |         for i in range(repeat):
360 |             output.extend(self.fn(*args, **kwargs))
361 | 
362 |         return output
363 | 
364 |     return repeat_wrapper
365 |
366 |
367 |
368 | def rotate_left(self, repeat=1, wait=True) 369 |
370 |
371 |
372 |
373 | 374 | Expand source code 375 | 376 |
@repeat_decorator
377 | def rotate_left(self, repeat=1, wait=True):
378 |     instruction = "W10"
379 |     return self._mirobot.send_msg(instruction, wait=wait)
380 |
381 |
382 |
383 | def rotate_right(self, repeat=1, wait=True) 384 |
385 |
386 |
387 |
388 | 389 | Expand source code 390 | 391 |
@repeat_decorator
392 | def rotate_right(self, repeat=1, wait=True):
393 |     instruction = "W11"
394 |     return self._mirobot.send_msg(instruction, wait=wait)
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 | 435 |
436 | 439 | 440 | 441 | 442 | -------------------------------------------------------------------------------- /docs/PythonSDK-API手册(en)/mirobot/exceptions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mirobot.exceptions API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

Module mirobot.exceptions

21 |
22 |
23 |
24 | 25 | Expand source code 26 | 27 |
import logging
 28 | 
 29 | 
 30 | class ExitOnExceptionStreamHandler(logging.StreamHandler):
 31 |     def emit(self, record):
 32 |         super().emit(record)
 33 |         if record.levelno >= logging.ERROR:
 34 |             raise SystemExit(-1)
 35 | 
 36 | 
 37 | class MirobotError(Exception):
 38 |     """ An inplace class for throwing Mirobot errors. """
 39 |     pass
 40 | 
 41 | 
 42 | class MirobotAlarm(Exception):
 43 |     """  An inplace class for throwing Mirobot alarms. """
 44 |     pass
 45 | 
 46 | 
 47 | class MirobotReset(Exception):
 48 |     """ An inplace class for when Mirobot resets. """
 49 |     pass
 50 | 
 51 | 
 52 | class MirobotAmbiguousPort(Exception):
 53 |     """ An inplace class for when the serial port is unconfigurable. """
 54 |     pass
 55 | 
 56 | 
 57 | class MirobotStatusError(Exception):
 58 |     """ An inplace class for when Mirobot's status message is unprocessable. """
 59 |     pass
 60 | 
 61 | 
 62 | class MirobotResetFileError(Exception):
 63 |     """ An inplace class for when Mirobot has problems using the given reset file. """
 64 |     pass
 65 | 
 66 | 
 67 | class MirobotVariableCommandError(Exception):
 68 |     """ An inplace class for when Mirobot finds a command that does not match variable setting-command syntax. """
 69 |     pass
 70 | 
 71 | 
 72 | class SerialDeviceReadError(Exception):
 73 |     """ An inplace class for when SerialDevice is unable to read the serial port """
 74 | 
 75 | 
 76 | class SerialDeviceOpenError(Exception):
 77 |     """ An inplace class for when SerialDevice is unable to open the serial port """
 78 | 
 79 | 
 80 | class SerialDeviceLockError(Exception):
 81 |     """ An inplace class for when SerialDevice is unable to lock the serial port """
 82 | 
 83 | 
 84 | class SerialDeviceCloseError(Exception):
 85 |     """ An inplace class for when SerialDevice is unable to close the serial port """
 86 | 
 87 | 
 88 | class SerialDeviceUnlockError(Exception):
 89 |     """ An inplace class for when SerialDevice is unable to unlock the serial port """
 90 | 
 91 | 
 92 | class SerialDeviceWriteError(Exception):
 93 |     """ An inplace class for when SerialDevice is unable to write to the serial port """
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |

Classes

104 |
105 |
106 | class ExitOnExceptionStreamHandler 107 | (stream=None) 108 |
109 |
110 |

A handler class which writes logging records, appropriately formatted, 111 | to a stream. Note that this class does not close the stream, as 112 | sys.stdout or sys.stderr may be used.

113 |

Initialize the handler.

114 |

If stream is not specified, sys.stderr is used.

115 |
116 | 117 | Expand source code 118 | 119 |
class ExitOnExceptionStreamHandler(logging.StreamHandler):
120 |     def emit(self, record):
121 |         super().emit(record)
122 |         if record.levelno >= logging.ERROR:
123 |             raise SystemExit(-1)
124 |
125 |

Ancestors

126 |
    127 |
  • logging.StreamHandler
  • 128 |
  • logging.Handler
  • 129 |
  • logging.Filterer
  • 130 |
131 |

Methods

132 |
133 |
134 | def emit(self, record) 135 |
136 |
137 |

Emit a record.

138 |

If a formatter is specified, it is used to format the record. 139 | The record is then written to the stream with a trailing newline. 140 | If 141 | exception information is present, it is formatted using 142 | traceback.print_exception and appended to the stream. 143 | If the stream 144 | has an 'encoding' attribute, it is used to determine how to do the 145 | output to the stream.

146 |
147 | 148 | Expand source code 149 | 150 |
def emit(self, record):
151 |     super().emit(record)
152 |     if record.levelno >= logging.ERROR:
153 |         raise SystemExit(-1)
154 |
155 |
156 |
157 |
158 |
159 | class MirobotAlarm 160 | (...) 161 |
162 |
163 |

An inplace class for throwing Mirobot alarms.

164 |
165 | 166 | Expand source code 167 | 168 |
class MirobotAlarm(Exception):
169 |     """  An inplace class for throwing Mirobot alarms. """
170 |     pass
171 |
172 |

Ancestors

173 |
    174 |
  • builtins.Exception
  • 175 |
  • builtins.BaseException
  • 176 |
177 |
178 |
179 | class MirobotAmbiguousPort 180 | (...) 181 |
182 |
183 |

An inplace class for when the serial port is unconfigurable.

184 |
185 | 186 | Expand source code 187 | 188 |
class MirobotAmbiguousPort(Exception):
189 |     """ An inplace class for when the serial port is unconfigurable. """
190 |     pass
191 |
192 |

Ancestors

193 |
    194 |
  • builtins.Exception
  • 195 |
  • builtins.BaseException
  • 196 |
197 |
198 |
199 | class MirobotError 200 | (...) 201 |
202 |
203 |

An inplace class for throwing Mirobot errors.

204 |
205 | 206 | Expand source code 207 | 208 |
class MirobotError(Exception):
209 |     """ An inplace class for throwing Mirobot errors. """
210 |     pass
211 |
212 |

Ancestors

213 |
    214 |
  • builtins.Exception
  • 215 |
  • builtins.BaseException
  • 216 |
217 |
218 |
219 | class MirobotReset 220 | (...) 221 |
222 |
223 |

An inplace class for when Mirobot resets.

224 |
225 | 226 | Expand source code 227 | 228 |
class MirobotReset(Exception):
229 |     """ An inplace class for when Mirobot resets. """
230 |     pass
231 |
232 |

Ancestors

233 |
    234 |
  • builtins.Exception
  • 235 |
  • builtins.BaseException
  • 236 |
237 |
238 |
239 | class MirobotResetFileError 240 | (...) 241 |
242 |
243 |

An inplace class for when Mirobot has problems using the given reset file.

244 |
245 | 246 | Expand source code 247 | 248 |
class MirobotResetFileError(Exception):
249 |     """ An inplace class for when Mirobot has problems using the given reset file. """
250 |     pass
251 |
252 |

Ancestors

253 |
    254 |
  • builtins.Exception
  • 255 |
  • builtins.BaseException
  • 256 |
257 |
258 |
259 | class MirobotStatusError 260 | (...) 261 |
262 |
263 |

An inplace class for when Mirobot's status message is unprocessable.

264 |
265 | 266 | Expand source code 267 | 268 |
class MirobotStatusError(Exception):
269 |     """ An inplace class for when Mirobot's status message is unprocessable. """
270 |     pass
271 |
272 |

Ancestors

273 |
    274 |
  • builtins.Exception
  • 275 |
  • builtins.BaseException
  • 276 |
277 |
278 |
279 | class MirobotVariableCommandError 280 | (...) 281 |
282 |
283 |

An inplace class for when Mirobot finds a command that does not match variable setting-command syntax.

284 |
285 | 286 | Expand source code 287 | 288 |
class MirobotVariableCommandError(Exception):
289 |     """ An inplace class for when Mirobot finds a command that does not match variable setting-command syntax. """
290 |     pass
291 |
292 |

Ancestors

293 |
    294 |
  • builtins.Exception
  • 295 |
  • builtins.BaseException
  • 296 |
297 |
298 |
299 | class SerialDeviceCloseError 300 | (...) 301 |
302 |
303 |

An inplace class for when SerialDevice is unable to close the serial port

304 |
305 | 306 | Expand source code 307 | 308 |
class SerialDeviceCloseError(Exception):
309 |     """ An inplace class for when SerialDevice is unable to close the serial port """
310 |
311 |

Ancestors

312 |
    313 |
  • builtins.Exception
  • 314 |
  • builtins.BaseException
  • 315 |
316 |
317 |
318 | class SerialDeviceLockError 319 | (...) 320 |
321 |
322 |

An inplace class for when SerialDevice is unable to lock the serial port

323 |
324 | 325 | Expand source code 326 | 327 |
class SerialDeviceLockError(Exception):
328 |     """ An inplace class for when SerialDevice is unable to lock the serial port """
329 |
330 |

Ancestors

331 |
    332 |
  • builtins.Exception
  • 333 |
  • builtins.BaseException
  • 334 |
335 |
336 |
337 | class SerialDeviceOpenError 338 | (...) 339 |
340 |
341 |

An inplace class for when SerialDevice is unable to open the serial port

342 |
343 | 344 | Expand source code 345 | 346 |
class SerialDeviceOpenError(Exception):
347 |     """ An inplace class for when SerialDevice is unable to open the serial port """
348 |
349 |

Ancestors

350 |
    351 |
  • builtins.Exception
  • 352 |
  • builtins.BaseException
  • 353 |
354 |
355 |
356 | class SerialDeviceReadError 357 | (...) 358 |
359 |
360 |

An inplace class for when SerialDevice is unable to read the serial port

361 |
362 | 363 | Expand source code 364 | 365 |
class SerialDeviceReadError(Exception):
366 |     """ An inplace class for when SerialDevice is unable to read the serial port """
367 |
368 |

Ancestors

369 |
    370 |
  • builtins.Exception
  • 371 |
  • builtins.BaseException
  • 372 |
373 |
374 |
375 | class SerialDeviceUnlockError 376 | (...) 377 |
378 |
379 |

An inplace class for when SerialDevice is unable to unlock the serial port

380 |
381 | 382 | Expand source code 383 | 384 |
class SerialDeviceUnlockError(Exception):
385 |     """ An inplace class for when SerialDevice is unable to unlock the serial port """
386 |
387 |

Ancestors

388 |
    389 |
  • builtins.Exception
  • 390 |
  • builtins.BaseException
  • 391 |
392 |
393 |
394 | class SerialDeviceWriteError 395 | (...) 396 |
397 |
398 |

An inplace class for when SerialDevice is unable to write to the serial port

399 |
400 | 401 | Expand source code 402 | 403 |
class SerialDeviceWriteError(Exception):
404 |     """ An inplace class for when SerialDevice is unable to write to the serial port """
405 |
406 |

Ancestors

407 |
    408 |
  • builtins.Exception
  • 409 |
  • builtins.BaseException
  • 410 |
411 |
412 |
413 |
414 |
415 | 477 |
478 | 481 | 482 | 483 | 484 | -------------------------------------------------------------------------------- /docs/PythonSDK-API手册(en)/mirobot/extended_dataclasses.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mirobot.extended_dataclasses API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

Module mirobot.extended_dataclasses

21 |
22 |
23 |
24 | 25 | Expand source code 26 | 27 |
from dataclasses import asdict, astuple, fields
 28 | import numbers
 29 | import operator
 30 | 
 31 | 
 32 | class basic_dataclass:
 33 |     def asdict(self):
 34 |         return asdict(self)
 35 | 
 36 |     def astuple(self):
 37 |         return astuple(self)
 38 | 
 39 |     def fields(self):
 40 |         return fields(self)
 41 | 
 42 |     @classmethod
 43 |     def _new_from_dict(cls, dictionary):
 44 |         return cls(**dictionary)
 45 | 
 46 | 
 47 | class featured_dataclass(basic_dataclass):
 48 |     def _cross_same_type(self, other, operation_function, single=False):
 49 |         new_values = {}
 50 |         for f in self.fields():
 51 |             this_value = getattr(self, f.name)
 52 | 
 53 |             if single:
 54 |                 other_value = other
 55 |             else:
 56 |                 other_value = getattr(other, f.name)
 57 | 
 58 |             result = operation_function(this_value, other_value)
 59 | 
 60 |             new_values[f.name] = result
 61 | 
 62 |         return new_values
 63 | 
 64 |     def _binary_operation(self, other, operation):
 65 |         def operation_function(this_value, other_value):
 66 |             if None in (this_value, other_value):
 67 |                 return None
 68 |             else:
 69 |                 return operation(this_value, other_value)
 70 | 
 71 |         if isinstance(other, type(self)):
 72 |             new_values = self._cross_same_type(other, operation_function)
 73 | 
 74 |         elif isinstance(other, numbers.Real):
 75 |             new_values = self._cross_same_type(other, operation_function, single=True)
 76 | 
 77 |         else:
 78 |             raise NotImplementedError(f"Cannot handle {type(self)} and {type(other)}")
 79 | 
 80 |         return self._new_from_dict(new_values)
 81 | 
 82 |     def _unary_operation(self, operation_function):
 83 |         new_values = {f.name: operation_function(f)
 84 |                       for f in self.fields()}
 85 | 
 86 |         return self._new_from_dict(new_values)
 87 | 
 88 |     def _basic_unary_operation(self, operation):
 89 |         def operation_function(field):
 90 |             value = getattr(self, field.name)
 91 |             if value is not None:
 92 |                 return operation(value)
 93 |             else:
 94 |                 return None
 95 | 
 96 |         return self._unary_operation(operation_function)
 97 | 
 98 |     def _comparision_operation(self, other, operation):
 99 |         def operation_function(this_value, other_value):
100 |             if None in (this_value, other_value):
101 |                 return True
102 |             else:
103 |                 return operation(this_value, other_value)
104 | 
105 |         if isinstance(other, type(self)):
106 |             new_values = self._cross_same_type(other, operation_function).values()
107 | 
108 |         elif isinstance(other, (int, float)):
109 |             new_values = self._cross_same_type(other, operation_function, single=True).values()
110 | 
111 |         else:
112 |             raise NotImplementedError(f"Cannot handle {type(self)} and {type(other)}")
113 | 
114 |         if all(new_values):
115 |             return True
116 | 
117 |         elif not any(new_values):
118 |             return False
119 | 
120 |         else:
121 |             return None
122 | 
123 |     def __or__(self, other):
124 |         def operation_function(this_value, other_value):
125 |             if this_value is None:
126 |                 return other_value
127 |             else:
128 |                 return this_value
129 | 
130 |         new_values = self._cross_same_type(other, operation_function)
131 |         return self._new_from_dict(new_values)
132 | 
133 |     def __and__(self, other):
134 |         def operation_function(this_value, other_value):
135 |             if None not in (this_value, other_value):
136 |                 return this_value
137 |             else:
138 |                 return None
139 | 
140 |         new_values = self._cross_same_type(other, operation_function)
141 |         return self._new_from_dict(new_values)
142 | 
143 |     def int(self):
144 |         def operation_function(field):
145 |             value = getattr(self, field.name)
146 |             if field.type in (float,) and value is not None:
147 |                 return int(value)
148 |             else:
149 |                 return value
150 | 
151 |         return self._unary_operation(operation_function)
152 | 
153 |     def round(self):
154 |         def operation_function(field):
155 |             value = getattr(self, field.name)
156 |             if field.type in (float,) and value is not None:
157 |                 return round(value)
158 |             else:
159 |                 return value
160 | 
161 |         return self._unary_operation(operation_function)
162 | 
163 |     def __add__(self, other):
164 |         return self._binary_operation(other, operator.add)
165 | 
166 |     def __radd__(self, other):
167 |         return self._binary_operation(other, operator.add)
168 | 
169 |     def __sub__(self, other):
170 |         return self._binary_operation(other, operator.sub)
171 | 
172 |     def __rsub__(self, other):
173 |         def rsub(dataclass_value, number):
174 |             return operator.sub(number, dataclass_value)
175 | 
176 |         return self._binary_operation(other, rsub)
177 | 
178 |     def __mul__(self, other):
179 |         return self._binary_operation(other, operator.mul)
180 | 
181 |     def __rmul__(self, other):
182 |         return self._binary_operation(other, operator.mul)
183 | 
184 |     def __div__(self, other):
185 |         return self._binary_operation(other, operator.div)
186 | 
187 |     def __rdiv__(self, other):
188 |         def rdiv(dataclass_value, number):
189 |             return operator.div(number, dataclass_value)
190 | 
191 |         return self._binary_operation(other, rdiv)
192 | 
193 |     def __truediv__(self, other):
194 |         return self._binary_operation(other, operator.truediv)
195 | 
196 |     def __rtruediv__(self, other):
197 |         def rtruediv(dataclass_value, number):
198 |             return operator.truediv(number, dataclass_value)
199 | 
200 |         return self._binary_operation(other, operator.truediv)
201 | 
202 |     def __mod__(self, other):
203 |         return self._binary_operation(other, operator.mod)
204 | 
205 |     def __abs__(self):
206 |         return self._basic_unary_operation(operator.abs)
207 | 
208 |     def __pos__(self):
209 |         return self._basic_unary_operation(operator.pos)
210 | 
211 |     def __neg__(self):
212 |         return self._basic_unary_operation(operator.neg)
213 | 
214 |     def __lt__(self, other):
215 |         return self._comparision_operation(other, operator.lt)
216 | 
217 |     def __le__(self, other):
218 |         return self._comparision_operation(other, operator.le)
219 | 
220 |     def __eq__(self, other):
221 |         return self._comparision_operation(other, operator.eq)
222 | 
223 |     def __ne__(self, other):
224 |         return self._comparision_operation(other, operator.ne)
225 | 
226 |     def __ge__(self, other):
227 |         return self._comparision_operation(other, operator.ge)
228 | 
229 |     def __gt__(self, other):
230 |         return self._comparision_operation(other, operator.gt)
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |

Classes

241 |
242 |
243 | class basic_dataclass 244 |
245 |
246 |
247 |
248 | 249 | Expand source code 250 | 251 |
class basic_dataclass:
252 |     def asdict(self):
253 |         return asdict(self)
254 | 
255 |     def astuple(self):
256 |         return astuple(self)
257 | 
258 |     def fields(self):
259 |         return fields(self)
260 | 
261 |     @classmethod
262 |     def _new_from_dict(cls, dictionary):
263 |         return cls(**dictionary)
264 |
265 |

Subclasses

266 | 270 |

Methods

271 |
272 |
273 | def asdict(self) 274 |
275 |
276 |
277 |
278 | 279 | Expand source code 280 | 281 |
def asdict(self):
282 |     return asdict(self)
283 |
284 |
285 |
286 | def astuple(self) 287 |
288 |
289 |
290 |
291 | 292 | Expand source code 293 | 294 |
def astuple(self):
295 |     return astuple(self)
296 |
297 |
298 |
299 | def fields(self) 300 |
301 |
302 |
303 |
304 | 305 | Expand source code 306 | 307 |
def fields(self):
308 |     return fields(self)
309 |
310 |
311 |
312 |
313 |
314 | class featured_dataclass 315 |
316 |
317 |
318 |
319 | 320 | Expand source code 321 | 322 |
class featured_dataclass(basic_dataclass):
323 |     def _cross_same_type(self, other, operation_function, single=False):
324 |         new_values = {}
325 |         for f in self.fields():
326 |             this_value = getattr(self, f.name)
327 | 
328 |             if single:
329 |                 other_value = other
330 |             else:
331 |                 other_value = getattr(other, f.name)
332 | 
333 |             result = operation_function(this_value, other_value)
334 | 
335 |             new_values[f.name] = result
336 | 
337 |         return new_values
338 | 
339 |     def _binary_operation(self, other, operation):
340 |         def operation_function(this_value, other_value):
341 |             if None in (this_value, other_value):
342 |                 return None
343 |             else:
344 |                 return operation(this_value, other_value)
345 | 
346 |         if isinstance(other, type(self)):
347 |             new_values = self._cross_same_type(other, operation_function)
348 | 
349 |         elif isinstance(other, numbers.Real):
350 |             new_values = self._cross_same_type(other, operation_function, single=True)
351 | 
352 |         else:
353 |             raise NotImplementedError(f"Cannot handle {type(self)} and {type(other)}")
354 | 
355 |         return self._new_from_dict(new_values)
356 | 
357 |     def _unary_operation(self, operation_function):
358 |         new_values = {f.name: operation_function(f)
359 |                       for f in self.fields()}
360 | 
361 |         return self._new_from_dict(new_values)
362 | 
363 |     def _basic_unary_operation(self, operation):
364 |         def operation_function(field):
365 |             value = getattr(self, field.name)
366 |             if value is not None:
367 |                 return operation(value)
368 |             else:
369 |                 return None
370 | 
371 |         return self._unary_operation(operation_function)
372 | 
373 |     def _comparision_operation(self, other, operation):
374 |         def operation_function(this_value, other_value):
375 |             if None in (this_value, other_value):
376 |                 return True
377 |             else:
378 |                 return operation(this_value, other_value)
379 | 
380 |         if isinstance(other, type(self)):
381 |             new_values = self._cross_same_type(other, operation_function).values()
382 | 
383 |         elif isinstance(other, (int, float)):
384 |             new_values = self._cross_same_type(other, operation_function, single=True).values()
385 | 
386 |         else:
387 |             raise NotImplementedError(f"Cannot handle {type(self)} and {type(other)}")
388 | 
389 |         if all(new_values):
390 |             return True
391 | 
392 |         elif not any(new_values):
393 |             return False
394 | 
395 |         else:
396 |             return None
397 | 
398 |     def __or__(self, other):
399 |         def operation_function(this_value, other_value):
400 |             if this_value is None:
401 |                 return other_value
402 |             else:
403 |                 return this_value
404 | 
405 |         new_values = self._cross_same_type(other, operation_function)
406 |         return self._new_from_dict(new_values)
407 | 
408 |     def __and__(self, other):
409 |         def operation_function(this_value, other_value):
410 |             if None not in (this_value, other_value):
411 |                 return this_value
412 |             else:
413 |                 return None
414 | 
415 |         new_values = self._cross_same_type(other, operation_function)
416 |         return self._new_from_dict(new_values)
417 | 
418 |     def int(self):
419 |         def operation_function(field):
420 |             value = getattr(self, field.name)
421 |             if field.type in (float,) and value is not None:
422 |                 return int(value)
423 |             else:
424 |                 return value
425 | 
426 |         return self._unary_operation(operation_function)
427 | 
428 |     def round(self):
429 |         def operation_function(field):
430 |             value = getattr(self, field.name)
431 |             if field.type in (float,) and value is not None:
432 |                 return round(value)
433 |             else:
434 |                 return value
435 | 
436 |         return self._unary_operation(operation_function)
437 | 
438 |     def __add__(self, other):
439 |         return self._binary_operation(other, operator.add)
440 | 
441 |     def __radd__(self, other):
442 |         return self._binary_operation(other, operator.add)
443 | 
444 |     def __sub__(self, other):
445 |         return self._binary_operation(other, operator.sub)
446 | 
447 |     def __rsub__(self, other):
448 |         def rsub(dataclass_value, number):
449 |             return operator.sub(number, dataclass_value)
450 | 
451 |         return self._binary_operation(other, rsub)
452 | 
453 |     def __mul__(self, other):
454 |         return self._binary_operation(other, operator.mul)
455 | 
456 |     def __rmul__(self, other):
457 |         return self._binary_operation(other, operator.mul)
458 | 
459 |     def __div__(self, other):
460 |         return self._binary_operation(other, operator.div)
461 | 
462 |     def __rdiv__(self, other):
463 |         def rdiv(dataclass_value, number):
464 |             return operator.div(number, dataclass_value)
465 | 
466 |         return self._binary_operation(other, rdiv)
467 | 
468 |     def __truediv__(self, other):
469 |         return self._binary_operation(other, operator.truediv)
470 | 
471 |     def __rtruediv__(self, other):
472 |         def rtruediv(dataclass_value, number):
473 |             return operator.truediv(number, dataclass_value)
474 | 
475 |         return self._binary_operation(other, operator.truediv)
476 | 
477 |     def __mod__(self, other):
478 |         return self._binary_operation(other, operator.mod)
479 | 
480 |     def __abs__(self):
481 |         return self._basic_unary_operation(operator.abs)
482 | 
483 |     def __pos__(self):
484 |         return self._basic_unary_operation(operator.pos)
485 | 
486 |     def __neg__(self):
487 |         return self._basic_unary_operation(operator.neg)
488 | 
489 |     def __lt__(self, other):
490 |         return self._comparision_operation(other, operator.lt)
491 | 
492 |     def __le__(self, other):
493 |         return self._comparision_operation(other, operator.le)
494 | 
495 |     def __eq__(self, other):
496 |         return self._comparision_operation(other, operator.eq)
497 | 
498 |     def __ne__(self, other):
499 |         return self._comparision_operation(other, operator.ne)
500 | 
501 |     def __ge__(self, other):
502 |         return self._comparision_operation(other, operator.ge)
503 | 
504 |     def __gt__(self, other):
505 |         return self._comparision_operation(other, operator.gt)
506 |
507 |

Ancestors

508 | 511 |

Subclasses

512 | 516 |

Methods

517 |
518 |
519 | def int(self) 520 |
521 |
522 |
523 |
524 | 525 | Expand source code 526 | 527 |
def int(self):
528 |     def operation_function(field):
529 |         value = getattr(self, field.name)
530 |         if field.type in (float,) and value is not None:
531 |             return int(value)
532 |         else:
533 |             return value
534 | 
535 |     return self._unary_operation(operation_function)
536 |
537 |
538 |
539 | def round(self) 540 |
541 |
542 |
543 |
544 | 545 | Expand source code 546 | 547 |
def round(self):
548 |     def operation_function(field):
549 |         value = getattr(self, field.name)
550 |         if field.type in (float,) and value is not None:
551 |             return round(value)
552 |         else:
553 |             return value
554 | 
555 |     return self._unary_operation(operation_function)
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 | 595 |
596 | 599 | 600 | 601 | 602 | -------------------------------------------------------------------------------- /docs/PythonSDK-API手册(en)/mirobot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mirobot API documentation 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |

Module mirobot

21 |
22 |
23 |
24 | 25 | Expand source code 26 | 27 |
from .mirobot import Mirobot
 28 | from .base_mirobot import BaseMirobot
 29 | from .mirobot_status import MirobotStatus, MirobotAngles, MirobotCartesians
 30 | 
 31 | # don't document our resources directory duh
 32 | __pdoc__ = {}
 33 | __pdoc__['resources'] = False
 34 | __pdoc__['resources.__init__'] = False
 35 | 
 36 | 
 37 | # if someone imports by '*' then import everything in the following modules
 38 | __all__ = ['mirobot', 'base_mirobot', 'mirobot_status']
39 |
40 |
41 |
42 |

Sub-modules

43 |
44 |
mirobot.base_mirobot
45 |
46 |
47 |
48 |
mirobot.base_rover
49 |
50 |
51 |
52 |
mirobot.bluetooth_low_energy_interface
53 |
54 |
55 |
56 |
mirobot.exceptions
57 |
58 |
59 |
60 |
mirobot.extended_dataclasses
61 |
62 |
63 |
64 |
mirobot.mirobot
65 |
66 |
67 |
68 |
mirobot.mirobot_status
69 |
70 |
71 |
72 |
mirobot.serial_device
73 |
74 |
75 |
76 |
mirobot.serial_interface
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | 110 |
111 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /docs/PythonSDK-安装指南/PythonSDK-安装指南.md: -------------------------------------------------------------------------------- 1 | # Mirobot PythonSDK-安装指南 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ## 安装构建好的库 10 | 11 | 12 | 13 | Ubuntu: 14 | 15 | ```bash 16 | sudo pip3 install mirobot-py 17 | ``` 18 | 19 | Windows 20 | 21 | ```bash 22 | pip install mirobot-py 23 | ``` 24 | 25 | 26 | 27 | ## 从源码安装 28 | 29 | 30 | 克隆代码 31 | ```bash 32 | git clone git@github.com:mushroom-x/mirobot-py.git 33 | ``` 34 | 35 | Windows下的安装 36 | ```bash 37 | python .\setup.py install 38 | ``` 39 | 40 | Ubuntu下的安装 41 | ```bash 42 | python3 .\setup.py install 43 | ``` -------------------------------------------------------------------------------- /docs/build_docs.sh: -------------------------------------------------------------------------------- 1 | pdoc3 --html -o docs/ mirobot -f 2 | -------------------------------------------------------------------------------- /examples/air_pump/air_pump.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 气泵控制 3 | ''' 4 | from mirobot import Mirobot 5 | import time 6 | arm = Mirobot(portname='COM7', debug=False) 7 | arm.home_simultaneous() 8 | 9 | # 气泵开启 10 | arm.pump_on() 11 | # 等待5s 12 | time.sleep(5) 13 | # 气泵关闭 14 | arm.pump_off() -------------------------------------------------------------------------------- /examples/get_status/get_status.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 获取机械臂的状态 3 | ''' 4 | from mirobot import Mirobot 5 | import time 6 | arm = Mirobot(portname='COM7', debug=False) 7 | 8 | # 注:一定要配置为wait=False,非阻塞式等待 9 | # 要不然会卡死 10 | arm.home_simultaneous(wait=False) 11 | # 等待15s 12 | time.sleep(15) 13 | # 打印机械臂当前的状态 14 | print("获取机械臂的状态 ?") 15 | print(arm.get_status()) -------------------------------------------------------------------------------- /examples/home/home.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 机械臂回归机械零点与状态查询 3 | ''' 4 | from mirobot import Mirobot 5 | import time 6 | 7 | print("实例化Mirobot机械臂实例") 8 | arm = Mirobot(portname='COM7', debug=False) 9 | 10 | # 机械臂Home 多轴并行 11 | print("机械臂Homing开始") 12 | arm.home_simultaneous(wait=True) 13 | print("机械臂Homing结束") 14 | 15 | # 状态更新与查询 16 | print("更新机械臂状态") 17 | arm.update_status() 18 | print(f"更新后的状态对象: {arm.status}") 19 | print(f"更新后的状态名称: {arm.status.state}") -------------------------------------------------------------------------------- /examples/set_joint_angle/set_joint_angle.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 设置机械臂关节的角度, 单位° 3 | ''' 4 | from mirobot import Mirobot 5 | import time 6 | arm = Mirobot(portname='COM7', debug=False) 7 | arm.home_simultaneous() 8 | 9 | # 设置单个关节的角度 10 | print("测试设置单个关节的角度") 11 | arm.set_joint_angle({1:100.0}, wait=True) 12 | print("动作执行完毕") 13 | # 状态查询 14 | print(f"状态查询: {arm.get_status()}") 15 | # 停顿2s 16 | time.sleep(2) 17 | 18 | # 设置多个关节的角度 19 | print("设置多个关节的角度") 20 | target_angles = {1:90.0, 2:30.0, 3:-20.0, 4:10.0, 5:0.0, 6:90.0} 21 | arm.set_joint_angle(target_angles, wait=True) 22 | print("动作执行完毕") 23 | # 状态查询 24 | print(f"状态查询: {arm.get_status()}") 25 | # 停顿2s 26 | time.sleep(2) 27 | -------------------------------------------------------------------------------- /examples/set_wrist_pose/set_wrist_pose.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 机械臂腕关节的位置控制, 点控 point to point 3 | ''' 4 | from mirobot import Mirobot 5 | import time 6 | arm = Mirobot(portname='COM7', debug=True) 7 | arm.home_simultaneous() 8 | 9 | print("运动到目标点 A") 10 | arm.set_wrist_pose(200, 20, 230) 11 | print(f"当前末端在机械臂坐标系下的位姿 {arm.pose}") 12 | time.sleep(1) 13 | 14 | print("运动到目标点 B") 15 | arm.set_wrist_pose(200, 20, 150) 16 | print(f"当前末端在机械臂坐标系下的位姿 {arm.pose}") 17 | time.sleep(1) 18 | 19 | print("运动到目标点 C, 指定末端的姿态角") 20 | arm.set_wrist_pose(150, -20, 230, roll=30.0, pitch=0, yaw=45.0) 21 | print(f"当前末端在机械臂坐标系下的位姿 {arm.pose}") -------------------------------------------------------------------------------- /examples/src/.ipynb_checkpoints/Mirobot-PythonSDK测试脚本-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 4 6 | } 7 | -------------------------------------------------------------------------------- /images/Mirobot_Solo_256.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlkata/mirobot-py/28f7efa5fe8ae27740068de2cf0322c3599c3e17/images/Mirobot_Solo_256.jpg -------------------------------------------------------------------------------- /mirobot/__init__.py: -------------------------------------------------------------------------------- 1 | from .mirobot import Mirobot 2 | from .mirobot_status import MirobotStatus, MirobotAngles, MirobotCartesians 3 | 4 | # don't document our resources directory duh 5 | __pdoc__ = {} 6 | __pdoc__['resources'] = False 7 | __pdoc__['resources.__init__'] = False 8 | 9 | 10 | # if someone imports by '*' then import everything in the following modules 11 | __all__ = ['mirobot', 'mirobot_status'] 12 | -------------------------------------------------------------------------------- /mirobot/base_rover.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from time import sleep 3 | 4 | 5 | class BaseRover: 6 | def __init__(self, mirobot): 7 | self._mirobot = mirobot 8 | 9 | def time_decorator(fn): 10 | @functools.wraps(fn) 11 | def time_wrapper(self, *args, **kwargs): 12 | args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount] 13 | args_dict = dict(zip(args_names, args)) 14 | 15 | def get_arg(arg_name, default=None): 16 | if arg_name in args_dict: 17 | return args_dict.get(arg_name) 18 | elif arg_name in kwargs: 19 | return kwargs.get(arg_name) 20 | else: 21 | return default 22 | 23 | time = get_arg('time', 0) 24 | wait = get_arg('wait', True) 25 | 26 | output = fn(self, *args, **kwargs) 27 | 28 | if time: 29 | sleep(time) 30 | self.stop(wait=wait) 31 | 32 | return output 33 | 34 | return time_wrapper 35 | 36 | @time_decorator 37 | def move_upper_left(self, time=0, wait=True): 38 | instruction = "W7" 39 | return self._mirobot.send_msg(instruction, wait=wait, terminator='\r\n') 40 | 41 | @time_decorator 42 | def move_upper_right(self, time=0, wait=True): 43 | instruction = "W9" 44 | return self._mirobot.send_msg(instruction, wait=wait, terminator='\r\n') 45 | 46 | @time_decorator 47 | def move_bottom_left(self, time=0, wait=True): 48 | instruction = "W1" 49 | return self._mirobot.send_msg(instruction, wait=wait, terminator='\r\n') 50 | 51 | @time_decorator 52 | def move_bottom_right(self, time=0, wait=True): 53 | instruction = "W3" 54 | return self._mirobot.send_msg(instruction, wait=wait, terminator='\r\n') 55 | 56 | @time_decorator 57 | def move_left(self, time=0, wait=True): 58 | instruction = "W4" 59 | return self._mirobot.send_msg(instruction, wait=wait, terminator='\r\n') 60 | 61 | @time_decorator 62 | def move_right(self, time=0, wait=True): 63 | instruction = "W6" 64 | return self._mirobot.send_msg(instruction, wait=wait, terminator='\r\n') 65 | 66 | @time_decorator 67 | def rotate_left(self, time=0, wait=True): 68 | instruction = "W10" 69 | return self._mirobot.send_msg(instruction, wait=wait, terminator='\r\n') 70 | 71 | @time_decorator 72 | def rotate_right(self, time=0, wait=True): 73 | instruction = "W11" 74 | return self._mirobot.send_msg(instruction, wait=wait, terminator='\r\n') 75 | 76 | @time_decorator 77 | def move_forward(self, time=0, wait=True): 78 | instruction = "W8" 79 | return self._mirobot.send_msg(instruction, wait=wait, terminator='\r\n') 80 | 81 | @time_decorator 82 | def move_backward(self, time=0, wait=True): 83 | instruction = "W2" 84 | return self._mirobot.send_msg(instruction, wait=wait, terminator='\r\n') 85 | 86 | def stop(self, wait=True): 87 | instruction = "W0" 88 | return self._mirobot.send_msg(instruction, wait=wait, terminator='\r\n') 89 | -------------------------------------------------------------------------------- /mirobot/bluetooth_low_energy_interface.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import re 4 | import time 5 | 6 | from bleak import discover, BleakClient 7 | 8 | from .exceptions import MirobotError, MirobotAlarm, MirobotReset, InvalidBluetoothAddressError 9 | 10 | 11 | os_is_posix = os.name == 'posix' 12 | 13 | 14 | def chunks(lst, n): 15 | """Yield successive n-sized chunks from lst. 16 | 17 | Parameters 18 | ---------- 19 | lst : Collection 20 | An iterable of items. 21 | n : int 22 | The size of the chunks to split the list into. 23 | 24 | Returns 25 | ------- 26 | result : Generator[List] 27 | A generator that yields each chunk of the list. 28 | """ 29 | for i in range(0, len(lst), n): 30 | yield lst[i:i + n] 31 | 32 | 33 | class BluetoothLowEnergyInterface: 34 | """ 35 | An interface for talking to the low-energy Bluetooth extender module for the Mirobot. 36 | NOTE: This mode is inherently instable at the moment (@rirze, Thu 14 May 2020). Sometimes commands may not be parsed correctly, causing execution to fail on a misparsing error. While this happens rarely, users should be made aware of the potential exceptions that may arise. It is recommended to only use this connection when serial communication is unavailable. 37 | """ 38 | def __init__(self, mirobot, address=None, debug=False, logger=None, autofindaddress=True): 39 | """ 40 | 41 | Parameters 42 | ---------- 43 | mirobot : `mirobot.base_mirobot.BaseMirobot` 44 | Mirobot object that this instance is attached to. 45 | address : str 46 | (Default value = None) Bluetooth address of the Mirobot's bluetooth extender module to connect to. If unknown, leave as `None` and this class will automatically scan and try to find the box on its own. If provided, it should be of the form `50:33:8B:L4:95:6X` (except on Apple products which use a format like `123JKDSF-F0E3-F96A-F0A3-64A68508A53C`) 47 | debug : bool 48 | (Default value = False) Whether to show debug statements in logger. 49 | logger : Logger 50 | (Default value = None) Logger instance to use for this class. Usually `mirobot.base_mirobot.BaseMirobot.logger`. 51 | autofindaddress : bool 52 | (Default value = True) Whether to automatically search for Mirobot's bluetooth module if `address` parameter is `None`. 53 | 54 | Returns 55 | ------- 56 | class : `mirobot.bluetooth_low_energy_interface.BluetoothLowEnergyInterface` 57 | """ 58 | self.mirobot = mirobot 59 | 60 | if logger is not None: 61 | self.logger = logger 62 | 63 | self._debug = debug 64 | 65 | self.loop = asyncio.new_event_loop() 66 | asyncio.set_event_loop(self.loop) 67 | 68 | self._run_and_get(self._ainit()) 69 | 70 | async def _ainit(self, address=None, autofindaddress=True): 71 | # if address was not passed in and autofindaddress is set to true, 72 | # then autosearch for a bluetooth device 73 | if not address: 74 | if autofindaddress: 75 | self.address = await self._find_address() 76 | """ The default address to use when making connections. To override this on a individual basis, provide portname to each invocation of `BaseMirobot.connect`. """ 77 | self.logger.info(f"Using Bluetooth Address \"{self.address}\"") 78 | else: 79 | self.logger.exception(InvalidBluetoothAddressError('Must either provide a Bluetooth address or turn on autodiscovery!')) 80 | else: 81 | self.address = address 82 | 83 | self.client = BleakClient(self.address, loop=self.loop) 84 | 85 | def _run_and_get(self, coro): 86 | return self.loop.run_until_complete(coro) 87 | 88 | @property 89 | def debug(self): 90 | """ Whether to show debug statements in the logger. """ 91 | return self._debug 92 | 93 | @debug.setter 94 | def debug(self, value): 95 | """Set the new value for the `debug` property of `mirobot.bluetooth_low_energy_interface.BluetoothLowEnergyInterface`. Use as in `BluetoothLowEnergyInterface.setDebug(value)`. 96 | 97 | Parameters 98 | ---------- 99 | value : bool 100 | New value for `debug` 101 | 102 | """ 103 | self._debug = bool(value) 104 | 105 | async def _find_address(self): 106 | """ Try to find the Bluetooth Address automagically """ 107 | devices = await discover() 108 | mirobot_bt = next((d for d in devices if d.name == 'QN-Mini6Axis'), None) 109 | if mirobot_bt is None: 110 | raise Exception('Could not find mirobot bt') 111 | 112 | return mirobot_bt.address 113 | 114 | def connect(self): 115 | """ Connect to the Bluetooth Extender Box """ 116 | async def start_connection(): 117 | connection = await self.client.connect() 118 | 119 | services = await self.client.get_services() 120 | service = services.get_service("0000ffe0-0000-1000-8000-00805f9b34fb") 121 | 122 | self.characteristics = [c.uuid for c in service.characteristics] 123 | 124 | return connection 125 | 126 | self.connection = self._run_and_get(start_connection()) 127 | 128 | def disconnect(self): 129 | """ Disconnect from the Bluetooth Extender Box """ 130 | async def async_disconnect(): 131 | try: 132 | await self.client.disconnect() 133 | except AttributeError: 134 | ''' 135 | File "/home/chronos/.local/lib/python3.7/site-packages/bleak/backends/bluezdbus/client.py", line 235, in is_connected 136 | return await self._bus.callRemote( 137 | AttributeError: 'NoneType' object has no attribute 'callRemote' 138 | ''' 139 | # don\t know why it happens, it shouldn't and doesn't in normal async flow 140 | # but if it complains that client._bus is None, then we're good, right...? 141 | pass 142 | 143 | self._run_and_get(async_disconnect()) 144 | 145 | @property 146 | def is_connected(self): 147 | """ Whether this class is connected to the Bluetooth Extender Box """ 148 | return self.connection 149 | 150 | def send(self, msg, disable_debug=False, terminator=None, wait=True, wait_idle=True): 151 | """ 152 | 153 | Send a message to the Bluetooth Extender Box. Shouldn't be used by the end user. 154 | Parameters 155 | ---------- 156 | msg : str 157 | The message/instruction to send. A `\\r\\n` will be appended to this message. 158 | disable_debug : bool 159 | (Default value = False) Whether to disable debug statements on `idle`-state polling. 160 | terminator : str 161 | (Default value = `None`) Dummy variable for this method. This implementation will always use `\\r\\n` as the line terminator. 162 | wait : bool 163 | (Default value = True) Whether to wait for the command to return a `ok` response. 164 | wait_idle : 165 | (Default value = True) Whether to wait for the Mirobot to be in an `Idle` state before returning. 166 | 167 | Returns 168 | ------- 169 | msg : List[str] or bool 170 | If `wait` is `True`, then return a list of strings which contains message output. 171 | If `wait` is `False`, then return whether sending the message succeeded. 172 | 173 | """ 174 | self.feedback = [] 175 | self.ok_counter = 0 176 | self.disable_debug = disable_debug 177 | 178 | reset_strings = ['Using reset pos!'] 179 | 180 | def matches_eol_strings(terms, s): 181 | for eol in terms: 182 | if s.endswith(eol): 183 | return True 184 | return False 185 | 186 | def notification_handler(sender, data): 187 | data = data.decode() 188 | 189 | data_lines = re.findall(r".*[\r\n]{0,1}", data) 190 | for line in data_lines[:-1]: 191 | if self._debug and not self.disable_debug: 192 | self.logger.debug(f"[RECV] {repr(line)}") 193 | 194 | if self.feedback and not self.feedback[-1].endswith('\r\n'): 195 | self.feedback[-1] += line 196 | else: 197 | if self.feedback: 198 | self.feedback[-1] = self.feedback[-1].strip('\r\n') 199 | 200 | if 'error' in line: 201 | self.logger.error(MirobotError(line.replace('error: ', ''))) 202 | 203 | if 'ALARM' in line: 204 | self.logger.error(MirobotAlarm(line.split('ALARM: ', 1)[1])) 205 | 206 | if matches_eol_strings(reset_strings, line): 207 | self.logger.error(MirobotReset('Mirobot was unexpectedly reset!')) 208 | 209 | self.feedback.append(line) 210 | 211 | if self.feedback[-1] == 'ok\r\n': 212 | self.ok_counter += 1 213 | 214 | async def async_send(msg): 215 | async def write(msg): 216 | for c in self.characteristics: 217 | await self.client.write_gatt_char(c, msg) 218 | 219 | if wait: 220 | for c in self.characteristics: 221 | await self.client.start_notify(c, notification_handler) 222 | 223 | for s in chunks(bytes(msg + '\r\n', 'utf-8'), 20): 224 | await write(s) 225 | 226 | if self._debug and not disable_debug: 227 | self.logger.debug(f"[SENT] {msg}") 228 | 229 | if wait: 230 | while self.ok_counter < 2: 231 | # print('waiting...', msg, self.ok_counter) 232 | await asyncio.sleep(0.1) 233 | 234 | if wait_idle: 235 | # TODO: really wish I could recursively call `send(msg)` here instead of 236 | # replicating logic. Alas... 237 | orig_feedback = self.feedback 238 | 239 | async def check_idle(): 240 | self.disable_debug = True 241 | self.feedback = [] 242 | self.ok_counter = 0 243 | await write(b'?\r\n') 244 | while self.ok_counter < 2: 245 | # print('waiting for idle...', msg, self.ok_counter) 246 | await asyncio.sleep(0.1) 247 | self.mirobot._set_status(self.mirobot._parse_status(self.feedback[0])) 248 | 249 | await check_idle() 250 | 251 | while self.mirobot.status.state != 'Idle': 252 | # print(self.mirobot.status.state) 253 | await check_idle() 254 | 255 | # print('finished idle') 256 | self.feedback = orig_feedback 257 | 258 | for c in self.characteristics: 259 | await self.client.stop_notify(c) 260 | 261 | self._run_and_get(async_send(msg)) 262 | 263 | if self.feedback: 264 | self.feedback[-1] = self.feedback[-1].strip('\r\n') 265 | 266 | # BUG: 267 | # the following bugs me so much, but I can't figure out why this is happening and needed: 268 | # Instant subsequent calls to `send_msg` hang, for some reason. 269 | # Like the second invocation doesn't start, it's gets stuck as `selector._poll` in asyncio 270 | # Putting a small delay fixes this but why...??? 271 | if os_is_posix: 272 | time.sleep(0.1) 273 | 274 | return self.feedback 275 | -------------------------------------------------------------------------------- /mirobot/exceptions.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | class ExitOnExceptionStreamHandler(logging.StreamHandler): 5 | '''数据流存在问题, 信息不能正常的发送''' 6 | def emit(self, record): 7 | super().emit(record) 8 | if record.levelno >= logging.ERROR: 9 | raise SystemExit(-1) 10 | 11 | 12 | class MirobotError(Exception): 13 | """ An inplace class for throwing Mirobot errors. """ 14 | pass 15 | 16 | 17 | class MirobotAlarm(Exception): 18 | """ An inplace class for throwing Mirobot alarms. """ 19 | pass 20 | 21 | 22 | class MirobotReset(Exception): 23 | """ An inplace class for when Mirobot resets. """ 24 | pass 25 | 26 | 27 | class MirobotAmbiguousPort(Exception): 28 | """ An inplace class for when the serial port is unconfigurable. """ 29 | pass 30 | 31 | 32 | class MirobotStatusError(Exception): 33 | """ An inplace class for when Mirobot's status message is unprocessable. """ 34 | pass 35 | 36 | 37 | class MirobotResetFileError(Exception): 38 | """ An inplace class for when Mirobot has problems using the given reset file. """ 39 | pass 40 | 41 | 42 | class MirobotVariableCommandError(Exception): 43 | """ An inplace class for when Mirobot finds a command that does not match variable setting-command syntax. """ 44 | pass 45 | 46 | 47 | class SerialDeviceReadError(Exception): 48 | """ An inplace class for when SerialDevice is unable to read the serial port """ 49 | pass 50 | 51 | 52 | class SerialDeviceOpenError(Exception): 53 | """ An inplace class for when SerialDevice is unable to open the serial port """ 54 | pass 55 | 56 | 57 | class SerialDeviceCloseError(Exception): 58 | """ An inplace class for when SerialDevice is unable to close the serial port """ 59 | pass 60 | 61 | 62 | class SerialDeviceWriteError(Exception): 63 | """ An inplace class for when SerialDevice is unable to write to the serial port """ 64 | pass 65 | 66 | 67 | class InvalidBluetoothAddressError(Exception): 68 | """ An inplace class for when an invalid Bluetooth address is given """ 69 | pass 70 | -------------------------------------------------------------------------------- /mirobot/extended_dataclasses.py: -------------------------------------------------------------------------------- 1 | from dataclasses import asdict, astuple, fields 2 | import numbers 3 | import operator 4 | 5 | 6 | class basic_dataclass: 7 | def asdict(self): 8 | return asdict(self) 9 | 10 | def astuple(self): 11 | return astuple(self) 12 | 13 | def fields(self): 14 | return fields(self) 15 | 16 | @classmethod 17 | def _new_from_dict(cls, dictionary): 18 | return cls(**dictionary) 19 | 20 | 21 | class featured_dataclass(basic_dataclass): 22 | def _cross_same_type(self, other, operation_function, single=False): 23 | new_values = {} 24 | for f in self.fields(): 25 | this_value = getattr(self, f.name) 26 | 27 | if single: 28 | other_value = other 29 | else: 30 | other_value = getattr(other, f.name) 31 | 32 | result = operation_function(this_value, other_value) 33 | 34 | new_values[f.name] = result 35 | 36 | return new_values 37 | 38 | def _binary_operation(self, other, operation): 39 | def operation_function(this_value, other_value): 40 | if None in (this_value, other_value): 41 | return None 42 | else: 43 | return operation(this_value, other_value) 44 | 45 | if isinstance(other, type(self)): 46 | new_values = self._cross_same_type(other, operation_function) 47 | 48 | elif isinstance(other, numbers.Real): 49 | new_values = self._cross_same_type(other, operation_function, single=True) 50 | 51 | else: 52 | raise NotImplementedError(f"Cannot handle {type(self)} and {type(other)}") 53 | 54 | return self._new_from_dict(new_values) 55 | 56 | def _unary_operation(self, operation_function): 57 | new_values = {f.name: operation_function(f) 58 | for f in self.fields()} 59 | 60 | return self._new_from_dict(new_values) 61 | 62 | def _basic_unary_operation(self, operation): 63 | def operation_function(field): 64 | value = getattr(self, field.name) 65 | if value is not None: 66 | return operation(value) 67 | else: 68 | return None 69 | 70 | return self._unary_operation(operation_function) 71 | 72 | def _comparision_operation(self, other, operation): 73 | def operation_function(this_value, other_value): 74 | if None in (this_value, other_value): 75 | return True 76 | else: 77 | return operation(this_value, other_value) 78 | 79 | if isinstance(other, type(self)): 80 | new_values = self._cross_same_type(other, operation_function).values() 81 | 82 | elif isinstance(other, (int, float)): 83 | new_values = self._cross_same_type(other, operation_function, single=True).values() 84 | 85 | else: 86 | raise NotImplementedError(f"Cannot handle {type(self)} and {type(other)}") 87 | 88 | if all(new_values): 89 | return True 90 | 91 | elif not any(new_values): 92 | return False 93 | 94 | else: 95 | return None 96 | 97 | def __or__(self, other): 98 | def operation_function(this_value, other_value): 99 | if this_value is None: 100 | return other_value 101 | else: 102 | return this_value 103 | 104 | new_values = self._cross_same_type(other, operation_function) 105 | return self._new_from_dict(new_values) 106 | 107 | def __and__(self, other): 108 | def operation_function(this_value, other_value): 109 | if None not in (this_value, other_value): 110 | return this_value 111 | else: 112 | return None 113 | 114 | new_values = self._cross_same_type(other, operation_function) 115 | return self._new_from_dict(new_values) 116 | 117 | def int(self): 118 | def operation_function(field): 119 | value = getattr(self, field.name) 120 | if field.type in (float,) and value is not None: 121 | return int(value) 122 | else: 123 | return value 124 | 125 | return self._unary_operation(operation_function) 126 | 127 | def round(self): 128 | def operation_function(field): 129 | value = getattr(self, field.name) 130 | if field.type in (float,) and value is not None: 131 | return round(value) 132 | else: 133 | return value 134 | 135 | return self._unary_operation(operation_function) 136 | 137 | def __add__(self, other): 138 | return self._binary_operation(other, operator.add) 139 | 140 | def __radd__(self, other): 141 | return self._binary_operation(other, operator.add) 142 | 143 | def __sub__(self, other): 144 | return self._binary_operation(other, operator.sub) 145 | 146 | def __rsub__(self, other): 147 | def rsub(dataclass_value, number): 148 | return operator.sub(number, dataclass_value) 149 | 150 | return self._binary_operation(other, rsub) 151 | 152 | def __mul__(self, other): 153 | return self._binary_operation(other, operator.mul) 154 | 155 | def __rmul__(self, other): 156 | return self._binary_operation(other, operator.mul) 157 | 158 | def __div__(self, other): 159 | return self._binary_operation(other, operator.div) 160 | 161 | def __rdiv__(self, other): 162 | def rdiv(dataclass_value, number): 163 | return operator.div(number, dataclass_value) 164 | 165 | return self._binary_operation(other, rdiv) 166 | 167 | def __truediv__(self, other): 168 | return self._binary_operation(other, operator.truediv) 169 | 170 | def __rtruediv__(self, other): 171 | def rtruediv(dataclass_value, number): 172 | return operator.truediv(number, dataclass_value) 173 | 174 | return self._binary_operation(other, operator.truediv) 175 | 176 | def __mod__(self, other): 177 | return self._binary_operation(other, operator.mod) 178 | 179 | def __abs__(self): 180 | return self._basic_unary_operation(operator.abs) 181 | 182 | def __pos__(self): 183 | return self._basic_unary_operation(operator.pos) 184 | 185 | def __neg__(self): 186 | return self._basic_unary_operation(operator.neg) 187 | 188 | def __lt__(self, other): 189 | return self._comparision_operation(other, operator.lt) 190 | 191 | def __le__(self, other): 192 | return self._comparision_operation(other, operator.le) 193 | 194 | def __eq__(self, other): 195 | return self._comparision_operation(other, operator.eq) 196 | 197 | def __ne__(self, other): 198 | return self._comparision_operation(other, operator.ne) 199 | 200 | def __ge__(self, other): 201 | return self._comparision_operation(other, operator.ge) 202 | 203 | def __gt__(self, other): 204 | return self._comparision_operation(other, operator.gt) 205 | -------------------------------------------------------------------------------- /mirobot/mirobot.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from typing import NamedTuple 3 | 4 | from .base_mirobot import BaseMirobot 5 | from .base_rover import BaseRover 6 | from .mirobot_status import MirobotAngles, MirobotCartesians 7 | 8 | dim_splitter: NamedTuple = namedtuple('dim_spliter', ['cartesian', 'angle', 'rail']) 9 | cartesian_type_splitter: NamedTuple = namedtuple('cartesian_type_splitter', ['ptp', 'lin']) 10 | left_right_splitter: NamedTuple = namedtuple('left_right_splitter', ['left', 'right']) 11 | upper_lower_splitter: NamedTuple = namedtuple('upper_lower_splitter', ['upper', 'lower']) 12 | four_way_splitter: NamedTuple = namedtuple('four_way_splitter', ['left', 'right', 'upper', 'lower']) 13 | forward_backward_splitter: NamedTuple = namedtuple('forward_backward_splitter', ['forward', 'backward']) 14 | rover_splitter: NamedTuple = namedtuple('rover_splitter', ['wheel', 'rotate', 'move']) 15 | 16 | 17 | class Mirobot(BaseMirobot): 18 | """ A class for managing and maintaining known Mirobot operations.""" 19 | 20 | def __init__(self, *base_mirobot_args, **base_mirobot_kwargs): 21 | """ 22 | Initialization of the `Mirobot` class. 23 | 24 | Parameters 25 | ---------- 26 | *base_mirobot_args : Any 27 | Arguments that are passed into `mirobot.base_mirobot.BaseMirobot`. See `mirobot.base_mirobot.BaseMirobot.__init__` for more details. 28 | 29 | **base_mirobot_kwargs : Any 30 | Keyword arguments that are passed into `mirobot.base_mirobot.BaseMirobot`. See `mirobot.base_mirobot.BaseMirobot.__init__` for more details. 31 | 32 | Returns 33 | ------- 34 | class : `Mirobot` 35 | 36 | """ 37 | super().__init__(*base_mirobot_args, **base_mirobot_kwargs) 38 | 39 | self._rover = BaseRover(self) 40 | 41 | self.move = dim_splitter(cartesian=cartesian_type_splitter(ptp=self.go_to_cartesian_ptp, 42 | lin=self.go_to_cartesian_lin), 43 | angle=self.go_to_axis, 44 | rail=self.go_to_slide_rail) 45 | """ The root of the move alias. Uses `go_to_...` methods. Can be used as `mirobot.move.ptp(...)` or `mirobot.move.angle(...)` """ 46 | 47 | self.increment = dim_splitter(cartesian=cartesian_type_splitter(ptp=self.increment_cartesian_ptp, 48 | lin=self.increment_cartesian_lin), 49 | angle=self.increment_axis, 50 | rail=self.increment_slide_rail) 51 | """ The root of the increment alias. Uses `increment_...` methods. Can be used as `mirobot.increment.ptp(...)` or `mirobot.increment.angle(...)` """ 52 | 53 | self.wheel = four_way_splitter(upper=left_right_splitter(left=self._rover.move_upper_left, 54 | right=self._rover.move_upper_right), 55 | lower=left_right_splitter(left=self._rover.move_bottom_left, 56 | right=self._rover.move_bottom_right), 57 | left=upper_lower_splitter(upper=self._rover.move_upper_left, 58 | lower=self._rover.move_bottom_left), 59 | right=upper_lower_splitter(upper=self._rover.move_upper_right, 60 | lower=self._rover.move_bottom_right)) 61 | 62 | self.rover = rover_splitter(wheel=self.wheel, 63 | rotate=left_right_splitter(left=self._rover.rotate_left, 64 | right=self._rover.rotate_right), 65 | move=forward_backward_splitter(forward=self._rover.move_forward, 66 | backward=self._rover.move_backward)) 67 | """ The root of the rover alias. Uses methods from `mirobot.base_rover.BaseRover`. Can be used as `mirobot.rover.wheel.upper.right(...)` or `mirobot.rover.rotate.left(...)` or `mirobot.rover.move.forward(...)`""" 68 | 69 | @property 70 | def state(self): 71 | """ The brief descriptor string for Mirobot's state. """ 72 | return self.status.state 73 | 74 | @property 75 | def cartesian(self): 76 | """ Dataclass that holds the cartesian values and roll/pitch/yaw angles. """ 77 | return self.status.cartesian 78 | 79 | @property 80 | def angle(self): 81 | """ Dataclass that holds Mirobot's angular values including the rail position value. """ 82 | return self.status.angle 83 | 84 | @property 85 | def rail(self): 86 | """ Location of external slide rail module """ 87 | return self.status.angle.d 88 | 89 | @property 90 | def valve_pwm(self): 91 | """ The current pwm of the value module. (eg. gripper) """ 92 | return self.status.valve_pwm 93 | 94 | @property 95 | def pump_pwm(self): 96 | """ The current pwm of the pnuematic pump module. """ 97 | return self.status.pump_pwm 98 | 99 | @property 100 | def motion_mode(self): 101 | """ Whether Mirobot is currently in coordinate mode (`False`) or joint-motion mode (`True`) """ 102 | return self.status.motion_mode 103 | 104 | def go_to_zero(self, speed=None, wait=None): 105 | """ 106 | Send all axes to their respective zero positions. 107 | 108 | Parameters 109 | ---------- 110 | speed : int 111 | (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s) 112 | wait : bool 113 | (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead. 114 | 115 | Returns 116 | ------- 117 | msg : List[str] or bool 118 | If `wait` is `True`, then return a list of strings which contains message output. 119 | If `wait` is `False`, then return whether sending the message succeeded. 120 | """ 121 | return self.go_to_axis(0, 0, 0, 0, 0, 0, 0, speed=speed, wait=wait) 122 | 123 | def go_to_cartesian_lin(self, x=None, y=None, z=None, a=None, b=None, c=None, speed=None, wait=None): 124 | """ 125 | Linear move to a position in cartesian coordinates. (Command: `M20 G90 G1`) 126 | 127 | Parameters 128 | ---------- 129 | x : Union[float, mirobot.mirobot_status.MirobotCartesians] 130 | (Default value = `None`) If `float`, this represents the X-axis position. 131 | If of type `mirobot.mirobot_status.MirobotCartesians`, then this will be used for all positional values instead. 132 | y : float 133 | (Default value = `None`) Y-axis position. 134 | z : float 135 | (Default value = `None`) Z-axis position. 136 | a : float 137 | (Default value = `None`) Orientation angle: Roll angle 138 | b : float 139 | (Default value = `None`) Orientation angle: Pitch angle 140 | c : float 141 | (Default value = `None`) Orientation angle: Yaw angle 142 | speed : int 143 | (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s) 144 | wait : bool 145 | (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead. 146 | 147 | Returns 148 | ------- 149 | msg : List[str] or bool 150 | If `wait` is `True`, then return a list of strings which contains message output. 151 | If `wait` is `False`, then return whether sending the message succeeded. 152 | """ 153 | if isinstance(x, MirobotCartesians): 154 | inputs = x.asdict() 155 | 156 | else: 157 | inputs = {'x': x, 'y': y, 'z': z, 'a': a, 'b': b, 'c': c} 158 | 159 | return super().go_to_cartesian_lin(**inputs, 160 | speed=speed, wait=wait) 161 | 162 | def set_wrist_pose(self, x=None, y=None, z=None, roll=0.0, pitch=0.0, yaw=0.0, mode='p2p', speed=None, wait=None): 163 | """ 164 | 设置腕关节的位姿 165 | 166 | Parameters 167 | ---------- 168 | x : float 169 | (Default value = `None`) 腕关节在机械臂基坐标系下的x轴坐标 170 | y : float 171 | (Default value = `None`) 腕关节在机械臂基坐标系下的y轴坐标 172 | z : float 173 | (Default value = `None`) 腕关节在机械臂基坐标系下的z轴坐标 174 | roll : float 175 | (Default value = `None`) 腕关节在机械臂基坐标系下的横滚角(Roll angle) 176 | pitch : float 177 | (Default value = `None`) 腕关节在机械臂基坐标系下的俯仰角(Pitch angle) 178 | yaw : float 179 | (Default value = `None`) 腕关节在机械臂基坐标系下的偏航角(Yaw angle) 180 | mode : string 181 | (Default value = `p2p`) 运动控制的模式, 默认选择p2p 182 | `p2p`: 点到点的控制(Point to Point) 183 | `linear`: 直线插补(Linear Interpolation) 184 | speed : int 185 | (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s) 186 | wait : bool 187 | (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead. 188 | 189 | Returns 190 | ------- 191 | msg : List[str] or bool 192 | If `wait` is `True`, then return a list of strings which contains message output. 193 | If `wait` is `False`, then return whether sending the message succeeded. 194 | """ 195 | if mode == "p2p": 196 | # 点控模式 Point To Point 197 | self.go_to_cartesian_ptp(x=x, y=y, z=z, a=yaw, b=pitch, c=yaw, speed=speed, wait=wait) 198 | elif mode == "linear": 199 | # 直线插补 Linera Interpolation 200 | self.go_to_cartesian_lin(x=x, y=y, z=z, a=yaw, b=pitch, c=yaw, speed=speed, wait=wait) 201 | else: 202 | # 默认是点到点 203 | self.go_to_cartesian_ptp(x=x, y=y, z=z, a=yaw, b=pitch, c=yaw, speed=speed, wait=wait) 204 | 205 | def go_to_cartesian_ptp(self, x=None, y=None, z=None, a=None, b=None, c=None, speed=None, wait=None): 206 | """ 207 | Point-to-point move to a position in cartesian coordinates. (Command: `M20 G90 G0`) 208 | 209 | Parameters 210 | ---------- 211 | x : Union[float, mirobot.mirobot_status.MirobotCartesians] 212 | (Default value = `None`) If `float`, this represents the X-axis position. 213 | If of type `mirobot.mirobot_status.MirobotCartesians`, then this will be used for all positional values instead. 214 | y : float 215 | (Default value = `None`) Y-axis position. 216 | z : float 217 | (Default value = `None`) Z-axis position. 218 | a : float 219 | (Default value = `None`) Orientation angle: Roll angle 220 | b : float 221 | (Default value = `None`) Orientation angle: Pitch angle 222 | c : float 223 | (Default value = `None`) Orientation angle: Yaw angle 224 | speed : int 225 | (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s) 226 | wait : bool 227 | (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead. 228 | 229 | Returns 230 | ------- 231 | msg : List[str] or bool 232 | If `wait` is `True`, then return a list of strings which contains message output. 233 | If `wait` is `False`, then return whether sending the message succeeded. 234 | """ 235 | 236 | if isinstance(x, MirobotCartesians): 237 | inputs = x.asdict() 238 | 239 | else: 240 | inputs = {'x': x, 'y': y, 'z': z, 'a': a, 'b': b, 'c': c} 241 | 242 | return super().go_to_cartesian_ptp(**inputs, 243 | speed=speed, wait=wait) 244 | 245 | def go_to_axis(self, x=None, y=None, z=None, a=None, b=None, c=None, d=None, speed=None, wait=None): 246 | """ 247 | Send all axes to a specific position in angular coordinates. (Command: `M21 G90`) 248 | 249 | Parameters 250 | ---------- 251 | x : Union[float, mirobot.mirobot_status.MirobotAngles] 252 | (Default value = `None`) If `float`, this represents the angle of axis 1. 253 | If of type `mirobot.mirobot_status.MirobotAngles`, then this will be used for all positional values instead. 254 | y : float 255 | (Default value = `None`) Angle of axis 2. 256 | z : float 257 | (Default value = `None`) Angle of axis 3. 258 | a : float 259 | (Default value = `None`) Angle of axis 4. 260 | b : float 261 | (Default value = `None`) Angle of axis 5. 262 | c : float 263 | (Default value = `None`) Angle of axis 6. 264 | d : float 265 | (Default value = `None`) Location of slide rail module. 266 | speed : int 267 | (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s) 268 | wait : bool 269 | (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead. 270 | 271 | Returns 272 | ------- 273 | msg : List[str] or bool 274 | If `wait` is `True`, then return a list of strings which contains message output. 275 | If `wait` is `False`, then return whether sending the message succeeded. 276 | """ 277 | if isinstance(x, MirobotAngles): 278 | inputs = x.asdict() 279 | 280 | else: 281 | inputs = {'x': x, 'y': y, 'z': z, 'a': a, 'b': b, 'c': c, 'd': d} 282 | 283 | return super().go_to_axis(**inputs, 284 | speed=speed, wait=wait) 285 | 286 | def increment_cartesian_lin(self, x=None, y=None, z=None, a=None, b=None, c=None, speed=None, wait=None): 287 | """ 288 | Linear increment in cartesian coordinates. (Command: `M20 G91 G1`) 289 | 290 | Parameters 291 | ---------- 292 | x : Union[float, mirobot.mirobot_status.MirobotCartesians] 293 | (Default value = `None`) If `float`, this represents the X-axis position. 294 | If of type `mirobot.mirobot_status.MirobotCartesians`, then this will be used for all positional values instead. 295 | y : float 296 | (Default value = `None`) Y-axis position 297 | z : float 298 | (Default value = `None`) Z-axis position. 299 | a : float 300 | (Default value = `None`) Orientation angle: Roll angle 301 | b : float 302 | (Default value = `None`) Orientation angle: Pitch angle 303 | c : float 304 | (Default value = `None`) Orientation angle: Yaw angle 305 | speed : int 306 | (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s) 307 | wait : bool 308 | (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead. 309 | 310 | Returns 311 | ------- 312 | msg : List[str] or bool 313 | If `wait` is `True`, then return a list of strings which contains message output. 314 | If `wait` is `False`, then return whether sending the message succeeded. 315 | """ 316 | if isinstance(x, MirobotCartesians): 317 | inputs = x.asdict() 318 | 319 | else: 320 | inputs = {'x': x, 'y': y, 'z': z, 'a': a, 'b': b, 'c': c} 321 | 322 | return super().increment_cartesian_lin(**inputs, 323 | speed=speed, wait=wait) 324 | 325 | def increment_cartesian_ptp(self, x=None, y=None, z=None, a=None, b=None, c=None, speed=None, wait=None): 326 | """ 327 | Point-to-point increment in cartesian coordinates. (Command: `M20 G91 G0`) 328 | 329 | Parameters 330 | ---------- 331 | x : Union[float, mirobot.mirobot_status.MirobotCartesians] 332 | (Default value = `None`) If `float`, this represents the X-axis position. 333 | If of type `mirobot.mirobot_status.MirobotCartesians`, then this will be used for all positional values instead. 334 | y : float 335 | (Default value = `None`) Y-axis position. 336 | z : float 337 | (Default value = `None`) Z-axis position. 338 | a : float 339 | (Default value = `None`) Orientation angle: Roll angle 340 | b : float 341 | (Default value = `None`) Orientation angle: Pitch angle 342 | c : float 343 | (Default value = `None`) Orientation angle: Yaw angle 344 | speed : int 345 | (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s) 346 | wait : bool 347 | (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead. 348 | 349 | Returns 350 | ------- 351 | msg : List[str] or bool 352 | If `wait` is `True`, then return a list of strings which contains message output. 353 | If `wait` is `False`, then return whether sending the message succeeded. 354 | """ 355 | if isinstance(x, MirobotCartesians): 356 | inputs = x.asdict() 357 | 358 | else: 359 | inputs = {'x': x, 'y': y, 'z': z, 'a': a, 'b': b, 'c': c} 360 | 361 | return super().increment_cartesian_ptp(**inputs, 362 | speed=speed, wait=wait) 363 | 364 | def increment_axis(self, x=None, y=None, z=None, a=None, b=None, c=None, d=None, speed=None, wait=None): 365 | """ 366 | Increment all axes a specified amount in angular coordinates. (Command: `M21 G91`) 367 | 368 | Parameters 369 | ---------- 370 | x : Union[float, mirobot.mirobot_status.MirobotAngles] 371 | (Default value = `None`) If `float`, this represents the angle of axis 1. 372 | If of type `mirobot.mirobot_status.MirobotAngles`, then this will be used for all positional values instead. 373 | y : float 374 | (Default value = `None`) Angle of axis 2. 375 | z : float 376 | (Default value = `None`) Angle of axis 3. 377 | a : float 378 | (Default value = `None`) Angle of axis 4. 379 | b : float 380 | (Default value = `None`) Angle of axis 5. 381 | c : float 382 | (Default value = `None`) Angle of axis 6. 383 | d : float 384 | (Default value = `None`) Location of slide rail module. 385 | speed : int 386 | (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s) 387 | wait : bool 388 | (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead. 389 | 390 | Returns 391 | ------- 392 | msg : List[str] or bool 393 | If `wait` is `True`, then return a list of strings which contains message output. 394 | If `wait` is `False`, then return whether sending the message succeeded. 395 | """ 396 | if isinstance(x, MirobotAngles): 397 | inputs = x.asdict() 398 | 399 | else: 400 | inputs = {'x': x, 'y': y, 'z': z, 'a': a, 'b': b, 'c': c, 'd': d} 401 | 402 | return super().increment_axis(**inputs, 403 | speed=speed, wait=wait) 404 | 405 | def increment_slide_rail(self, d, speed=None, wait=None): 406 | """ 407 | Increment slide rail position a specified amount. (Command: `M21 G91`) 408 | 409 | Parameters 410 | ---------- 411 | d : float 412 | Location of slide rail module. 413 | speed : int 414 | (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s) 415 | wait : bool 416 | (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead. 417 | 418 | Returns 419 | ------- 420 | msg : List[str] or bool 421 | If `wait` is `True`, then return a list of strings which contains message output. 422 | If `wait` is `False`, then return whether sending the message succeeded. 423 | """ 424 | 425 | return super().increment_axis(d=d, 426 | speed=speed, wait=wait) 427 | 428 | def go_to_slide_rail(self, d, speed=None, wait=None): 429 | """ 430 | Go to the slide rail position specified. (Command: `M21 G90`) 431 | 432 | Parameters 433 | ---------- 434 | d : float 435 | Location of slide rail module. 436 | speed : int 437 | (Default value = `None`) The speed in which the Mirobot moves during this operation. (mm/s) 438 | wait : bool 439 | (Default value = `None`) Whether to wait for output to return from the Mirobot before returning from the function. This value determines if the function will block until the operation recieves feedback. If `None`, use class default `BaseMirobot.wait` instead. 440 | 441 | Returns 442 | ------- 443 | msg : List[str] or bool 444 | If `wait` is `True`, then return a list of strings which contains message output. 445 | If `wait` is `False`, then return whether sending the message succeeded. 446 | """ 447 | 448 | return super().go_to_axis(d=d, 449 | speed=speed, wait=wait) 450 | @property 451 | def pose(self): 452 | return self.cartesian -------------------------------------------------------------------------------- /mirobot/mirobot_status.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from .extended_dataclasses import basic_dataclass, featured_dataclass 4 | 5 | 6 | @dataclass 7 | class MirobotAngles(featured_dataclass): 8 | """ A dataclass to hold Mirobot's angular values. """ 9 | a: float = None 10 | """ Angle of axis 1 """ 11 | b: float = None 12 | """ Angle of axis 2 """ 13 | c: float = None 14 | """ Angle of axis 3 """ 15 | x: float = None 16 | """ Angle of axis 4 """ 17 | y: float = None 18 | """ Angle of axis 5 """ 19 | z: float = None 20 | """ Angle of axis 6 """ 21 | d: float = None 22 | """ Location of external slide rail module """ 23 | 24 | @property 25 | def a1(self): 26 | """ Angle of axis 1 """ 27 | return self.a 28 | 29 | @property 30 | def a2(self): 31 | """ Angle of axis 2 """ 32 | return self.b 33 | 34 | @property 35 | def a3(self): 36 | """ Angle of axis 3 """ 37 | return self.c 38 | 39 | @property 40 | def a4(self): 41 | """ Angle of axis 4 """ 42 | return self.x 43 | 44 | @property 45 | def a5(self): 46 | """ Angle of axis 5 """ 47 | return self.y 48 | 49 | @property 50 | def a6(self): 51 | """ Angle of axis 6 """ 52 | return self.z 53 | 54 | @property 55 | def rail(self): 56 | """ 57 | Location of external slide rail module 58 | 第七轴也就是直线滑轨的平移 59 | """ 60 | return self.d 61 | 62 | @property 63 | def joint1(self): 64 | """ 65 | 关节1的角度, 单位° 66 | """ 67 | return self.x 68 | 69 | @property 70 | def joint2(self): 71 | """ 72 | 关节2的角度, 单位° 73 | """ 74 | return self.y 75 | 76 | @property 77 | def joint3(self): 78 | """ 79 | 关节2的角度, 单位° 80 | """ 81 | return self.z 82 | 83 | @property 84 | def joint4(self): 85 | """ 86 | 关节4的角度, 单位° 87 | """ 88 | return self.a 89 | 90 | @property 91 | def joint5(self): 92 | """ 93 | 关节5的角度, 单位° 94 | """ 95 | return self.b 96 | 97 | @property 98 | def joint6(self): 99 | """ 100 | 关节4的角度, 单位° 101 | """ 102 | return self.c 103 | 104 | @dataclass 105 | class MirobotCartesians(featured_dataclass): 106 | """ A dataclass to hold Mirobot's cartesian values and roll/pitch/yaw angles. """ 107 | x: float = None 108 | """ Position on X-axis """ 109 | y: float = None 110 | """ Position of Y-axis """ 111 | z: float = None 112 | """ Position of Z-axis """ 113 | a: float = None 114 | """ Position of Roll angle """ 115 | b: float = None 116 | """ Position of Pitch angle """ 117 | c: float = None 118 | """ Position of Yaw angle """ 119 | 120 | @property 121 | def tx(self): 122 | """ Position on X-axis """ 123 | return self.x 124 | 125 | @property 126 | def ty(self): 127 | """ Position on Y-axis """ 128 | return self.y 129 | 130 | @property 131 | def tz(self): 132 | """ Position on Z-axis """ 133 | return self.z 134 | 135 | 136 | @property 137 | def rx(self): 138 | """ Position of Roll angle """ 139 | return self.a 140 | 141 | @property 142 | def ry(self): 143 | """ Position of Pitch angle """ 144 | return self.b 145 | 146 | @property 147 | def rz(self): 148 | """ Position of Yaw angle """ 149 | return self.c 150 | 151 | @property 152 | def roll(self): 153 | """ 横滚角,单位° """ 154 | return self.a 155 | 156 | @property 157 | def pitch(self): 158 | """ 俯仰角,单位° """ 159 | return self.b 160 | 161 | @property 162 | def yaw(self): 163 | """ 偏航角,单位° """ 164 | return self.c 165 | 166 | def __str__(self): 167 | return f"Pose(x={self.x},y={self.y},z={self.z},roll={self.roll},pitch={self.pitch},yaw={self.yaw})" 168 | 169 | @dataclass 170 | class MirobotStatus(basic_dataclass): 171 | """ A composite dataclass to hold all of Mirobot's trackable quantities. """ 172 | state: str = '' 173 | """ The brief descriptor string for Mirobot's state. """ 174 | angle: MirobotAngles = MirobotAngles() 175 | """ Dataclass that holds Mirobot's angular values including the rail position value. """ 176 | cartesian: MirobotCartesians = MirobotCartesians() 177 | """ Dataclass that holds the cartesian values and roll/pitch/yaw angles. """ 178 | pump_pwm: int = None 179 | """ The current pwm of the pnuematic pump module. """ 180 | valve_pwm: int = None 181 | """ The current pwm of the value module. (eg. gripper) """ 182 | motion_mode: bool = False 183 | """ Whether Mirobot is currently in coordinate mode (`False`) or joint-motion mode (`True`) """ 184 | -------------------------------------------------------------------------------- /mirobot/resources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlkata/mirobot-py/28f7efa5fe8ae27740068de2cf0322c3599c3e17/mirobot/resources/__init__.py -------------------------------------------------------------------------------- /mirobot/resources/reset.xml: -------------------------------------------------------------------------------- 1 | $1=0 2 | $2=0 3 | $3=119 4 | $4=0 5 | $5=0 6 | $6=0 7 | $10=99 8 | $11=0.010 9 | $12=0.002 10 | $13=0 11 | $20=1 12 | $21=1 13 | $22=1 14 | $23=48 15 | $24=1500.000 16 | $25=2000.000 17 | $26=250 18 | $27=12.000 19 | $28=65 20 | $29=78.000 21 | $30=32.000 22 | $31=108.000 23 | $32=20.000 24 | $33=170.000 25 | $34=-25.000 26 | $35=0.000 27 | $36=50 28 | $37=0 29 | $38=2 30 | $39=1 31 | $40=0 32 | $41=0.000 33 | $42=0.000 34 | $43=0.000 35 | $100=16.000 36 | $101=22.220 37 | $102=11.110 38 | $103=200.000 39 | $104=56.730 40 | $105=113.330 41 | $106=64.000 42 | $110=1800.000 43 | $111=1500.000 44 | $112=2500.000 45 | $113=500.000 46 | $114=1800.000 47 | $115=1500.000 48 | $116=1800.000 49 | $120=50.000 50 | $121=50.000 51 | $122=50.000 52 | $123=50.000 53 | $124=50.000 54 | $125=50.000 55 | $126=50.000 56 | $130=350.000 57 | $131=40.000 58 | $132=360.000 59 | $133=1000.000 60 | $134=160.000 61 | $135=70.000 62 | $136=60.000 63 | $140=350.000 64 | $141=205.000 65 | $142=360.000 66 | $143=1000.000 67 | $144=100.000 68 | $145=30.000 69 | $146=170.000 70 | -------------------------------------------------------------------------------- /mirobot/serial_device.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import time 4 | import serial 5 | 6 | from .exceptions import ExitOnExceptionStreamHandler, SerialDeviceOpenError, SerialDeviceReadError, SerialDeviceCloseError, SerialDeviceWriteError 7 | 8 | 9 | class SerialDevice: 10 | """ A class for establishing a connection to a serial device. """ 11 | def __init__(self, portname='', baudrate=0, stopbits=1, exclusive=True, debug=False): 12 | """ Initialization of `SerialDevice` class 13 | 串口设备初始化 14 | 15 | Parameters 16 | ---------- 17 | portname : str 18 | (Default value = `''`) Name of the port to connect to. (Example: 'COM3' or '/dev/ttyUSB1') 19 | baudrate : int 20 | (Default value = `0`) Baud rate of the connection. 21 | stopbits : int 22 | (Default value = `1`) Stopbits of the connection. 23 | exclusive : bool 24 | (Default value = `True`) Whether to (try) forcing exclusivity of serial port for this instance. Is only a true toggle on Linux and OSx; Windows always exclusively blocks serial ports. Setting this variable to `False` on Windows will throw an error. 25 | debug : bool 26 | (Default value = `False`) Whether to print DEBUG-level information from the runtime of this class. Show more detailed information on screen output. 27 | 28 | Returns 29 | ------- 30 | class : SerialDevice 31 | 32 | """ 33 | self.portname = str(portname) 34 | self.baudrate = int(baudrate) 35 | self.stopbits = int(stopbits) 36 | self.exclusive = exclusive 37 | self._debug = debug 38 | 39 | self.logger = logging.getLogger(__name__) 40 | self.logger.setLevel(logging.DEBUG) 41 | 42 | self.stream_handler = ExitOnExceptionStreamHandler() 43 | self.stream_handler.setLevel(logging.DEBUG if self._debug else logging.INFO) 44 | 45 | formatter = logging.Formatter(f"[{self.portname}] [%(levelname)s] %(message)s") 46 | self.stream_handler.setFormatter(formatter) 47 | self.logger.addHandler(self.stream_handler) 48 | 49 | self.serialport = serial.Serial(exclusive=exclusive) 50 | self._is_open = False 51 | 52 | def __del__(self): 53 | """ Close the serial port when the class is deleted """ 54 | self.close() 55 | 56 | @property 57 | def debug(self): 58 | """ Return the `debug` property of `SerialDevice` """ 59 | return self._debug 60 | 61 | @debug.setter 62 | def debug(self, value): 63 | """ 64 | Set the new `debug` property of `SerialDevice`. Use as in `SerialDevice.setDebug(value)`. 65 | 66 | Parameters 67 | ---------- 68 | value : bool 69 | The new value for `SerialDevice.debug`. User this setter method as it will also update the logging method. As opposed to setting `SerialDevice.debug` directly which will not update the logger. 70 | 71 | """ 72 | self._debug = bool(value) 73 | self.stream_handler.setLevel(logging.DEBUG if self._debug else logging.INFO) 74 | 75 | @property 76 | def is_open(self): 77 | """ Check if the serial port is open """ 78 | self._is_open = self.serialport.is_open 79 | return self._is_open 80 | 81 | def listen_to_device(self, timeout=0.1): 82 | """ 83 | Listen to the serial port and return a message. 84 | 85 | Returns 86 | ------- 87 | msg : str 88 | A single line that is read from the serial port. 89 | 90 | """ 91 | t_start = time.time() 92 | while self._is_open: 93 | # 超时判断 94 | t_cur = time.time() 95 | if (t_cur - t_start) >= timeout: 96 | return b'' 97 | 98 | try: 99 | msg = self.serialport.readline() 100 | if msg != b'': 101 | msg = msg.decode().strip() 102 | return msg 103 | 104 | except Exception as e: 105 | self.logger.exception(SerialDeviceReadError(e)) 106 | 107 | def open(self): 108 | """ Open the serial port. """ 109 | if not self._is_open: 110 | # serialport = 'portname', baudrate, bytesize = 8, parity = 'N', stopbits = 1, timeout = None, xonxoff = 0, rtscts = 0) 111 | self.serialport.port = self.portname 112 | self.serialport.baudrate = self.baudrate 113 | self.serialport.stopbits = self.stopbits 114 | 115 | try: 116 | self.logger.debug(f"Welcome to use mirobot_py (version: kyle2020-0821)") 117 | self.logger.debug(f"Attempting to open serial port {self.portname}") 118 | self.serialport.open() 119 | self._is_open = True 120 | 121 | self.logger.debug(f"Succeeded in opening serial port {self.portname}") 122 | 123 | except Exception as e: 124 | self.logger.exception(SerialDeviceOpenError(e)) 125 | 126 | def close(self): 127 | """ Close the serial port. """ 128 | if self._is_open: 129 | try: 130 | self.logger.debug(f"Attempting to close serial port {self.portname}") 131 | 132 | self._is_open = False 133 | self.serialport.close() 134 | 135 | self.logger.debug(f"Succeeded in closing serial port {self.portname}") 136 | 137 | except Exception as e: 138 | self.logger.exception(SerialDeviceCloseError(e)) 139 | 140 | def send(self, message, terminator=os.linesep): 141 | """ 142 | Send a message to the serial port. 143 | 144 | 145 | Parameters 146 | ---------- 147 | message : str 148 | The string to send to serial port. 149 | 150 | terminator : str 151 | (Default value = `os.linesep`) The line separator to use when signaling a new line. Usually `'\\r\\n'` for windows and `'\\n'` for modern operating systems. 152 | 153 | Returns 154 | ------- 155 | result : bool 156 | Whether the sending of `message` succeeded. 157 | 158 | """ 159 | if self._is_open: 160 | try: 161 | # 自动添加换行符 162 | if not message.endswith(terminator): 163 | message += terminator 164 | self.serialport.write(message.encode('utf-8')) 165 | 166 | except Exception as e: 167 | self.logger.exception(SerialDeviceWriteError(e)) 168 | 169 | else: 170 | return True 171 | else: 172 | return False 173 | -------------------------------------------------------------------------------- /mirobot/serial_interface.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | # 使用pyserial的串口设备列表查看器 4 | import serial.tools.list_ports as lp 5 | 6 | from .serial_device import SerialDevice 7 | from .exceptions import MirobotError, MirobotAlarm, MirobotReset, MirobotAmbiguousPort 8 | 9 | # 当前操作系统的类型 10 | # posix: Linux 11 | # nt: Windows 12 | # java: Java虚拟机 13 | os_is_nt = os.name == 'nt' 14 | os_is_posix = os.name == 'posix' 15 | 16 | 17 | class SerialInterface: 18 | """ A class for bridging the interface between `mirobot.base_mirobot.BaseMirobot` and `mirobot.serial_device.SerialDevice`""" 19 | def __init__(self, mirobot, portname=None, baudrate=None, stopbits=None, exclusive=True, debug=False, logger=None, autofindport=True): 20 | """ Initialization of `SerialInterface` class 21 | 22 | Parameters 23 | ---------- 24 | mirobot : `mirobot.base_mirobot.BaseMirobot` 25 | Mirobot object that this instance is attached to. 26 | portname : str 27 | (Default value = None) The portname to attach to. If `None`, and the `autofindport` parameter is `True`, then this class will automatically try to find an open port. It will attach to the first one that is available. 28 | baudrate : int 29 | (Default value = None) Baud rate of the connection. 30 | stopbits : int 31 | (Default value = None) Stopbits of the connection. 32 | exclusive : bool 33 | (Default value = True) Whether to exclusively block the port for this instance. Is only a true toggle on Linux and OSx; Windows always exclusively blocks serial ports. Setting this variable to `False` on Windows will throw an error. 34 | debug : bool 35 | (Default value = False) Whether to show debug statements in logger. 36 | logger : logger.Logger 37 | (Default value = None) Logger instance to use for this class. Usually `mirobot.base_mirobot.BaseMirobot.logger`. 38 | autofindport : bool 39 | (Default value = True) Whether to automatically search for an available port if `address` parameter is `None`. 40 | 41 | Returns 42 | ------- 43 | 44 | """ 45 | 46 | self.mirobot = mirobot 47 | 48 | if logger is not None: 49 | self.logger = logger 50 | 51 | self._debug = debug 52 | serial_device_kwargs = {'debug': debug, 'exclusive': exclusive} 53 | 54 | # check if baudrate was passed in args or kwargs, if not use the default value instead 55 | if baudrate is None: 56 | # 设置默认的波特率 57 | serial_device_kwargs['baudrate'] = 115200 58 | # check if stopbits was passed in args or kwargs, if not use the default value instead 59 | if stopbits is None: 60 | # 设置默认的停止位配置 61 | serial_device_kwargs['stopbits'] = 1 62 | 63 | # if portname was not passed in and autofindport is set to true, autosearch for a serial port 64 | # 如果没有指定端口号,自行进行搜索 65 | if autofindport and portname is None: 66 | self.default_portname = self._find_portname() 67 | """ The default portname to use when making connections. To override this on a individual basis, provide portname to each invokation of `BaseMirobot.connect`. """ 68 | serial_device_kwargs['portname'] = self.default_portname 69 | self.logger.info(f"Using Serial Port \"{self.default_portname}\"") 70 | else: 71 | # 设置端口号 72 | self.default_portname = portname 73 | 74 | self.serial_device = SerialDevice(**serial_device_kwargs) 75 | 76 | @property 77 | def debug(self): 78 | """ Return the `debug` property of `SerialInterface` """ 79 | return self._debug 80 | 81 | @debug.setter 82 | def debug(self, value): 83 | """ 84 | Set the new value for the `debug` property of `mirobot.serial_interface.SerialInterface`. Use as in `BaseMirobot.setDebug(value)`. 85 | Use this setter method as it will also update the logging objects of `mirobot.serial_interface.SerialInterface` and its `mirobot.serial_device.SerialDevice`. As opposed to setting `mirobot.serial_interface.SerialInterface._debug` directly which will not update the loggers. 86 | 87 | Parameters 88 | ---------- 89 | value : bool 90 | The new value for `mirobot.serial_interface.SerialInterface._debug`. 91 | 92 | """ 93 | self._debug = bool(value) 94 | self.serial_device.setDebug(value) 95 | 96 | def send(self, msg, disable_debug=False, terminator=os.linesep, wait=True, wait_idle=False): 97 | """ 98 | Send a message to the Mirobot. 99 | 100 | Parameters 101 | ---------- 102 | msg : str or bytes 103 | A message or instruction to send to the Mirobot. 104 | var_command : bool 105 | (Default value = `False`) Whether `msg` is a variable command (of form `$num=value`). Will throw an error if does not validate correctly. 106 | disable_debug : bool 107 | (Default value = `False`) Whether to override the class debug setting. Used primarily by ` BaseMirobot.device.wait_until_idle`. 108 | terminator : str 109 | (Default value = `os.linesep`) The line separator to use when signaling a new line. Usually `'\\r\\n'` for windows and `'\\n'` for modern operating systems. 110 | wait : bool 111 | (Default value = `None`) Whether to wait for output to end and to return that output. If `None`, use class default `BaseMirobot.wait` instead. 112 | wait_idle : bool 113 | (Default value = `False`) Whether to wait for Mirobot to be idle before returning. 114 | 115 | Returns 116 | ------- 117 | msg : List[str] or bool 118 | If `wait` is `True`, then return a list of strings which contains message output. 119 | If `wait` is `False`, then return whether sending the message succeeded. 120 | """ 121 | # 发送消息前需要先清除缓冲区 122 | cache_msg = self.empty_cache() 123 | if self._debug and not disable_debug: 124 | # 将缓冲数据打印出来 125 | self.logger.debug(f"[RECV CACHE] {cache_msg}") 126 | 127 | output = self.serial_device.send(msg, terminator=terminator) 128 | 129 | if self._debug and not disable_debug: 130 | self.logger.debug(f"[SENT] {msg}") 131 | 132 | if wait_idle: 133 | self.wait_until_idle() 134 | elif wait: 135 | output = self.wait_for_ok(disable_debug=disable_debug) 136 | 137 | return output 138 | 139 | @property 140 | def is_connected(self): 141 | """ 142 | Check if Mirobot is connected. 143 | 144 | Returns 145 | ------- 146 | connected : bool 147 | Whether the Mirobot is connected. 148 | """ 149 | return self.serial_device.is_open 150 | 151 | def _find_portname(self): 152 | """ 153 | Find the port that might potentially be connected to the Mirobot. 154 | 自动检索可能是Mirobot的端口号 155 | 156 | Returns 157 | ------- 158 | device_name : str 159 | The name of the device that is (most-likely) connected to the Mirobot. 160 | 端口号 161 | """ 162 | port_objects = lp.comports() 163 | 164 | if not port_objects: 165 | self.logger.exception(MirobotAmbiguousPort("No ports found! Make sure your Mirobot is connected and recognized by your operating system.")) 166 | 167 | else: 168 | for p in port_objects: 169 | if os_is_posix: 170 | try: 171 | open(p.device) 172 | except Exception: 173 | continue 174 | else: 175 | return p.device 176 | else: 177 | return p.device 178 | 179 | self.logger.exception(MirobotAmbiguousPort("No open ports found! Make sure your Mirobot is connected and is not being used by another process.")) 180 | 181 | def wait_for_ok(self, reset_expected=False, disable_debug=False): 182 | """ 183 | Continuously loops over and collects message output from the serial device. 184 | It stops when it encounters an 'ok' or otherwise terminal condition phrase. 185 | 持续等待有'ok'返回 186 | 187 | Parameters 188 | ---------- 189 | reset_expected : bool 190 | (Default value = `False`) Whether a reset string is expected in the output (Example: on starting up Mirobot, output ends with a `'Using reset pos!'` rather than the traditional `'Ok'`) 191 | disable_debug : bool 192 | (Default value = `False`) Whether to override the class debug setting. Otherwise one will see status message debug output every 0.1 seconds, thereby cluttering standard output. Used primarily by `BaseMirobot.wait_until_idle`. 193 | 194 | Returns 195 | ------- 196 | output : List[str] 197 | A list of output strings upto and including the terminal string. 198 | """ 199 | output = [''] 200 | # 代表ok的后缀 201 | ok_eols = ['ok'] 202 | # Reset重置的字符 203 | reset_strings = ['Using reset pos!'] 204 | 205 | # eol: end of line 一行的末尾 206 | def matches_eol_strings(terms, s): 207 | # print("matches_eol_strings: s={}".format(s)) 208 | for eol in terms: 209 | # 修改了这里的ok的判断条件 210 | # 因为homing成功之后,返回的不是ok而是homeing moving...ok 211 | # 针对这种情况做了优化, 防止卡死 212 | if s.endswith(eol) or eol in s: 213 | # self.logger.debug(f'String {s} terms:{terms}, match') 214 | return True 215 | return False 216 | 217 | if reset_expected: 218 | eols = ok_eols + reset_strings 219 | else: 220 | eols = ok_eols 221 | 222 | if os_is_nt and not reset_expected: 223 | # Window下的期待的ok返回次数 224 | # eol_threshold = 2 # 感觉是作者写错了 225 | eol_threshold = 1 226 | else: 227 | # Linux下的期待的ok返回次数 228 | eol_threshold = 1 229 | 230 | eol_counter = 0 231 | while eol_counter < eol_threshold: 232 | # 读取消息 233 | # 这里其实存在问题就是这里的listen_to_device是死循环 234 | msg = self.serial_device.listen_to_device(timeout=0.1) 235 | 236 | # 调试, 打印接收的消息 237 | if self._debug and not disable_debug: 238 | self.logger.debug(f"[RECV] {msg}") 239 | 240 | # 异常情况判断 241 | if 'error' in msg: 242 | self.logger.error(MirobotError(msg.replace('error: ', ''))) 243 | # 异常情况判断 244 | if 'ALARM' in msg: 245 | self.logger.error(MirobotAlarm(msg.split('ALARM: ', 1)[1])) 246 | 247 | output.append(msg) 248 | 249 | if not reset_expected and matches_eol_strings(reset_strings, msg): 250 | self.logger.error(MirobotReset('Mirobot was unexpectedly reset!')) 251 | 252 | if matches_eol_strings(eols, output[-1]): 253 | eol_counter += 1 254 | 255 | return output[1:] # don't include the dummy empty string at first index 256 | 257 | def wait_until_idle(self, refresh_rate=0.1): 258 | """ 259 | Continuously loops over and refreshes state of the Mirobot. 260 | It stops when it encounters an 'Idle' state string. 261 | 等待直到系统状态为Idle空闲状态 262 | 263 | Parameters 264 | ---------- 265 | refresh_rate : float 266 | (Default value = `0.1`) The rate in seconds to check for the 'Idle' state. Choosing a low number might overwhelm the controller on Mirobot. Be cautious when lowering this parameter. 267 | 268 | Returns 269 | ------- 270 | output : List[str] 271 | A list of output strings upto and including the terminal string. 272 | """ 273 | # 更新一下当前Mirobot的状态 274 | self.mirobot.update_status(disable_debug=True) 275 | # self.mirobot.update_status(disable_debug=False) 276 | while self.mirobot.status is None or self.mirobot.status.state != 'Idle': 277 | time.sleep(refresh_rate) 278 | # 不断的发送状态查询, 更新状态 279 | self.mirobot.update_status(disable_debug=True) 280 | # self.mirobot.update_status(disable_debug=False) 281 | # 打印mirobot当前的状态 282 | if self.mirobot.status is not None: 283 | self.logger.debug(f"current mirobot state: {self.mirobot.status.state}") 284 | 285 | def empty_cache(self): 286 | '''清空接收缓冲区''' 287 | cache_msg = "" 288 | while(self.serial_device.serialport.in_waiting): 289 | cache_msg += self.serial_device.serialport.read().decode('utf-8') 290 | return cache_msg 291 | 292 | def connect(self, portname=None): 293 | """ 294 | Connect to the Mirobot. 295 | 建立串口连接 296 | 297 | Parameters 298 | ---------- 299 | portname : str 300 | (Default value = `None`) The name of the port to connnect to. If this is `None`, then it will try to use `self.default_portname`. If both are `None`, then an error will be thrown. To avoid this, specify a portname. 301 | 302 | Returns 303 | ------- 304 | ok_msg : List[str] 305 | The output from an initial Mirobot connection. 306 | """ 307 | if portname is None: 308 | if self.default_portname is not None: 309 | portname = self.default_portname 310 | else: 311 | self.logger.exception(ValueError('Portname must be provided! Example: `portname="COM3"`')) 312 | 313 | self.serial_device.portname = portname 314 | 315 | self.serial_device.open() 316 | 317 | return self.wait_for_ok(reset_expected=True) 318 | 319 | def disconnect(self): 320 | """ 321 | Disconnect from the Mirobot. Close the serial device connection. 322 | 断开与Mirobot的连接,断开串口 323 | """ 324 | if getattr(self, 'serial_device', None) is not None: 325 | self.serial_device.close() 326 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyserial 2 | bleak 3 | -------------------------------------------------------------------------------- /scripts/live_docs.sh: -------------------------------------------------------------------------------- 1 | pdoc3 --http : mirobot 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import setuptools 4 | 5 | 6 | setuptools.setup(name='mirobot-py', 7 | version='v2.0.0-beta', 8 | description="A Python interface library for WKlata's Mirobot", 9 | author='Sourabh Cheedella', 10 | author_email='cheedella.sourabh@gmail.com', 11 | long_description=open("README.md", "r").read(), 12 | long_description_content_type='text/markdown', 13 | url="https://github.com/rirze/mirobot-py", 14 | packages=['mirobot'], 15 | classifiers=""" 16 | Development Status :: 4 - Beta 17 | Programming Language :: Python :: 3 :: Only 18 | Programming Language :: Python :: 3.6 19 | Programming Language :: Python :: 3.7 20 | Programming Language :: Python :: 3.8 21 | License :: OSI Approved :: MIT License 22 | Operating System :: OS Independent 23 | Operating System :: Microsoft :: Windows 24 | Operating System :: POSIX 25 | Operating System :: Unix 26 | Operating System :: MacOS 27 | Topic :: Scientific/Engineering 28 | Topic :: Education 29 | Topic :: Documentation 30 | Topic :: Home Automation 31 | Topic :: Scientific/Engineering :: Artificial Intelligence 32 | Topic :: Scientific/Engineering :: Electronic Design Automation (EDA) 33 | Topic :: Scientific/Engineering :: Image Recognition 34 | Topic :: Software Development :: Embedded Systems 35 | Topic :: Software Development :: Version Control :: Git 36 | Topic :: Terminals :: Serial 37 | Intended Audience :: Education 38 | Intended Audience :: Science/Research 39 | Intended Audience :: Manufacturing 40 | Intended Audience :: Developers 41 | """.splitlines(), 42 | python_requires='>=3.6', 43 | install_requires=open('requirements.txt', 'r').read().splitlines(), 44 | package_dir={'mirobot': 'mirobot'}, 45 | package_data={'mirobot': ['resources/*']} 46 | ) 47 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wlkata/mirobot-py/28f7efa5fe8ae27740068de2cf0322c3599c3e17/test.py --------------------------------------------------------------------------------