├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile ├── conf.py ├── examples.rst ├── index.rst ├── pyuarm.rst ├── pyuarm_api.rst ├── requirements.txt └── tools.rst ├── pyuarm ├── __init__.py ├── config.py ├── log.py ├── protocol.py ├── threaded.py ├── tools │ ├── __init__.py │ ├── calibrate.py │ ├── firmware.py │ ├── list_uarms.py │ ├── miniterm.py │ └── scripts.py ├── uarm.py ├── util.py └── version.py ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | 64 | 65 | .pypirc 66 | .DS_Store 67 | .idea 68 | upx391w/ 69 | firmware.hex 70 | *.sh 71 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Copyright UFactory 2 | 3 | language: python 4 | 5 | python: 6 | - 2.7 7 | - pypy 8 | 9 | script: 10 | - python setup.py install 11 | - python test/test.py loop:// 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ##[2.4.0.12] - 2017-03-11 2 | ### Update -Alex Tan 3 | 4 | - Firmware 2.2.1 5 | 6 | #### Changes 7 | 8 | 1. Improve Firmware flash 9 | 2. Improve Log description 10 | 11 | ## [2.4.0.9] - 2017-03-10 12 | ### Updated -Alex Tan 13 | 14 | - Firmware 2.2.1 15 | 16 | # Changes 17 | 1. Rewrite threading part improve performance 18 | 2. Add Wait parameters for Set Functions. 19 | 20 | 21 | ## [2.3.0.7] - 2016-12-04 22 | ### Updated -Alex Tan 23 | 24 | ### Compatible 25 | - Firmware 2.2 26 | 27 | ### Changes 28 | 1. Fix Bugs for Non-block Mode 29 | 2. Add Teach function 30 | 31 | ## [2.3.0.3] - 2016-12-04 32 | ### Updated -Alex Tan 33 | 34 | ### Compatible 35 | - Firmware 2.2 36 | 37 | ### Changes 38 | 1. Support Unblock Mode 39 | 2. rewrite FlashFirmware & calibrate. Flash Firmware only support external avrdude, calibrate only support query 40 | 3. Fix connect Flag support 41 | 4. Add set_polar support 42 | 43 | 44 | ## [2.2.5.1] - 2016-11-25 45 | ### Updated -Alex Tan 46 | 47 | ### Compatible 48 | - Firmware 2.2 49 | 50 | ### Changes 51 | 1. Support wait for set_position 52 | 53 | ## [2.2.5.1] - 2016-11-25 54 | ### Updated -Alex Tan 55 | 56 | ### Compatible 57 | - Firmware 2.2 58 | 59 | ### Changes 60 | 1. Fix Tip Sensor return False always 61 | 2. Rewrite logger logic 62 | 3. Add Relative coordinate support 63 | 4. Use printf in Calibrate 64 | 65 | 66 | ## [2.2.4] - 2016-11-16 67 | ### Updated -Alex Tan 68 | 69 | ### Compatible 70 | - Firmware 2.2 71 | 72 | ### Changes 73 | 1. Fix install bug 74 | 75 | 76 | ## [2.2.3] - 2016-11-16 77 | ### Updated -Alex Tan 78 | 79 | ### Compatible 80 | - Firmware 2.2 81 | 82 | ### Changes 83 | 1. Add function check_port_plug_in (beta) 84 | 2. Get Hardware version once connect 85 | 3. Fix is_connected when uarm plug out but still return True 86 | 4. Add Set Debug for logger 87 | 88 | ## [2.2.2] - 2016-11-16 89 | ### Updated -Alex Tan 90 | 91 | ### Compatible 92 | - Firmware 2.2 93 | 94 | ### Changes 95 | 1. Fix pyuarm.__version__ 96 | 2. Fix pypi MANIFEST.in not found 97 | 98 | 99 | ## [2.2.0] - 2016-11-16 100 | ### Updated -Alex Tan 101 | 102 | ### Compatible 103 | - Firmware 2.2 104 | 105 | ### Changes 106 | 1. Compatible with firmware 2.2 (Gcode) 107 | 2. Fix Camera Disconnect No response 108 | 3. Add Protocol Version 109 | 110 | ## [2.1.1] - 2016-10-28 111 | ### Updated -Alex Tan 112 | 113 | ### Compatible 114 | - Firmware 2.1 115 | 116 | ### Changes 117 | 1. Compatible with firmware 2.1 118 | 2. Compatible with Python2.x and Python 3.x 119 | 3. Rewrite Calibration logic 120 | 4. Rewrite Firmware logic (Reduce 3rd-party library dependencies) Include `avrdude` on Mac/Windows/Linux 121 | 5. Rename Functions in uarm. get/set for all communication protocol. 122 | 123 | ## [2.0.7] - 2016-09-06 124 | ### Updated - Alex Tan 125 | 126 | ### Compatible 127 | - Firmware 2.0 128 | 129 | ### Fix 130 | 1. Fix list port issue 131 | 2. Fix receive F start error 132 | 133 | ## [2.0.6] - 2016-09-06 134 | ### Updated - Alex Tan 135 | 136 | ### Compatible 137 | - Firmware 2.0 138 | 139 | ### Fix 140 | 1. Protocol: 141 | - Support Raw Servo Angle, SET_ANGLE = "sAngN{}V{}", SET_RAW_ANGLE = "sSerN{}V{}", GET_RAW_ANGLE = "gSer", GET_ANGLE = "gAng" 142 | - Buzzer add stop Duration, SET_BUZZER = "sBuzF{}T{}S{}" 143 | - version start with "v" 144 | 2. pyuarm: 145 | - set Debug before any action. 146 | - if response message start with "f", means error 147 | - set & get EEPROM return value depend on data type 148 | 3. Calibrate: 149 | - Add version 1.1.0 150 | - Compatible with 2.0 firmware 151 | 4. list_uarm: 152 | - get_uarm() 153 | - get_uarm_port_cli() 154 | 5. Miniterm: 155 | - change read_servo_angle to get_servo_angle, write_servo_angle to set_servo_angle 156 | - Add version 0.1.3 157 | 6. Others: 158 | - `python -m pyuarm.version` will display the library version and support version 159 | 160 | 161 | ## [2.0.5] - 2016-08-31 162 | ### Updated - Alex Tan 163 | 164 | ### Compatible 165 | - Firmware 2.0 166 | 167 | ### Fix 168 | **uarm-cli** 169 | - get_analog, get_eeprom, set_eeprom, get_serial_number 170 | 171 | ## [2.0.3] - 2016-08-10 172 | ### Updated - Alex Tan 173 | 174 | ### Compatible 175 | - Firmware 0.9.9 176 | 177 | ### Fix 178 | **uarm-cli** 179 | - help topic hide welcome message 180 | - compatible with 0.9.9 181 | - uarm-cli add debug option 182 | - uarm-firmware fix -d argument issue 183 | 184 | ## [2.0.2] - 2016-08-10 185 | ### Updated - Alex Tan 186 | 187 | ### Compatible 188 | - Firmware 0.9.8b 189 | 190 | ### Fix 191 | **uarm-cli** 192 | - Use colorama Compatible with Windows platform 193 | - fix firmware error if no uarm connect 194 | - Catch KeyboardInterrupt 195 | - add shortcut tip when cli start, and help center 196 | 197 | 198 | ## [2.0.0] - 2016-07-13 199 | ### Updated - Alex Tan 200 | 201 | ### Compatible 202 | - Firmware 2.0 203 | 204 | ### Changes 205 | - Brand new Protocol 206 | 207 | ### detail -2016-07-13 208 | - Fix timeout issue when new Serial port 209 | - remove time.sleep(3) after new Serial port, use is_ready() function instead 210 | - Add UArmConnectException 211 | - add disconnect() function 212 | - flushInput before read anything 213 | - use readline() replace old read logic 214 | - add validate_coordinate() 215 | - update move_to,pump_on, pump_off, set_buzzer, is_moving, get_servo_angle, get_tip_sensor 216 | - add write_servos_angle 217 | - update firmware_helper.py with new Protocol 218 | - add cmd for for control 219 | 220 | ### detail - 2016-08-09 221 | - connect() function after open port 222 | - cli add serial mode 223 | 224 | ### detail - 2016-08-03 225 | - Compatible with v0.9.7b 226 | 227 | ## [1.3.27] - 2016-07-08 228 | ### Updated - Alex Tan 229 | 230 | ### Compatible 231 | - Firmware 1.7 232 | 233 | ### Fix 234 | - Fix calibrate issue when use GUI. 235 | 236 | ## [1.3.26] - 2016-07-05 237 | ### Updated - Alex Tan 238 | 239 | ### Compatible 240 | - Firmware 1.7 241 | 242 | ### Fix 243 | - Firmware helper support port argument. 244 | - improve firmware helper in mac os x if not install avrdude. 245 | - add console-script: uarm-firmware, uarm-calibrate, uarm-list. 246 | 247 | ## [1.3.25] - 2016-07-01 248 | ### Updated - Alex Tan 249 | 250 | ### Compatible 251 | - Firmware 1.7 252 | 253 | ### Fix 254 | - Move Back to use version.py due to pyinstaller encounter error 255 | 256 | ## [1.3.24] - 2016-06-30 257 | ### Updated - Alex Tan 258 | 259 | ### Compatible 260 | - Firmware 1.7 261 | 262 | ### Fix 263 | - Use version.json replace version.py 264 | - Add Support firmware version in version.json, if not Compatible will raise UnsupportFirmwareVersionException 265 | - Add argument for calibrate.py 266 | - Disable Stretch Calibration 267 | - Change Linear Calibration, Increasing moving points 268 | - Merge [frankyjuang](https://github.com/frankyjuang), fix typo 269 | 270 | ## [1.3.21] - 2016-06-28 271 | ### Updated - Alex Tan 272 | 273 | ### Compatible 274 | - Firmware 1.7.1 275 | 276 | ### Fix 277 | - rewrite Firmware helper. remove pycurl 278 | 279 | ## [1.3.20] - 2016-06-28 280 | ### Updated - Alex Tan 281 | 282 | ### Compatible 283 | - Firmware 1.7.1 284 | 285 | ### Fix 286 | - Increase More delay on calibrate. rewrite Linear Offset points. 287 | - Update requirements.txt 288 | 289 | 290 | ## [1.3.19] - 2016-06-27 291 | ### Updated - Alex Tan 292 | 293 | ### Fix 294 | - Increase More delay on calibrate. rewrite Linear Offset points. 295 | - Update requirements.txt 296 | 297 | ## [1.3.18] - 2016-06-22 298 | ### Updated - Alex Tan 299 | 300 | ### Fix 301 | - firmware_helper -f won't download the firmware.hex use firmware_helper -d instead 302 | 303 | ## [1.3.17] - 2016-06-22 304 | ### Updated - Alex Tan 305 | 306 | ### Fix 307 | - firmware_helper remove access token 308 | 309 | ### To-Do 310 | - Setup Firmware_checker at UFactory Server 311 | 312 | ## [1.3.16] - 2016-06-22 313 | ### Updated - Alex Tan 314 | 315 | ### Fix 316 | - firmware_helper add APIError check 317 | 318 | ## [1.3.15] - 2016-06-22 319 | ### Updated - Alex Tan 320 | 321 | ### Fix 322 | - Add enable_hand support Firmware 1.6.1 323 | 324 | ## [1.3.14] - 2016-06-22 325 | ### Updated - Alex Tan 326 | 327 | ### Fix 328 | - Raise UnkwonFirmwareException when init uArm. 329 | 330 | ## [1.3.13] - 2016-06-14 331 | ### Updated - Alex Tan 332 | 333 | ### Changes 334 | - Try NetworkError in firmware_helper 335 | 336 | ## [1.3.12] - 2016-06-14 337 | ### Updated - Alex Tan 338 | 339 | ### Changes 340 | - Compatible with Firmware 1.6.0 341 | - Time.sleep(3) when init Serial Port 342 | - read_servo_angle() when servo_number is none, if servo_number is None, will return all servo angles (SERVO_BOTTOM, SERVO_LEFT, SERVO_RIGHT, SERVO_HAND). 343 | - Update Firmware Release URL 344 | 345 | ### Fix 346 | - Fix pyuarm.tools.calibrate print format & raw_input 347 | 348 | ## [1.3.11] - 2016-06-14 349 | ### Updated - Alex Tan 350 | 351 | ### Changes 352 | - include requirements, README.rst 353 | 354 | ## [1.3.10] - 2016-06-14 355 | ### Updated - Alex Tan 356 | 357 | ### Changes 358 | - include requirements, README.rst 359 | 360 | ## [1.3.9] - 2016-06-14 361 | ### Updated - Alex Tan 362 | 363 | ### Changes 364 | - add version.py for version variable 365 | 366 | ## [1.3.8] - 2016-06-14 367 | ### Updated - Alex Tan 368 | 369 | ### Changes 370 | - Replace progressbar with tqdm 371 | - Rewrite firmware_helper into class 372 | - calibrate.py confirm manual trigger in raw_input: y 373 | 374 | ## [1.3.7] - 2016-06-13 375 | ### Updated - Alex Tan 376 | 377 | ### Changes 378 | - Add requirements.txt 379 | 380 | ## [1.3.7] - 2016-06-13 381 | ### Updated - Alex Tan 382 | 383 | ### Changes 384 | - Update read_coordinate() 385 | - Update util.py function name getOne7BitBytesFloatArray, getThree7BitBytesFloatArray 386 | 387 | ## [1.3.6] - 2016-06-13 388 | ### Updated - Alex Tan 389 | 390 | ### Changes 391 | - Update read_coordinate() 392 | - Update util.py function name getOne7BitBytesFloatArray, getThree7BitBytesFloatArray 393 | 394 | ## [1.3.5] - 2016-06-13 395 | ### Updated - Alex Tan 396 | 397 | ### Changes 398 | - Update Version 1.3.5 399 | - Update Documentation 400 | - Update README.md to README.rst 401 | 402 | ## [1.3.4] - 2016-06-10 403 | ### Updated - Alex Tan 404 | 405 | ### Changes 406 | - move pyuarm.calibrate to pyuarm.tools.calibrate 407 | - move pyuarm.list_uarms function to pyuarm.tools.list_uarms 408 | - pyuarm.py replace serial_read_byte() with sp.read(1), support debug read message 409 | - firmware_helper.py add NetworkError 410 | - Docstring 411 | - move Exception get_uarm to pyuarm.util 412 | - pyuarm.py set_firmware_version, set_firmata_version to get_firmware_version, get_firmata_version. 413 | - pyuarm.py move_to_opts, move_to_simple, move integrate with one Function move_to 414 | - pyuarm.py changes alert() function to alarm() 415 | 416 | ## [1.3.3] - 2016-06-08 417 | ### Updated - Alex Tan 418 | 419 | ### Fix 420 | - fix setup.py package_data avrdude folder, MANIFEST.in 421 | 422 | ## [1.3.2] - 2016-06-08 423 | ### Updated - Alex Tan 424 | 425 | ### Fix 426 | - fix setup.py 'pycurl>=7.43.0', 'certifi>=2016.02.28', 'tqdm>=4.7.2', 'requests>=2.10.0' 427 | 428 | ## [1.3.2] - 2016-06-08 429 | ### Updated - Alex Tan 430 | 431 | ### Fix 432 | - firmware_helper, improve download progress bar 433 | 434 | ## [1.3.0] - 2016-06-08 435 | ### Updated - Alex Tan 436 | 437 | ### Changes 438 | - Add moudle pyuarm.tools, integrate with firmware_helper, list_uarms 439 | 440 | ## [1.2.11] - 2016-06-04 441 | ### Updated - Alex Tan 442 | 443 | ### Fix 444 | - Add NoUArmPortException, UnkwonFirmwareException 445 | 446 | ## [1.2.10] - 2016-06-04 447 | ### Updated - Alex Tan 448 | 449 | ### Fix 450 | - uArm() Default use list_uarm()[0] 451 | 452 | ## [1.2.9] - 2016-06-01 453 | ### Updated - Alex Tan 454 | 455 | ### Fix 456 | - Add firmware_version and firmata_version 457 | 458 | ## [1.2.8] - 2016-05-11 459 | ### Updated - Alex Tan 460 | 461 | ### Fix 462 | - Add Default Timeout is 5 sec 463 | 464 | ## [1.2.7] - 2016-05-11 465 | ### Updated - Alex Tan 466 | 467 | ### Fix 468 | - Changes Alert as 3, 100, 100 469 | 470 | ## [1.2.6] - 2016-05-11 471 | ### Updated - Alex Tan 472 | 473 | ### Changes 474 | - alert uarm when start manual calibration 475 | - Reduce delay during linear calibration 476 | 477 | ## [1.2.5] - 2016-05-10 478 | ### Updated - Alex Tan 479 | 480 | ### Changes 481 | - Add BUZZER_ALERT 0x24 482 | 483 | ## [1.2.4] - 2016-05-06 484 | ### Updated - Alex Tan 485 | 486 | ### Fix 487 | - remove py_modules 488 | 489 | ## [1.2.3] - 2016-05-06 490 | ### Updated - Alex Tan 491 | 492 | ### Fix 493 | - remove docutils from dependency 494 | 495 | ## [1.2.2] - 2016-05-06 496 | ### Updated - Alex Tan 497 | 498 | ### Fix 499 | - Callback Function default to None 500 | - Update SERIAL_NUMBER_ADDRESS to 100 501 | 502 | ## [1.2.1] - 2016-05-03 503 | ### Updated - Alex Tan 504 | 505 | ### Fix 506 | - Change all function names as lowercase 507 | - Use CONFIRM_FLAG 0x80 for Confirmation Flag 508 | 509 | ## [1.2.0] - 2016-05-03 510 | ### Updated - Alex Tan 511 | 512 | ### Changes 513 | - Add uArm Library Version (Firmware Version) and Firmata Version 514 | 515 | ## [1.1.9] - 2016-05-03 516 | ### Updated - Alex Tan 517 | 518 | ### Fix 519 | - Add Stretch Calibration Flag support stop during Stretch Calibration 520 | - Add PUMP_PIN, VALVE_PIN Constant Values 521 | 522 | ## [1.1.8] - 2016-05-02 523 | ### Updated - Alex Tan 524 | 525 | ### Changes 526 | - Add readSerialNumber and writeSerialNumber Function 527 | 528 | ## [1.1.7] - 2016-05-01 529 | ### Updated - Alex Tan 530 | 531 | ### Fix 532 | - Change calibrate function name "calibrate" to calibration 533 | 534 | ## [1.1.6] - 2016-05-01 535 | ### Updated - Alex Tan 536 | 537 | ### Changes 538 | - Changes library name uarm4py to pyuarm 539 | 540 | ## [1.1.6] - 2016-04-30 541 | ### Updated - Alex Tan 542 | 543 | ### Fix 544 | - read Analog, read EEPROM, read Digital, read Servo Angle, add pin number/ address / servo number before receive data 545 | 546 | ## [1.1.5] - 2016-04-29 547 | ### Updated - Alex Tan 548 | 549 | ### Fix 550 | - uarm.py Fix MoveTo, Move, moveToOpts 551 | 552 | 553 | ## [1.1.4] - 2016-04-25 554 | ### Updated - Alex Tan 555 | 556 | ### Fix 557 | - calibrate.py Fix is_all_calibrated, is_manual_calibrated, is_linear_calibrated, name issue 558 | - uarm.py Fix PumpStatus , GripperStatus 559 | 560 | ### Changes 561 | 562 | - Add Comment into calibrate.py 563 | - Add Function Complete Flag 564 | - Add Calibrate All Function 565 | 566 | 567 | ## [1.1.3] - 2016-04-25 568 | ### Updated - Alex Tan 569 | 570 | ### Fix 571 | - calibrate.py is_all_calibrated, is_stretch_calibrated, is_manual_calibrated, is_linear_calibrated rename 572 | 573 | 574 | ## [1.1.2] - 2016-04-24 575 | ### Updated - Alex Tan 576 | 577 | ### Changes 578 | - add setup.py for script install 579 | - rename calibrate.py function names to match the Python Standard 580 | 581 | ## [1.1.1] - 2016-04-21 582 | ### Updated - Alex Tan 583 | 584 | ### Fix 585 | - rename self.uarm to self.sp 586 | 587 | ### Changes 588 | - Provide uarm isConnected Function 589 | 590 | 591 | ## [1.1.0] - 2016-04-18 592 | ### Updated - Alex Tan 593 | 594 | ### Fix 595 | - Fix float decimal accuracy 596 | 597 | ### Changes 598 | - Compatible with uArm Firmata v1.5 599 | - New function *list_uarms()* will return all the available uArm ports 600 | - New function *get_uarm()* will return the instance from first port of *list_uarms()* 601 | - get Firmware version when initialize 602 | - Move(x,y,z) Relative action control 603 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Alex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG.md 2 | 3 | include requirements.txt 4 | include README.rst 5 | 6 | global-exclude .DS_Store 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =============================== 2 | pyuarm (uArm Metal only) 3 | =============================== 4 | 5 | ## This Branch is currently on Developing. 6 | 7 | This library support uArm Metal only, for Swift and Swift PRO, 8 | please use https://github.com/uArm-Developer/pyuf instead. 9 | 10 | Overview 11 | ======== 12 | 13 | This module encapsulates the operations for uArm. It provides basic Movement on Python. 14 | Also provides Firmware_helper and Calibration. The module named "pyuarm" makes the API more pythonic. 15 | 16 | Other pages (online) 17 | 18 | - `project page on Github`_ 19 | - `Download Page`_ with releases 20 | - This page, when viewed online is at https://pyuarm.readthedocs.io. 21 | 22 | 23 | Features 24 | ======== 25 | - Auto uArm Port detection 26 | - Provide firmware_helper, firmware upgrade online 27 | - Provide Auto Calibration tool 28 | 29 | Requirements 30 | ============ 31 | - Python 2.7x or Python 3.4x above 32 | - uArmProtocol Firmware (Please use ``uarmcli firmware -d`` or ``python -m pyuarm.tools.firmware -d`` to upgrade your firmware) 33 | 34 | Installation 35 | ============ 36 | 37 | pyuarm 38 | ------ 39 | This install a package that can be used from Python (``import pyuarm``). 40 | 41 | To install for all users on the system, administrator rights (root) may be required. 42 | 43 | From PyPI 44 | ~~~~~~~~~ 45 | pyuarm can be installed from PyPI, either manually downloading the files and installing as described below or using:: 46 | 47 | pip install pyuarm 48 | 49 | From source (tar.gz or checkout) 50 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 51 | Download the archive from http://pypi.python.org/pypi/pyuarm. 52 | Unpack the archive, enter the ``pyuarm-x.y`` directory and run:: 53 | 54 | python setup.py install 55 | 56 | .. _`project page on GitHub`: https://github.com/uArm-Developer/pyuarm 57 | .. _`Download Page`: http://pypi.python.org/pypi/pyuarm 58 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help 18 | help: 19 | @echo "Please use \`make ' where is one of" 20 | @echo " html to make standalone HTML files" 21 | @echo " dirhtml to make HTML files named index.html in directories" 22 | @echo " singlehtml to make a single large HTML file" 23 | @echo " pickle to make pickle files" 24 | @echo " json to make JSON files" 25 | @echo " htmlhelp to make HTML files and a HTML help project" 26 | @echo " qthelp to make HTML files and a qthelp project" 27 | @echo " applehelp to make an Apple Help Book" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " epub3 to make an epub3" 31 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 33 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 34 | @echo " text to make text files" 35 | @echo " man to make manual pages" 36 | @echo " texinfo to make Texinfo files" 37 | @echo " info to make Texinfo files and run them through makeinfo" 38 | @echo " gettext to make PO message catalogs" 39 | @echo " changes to make an overview of all changed/added/deprecated items" 40 | @echo " xml to make Docutils-native XML files" 41 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 42 | @echo " linkcheck to check all external links for integrity" 43 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 44 | @echo " coverage to run coverage check of the documentation (if enabled)" 45 | @echo " dummy to check syntax errors of document sources" 46 | 47 | .PHONY: clean 48 | clean: 49 | rm -rf $(BUILDDIR)/* 50 | 51 | .PHONY: html 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | .PHONY: dirhtml 58 | dirhtml: 59 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 60 | @echo 61 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 62 | 63 | .PHONY: singlehtml 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | .PHONY: pickle 70 | pickle: 71 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 72 | @echo 73 | @echo "Build finished; now you can process the pickle files." 74 | 75 | .PHONY: json 76 | json: 77 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 78 | @echo 79 | @echo "Build finished; now you can process the JSON files." 80 | 81 | .PHONY: htmlhelp 82 | htmlhelp: 83 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 84 | @echo 85 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 86 | ".hhp project file in $(BUILDDIR)/htmlhelp." 87 | 88 | .PHONY: qthelp 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyuarm.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyuarm.qhc" 97 | 98 | .PHONY: applehelp 99 | applehelp: 100 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 101 | @echo 102 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 103 | @echo "N.B. You won't be able to view it unless you put it in" \ 104 | "~/Library/Documentation/Help or install it in your application" \ 105 | "bundle." 106 | 107 | .PHONY: devhelp 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pyuarm" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyuarm" 115 | @echo "# devhelp" 116 | 117 | .PHONY: epub 118 | epub: 119 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 120 | @echo 121 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 122 | 123 | .PHONY: epub3 124 | epub3: 125 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 126 | @echo 127 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 128 | 129 | .PHONY: latex 130 | latex: 131 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 132 | @echo 133 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 134 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 135 | "(use \`make latexpdf' here to do that automatically)." 136 | 137 | .PHONY: latexpdf 138 | latexpdf: 139 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 140 | @echo "Running LaTeX files through pdflatex..." 141 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 142 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 143 | 144 | .PHONY: latexpdfja 145 | latexpdfja: 146 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 147 | @echo "Running LaTeX files through platex and dvipdfmx..." 148 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 149 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 150 | 151 | .PHONY: text 152 | text: 153 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 154 | @echo 155 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 156 | 157 | .PHONY: man 158 | man: 159 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 160 | @echo 161 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 162 | 163 | .PHONY: texinfo 164 | texinfo: 165 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 166 | @echo 167 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 168 | @echo "Run \`make' in that directory to run these through makeinfo" \ 169 | "(use \`make info' here to do that automatically)." 170 | 171 | .PHONY: info 172 | info: 173 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 174 | @echo "Running Texinfo files through makeinfo..." 175 | make -C $(BUILDDIR)/texinfo info 176 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 177 | 178 | .PHONY: gettext 179 | gettext: 180 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 181 | @echo 182 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 183 | 184 | .PHONY: changes 185 | changes: 186 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 187 | @echo 188 | @echo "The overview file is in $(BUILDDIR)/changes." 189 | 190 | .PHONY: linkcheck 191 | linkcheck: 192 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 193 | @echo 194 | @echo "Link check complete; look for any errors in the above output " \ 195 | "or in $(BUILDDIR)/linkcheck/output.txt." 196 | 197 | .PHONY: doctest 198 | doctest: 199 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 200 | @echo "Testing of doctests in the sources finished, look at the " \ 201 | "results in $(BUILDDIR)/doctest/output.txt." 202 | 203 | .PHONY: coverage 204 | coverage: 205 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 206 | @echo "Testing of coverage in the sources finished, look at the " \ 207 | "results in $(BUILDDIR)/coverage/python.txt." 208 | 209 | .PHONY: xml 210 | xml: 211 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 212 | @echo 213 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 214 | 215 | .PHONY: pseudoxml 216 | pseudoxml: 217 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 218 | @echo 219 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 220 | 221 | .PHONY: dummy 222 | dummy: 223 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 224 | @echo 225 | @echo "Build finished. Dummy builder generates no files." 226 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import sphinx_bootstrap_theme 4 | 5 | sys.path.insert(0, os.path.abspath('..')) 6 | #from mock import Mock as MagicMock 7 | 8 | #class Mock(MagicMock): 9 | # @classmethod 10 | # def __getattr__(cls, name): 11 | # return Mock() 12 | # 13 | #MOCK_MODULES = ['pygtk', 'gtk', 'gobject', 'argparse', 'numpy', 'pandas', 'libcurl'] 14 | #sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) 15 | 16 | # -*- coding: utf-8 -*- 17 | # 18 | # pyuarm documentation build configuration file, created by 19 | # sphinx-quickstart on Sat Jun 11 08:54:45 2016. 20 | # 21 | # This file is execfile()d with the current directory set to its 22 | # containing dir. 23 | # 24 | # Note that not all possible configuration values are present in this 25 | # autogenerated file. 26 | # 27 | # All configuration values have a default; values that are commented out 28 | # serve to show the default. 29 | 30 | # If extensions (or modules to document with autodoc) are in another directory, 31 | # add these directories to sys.path here. If the directory is relative to the 32 | # documentation root, use os.path.abspath to make it absolute, like shown here. 33 | # 34 | 35 | 36 | # -- General configuration ------------------------------------------------ 37 | 38 | # If your documentation needs a minimal Sphinx version, state it here. 39 | # 40 | # needs_sphinx = '1.0' 41 | 42 | # Add any Sphinx extension module names here, as strings. They can be 43 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 44 | # ones. 45 | extensions = [ 46 | 'sphinx.ext.autodoc', 47 | ] 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ['_templates'] 51 | 52 | # The suffix(es) of source filenames. 53 | # You can specify multiple suffix as a list of string: 54 | # 55 | # source_suffix = ['.rst', '.md'] 56 | source_suffix = '.rst' 57 | 58 | # The encoding of source files. 59 | # 60 | # source_encoding = 'utf-8-sig' 61 | 62 | # The master toctree document. 63 | master_doc = 'index' 64 | 65 | # General information about the project. 66 | project = u'pyuarm' 67 | copyright = u'2017, Alex Tan' 68 | author = u'Alex Tan' 69 | 70 | # The version info for the project you're documenting, acts as replacement for 71 | # |version| and |release|, also used in various other places throughout the 72 | # built documents. 73 | # 74 | # The short X.Y version. 75 | from distutils.util import convert_path 76 | main_ns = {} 77 | ver_path = convert_path('../pyuarm/version.py') 78 | with open(ver_path) as ver_file: 79 | exec(ver_file.read(), main_ns) 80 | version = main_ns['__version__'] 81 | release = version 82 | # version = u'2.1.1' 83 | # The full version, including alpha/beta/rc tags. 84 | # release = u'2.1.1' 85 | 86 | # The language for content autogenerated by Sphinx. Refer to documentation 87 | # for a list of supported languages. 88 | # 89 | # This is also used if you do content translation via gettext catalogs. 90 | # Usually you set "language" from the command line for these cases. 91 | language = None 92 | 93 | # There are two options for replacing |today|: either, you set today to some 94 | # non-false value, then it is used: 95 | # 96 | # today = '' 97 | # 98 | # Else, today_fmt is used as the format for a strftime call. 99 | # 100 | # today_fmt = '%B %d, %Y' 101 | 102 | # List of patterns, relative to source directory, that match files and 103 | # directories to ignore when looking for source files. 104 | # This patterns also effect to html_static_path and html_extra_path 105 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 106 | 107 | # The reST default role (used for this markup: `text`) to use for all 108 | # documents. 109 | # 110 | # default_role = None 111 | 112 | # If true, '()' will be appended to :func: etc. cross-reference text. 113 | # 114 | # add_function_parentheses = True 115 | 116 | # If true, the current module name will be prepended to all description 117 | # unit titles (such as .. function::). 118 | # 119 | # add_module_names = True 120 | 121 | # If true, sectionauthor and moduleauthor directives will be shown in the 122 | # output. They are ignored by default. 123 | # 124 | # show_authors = False 125 | 126 | # The name of the Pygments (syntax highlighting) style to use. 127 | pygments_style = 'sphinx' 128 | 129 | # A list of ignored prefixes for module index sorting. 130 | # modindex_common_prefix = [] 131 | 132 | # If true, keep warnings as "system message" paragraphs in the built documents. 133 | # keep_warnings = False 134 | 135 | # If true, `todo` and `todoList` produce output, else they produce nothing. 136 | todo_include_todos = False 137 | 138 | 139 | # -- Options for HTML output ---------------------------------------------- 140 | 141 | # The theme to use for HTML and HTML Help pages. See the documentation for 142 | # a list of builtin themes. 143 | # 144 | html_theme = 'bootstrap' 145 | 146 | # Theme options are theme-specific and customize the look and feel of a theme 147 | # further. For a list of options available for each theme, see the 148 | # documentation. 149 | # 150 | # html_theme_options = {} 151 | html_theme_options = { 152 | # Bootswatch (http://bootswatch.com/) theme. 153 | # 154 | # Options are nothing with "" (default) or the name of a valid theme such 155 | # as "amelia" or "cosmo". 156 | # 157 | # Note that this is served off CDN, so won't be available offline. 158 | 'bootswatch_theme': "flatly", 159 | } 160 | 161 | # Add any paths that contain custom themes here, relative to this directory. 162 | # html_theme_path = [] 163 | html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() 164 | 165 | # The name for this set of Sphinx documents. 166 | # " v documentation" by default. 167 | # 168 | # html_title = u'pyuarm v1.3.3' 169 | 170 | # A shorter title for the navigation bar. Default is the same as html_title. 171 | # 172 | # html_short_title = None 173 | 174 | # The name of an image file (relative to this directory) to place at the top 175 | # of the sidebar. 176 | # 177 | #html_logo = 'logo.png' 178 | 179 | # The name of an image file (relative to this directory) to use as a favicon of 180 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 181 | # pixels large. 182 | # 183 | # html_favicon = None 184 | 185 | # Add any paths that contain custom static files (such as style sheets) here, 186 | # relative to this directory. They are copied after the builtin static files, 187 | # so a file named "default.css" will overwrite the builtin "default.css". 188 | html_static_path = ['_static'] 189 | 190 | # Add any extra paths that contain custom files (such as robots.txt or 191 | # .htaccess) here, relative to this directory. These files are copied 192 | # directly to the root of the documentation. 193 | # 194 | # html_extra_path = [] 195 | 196 | # If not None, a 'Last updated on:' timestamp is inserted at every page 197 | # bottom, using the given strftime format. 198 | # The empty string is equivalent to '%b %d, %Y'. 199 | # 200 | # html_last_updated_fmt = None 201 | 202 | # If true, SmartyPants will be used to convert quotes and dashes to 203 | # typographically correct entities. 204 | # 205 | # html_use_smartypants = True 206 | 207 | # Custom sidebar templates, maps document names to template names. 208 | # 209 | # html_sidebars = {} 210 | 211 | # Additional templates that should be rendered to pages, maps page names to 212 | # template names. 213 | # 214 | # html_additional_pages = {} 215 | 216 | # If false, no module index is generated. 217 | # 218 | # html_domain_indices = True 219 | 220 | # If false, no index is generated. 221 | # 222 | # html_use_index = True 223 | 224 | # If true, the index is split into individual pages for each letter. 225 | # 226 | # html_split_index = False 227 | 228 | # If true, links to the reST sources are added to the pages. 229 | # 230 | # html_show_sourcelink = True 231 | 232 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 233 | # 234 | # html_show_sphinx = True 235 | 236 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 237 | # 238 | # html_show_copyright = True 239 | 240 | # If true, an OpenSearch description file will be output, and all pages will 241 | # contain a tag referring to it. The value of this option must be the 242 | # base URL from which the finished HTML is served. 243 | # 244 | # html_use_opensearch = '' 245 | 246 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 247 | # html_file_suffix = None 248 | 249 | # Language to be used for generating the HTML full-text search index. 250 | # Sphinx supports the following languages: 251 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 252 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' 253 | # 254 | # html_search_language = 'en' 255 | 256 | # A dictionary with options for the search language support, empty by default. 257 | # 'ja' uses this config value. 258 | # 'zh' user can custom change `jieba` dictionary path. 259 | # 260 | # html_search_options = {'type': 'default'} 261 | 262 | # The name of a javascript file (relative to the configuration directory) that 263 | # implements a search results scorer. If empty, the default will be used. 264 | # 265 | # html_search_scorer = 'scorer.js' 266 | 267 | # Output file base name for HTML help builder. 268 | htmlhelp_basename = 'pyuarmdoc' 269 | 270 | # -- Options for LaTeX output --------------------------------------------- 271 | 272 | latex_elements = { 273 | # The paper size ('letterpaper' or 'a4paper'). 274 | # 275 | # 'papersize': 'letterpaper', 276 | 277 | # The font size ('10pt', '11pt' or '12pt'). 278 | # 279 | # 'pointsize': '10pt', 280 | 281 | # Additional stuff for the LaTeX preamble. 282 | # 283 | # 'preamble': '', 284 | 285 | # Latex figure (float) alignment 286 | # 287 | # 'figure_align': 'htbp', 288 | } 289 | 290 | # Grouping the document tree into LaTeX files. List of tuples 291 | # (source start file, target name, title, 292 | # author, documentclass [howto, manual, or own class]). 293 | latex_documents = [ 294 | (master_doc, 'pyuarm.tex', u'pyuarm Documentation', 295 | u'Alex Tan', 'manual'), 296 | ] 297 | 298 | # The name of an image file (relative to this directory) to place at the top of 299 | # the title page. 300 | # 301 | # latex_logo = None 302 | 303 | # For "manual" documents, if this is true, then toplevel headings are parts, 304 | # not chapters. 305 | # 306 | # latex_use_parts = False 307 | 308 | # If true, show page references after internal links. 309 | # 310 | # latex_show_pagerefs = False 311 | 312 | # If true, show URL addresses after external links. 313 | # 314 | # latex_show_urls = False 315 | 316 | # Documents to append as an appendix to all manuals. 317 | # 318 | # latex_appendices = [] 319 | 320 | # If false, no module index is generated. 321 | # 322 | # latex_domain_indices = True 323 | 324 | 325 | # -- Options for manual page output --------------------------------------- 326 | 327 | # One entry per manual page. List of tuples 328 | # (source start file, name, description, authors, manual section). 329 | man_pages = [ 330 | (master_doc, 'pyuarm', u'pyuarm Documentation', 331 | [author], 1) 332 | ] 333 | 334 | # If true, show URL addresses after external links. 335 | # 336 | # man_show_urls = False 337 | 338 | 339 | # -- Options for Texinfo output ------------------------------------------- 340 | 341 | # Grouping the document tree into Texinfo files. List of tuples 342 | # (source start file, target name, title, author, 343 | # dir menu entry, description, category) 344 | texinfo_documents = [ 345 | (master_doc, 'pyuarm', u'pyuarm Documentation', 346 | author, 'pyuarm', 'One line description of project.', 347 | 'Miscellaneous'), 348 | ] 349 | 350 | # Documents to append as an appendix to all manuals. 351 | # 352 | # texinfo_appendices = [] 353 | 354 | # If false, no module index is generated. 355 | # 356 | # texinfo_domain_indices = True 357 | 358 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 359 | # 360 | # texinfo_show_urls = 'footnote' 361 | 362 | # If true, do not generate a @detailmenu in the "Top" node's menu. 363 | # 364 | # texinfo_no_detailmenu = False 365 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Examples 3 | ======== 4 | 5 | Usage 6 | ===== 7 | 8 | Initialize uArm 9 | --------------- 10 | There are two methods to Initialize uArm. 11 | 12 | - Use get_uarm() 13 | 14 | .. code-block:: python 15 | 16 | >>> import pyuarm 17 | >>> arm = pyuarm.get_uarm() 18 | pyuarm - INFO - pyuarm version: 2.4.0.7 19 | >>> arm.connect() 20 | pyuarm - INFO - Connecting from port - /dev/cu.usbserial-A600CRJU... 21 | 22 | if no uArm connected, will return ``None`` and display "There is no uArm Port available" 23 | 24 | .. code-block:: python 25 | 26 | >>> import pyuarm 27 | >>> arm = pyuarm.get_uarm() 28 | pyuarm - INFO - pyuarm version: 2.4.0.7 29 | pyuarm - ERROR - There is no uArm port available 30 | 31 | - Use uArm() 32 | 33 | .. code-block:: python 34 | 35 | >>> import pyuarm 36 | a>>> arm = pyuarm.UArm() 37 | pyuarm - INFO - pyuarm version: 2.4.0.7 38 | >>> arm.connect() 39 | pyuarm - INFO - Connecting from port - /dev/cu.usbserial-A600CRJU... 40 | 41 | 42 | If no uArm connected, will raise ``UArmConnectException``. 43 | 44 | .. code-block:: python 45 | 46 | >>> import pyuarm 47 | >>> arm = pyuarm.UArm() 48 | pyuarm - INFO - pyuarm version: 2.4.0.7 49 | >>> arm.connect() 50 | Traceback (most recent call last): 51 | File "", line 1, in 52 | File "pyuarm/uarm.py", line 112, in connect 53 | raise UArmConnectException(3) 54 | pyuarm.util.UArmConnectException: 'No available uArm Port-' 55 | 56 | 57 | You could turn on Debug Mode 58 | 59 | .. code-block:: python 60 | 61 | >>> import pyuarm 62 | >>> arm = pyuarm.UArm(debug=True) 63 | pyuarm - INFO - pyuarm version: 2.4.0.7 64 | >>> arm.connect() 65 | pyuarm - INFO - Connecting from port - /dev/cu.usbserial-A600CRJU... 66 | pyuarm - DEBUG - Received MSG: @1 67 | >>> arm.firmware_version 68 | pyuarm - DEBUG - #2 P203 69 | '2.2.1' 70 | >>> arm.hardware_version 71 | pyuarm - DEBUG - #3 P202 72 | '2.1' 73 | 74 | Define the uArm port 75 | 76 | .. code-block:: python 77 | 78 | >>> import pyuarm 79 | >>> arm = pyuarm.UArm(port_name='/dev/cu.usbserial-A600CRJU') 80 | pyuarm - INFO - pyuarm version: 2.4.0.7 81 | >>> arm.connect() 82 | pyuarm - INFO - Connecting from port - /dev/cu.usbserial-A600CRJU... 83 | 84 | Connection 85 | ~~~~~~~~~~ 86 | Please connect before any operation or you will received exception 87 | 88 | .. code-block:: python 89 | 90 | >>> import pyuarm 91 | >>> arm = pyuarm.UArm(port_name='/dev/cu.usbserial-A600CRJU') 92 | pyuarm - INFO - pyuarm version: 2.4.0.7 93 | >>> arm.set_pump(True) 94 | Traceback (most recent call last): 95 | File "", line 1, in 96 | File "pyuarm/uarm.py", line 610, in set_pump 97 | self.__send_msg(command) 98 | File "pyuarm/uarm.py", line 311, in __send_msg 99 | raise UArmConnectException(4) 100 | pyuarm.util.UArmConnectException: 'uArm is not connected-' 101 | 102 | Use connect() 103 | 104 | .. code-block:: python 105 | 106 | >>> arm.connect() 107 | pyuarm - INFO - Connecting from port - /dev/cu.usbserial-A600CRJU... 108 | 109 | 110 | Use disconnect(), Disconnect uArm and release the port 111 | 112 | .. code-block:: python 113 | 114 | >>> arm.disconnect() 115 | pyuarm - ERROR - Receive Process Error - read failed: (9, 'Bad file descriptor') 116 | pyuarm - INFO - Disconnect from /dev/cu.usbserial-A600CRJU 117 | 118 | * Because receive process in a different thread, so will raise Error, you could ignore it * 119 | 120 | Please remember to use close() if program exit or finished 121 | 122 | .. code-block:: python 123 | 124 | >>> arm.close() 125 | 126 | Movement 127 | ~~~~~~~~ 128 | 129 | .. code-block:: python 130 | 131 | >>> arm.set_position(150, 150, 150) # Move to (150, 150, 150) at speed 300 mm/min 132 | >>> arm.set_position(150, 150, 150, speed=100) # Move to (150, 150, 150) at 100 mm/min 133 | >>> arm.set_position(x=20, speed=100, relative=True) # Move (20, 0, 0) at 100 mm/min 134 | >>> arm.set_position(150, 150, 150, wait=True) # wait=True will block until uArm finish moving 135 | 136 | Current Positions 137 | ~~~~~~~~~~~~~~~~~ 138 | 139 | .. code-block:: python 140 | 141 | >>> arm.get_position() # Get Current position, will return a position array 142 | [-2.74, 140.88, 151.0] 143 | 144 | 145 | Pump Gripper control 146 | ~~~~~~~~~~~~~~~~~~~~ 147 | 148 | .. code-block:: python 149 | 150 | >>> arm.set_pump(True) ### Pump On 151 | >>> arm.set_pump(False) ### Pump 152 | >>> arm.set_pump(True, wait=True) # This will block until finish the pump 153 | >>> arm.set_gripper(True) # Same as Pump Control 154 | 155 | Set Servo Attach/Detach 156 | ~~~~~~~~~~~~~~~~~~~~~~~ 157 | Servo Attach will lock the servo, You can't move uArm with your hands. 158 | 159 | .. code-block:: python 160 | 161 | >>> arm.set_servo_attach() # This will attach all servos 162 | 163 | >>> from pyuarm import SERVO_BOTTOM, SERVO_LEFT, SERVO_RIGHT, SERVO_HAND 164 | >>> arm.set_servo_attach(servo_num=SERVO_BOTTOM) # This will only attach SERVO_BOTTOM 165 | 166 | >>> arm.set_servo_attach(move=True) # if Move is True, servo will set will attach in current angle, if not, servo will set to last detach angle 167 | 168 | >>> arm.set_servo_attach(move=True, wait=True) # wait is True will block until finish moving 169 | 170 | 171 | Servo Detach will unlock the servo, You can move uArm with your hands. 172 | 173 | .. code-block:: python 174 | 175 | >>> arm.set_servo_detach() # This will detach all servos 176 | 177 | >>> from pyuarm import SERVO_BOTTOM, SERVO_LEFT, SERVO_RIGHT, SERVO_HAND 178 | >>> arm.set_servo_detach(servo_num=SERVO_BOTTOM) # This will only detach SERVO_BOTTOM 179 | 180 | >>> arm.set_servo_detach(wait=True) # wait is True will block until finish detaching 181 | 182 | Report Position 183 | ~~~~~~~~~~~~~~~ 184 | You could turn on a position report interval 185 | 186 | .. code-block:: python 187 | 188 | >>> arm.set_report_position(0.2) # uArm will report current position in 0.2 sec 189 | >>> arm.set_servo_detach() # you could try with servo detach to watch the position 190 | >>> arm.get_report_position() # all position will store in a LIFO (Last In First Out) queue 191 | 192 | Try to move uArm with below code 193 | 194 | .. code-block:: python 195 | 196 | arm.set_servo_detach() 197 | start_time = time.time() 198 | while (time.time() - start_time) < 60: 199 | print (arm.get_report_position()) 200 | 201 | Close Report 202 | 203 | .. code-block:: python 204 | 205 | >>> arm.close_report_position() # turn of position report 206 | >>> arm.set_report_position(0) # same as close report 207 | 208 | 209 | 210 | Others 211 | ~~~~~~ 212 | 213 | - List uArm Ports 214 | 215 | .. code-block:: python 216 | 217 | >>> from pyuarm.tools.list_uarms import uarm_ports 218 | >>> uarm_ports() 219 | ['/dev/cu.usbserial-A600CRE6'] 220 | 221 | 222 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyuarm documentation master file, created by 2 | sphinx-quickstart on Sat Jun 11 08:54:45 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to pyuarm's documentation! 7 | ================================== 8 | 9 | This module encapsulates the operations for uArm. It provides basic Movement on Python. 10 | Also provides Firmware_helper and Calibration. The module named "pyuarm" makes the API more pythonic. 11 | 12 | Other pages (online) 13 | 14 | - `project page on Github`_ 15 | - `Download Page`_ with releases 16 | - This page, when viewed online is at https://pyuarm.readthedocs.io/en/latest/. 17 | 18 | .. _`project page on GitHub`: https://github.com/uArm-Developer/pyuarm 19 | .. _`Download Page`: http://pypi.python.org/pypi/pyuarm 20 | 21 | Contents: 22 | 23 | .. toctree:: 24 | :maxdepth: 2 25 | 26 | pyuarm 27 | pyuarm_api 28 | tools 29 | examples 30 | 31 | 32 | Indices and tables 33 | ================== 34 | 35 | * :ref:`genindex` 36 | * :ref:`modindex` 37 | * :ref:`search` 38 | -------------------------------------------------------------------------------- /docs/pyuarm.rst: -------------------------------------------------------------------------------- 1 | ====== 2 | pyuarm 3 | ====== 4 | 5 | Overview 6 | ======== 7 | 8 | This module encapsulates the operations for uArm. It provides basic Movement on Python. 9 | Also provides Firmware_helper and Calibration. The module named "pyuarm" makes the API more pythonic. 10 | 11 | Other pages (online) 12 | 13 | - `project page on Github`_ 14 | - `Download Page`_ with releases 15 | - This page, when viewed online is at https://pyuarm.readthedocs.io/en/latest/. 16 | 17 | 18 | Features 19 | ======== 20 | - Auto uArm Port detection 21 | - Provide firmware_helper, firmware upgrade online 22 | - Provide Auto Calibration tool 23 | 24 | Requirements 25 | ============ 26 | - Python 2.7x and Python 3.4x or above 27 | - Latest uArm Firmware 28 | 29 | Installation 30 | ============ 31 | 32 | pyuarm 33 | ------ 34 | This install a package that can be used from Python (``import pyuarm``). 35 | 36 | To install for all users on the system, administrator rights (root) may be required. 37 | 38 | Firmware 39 | -------- 40 | 41 | pyuarm requires you install uArm Firmware, Please install firmware before. 42 | 43 | There are three ways to install firmware. 44 | 45 | - Use GUI uArm Assistant 46 | 47 | - Use ``uarmcli firmware -d`` after you install pyuarm 48 | 49 | - if ``uarmcli`` not working, you could use this way ``python -m pyuarm.tools.firmware -d`` 50 | 51 | From PyPI 52 | ~~~~~~~~~ 53 | pyuarm can be installed from PyPI, either manually downloading the files and installing as described below or using:: 54 | 55 | pip install pyuarm 56 | 57 | From source (tar.gz or checkout) 58 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 59 | Download the archive from http://pypi.python.org/pypi/pyuarm. 60 | Unpack the archive, enter the ``pyuarm-x.y`` directory and run:: 61 | 62 | python setup.py install 63 | 64 | .. _`project page on GitHub`: https://github.com/uArm-Developer/pyuarm 65 | .. _`Download Page`: http://pypi.python.org/pypi/pyuarm -------------------------------------------------------------------------------- /docs/pyuarm_api.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | pyuarm API 3 | ========== 4 | 5 | Classes 6 | ------- 7 | 8 | .. automodule:: pyuarm.uarm 9 | :members: 10 | :undoc-members: 11 | :show-inheritance: 12 | 13 | Exception 14 | --------- 15 | 16 | .. autoclass:: pyuarm.UArmConnectException 17 | 18 | 19 | Constants 20 | --------- 21 | 22 | TO-DO 23 | 24 | Module Functions and attributes 25 | ------------------------------- 26 | 27 | pyuarm.tools.list_uarms 28 | ~~~~~~~~~~~~~~~~~~~~~~~ 29 | 30 | .. automodule:: pyuarm.tools.list_uarms 31 | :members: 32 | :undoc-members: 33 | :show-inheritance: 34 | :exclude-members: main 35 | 36 | pyuarm.tools.firmware 37 | ~~~~~~~~~~~~~~~~~~~~~ 38 | 39 | .. automodule:: pyuarm.tools.firmware 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | :exclude-members: main 44 | 45 | pyuarm.tools.calibrate 46 | ~~~~~~~~~~~~~~~~~~~~~~ 47 | 48 | .. automodule:: pyuarm.tools.calibrate 49 | :members: 50 | :undoc-members: 51 | :show-inheritance: 52 | :exclude-members: main 53 | 54 | 55 | pyuarm.tools.miniterm 56 | ~~~~~~~~~~~~~~~~~~~~~ 57 | 58 | .. automodule:: pyuarm.tools.miniterm 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | :exclude-members: main -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | pyserial>=3.0 2 | sphinx_bootstrap_theme>=0.4.9 3 | -------------------------------------------------------------------------------- /docs/tools.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Tools 3 | ===== 4 | 5 | pyuarm provides some tools for developer. 6 | 7 | - list_uarms, you could filter the uArm Ports from system easily 8 | 9 | :: 10 | 11 | $python -m pyuarm.tools.list_uarms 12 | /dev/cu.usbserial-A600CRJU 13 | 1 ports found 14 | 15 | Here is the example use in code. It will return the first port scanned in your system. 16 | 17 | .. code-block:: python 18 | 19 | def get_uarm(): 20 | ports = uarm_ports() 21 | if len(ports) > 0: 22 | return UArm(port_name=ports[0]) 23 | else: 24 | return None 25 | 26 | - firmware, you could flash the firmware and download the latest firmware 27 | 28 | :: 29 | 30 | $python -m pyuarm.tools.firmware -d 31 | pyuarm - INFO - pyuarm version: 2.4.0.9 32 | pyuarm - INFO - writing to /Users/alex/uarm/assistant/firmware.hex, file size: 80486 bytes 33 | Downloading: [==================================================] 100.00% 34 | pyuarm - INFO - Flash Command: 35 | avrdude -patmega328p -carduino -P/dev/cu.usbserial-A600CRJU -b115200 -D -Uflash:w:/Users/alex/uarm/assistant/firmware.hex:i 36 | Flashing: [==================================================] 100.00% 37 | 38 | 39 | - calibrate, query calibrate information 40 | 41 | :: 42 | 43 | $python -m pyuarm.tools.calibrate 44 | pyuarm - INFO - pyuarm version: 2.4.0.9 45 | pyuarm - INFO - Connecting from port - /dev/cu.usbserial-A600CRJU... 46 | pyuarm - INFO - All Calibration: COMPLETED 47 | pyuarm - INFO - Linear Calibration: COMPLETED 48 | pyuarm - INFO - Manual Calibration: COMPLETED 49 | pyuarm - INFO - Servo 0 INTERCEPT: -29.7, SLOPE: 0.35, MANUAL: -20.84 50 | pyuarm - INFO - Servo 1 INTERCEPT: -28.41, SLOPE: 0.34, MANUAL: 5.0 51 | pyuarm - INFO - Servo 2 INTERCEPT: -30.68, SLOPE: 0.35, MANUAL: -11.0 52 | pyuarm - INFO - Servo 3 INTERCEPT: -45.37, SLOPE: 0.51, MANUAL: 0.0 53 | 54 | 55 | 56 | You could use this summary script 57 | 58 | ``uarmcli -h`` or ``python -m pyuarm.tools.scripts -h`` 59 | 60 | :: 61 | 62 | $python -m pyuarm.tools.scripts -h 63 | usage: scripts.py [-h] [-v] {miniterm,calibrate,list,firmware} ... 64 | 65 | positional arguments: 66 | {miniterm,calibrate,list,firmware} 67 | 68 | optional arguments: 69 | -h, --help show this help message and exit 70 | -v, --version show program's version number and exit 71 | 72 | 73 | :: 74 | 75 | $python -m pyuarm.tools.scripts list 76 | /dev/cu.usbserial-A600CRJU 77 | 1 ports found 78 | 79 | :: 80 | 81 | python -m pyuarm.tools.scripts firmware -h 82 | usage: scripts.py firmware [-h] [-p PORT] [--path PATH] [--debug] [-d] 83 | 84 | optional arguments: 85 | -h, --help show this help message and exit 86 | -p PORT, --port PORT specify port number 87 | --path PATH firmware path 88 | --debug Turn on Debug Mode 89 | -d, --download download firmware online 90 | 91 | -------------------------------------------------------------------------------- /pyuarm/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | if sys.version > '3': 3 | PY3 = True 4 | else: 5 | PY3 = False 6 | from .uarm import UArm, UArmConnectException 7 | from .config import ua_dir, home_dir 8 | from .util import get_uarm 9 | from .version import __version__ 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /pyuarm/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os.path import expanduser 3 | import io 4 | import json 5 | from . import PY3 6 | from .log import printf 7 | if PY3: 8 | import urllib.request as req 9 | else: 10 | import urllib2 as req 11 | 12 | # ################################### Config ################################ 13 | home_dir = os.path.join(expanduser("~"), "uarm", "") 14 | if not os.path.exists(home_dir): 15 | os.mkdir(home_dir) 16 | ua_dir = os.path.join(home_dir, "assistant") 17 | if not os.path.exists(ua_dir): 18 | os.mkdir(ua_dir) 19 | 20 | config_file = os.path.join(ua_dir, "config.json") 21 | default_config = { 22 | "branch": "dev", 23 | "firmware_filename": "firmware.hex", 24 | "bluetooth_filename": "bluetooth.hex", 25 | "hardware_id": "USB VID:PID=0403:6001", 26 | "calibration_filename": "calibration.hex", 27 | "calibration_url": "http://download.ufactory.cc/firmware/pro/calibration.hex", 28 | "firmware_url": "http://download.ufactory.cc/firmware/dev/firmware.hex", 29 | "driver_url": "http://download.ufactory.cc/driver/ftdi_win.zip", 30 | "bluetooth_url": "http://download.ufactory.cc/firmware/pro/bluetooth.hex", 31 | "latest_version": "", 32 | } 33 | 34 | 35 | def load_config(): 36 | try: 37 | if not os.path.exists(config_file): 38 | save_default_config() 39 | with io.open(config_file, "r", encoding="utf-8") as data_file: 40 | settings = json.load(data_file) 41 | return settings 42 | except Exception as e: 43 | printf("Error occurred when reading settings. {}".format(e)) 44 | return default_config 45 | 46 | 47 | def save_config(settings): 48 | cf = open(config_file, "w") 49 | json.dump(settings, open(config_file, 'w'), 50 | sort_keys=False, indent=4) 51 | cf.close() 52 | 53 | 54 | def save_default_config(): 55 | json.dump(default_config, open(config_file, 'w'), 56 | sort_keys=False, indent=4, separators=(',', ': ')) 57 | settings = default_config 58 | save_config(settings) 59 | 60 | 61 | # ############## online config file ################# 62 | def get_online_config(): 63 | online_config_url = "http://download.ufactory.cc/version.json" 64 | response = req.urlopen(online_config_url) 65 | online_config_data = json.loads(response.read().decode(response.info().get_param('charset') or 'utf-8')) 66 | return online_config_data 67 | -------------------------------------------------------------------------------- /pyuarm/log.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from logging import DEBUG, ERROR, INFO 3 | import sys 4 | from . import PY3 5 | import logging 6 | from .version import __version__ 7 | 8 | # ################################### Log ################################ 9 | STREAM = 55 10 | pylogger = None 11 | stream_logger = None 12 | 13 | 14 | def get_logger_level(): 15 | if pylogger is not None: 16 | return pylogger.level 17 | 18 | 19 | def set_default_logger(debug=False): 20 | if debug: 21 | logging_level = logging.DEBUG 22 | else: 23 | logging_level = logging.INFO 24 | logger = logging.getLogger('pyuarm') 25 | if not logger.handlers: 26 | logger.setLevel(logging_level) 27 | formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') 28 | ch = logging.StreamHandler() 29 | ch.setFormatter(formatter) 30 | ch.setLevel(logging_level) 31 | logger.addHandler(ch) 32 | init_logger(logger) 33 | 34 | 35 | def init_logger(logger): 36 | """ 37 | initialize global logger 38 | :param logger 39 | :return: 40 | """ 41 | global pylogger 42 | pylogger = logger 43 | printf('pyuarm version: ' + __version__) 44 | 45 | 46 | def set_stream_logger(): 47 | global stream_logger 48 | stream_logger = logging.getLogger('UA_STREAM') 49 | if not stream_logger.handlers: 50 | my_formatter = logging.Formatter('%(message)s') 51 | stream_logger.setLevel(logging.DEBUG) 52 | sch = logging.StreamHandler() 53 | sch.setFormatter(my_formatter) 54 | if PY3: 55 | sch.terminator = "" 56 | sch.setLevel(logging.DEBUG) 57 | stream_logger.addHandler(sch) 58 | 59 | 60 | def close_logger(): 61 | if pylogger is not None: 62 | handlers = pylogger.handlers 63 | for p in handlers: 64 | p.close() 65 | pylogger.removeHandler(p) 66 | if stream_logger is not None: 67 | handlers = stream_logger.handlers 68 | for p in handlers: 69 | p.close() 70 | stream_logger.removeHandler(p) 71 | 72 | 73 | def printf(msg, type=INFO): 74 | """ 75 | global print log function 76 | :param msg: 77 | :param type: 78 | :return: 79 | """ 80 | if pylogger is None: 81 | set_default_logger() 82 | if type == INFO: 83 | pylogger.info(msg) 84 | elif type == DEBUG: 85 | pylogger.debug(msg) 86 | elif type == ERROR: 87 | pylogger.error(msg) 88 | elif type == STREAM: 89 | if pylogger.level == DEBUG: 90 | if PY3: 91 | if stream_logger is None: 92 | set_stream_logger() 93 | stream_logger.debug(msg.decode(encoding=sys.getdefaultencoding())) 94 | else: 95 | print(msg, end='') 96 | -------------------------------------------------------------------------------- /pyuarm/protocol.py: -------------------------------------------------------------------------------- 1 | # parameters 2 | 3 | ## SERVO NUMBER INDEX 4 | SERVO_BOTTOM = 0 5 | SERVO_LEFT = 1 6 | SERVO_RIGHT = 2 7 | SERVO_HAND = 3 8 | 9 | ## CALIBRATION FLAG 10 | CONFIRM_FLAG = 0x80 11 | CALIBRATION_FLAG = 10 12 | CALIBRATION_LINEAR_FLAG = 11 13 | CALIBRATION_SERVO_FLAG = 12 14 | CALIBRATION_STRETCH_FLAG = 13 15 | 16 | ## OFFSET EEPROM ADDRESS 17 | LINEAR_INTERCEPT_START_ADDRESS = 70 18 | LINEAR_SLOPE_START_ADDRESS = 50 19 | OFFSET_START_ADDRESS = 30 20 | OFFSET_STRETCH_START_ADDRESS = 20 21 | 22 | SERIAL_NUMBER_ADDRESS = 100 23 | 24 | ## EEPROM DATA TYPE INDEX 25 | EEPROM_DATA_TYPE_BYTE = 1 26 | EEPROM_DATA_TYPE_INTEGER = 2 27 | EEPROM_DATA_TYPE_FLOAT = 4 28 | 29 | ## PROTOCOL MESSAGE 30 | # READY & Response 31 | READY = "@1" 32 | OK = "OK" 33 | # Set Command 34 | SET_POSITION = "G0 X{} Y{} Z{} F{}" 35 | SET_POSITION_RELATIVE = "G204 X{} Y{} Z{} F{}" 36 | SET_SERVO_ANGLE = "G202 N{} V{}" 37 | STOP_MOVING = "G203" 38 | SET_PUMP = "M231 V{}" 39 | GET_PUMP = "P231" 40 | SET_GRIPPER = "M232 V{}" 41 | SET_BUZZER = "M210 F{} T{}" 42 | SET_POLAR = "G201 S{} R{} H{} F{}" 43 | ATTACH_SERVO = "M201 N{}" 44 | DETACH_SERVO = "M202 N{}" 45 | # SET_REPORT_BUTTON = "M213 V{}" #1 Open #0 Close 46 | # Get Command 47 | GET_SIMULATION = "M222 X{} Y{} Z{} P0" 48 | GET_FIRMWARE_VERSION = "P203" 49 | GET_HARDWARE_VERSION = "P202" 50 | GET_COOR = "P220" 51 | GET_SERVO_STATUS = "M203" 52 | GET_SERVO_ANGLE = "P200" 53 | GET_IS_MOVE = "M200" 54 | GET_TIP_SENSOR = "P233" 55 | GET_POLAR = "P221" 56 | GET_GRIPPER = "P232" 57 | GET_EEPROM = "M211 N0 A{} T{}" 58 | SET_EEPROM = "M212 N0 A{} T{} V{}" 59 | GET_ANALOG = "P241 N{}" 60 | GET_DIGITAL = "P240 N{}" 61 | 62 | # Report Command 63 | SET_REPORT_POSITION = "M120 V{}" 64 | REPORT_POSITION_PREFIX = "@3" 65 | # REPORT_BUTTON_PRESSED = "@4" 66 | # BUTTON_MENU = "B0" 67 | # BUTTON_PLAY = "B1" 68 | 69 | -------------------------------------------------------------------------------- /pyuarm/threaded.py: -------------------------------------------------------------------------------- 1 | from serial.threaded import Packetizer 2 | import serial 3 | import threading 4 | import sys 5 | 6 | class UArmLineReader(Packetizer): 7 | """ 8 | Read and write (Unicode) lines from/to serial port. 9 | The encoding is applied. 10 | """ 11 | 12 | TERMINATOR = b'\r\n' 13 | ENCODING = 'utf-8' 14 | UNICODE_HANDLING = 'replace' 15 | 16 | def __init__(self): 17 | super(UArmLineReader, self).__init__() 18 | self.connected_status = False 19 | 20 | def handle_packet(self, packet): 21 | self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING)) 22 | 23 | def handle_line(self, line): 24 | """Process one line - to be overridden by subclassing""" 25 | raise NotImplementedError('please implement functionality in handle_line') 26 | 27 | def write_line(self, text): 28 | """ 29 | Write text to the transport. ``text`` is a Unicode string and the encoding 30 | is applied before sending ans also the newline is append. 31 | """ 32 | # + is not the best choice but bytes does not support % or .format in py3 and we want a single write call 33 | self.transport.write(text.encode(self.ENCODING, self.UNICODE_HANDLING) + self.TERMINATOR) 34 | 35 | def get_connect_status(self): 36 | return self.connected_status 37 | 38 | 39 | class UArmSerial(UArmLineReader): 40 | 41 | def __init__(self, data): 42 | super(UArmSerial, self).__init__() 43 | self.data = data 44 | 45 | def connection_made(self, transport): 46 | super(UArmSerial, self).connection_made(transport) 47 | # sys.stdout.write('port opened\n') 48 | self.connected_status = True 49 | 50 | def handle_line(self, data): 51 | self.data.append(data) 52 | # sys.stdout.write('line received: {}\n'.format(repr(data))) 53 | 54 | def connection_lost(self, exc): 55 | self.data = [] 56 | self.connected_status = False 57 | # print("Connect_status: {}".format(self.get_connect_status())) 58 | # if exc: 59 | # traceback.printf_exc(exc) 60 | # sys.stdout.write('port closed\n') 61 | 62 | 63 | class UArmReaderThread(threading.Thread): 64 | """\ 65 | Implement a serial port read loop and dispatch to a Protocol instance (like 66 | the asyncio.Protocol) but do it with threads. 67 | 68 | Calls to close() will close the serial port but it is also possible to just 69 | stop() this thread and continue the serial port instance otherwise. 70 | """ 71 | 72 | def __init__(self, serial_instance, protocol_factory, data): 73 | """\ 74 | Initialize thread. 75 | 76 | Note that the serial_instance' timeout is set to one second! 77 | Other settings are not changed. 78 | """ 79 | super(UArmReaderThread, self).__init__() 80 | self.daemon = True 81 | self.serial = serial_instance 82 | self.protocol_factory = protocol_factory 83 | self.alive = True 84 | self._lock = threading.Lock() 85 | self._connection_made = threading.Event() 86 | self.protocol = None 87 | self.data = data 88 | 89 | def stop(self): 90 | """Stop the reader thread""" 91 | self.alive = False 92 | if hasattr(self.serial, 'cancel_read'): 93 | self.serial.cancel_read() 94 | self.join(2) 95 | 96 | def run(self): 97 | """Reader loop""" 98 | if not hasattr(self.serial, 'cancel_read'): 99 | self.serial.timeout = 1 100 | self.protocol = self.protocol_factory(self.data) 101 | try: 102 | self.protocol.connection_made(self) 103 | except Exception as e: 104 | self.alive = False 105 | self.protocol.connection_lost(e) 106 | self._connection_made.set() 107 | return 108 | error = None 109 | self._connection_made.set() 110 | while self.alive and self.serial.is_open: 111 | try: 112 | # read all that is there or wait for one byte (blocking) 113 | data = self.serial.read(self.serial.in_waiting or 1) 114 | except serial.SerialException as e: 115 | # probably some I/O problem such as disconnected USB serial 116 | # adapters -> exit 117 | error = e 118 | break 119 | else: 120 | if data: 121 | # make a separated try-except for called used code 122 | try: 123 | self.protocol.data_received(data) 124 | except Exception as e: 125 | error = e 126 | break 127 | self.alive = False 128 | self.protocol.connection_lost(error) 129 | self.protocol = None 130 | 131 | def write(self, data): 132 | """Thread safe writing (uses lock)""" 133 | with self._lock: 134 | self.serial.write(data) 135 | 136 | def close(self): 137 | """Close the serial port and exit reader thread (uses lock)""" 138 | # use the lock to let other threads finish writing 139 | with self._lock: 140 | # first stop reading, so that closing can be done on idle port 141 | self.stop() 142 | self.serial.close() 143 | 144 | def connect(self): 145 | """ 146 | Wait until connection is set up and return the transport and protocol 147 | instances. 148 | """ 149 | if self.alive: 150 | self._connection_made.wait() 151 | if not self.alive: 152 | raise RuntimeError('connection_lost already called') 153 | return (self, self.protocol) 154 | else: 155 | raise RuntimeError('already stopped') 156 | 157 | # - - context manager, returns protocol 158 | 159 | def __enter__(self): 160 | """\ 161 | Enter context handler. May raise RuntimeError in case the connection 162 | could not be created. 163 | """ 164 | self.start() 165 | self._connection_made.wait() 166 | if not self.alive: 167 | raise RuntimeError('connection_lost already called') 168 | return self.protocol 169 | 170 | def __exit__(self, exc_type, exc_val, exc_tb): 171 | """Leave context: close port""" 172 | self.close() 173 | 174 | if __name__ == '__main__': 175 | # pylint: disable=wrong-import-position 176 | import sys 177 | import time 178 | import traceback 179 | 180 | #~ PORT = 'spy:///dev/ttyUSB0' 181 | PORT = '/dev/tty.usbserial-A600CRJU' 182 | args = [] 183 | ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1) 184 | with UArmReaderThread(ser, UArmSerial, args) as protocol: 185 | time.sleep(2) 186 | protocol.write_line('#1 P203') 187 | time.sleep(10) 188 | for a in args: 189 | print("args: {}".format(a)) 190 | time.sleep(10) 191 | 192 | # alternative usage 193 | # ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1) 194 | # t = UArmReaderThread(ser, UArmSerial, args) 195 | # t.start() 196 | # transport, protocol = t.connect() 197 | # protocol.write_line('#1 P203') 198 | # time.sleep(2) 199 | # for a in args: 200 | # print ("args: {}".format(a)) 201 | # t.close() -------------------------------------------------------------------------------- /pyuarm/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uArm-Developer/pyuarm/17dc08c6c5a6533064614aecdcdcc41052554a55/pyuarm/tools/__init__.py -------------------------------------------------------------------------------- /pyuarm/tools/calibrate.py: -------------------------------------------------------------------------------- 1 | """ 2 | pyuarm.tools.calibrate 3 | This is part of pyuarm. Provide the calibrate information query. Include three sections. 4 | 1. Linear Offset 5 | 2. Manual Offset 6 | 3. Complete flag 7 | If you want to calibrate uArm. Please use uArm assistant. 8 | """ 9 | 10 | 11 | from __future__ import print_function 12 | import sys 13 | import copy 14 | 15 | from pyuarm import PY3 16 | from pyuarm.uarm import UArm 17 | from pyuarm.tools.list_uarms import get_uarm_port_cli 18 | from pyuarm.protocol import * 19 | from pyuarm.util import printf 20 | 21 | 22 | if PY3: 23 | izip = zip 24 | else: 25 | from itertools import izip 26 | 27 | __version__ = "2.0.0" 28 | 29 | def read_manual_offset(uarm): 30 | """ 31 | Read Manual Offset from uArm EEPROM 32 | :param uarm: uArm instance 33 | :return: 34 | """ 35 | address = OFFSET_START_ADDRESS 36 | read_manual_offset = [] 37 | for i in range(4): 38 | read_manual_offset.append(round(uarm.get_rom_data(address, EEPROM_DATA_TYPE_FLOAT), 2)) 39 | address += 4 40 | return read_manual_offset 41 | 42 | 43 | def read_linear_offset(uarm): 44 | """ 45 | Read Linear Offset from uArm EEPROM 46 | :param uarm: uArm instance 47 | :return: 48 | """ 49 | linear_offset_template = {"INTERCEPT": 0.00, "SLOPE": 0.00} 50 | intercept_address = LINEAR_INTERCEPT_START_ADDRESS 51 | slope_address = LINEAR_SLOPE_START_ADDRESS 52 | linear_offset_data = [] 53 | for i in range(4): 54 | linear_offset= copy.deepcopy(linear_offset_template) 55 | linear_offset['INTERCEPT'] = round( 56 | uarm.get_rom_data(intercept_address, EEPROM_DATA_TYPE_FLOAT), 2) 57 | linear_offset['SLOPE'] = round(uarm.get_rom_data(slope_address, EEPROM_DATA_TYPE_FLOAT), 2) 58 | linear_offset_data.append(linear_offset) 59 | intercept_address += 4 60 | slope_address += 4 61 | return linear_offset_data 62 | 63 | 64 | def read_completed_flag(uarm, flag_type): 65 | """ 66 | Read Complete Flag from EEPROM 67 | :param uarm: uArm instance 68 | :param flag_type: protocol.CALIBRATION_FLAG, protocol.CALIBRATION_LINEAR_FLAG, procotol.CALIBRATION_SERVO_FLAG 69 | :return: 70 | """ 71 | if flag_type == CALIBRATION_FLAG: 72 | if uarm.get_rom_data(CALIBRATION_FLAG) == CONFIRM_FLAG: 73 | return True 74 | else: 75 | return False 76 | elif flag_type == CALIBRATION_LINEAR_FLAG: 77 | if uarm.get_rom_data(CALIBRATION_LINEAR_FLAG) == CONFIRM_FLAG: 78 | return True 79 | else: 80 | return False 81 | elif flag_type == CALIBRATION_SERVO_FLAG: 82 | if uarm.get_rom_data(CALIBRATION_SERVO_FLAG) == CONFIRM_FLAG: 83 | return True 84 | else: 85 | return False 86 | 87 | 88 | def exit_fun(): 89 | try: 90 | input("\nPress Enter to Exit...") 91 | sys.exit(0) 92 | except Exception: 93 | pass 94 | 95 | 96 | def main(args): 97 | if args.port: 98 | port_name = args.port 99 | else: 100 | port_name = get_uarm_port_cli() 101 | 102 | if args.debug: 103 | debug = True 104 | else: 105 | debug = False 106 | 107 | uarm = UArm(port_name=port_name, debug=debug) 108 | uarm.connect() 109 | printf("All Calibration: {}".format("COMPLETED" if read_completed_flag(uarm, CALIBRATION_FLAG) else "NOT COMPLETED")) 110 | printf("Linear Calibration: {}".format("COMPLETED" if read_completed_flag(uarm, CALIBRATION_LINEAR_FLAG) else "NOT COMPLETED")) 111 | printf("Manual Calibration: {}".format("COMPLETED" if read_completed_flag(uarm, CALIBRATION_SERVO_FLAG) else "NOT COMPLETED")) 112 | for linear_offset, manual_offset, i in izip(read_linear_offset(uarm), read_manual_offset(uarm), range(4)): 113 | printf("Servo {} INTERCEPT: {}, SLOPE: {}, MANUAL: {}".format(i, linear_offset['INTERCEPT'], linear_offset['SLOPE'], manual_offset)) 114 | 115 | 116 | def run(): 117 | try: 118 | import argparse 119 | parser = argparse.ArgumentParser() 120 | parser.add_argument("-p", "--port", help="specify port number") 121 | parser.add_argument("-d", "--debug", help="Open Debug Message", action="store_true") 122 | args = parser.parse_args() 123 | main(args) 124 | except Exception as e: 125 | printf("{} - {}".format(type(e).__name__, e)) 126 | 127 | if __name__ == '__main__': 128 | run() 129 | -------------------------------------------------------------------------------- /pyuarm/tools/firmware.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from __future__ import division 3 | import platform 4 | import threading 5 | import os 6 | from ..util import progressbar 7 | from ..log import get_logger_level, printf, ERROR, DEBUG, STREAM, set_default_logger, set_stream_logger, INFO 8 | from ..config import get_online_config, save_default_config, save_config, load_config, ua_dir, home_dir 9 | from .list_uarms import get_uarm_port_cli 10 | from subprocess import Popen, PIPE, STDOUT 11 | import subprocess 12 | from ..version import check_version_update 13 | import time 14 | from .. import PY3 15 | if PY3: 16 | import urllib.request 17 | else: 18 | import urllib2 19 | 20 | 21 | __version__ = '1.1.1' 22 | __author__ = 'Alex Tan' 23 | ''' 24 | This Tool is for uArm firmware flashing. Also support download firmware online 25 | ''' 26 | 27 | process = None 28 | error_description = None 29 | 30 | 31 | def exit_fun(): 32 | try: 33 | input("\nPress Enter to Exit...") 34 | except Exception: 35 | pass 36 | 37 | 38 | def catch_exception(func): 39 | def decorator(*args, **kwargs): 40 | try: 41 | return func(*args, **kwargs) 42 | except OSError as e: 43 | if e.errno == 2: 44 | printf("{}".format(error_description), ERROR) 45 | except Exception as e: 46 | printf("{} - {} - {}".format(type(e).__name__, func.__name__, e), ERROR) 47 | return decorator 48 | 49 | 50 | @catch_exception 51 | def read_std_output(cmd, progress_step=None): 52 | global process 53 | process = Popen(cmd, stdout=PIPE, 54 | stderr=STDOUT, shell=False, bufsize=1) 55 | progress = 0 56 | total_progress = 100 57 | while True: 58 | data = process.stdout.read(1) 59 | if data == '' or process.poll() is not None: 60 | break 61 | if data != '': 62 | if data == b'#': # Progress 63 | progress += 1 64 | if progress >= 50 and progress_step: # ignore first 50 '#' 65 | progress_step(progress-50, total_progress) 66 | time.sleep(0.1) 67 | print("") 68 | process.wait() 69 | exitcode = process.returncode 70 | process.stdout.close() 71 | process.terminate() 72 | if exitcode == 1: # Error 73 | return 74 | else: # succeed 75 | pass 76 | 77 | 78 | @catch_exception 79 | def download(url, filepath): # To - improve, add logger to support show the download prgress 80 | if PY3: 81 | u = urllib.request.urlopen(url) 82 | fileTotalbytes = u.length 83 | else: 84 | u = urllib2.urlopen(url) 85 | fileTotalbytes = int(u.info().getheaders("Content-Length")[0]) 86 | printf("writing to {}, file size: {} bytes ".format(filepath, str(fileTotalbytes))) 87 | 88 | data_blocks = [] 89 | total = 0 90 | 91 | while True: 92 | block = u.read(1024) 93 | data_blocks.append(block) 94 | total += len(block) 95 | title = "Downloading: " 96 | progressbar(title, total, fileTotalbytes) 97 | if not len(block): 98 | break 99 | print("") 100 | 101 | data = b''.join(data_blocks) # had to add b because I was joining bytes not strings 102 | u.close() 103 | 104 | with open(filepath, "wb") as f: 105 | f.write(data) 106 | 107 | 108 | def gen_flash_cmd(port, firmware_path, avrdude_path=None, debug=False): 109 | global error_description 110 | if platform.system() == 'Darwin': 111 | avrdude_bin = 'avrdude' 112 | error_description = "avrdude is required, Please use `brew install avrdude` " 113 | elif platform.system() == 'Windows': 114 | avrdude_bin = 'avrdude.exe' 115 | error_description = "avrdude is required, Please install winavr from " \ 116 | "https://sourceforge.net/projects/winavr/files/WinAVR/20100110/" 117 | elif platform.system() == 'Linux': 118 | avrdude_bin = 'avrdude' 119 | error_description = "avrdude is required, Please use `sudo apt-get install avrdude`" 120 | else: 121 | return None, "Not support System" 122 | cmd = [] 123 | if avrdude_path is not None: 124 | cmd.append(os.path.join(avrdude_path, avrdude_bin)) 125 | cmd.append(os.path.join(avrdude_path, 'avrdude.conf')) 126 | else: 127 | cmd.append(avrdude_bin) 128 | cmd.append('-patmega328p') 129 | cmd.append('-carduino') 130 | cmd.append('-P{}'.format(port)) 131 | cmd.append('-b115200') 132 | cmd.append('-D') 133 | cmd.append('-Uflash:w:{}:i'.format(firmware_path)) 134 | if debug: 135 | cmd.append('-v') 136 | return cmd 137 | 138 | 139 | @catch_exception 140 | def flash(port, firmware_path, avrdude_path=None): 141 | cmd = gen_flash_cmd(port, firmware_path, avrdude_path) 142 | printf("Flash Command:\n{}".format(' '.join(cmd)), INFO) 143 | title = "Flashing: " 144 | 145 | def progress_step(progress, total): 146 | progressbar(title, progress, total) 147 | if get_logger_level() != DEBUG: 148 | flash_thread = threading.Thread(target=read_std_output, args=(cmd, progress_step, )) 149 | flash_thread.start() 150 | else: 151 | cmd.append('-v') 152 | subprocess.call(cmd) 153 | 154 | 155 | def get_latest_firmware_version(branch='pro'): 156 | try: 157 | config = get_online_config() 158 | if branch == 'pro': 159 | vs = config['data']['firmware']['pro'] 160 | elif branch == 'dev': 161 | vs = config['data']['firmware']['dev'] 162 | versions = {} 163 | for v in vs: 164 | versions = {v['version']: v['url']} 165 | latest_version = max(versions.keys()) 166 | latest_version_url = versions[latest_version] 167 | settings = load_config() 168 | if settings['latest_version'] != "": 169 | if check_version_update(latest_version, settings['latest_version']): 170 | settings['latest_version'] = latest_version 171 | settings['firmware_url'] = latest_version_url 172 | save_config(settings) 173 | except KeyError: 174 | save_default_config() 175 | 176 | 177 | def main(args): 178 | settings = load_config() 179 | if args.debug: 180 | set_default_logger(debug=True) 181 | set_stream_logger() 182 | else: 183 | set_default_logger() 184 | 185 | if args.port: 186 | port_name = args.port 187 | else: 188 | port_name = get_uarm_port_cli() 189 | 190 | if args.path: 191 | firmware_path = args.path 192 | else: 193 | firmware_path = os.path.join(ua_dir, settings['firmware_filename']) 194 | 195 | if args.download: 196 | download(settings['firmware_url'], firmware_path) 197 | 198 | if port_name is not None: 199 | flash(port_name, firmware_path) 200 | else: 201 | printf("No uArm Port Found", ERROR) 202 | 203 | 204 | if __name__ == '__main__': 205 | try: 206 | import argparse 207 | 208 | parser = argparse.ArgumentParser() 209 | parser.add_argument("--port", help="specify port number") 210 | parser.add_argument("--path", help="firmware path") 211 | parser.add_argument("--debug", help="open Debug Mode", action="store_true") 212 | parser.add_argument("-d", "--download", help="download firmware online", action="store_true") 213 | args = parser.parse_args() 214 | main(args) 215 | except SystemExit: 216 | exit_fun() 217 | -------------------------------------------------------------------------------- /pyuarm/tools/list_uarms.py: -------------------------------------------------------------------------------- 1 | from serial.tools import list_ports 2 | 3 | UARM_HWID_KEYWORD = "USB VID:PID=0403:6001" 4 | 5 | 6 | def uarm_ports(): 7 | uarm_ports = [] 8 | for i in list_ports.comports(): 9 | if i.hwid[0:len(UARM_HWID_KEYWORD)] == UARM_HWID_KEYWORD: 10 | uarm_ports.append(i[0]) 11 | return uarm_ports 12 | 13 | 14 | def check_port_plug_in(serial_id): 15 | ports = list_ports.comports() 16 | for p in ports: 17 | if p.serial_number == serial_id: 18 | return True 19 | return False 20 | 21 | 22 | def get_uarm_port_cli(): 23 | uarm_list = uarm_ports() 24 | ports = uarm_ports() 25 | if len(ports) > 1: 26 | i = 1 27 | for port in ports: 28 | print("[{}] - {}".format(i, port)) 29 | i += 1 30 | port_index = input("Please Choose the uArm Port: ") 31 | uarm_port = ports[int(port_index) - 1] 32 | return uarm_port 33 | elif len(ports) == 1: 34 | return uarm_list[0] 35 | elif len(ports) == 0: 36 | return None 37 | 38 | 39 | def get_port_property(port_name): 40 | for p in list_ports.comports(): 41 | if p.device == port_name: 42 | return p 43 | return None 44 | 45 | 46 | def main(): 47 | """ 48 | :: 49 | 50 | $ python -m pyuarm.tools.list_uarms 51 | /dev/cu.usbserial-A600CVS9 52 | 1 ports found 53 | 54 | """ 55 | ports = uarm_ports() 56 | for p in ports: 57 | print(p) 58 | print("{0} ports found".format(len(ports))) 59 | 60 | 61 | if __name__ == '__main__': 62 | main() 63 | -------------------------------------------------------------------------------- /pyuarm/tools/miniterm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This is a module that help user to control your uArm. 4 | # Please use -h to list all parameters of this script 5 | 6 | # This file is part of pyuarm. https://github.com/uArm-Developer/pyuarm 7 | # (C) 2016 UFACTORY 8 | 9 | 10 | from cmd import Cmd 11 | from .list_uarms import get_uarm_port_cli, uarm_ports 12 | from ..uarm import UArm, UArmConnectException 13 | from ..log import DEBUG, printf 14 | 15 | version = "0.1.6" 16 | 17 | 18 | class UArmCmd(Cmd): 19 | help_msg = "Shortcut:" + "\n" 20 | help_msg += "Quit: " + "Ctrl + D" 21 | help_msg += ", or input: " + "quit" + "\n" 22 | help_msg += "Clear Screen: " + "Ctrl + L" 23 | 24 | ON_OFF = ['on', 'off'] 25 | 26 | FIRMWARE = ['version', 'force', 'upgrade'] 27 | 28 | SERVO_STATUS = ['attach', 'detach'] 29 | 30 | prompt = ">>> " 31 | intro = "Welcome to use uArm Command Line - v{}\n" \ 32 | .format(version) 33 | 34 | intro += help_msg 35 | intro += "\n\n" 36 | intro += "Input help for more usage" 37 | 38 | # doc_header = "documented commands:" 39 | ruler = '-' 40 | 41 | arm = None 42 | 43 | def __init__(self, port=None, debug=False, *args, **kwargs): 44 | Cmd.__init__(self, *args, **kwargs) 45 | self.__connect(port=port, debug=debug) 46 | 47 | def __is_connected(self): 48 | if self.arm is None: 49 | print("No uArm is connected, please use connect") 50 | return False 51 | else: 52 | if self.arm.connection_state: 53 | return True 54 | else: 55 | print("No uArm is connected, please use connect command") 56 | return False 57 | 58 | def do_connect(self, arg): 59 | """ 60 | connect, Open uArm Port, if more than one uArm Port found, will prompt options to choose. 61 | Please connect to uArm before you do any control action 62 | """ 63 | if len(arg) != 0: 64 | self.__connect(arg) 65 | elif len(arg) == 0: 66 | self.__connect() 67 | 68 | def __connect(self, port=None, debug=False, timeout=1): 69 | """ 70 | connect uArm. 71 | :param port: 72 | :param debug: 73 | :return: 74 | """ 75 | 76 | if self.arm is None: 77 | if port is not None: 78 | self.arm = UArm(port_name=port, debug=debug, timeout=timeout) 79 | else: 80 | ports = uarm_ports() 81 | if len(ports) > 1: 82 | uarm_port = get_uarm_port_cli() 83 | try: 84 | self.arm = UArm(port_name=uarm_port, debug=debug, timeout=timeout) 85 | except UArmConnectException as e: 86 | print("uArm Connect failed, {}".format(str(e))) 87 | elif len(ports) == 1: 88 | self.arm = UArm(debug=debug, timeout=timeout) 89 | self.arm.connect() 90 | elif len(ports) == 0: 91 | print("No uArm ports is found.") 92 | else: 93 | if self.arm.connection_state: 94 | print("uArm is already connected, port: {}".format(self.arm.port_name)) 95 | else: 96 | if self.arm.connect(): 97 | print("uArm port: {} is reconnected") 98 | 99 | def do_disconnect(self, arg): 100 | """ 101 | disconnect, Release uarm port. 102 | """ 103 | if self.arm is not None: 104 | if self.arm.connection_state: 105 | self.arm.disconnect() 106 | 107 | def do_set_position(self, arg): 108 | """ 109 | set_position, move to destination coordinate. 110 | format: set_position X Y Z or move_to X Y Z S 111 | X,Y,Z unit millimeter, S means Speed, unit mm/s 112 | eg. set_position 100 200 150 113 | """ 114 | if self.__is_connected(): 115 | values = arg.split(' ') 116 | if len(values) == 3: 117 | result = self.arm.set_position(int(values[0]), int(values[1]), int(values[2]), wait=True) 118 | # msg = "succeed" if result else "failed" 119 | # print(msg) 120 | elif len(values) == 4: 121 | result = self.arm.set_position(int(values[0]), int(values[1]), int(values[2]), speed=int(values[3]), wait=True) 122 | # msg = "succeed" if result else "failed" 123 | # print(msg) 124 | 125 | def do_sp(self, args): 126 | """ 127 | same with set_position 128 | """ 129 | self.do_set_position(args) 130 | 131 | def do_get_position(self, arg): 132 | """ 133 | get_position get current coordinate 134 | """ 135 | if self.__is_connected(): 136 | coords = self.arm.get_position() 137 | print("Current coordinate: X:{} Y:{} Z:{}".format(*coords)) 138 | 139 | def do_pump(self, arg): 140 | """ 141 | pump 142 | turn on/off pump 143 | """ 144 | if self.__is_connected(): 145 | if arg == 'on': 146 | result = self.arm.set_pump(True, wait=True) 147 | print("succeed" if result else "failed") 148 | elif arg == 'off': 149 | result = self.arm.set_pump(False, wait=True) 150 | print("succeed" if result else "failed") 151 | elif arg == '': 152 | print("please input argument: {}".format(','.join(self.ON_OFF))) 153 | else: 154 | print("Command not found {}".format(arg)) 155 | 156 | def complete_pump(self, text, line, begidx, endidx): 157 | if not text: 158 | completions = self.ON_OFF[:] 159 | else: 160 | completions = [f 161 | for f in self.ON_OFF 162 | if f.startswith(text) 163 | ] 164 | return completions 165 | 166 | # def do_sim(self, arg): 167 | # """ 168 | # sim 169 | # format: sim X Y Z 170 | # validate the coordinate. 171 | # eg. sim 100 200 100 172 | # succeed 173 | # """ 174 | # if self.__is_connected(): 175 | # values = arg.split(' ') 176 | # if len(values) == 3: 177 | # result = self.arm.get_simulation(int(values[0]), int(values[1]), int(values[2])) 178 | # msg = "succeed" if result else "failed" 179 | # print (msg) 180 | 181 | def do_set_angle(self, arg): 182 | """ 183 | set_angle 184 | format: write_angle servo_number angle 185 | servo_number: 186 | - 0 bottom servo, 187 | - 1 left servo, 188 | - 2 right servo, 189 | - 3 hand servo 190 | eg. 191 | >>> set_angle 0 90 192 | succeed 193 | """ 194 | if self.__is_connected(): 195 | values = arg.split(' ') 196 | if len(values) == 2: 197 | servo_num = int(values[0]) 198 | angle = float(values[1]) 199 | result = self.arm.set_servo_angle(servo_num, angle, wait=True) 200 | msg = "succeed" if result else "failed" 201 | print(msg) 202 | 203 | def do_get_angle(self, arg): 204 | """ 205 | get_angle 206 | Read current servo angle. 207 | format: read_angle servo_number 208 | servo_number: 209 | - 0 bottom servo, 210 | - 1 left servo, 211 | - 2 right servo, 212 | if no servo_number provide, will list all servos angle 213 | eg. 214 | >>> get_angle 215 | Current Servo Angles: b:17.97, l:112.72, r:17.97, h:151.14 216 | """ 217 | if self.__is_connected(): 218 | values = arg.split(' ') 219 | if len(values) == 1: 220 | if values[0] == '': 221 | angles = self.arm.get_servo_angle() 222 | print("Current Servo Angles: t:{}, l:{}, r:{}, h:{}".format(*angles)) 223 | 224 | else: 225 | servo_num = int(values[0]) 226 | angle = self.arm.get_servo_angle(servo_num) 227 | print(angle) 228 | 229 | def do_alert(self, arg): 230 | """ 231 | alert 232 | control buzzer 233 | format: alert frequency duration 234 | eg. 235 | alert 1 1 236 | """ 237 | if self.__is_connected(): 238 | values = arg.split(' ') 239 | if len(values) == 2: 240 | frequency = int(values[0]) 241 | duration = float(values[1]) 242 | result = self.arm.set_buzzer(frequency, duration, wait=True) 243 | msg = "succeed" if result else "failed" 244 | print(msg) 245 | 246 | # def do_set_polar(self, arg): 247 | # if self.__is_connected(): 248 | # values = arg.split(' ') 249 | # if len(values) == 4: 250 | # result = self.arm.set_polar_coordinate(values[0], values[1], values[2], values[3]) 251 | # msg = "succeed" if result else "failed" 252 | # print (msg) 253 | # if len(values) == 3: 254 | # result = self.arm.set_polar_coordinate(values[0], values[1], values[2]) 255 | # msg = "succeed" if result else "failed" 256 | # print (msg) 257 | # 258 | # def do_get_polar(self, arg): 259 | # if self.__is_connected(): 260 | # result = self.arm.get_polar_coordinate() 261 | # if result: 262 | # print ("polar coordinate: {}".format(result)) 263 | 264 | def do_servo(self, arg): 265 | """ 266 | Servo status 267 | format: 268 | - servo attach servo_number 269 | - servo detach servo_number 270 | servo_number: 271 | - 0 bottom servo, 272 | - 1 left servo, 273 | - 2 right servo, 274 | - 3 hand servo 275 | - all 276 | eg. 277 | - servo attach all 278 | - servo detach all 279 | """ 280 | if self.__is_connected(): 281 | values = arg.split(' ') 282 | if len(values) == 2: 283 | if values[0] == 'attach': 284 | if values[1] == 'all': 285 | self.arm.set_servo_attach() 286 | elif values[1].isdigit(): 287 | v = int(values[1]) 288 | if 0 <= v <= 3: 289 | self.arm.set_servo_attach(v) 290 | elif values[0] == 'detach': 291 | if values[1] == 'all': 292 | self.arm.set_servo_detach() 293 | elif values[1].isdigit(): 294 | v = int(values[1]) 295 | if 0 <= v <= 3: 296 | self.arm.set_servo_detach(v) 297 | 298 | def complete_servo(self, text, line, begidx, endidx): 299 | if not text: 300 | completions = self.SERVO_STATUS[:] 301 | else: 302 | completions = [f 303 | for f in self.SERVO_STATUS 304 | if f.startswith(text) 305 | ] 306 | return completions 307 | 308 | def do_serial(self, arg): 309 | """ 310 | Raw Serial Mode 311 | You could direct input the communication protocol here. 312 | """ 313 | if self.__is_connected(): 314 | serial_mode = SerialMode(self.arm) 315 | serial_mode.cmdloop() 316 | 317 | def do_help(self, arg): 318 | values = arg.split(' ') 319 | if len(values) == 1 and values[0] == '': 320 | help_title = "uArm Command line Help Center" 321 | help_title += "\n\n" 322 | help_title += "Please use connect before any control action" 323 | help_title += "\n" 324 | help_title += self.help_msg 325 | print(help_title) 326 | # print (self.help_msg) 327 | Cmd.do_help(self, arg) 328 | 329 | def do_quit(self, args): 330 | """ 331 | Quit, if uarm is connected, will disconnect before quit 332 | """ 333 | if self.arm is not None: 334 | if self.arm.connection_state: 335 | self.arm.disconnect() 336 | print("Quiting") 337 | raise SystemExit 338 | 339 | do_EOF = do_quit 340 | 341 | 342 | class SerialMode(Cmd): 343 | prompt = "Serial >>> " 344 | intro = "Welcome to Serial Mode." 345 | uarm = None 346 | 347 | def __init__(self, uarm): 348 | Cmd.__init__(self) 349 | self.arm = uarm 350 | 351 | def default(self, line): 352 | if self.arm.connection_state: 353 | try: 354 | serial_id, response = self.arm.send_and_receive(line) 355 | # print(serial_id, response) 356 | print("${} {}".format(serial_id, ' '.join(response))) 357 | except TypeError as e: 358 | print("Command not correct") 359 | printf("Error: {}".format(e), DEBUG) 360 | 361 | def do_quit(self, args): 362 | return True 363 | 364 | do_EOF = do_quit 365 | 366 | 367 | def main(args): 368 | """ 369 | :: 370 | 371 | python -m pyuarm.tools.miniterm 372 | pyuarm - INFO - pyuarm version: 2.1.1 373 | pyuarm - INFO - Connecting from port - /dev/cu.usbserial-A600CRE6... 374 | pyuarm - INFO - connected... 375 | pyuarm - INFO - Firmware Version: 2.1.4 376 | Welcome to use uArm Command Line - v0.1.4 377 | Shortcut: 378 | Quit: Ctrl + D, or input: quit 379 | Clear Screen: Ctrl + L 380 | 381 | Input help for more usage 382 | >>> 383 | 384 | """ 385 | try: 386 | uarm_cmd = UArmCmd(port=args.port, debug=args.debug) 387 | uarm_cmd.cmdloop() 388 | except KeyboardInterrupt: 389 | print("KeyboardInterrupt") 390 | 391 | 392 | if __name__ == '__main__': 393 | import argparse 394 | 395 | parser = argparse.ArgumentParser() 396 | parser.add_argument("-p", "--port", help="specify port number") 397 | parser.add_argument("-d", "--debug", help="Open Debug Message") 398 | args = parser.parse_args() 399 | main(args) 400 | -------------------------------------------------------------------------------- /pyuarm/tools/scripts.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from . import miniterm, list_uarms, calibrate, firmware 4 | from ..version import __version__ 5 | 6 | 7 | def main(): 8 | parser = argparse.ArgumentParser() 9 | 10 | subparsers = parser.add_subparsers(dest='cmd') 11 | 12 | parser.add_argument('-v', '--version', action='version', version='pyuarm version: {}'.format(__version__)) 13 | pm = subparsers.add_parser("miniterm") 14 | pm.add_argument("-p", "--port", help="specify port number") 15 | pm.add_argument("-d", "--debug", help="Turn on Debug Mode", action="store_true") 16 | 17 | pc = subparsers.add_parser("calibrate") 18 | pc.add_argument("-p", "--port", help="specify port number") 19 | pc.add_argument("-d", "--debug", help="Turn on Debug Mode", action="store_true") 20 | pc.add_argument("-c", "--check", help="Check the calibrate offset values", action="store_true") 21 | 22 | pl = subparsers.add_parser("list") 23 | 24 | pf = subparsers.add_parser("firmware") 25 | pf.add_argument("-p", "--port", help="specify port number") 26 | pf.add_argument("--path", help="firmware path") 27 | pf.add_argument("--debug", help="Turn on Debug Mode", action="store_true") 28 | pf.add_argument("-d", "--download", help="download firmware online", action="store_true") 29 | 30 | args = parser.parse_args() 31 | 32 | if args.cmd: 33 | if args.cmd == 'miniterm': 34 | miniterm.main(args) 35 | elif args.cmd == 'calibrate': 36 | calibrate.main(args) 37 | elif args.cmd == 'list': 38 | list_uarms.main() 39 | elif args.cmd == 'firmware': 40 | firmware.main(args) 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /pyuarm/uarm.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import serial 3 | from . import protocol 4 | from .log import DEBUG, INFO, ERROR, printf, init_logger, set_default_logger, close_logger 5 | from . import PY3 6 | import time 7 | import threading 8 | from .tools.list_uarms import uarm_ports, get_port_property 9 | 10 | if PY3: 11 | from queue import Queue, LifoQueue, Empty 12 | else: 13 | from Queue import Queue, LifoQueue, Empty 14 | 15 | # ################################### Exception ################################ 16 | 17 | 18 | def catch_exception(func): 19 | def decorator(*args, **kwargs): 20 | try: 21 | return func(*args, **kwargs) 22 | except Exception as e: 23 | printf("{} - {} - {}".format(type(e).__name__, func.__name__, e), ERROR) 24 | return decorator 25 | 26 | 27 | class UArmConnectException(Exception): 28 | def __init__(self, errno, message=None): 29 | """ 30 | uArm Connect Exception 31 | :param errno: 0 Unable to connect uArm, 1 unknown firmware version, 2 unsupported uArm Firmware version 32 | :param message: 33 | """ 34 | if message is None: 35 | self.message = "" 36 | else: 37 | self.message = message 38 | self.errno = errno 39 | if self.errno == 0: 40 | self.error = "Unable to connect uArm" 41 | elif self.errno == 1: 42 | self.error = "Unknown Firmware Version" 43 | elif self.errno == 2: 44 | self.error = "Unsupported uArm Firmware Version" 45 | elif self.errno == 3: 46 | self.error = "No available uArm Port" 47 | elif self.errno == 4: 48 | self.error = "uArm is not connected" 49 | else: 50 | self.error = "Not Defined Error" 51 | 52 | def __str__(self): 53 | return repr(self.error + "-" + self.message) 54 | 55 | 56 | class UArm(object): 57 | def __init__(self, port_name=None, timeout=2, debug=False, logger=None): 58 | """ 59 | :param port_name: UArm Serial Port name, if no port provide, will try first port we detect 60 | :param logger: if no logger provide, will create a logger by default 61 | :param debug: if Debug is True, create a Debug Logger by default 62 | :param timeout: default timeout is 5 sec. 63 | :raise UArmConnectException 64 | 65 | | if no port provide, we will detect all connected uArm serial devices. 66 | | please reference `pyuarm.tools.list_uarms` 67 | | port is a device name: depending on operating system. 68 | | eg. `/dev/ttyUSB0` on GNU/Linux or `COM3` on Windows. 69 | | logger will display all info/debug/error/warning messages. 70 | """ 71 | self.__init_property() 72 | self.timeout = timeout 73 | if port_name is not None: 74 | self.port_name = port_name 75 | if logger is None: 76 | set_default_logger(debug) 77 | else: 78 | init_logger(logger) 79 | 80 | def __init_property(self): 81 | self.timeout = None 82 | self.port_name = None 83 | self.__data_buf = None 84 | self.__position_queue = None 85 | self.__menu_button_queue = None 86 | self.__play_button_queue = None 87 | self.__send_queue = None 88 | self.__firmware_version = None 89 | self.__hardware_version = None 90 | self.__isReady = None 91 | self.__receive_thread = None 92 | self.__send_thread = None 93 | self.serial_id = None 94 | self.msg_buff = None 95 | self.__serial = None 96 | self.__reader_thread = None 97 | self.__transport = None 98 | self.__protocol = None 99 | self.port = None 100 | self.__connect_flag = False 101 | 102 | def __init_serial_core(self): 103 | if PY3: 104 | from .threaded import UArmSerial, UArmReaderThread 105 | self.__reader_thread = UArmReaderThread(self.__serial, UArmSerial, self.__data_buf) 106 | self.__reader_thread.start() 107 | self.__reader_thread.connect() 108 | self.__transport, self.__protocol = self.__reader_thread.connect() 109 | else: 110 | self.__connect_flag = True 111 | 112 | def __close_serial_core(self): 113 | if PY3: 114 | self.__reader_thread.stop() 115 | else: 116 | self.__connect_flag = False 117 | 118 | @catch_exception 119 | def connect(self): 120 | """ 121 | This function will open the port immediately. Function will wait for the READY Message for 5 secs. 122 | | Once received READY message, will finish connection. 123 | """ 124 | if self.port_name is None: 125 | ports = uarm_ports() 126 | if len(ports) > 0: 127 | self.port_name = ports[0] 128 | else: 129 | raise UArmConnectException(3) 130 | self.__data_buf = [] 131 | self.__position_queue = LifoQueue() 132 | self.__menu_button_queue = LifoQueue() 133 | self.__play_button_queue = LifoQueue() 134 | self.__send_queue = Queue() 135 | self.__firmware_version = None 136 | self.__hardware_version = None 137 | self.__isReady = False 138 | self.port = get_port_property(self.port_name) 139 | self.__receive_thread = threading.Thread(target=self.__receive_thread_process) 140 | self.__send_thread = threading.Thread(target=self.__send_thread_process) 141 | self.__receive_thread.setDaemon(True) 142 | self.__send_thread.setDaemon(True) 143 | self.serial_id = 1 144 | self.msg_buff = {} 145 | self.__serial = serial.Serial(baudrate=115200, timeout=0.1) 146 | try: 147 | self.__serial.port = self.port.device 148 | printf("Connecting from port - {0}...".format(self.port.device)) 149 | self.__serial.open() 150 | self.__init_serial_core() 151 | self.__connect() 152 | except serial.SerialException as e: 153 | raise UArmConnectException(0, "port: {}, Error: {}".format(self.port.device, e.strerror)) 154 | 155 | def __connect(self): 156 | start_time = time.time() 157 | while time.time() - start_time < 5: 158 | if self.connection_state: 159 | break 160 | self.__receive_thread.start() 161 | self.__send_thread.start() 162 | start_time = time.time() 163 | while time.time() - start_time < self.timeout: 164 | if self.__isReady: 165 | break 166 | 167 | @property 168 | def connection_state(self): 169 | """ 170 | Return the uArm Connection status. 171 | :return: boolean 172 | """ 173 | if PY3: 174 | if self.__protocol is not None: 175 | return self.__protocol.get_connect_status() 176 | else: 177 | return False 178 | else: 179 | if self.__serial is not None: 180 | return self.__serial.is_open and self.__connect_flag 181 | else: 182 | return False 183 | 184 | @catch_exception 185 | def disconnect(self): 186 | """ 187 | Disconnect the serial connection, terminate all queue and thread 188 | """ 189 | self.__close_serial_core() 190 | self.__serial.close() 191 | printf("Disconnect from {}".format(self.port_name)) 192 | 193 | @catch_exception 194 | def close(self): 195 | """ 196 | Release all resources: 197 | | - logger 198 | | - queue 199 | | - thread 200 | """ 201 | if self.connection_state: 202 | self.disconnect() 203 | close_logger() 204 | self.__init_property() 205 | 206 | def __process_line(self, line): 207 | if line is not None: 208 | if line.startswith("$"): 209 | values = line.split(' ') 210 | msg_id = int(values[0].replace('$', '')) 211 | self.msg_buff[msg_id] = values[1:] 212 | printf("MSG Received: {}".format(line), DEBUG) 213 | elif line.startswith(protocol.READY): 214 | printf("Received MSG: {}".format(line), DEBUG) 215 | self.__isReady = True 216 | elif line.startswith(protocol.REPORT_POSITION_PREFIX): 217 | printf("POSITION REPORT: {}".format(line), DEBUG) 218 | values = line.split(' ') 219 | pos_array = [float(values[1][1:]), float(values[2][1:]), 220 | float(values[3][1:])] 221 | self.__position_queue.put(pos_array, block=False) 222 | 223 | def __receive_thread_process(self): 224 | """ 225 | This Function is for receiving thread. Under Python3.x we will use `pyserial threading`_ to manage 226 | the send/receive logic. 227 | | This thread will be finished if serial connection is end. 228 | .. _pyserial threading: http://pyserial.readthedocs.io/en/latest/pyserial_api.html#module-serial.threaded 229 | """ 230 | while self.connection_state: 231 | try: 232 | line = None 233 | if PY3: 234 | if len(self.__data_buf) > 0: 235 | line = self.__data_buf.pop().rstrip('\r\n') 236 | else: 237 | line = self.__serial.readline().rstrip('\r\n') 238 | if not line: 239 | continue 240 | self.__process_line(line) 241 | except serial.SerialException as e: 242 | printf("Receive Process Fatal - {}".format(e), ERROR) 243 | if not PY3: 244 | self.__connect_flag = False 245 | except Exception as e: 246 | printf("Receive Process {} - {}".format(type(e).__name__, e), ERROR) 247 | time.sleep(0.001) 248 | # Make Sure all queues were release 249 | self.__position_queue.join() 250 | self.__play_button_queue.join() 251 | self.__menu_button_queue.join() 252 | 253 | def __send_thread_process(self): 254 | """ 255 | This function is for sending thread function. 256 | | All functions which start with ``get_`` and with ``wait=True`` function will send out with this thread. 257 | | thread will be finished if serial connection is end. 258 | """ 259 | while self.connection_state: 260 | try: 261 | item = self.__send_queue.get() 262 | if item is None: 263 | break 264 | msg_id = item['id'] 265 | msg_content = item['msg'] 266 | msg = '#{} {}'.format(msg_id, msg_content) 267 | if PY3: 268 | self.__protocol.write_line(msg) 269 | else: 270 | self.__serial.write(msg) 271 | self.__serial.write('\n') 272 | printf("Send {}".format(msg), DEBUG) 273 | start_time = time.time() 274 | while time.time() - start_time < self.timeout: 275 | if msg_id in self.msg_buff.keys(): 276 | break 277 | self.__send_queue.task_done() 278 | except Exception as e: 279 | printf("Error: {}".format(e), ERROR) 280 | time.sleep(0.001) 281 | # Make Sure all queues were release 282 | self.__send_queue.join() 283 | 284 | def __gen_serial_id(self): 285 | """ 286 | Generate a serial id to identify the message. 287 | :return: Integer serial id 288 | """ 289 | if self.serial_id == 65535: # Maximum id 290 | self.serial_id = 1 291 | else: 292 | self.serial_id += 1 293 | return self.serial_id 294 | 295 | def send_and_receive(self, msg): 296 | """ 297 | This function will block until receive the response message. 298 | :param msg: String Serial Command 299 | :return: (Integer msg_id, String response) and None if no response 300 | """ 301 | if self.connection_state: 302 | msg_id = self.__gen_serial_id() 303 | item = {'id': msg_id, 'msg': msg} 304 | self.__send_queue.put(item) 305 | start_time = time.time() 306 | while time.time() - start_time < self.timeout: 307 | if msg_id in self.msg_buff.keys(): 308 | return msg_id, self.msg_buff[msg_id] 309 | # print("duration: {}".format(time.time() - start_time)) 310 | return None, None 311 | else: 312 | raise UArmConnectException(4) 313 | 314 | def send_msg(self, msg): 315 | """ 316 | This function will send out the message and return the serial_id immediately. 317 | :param msg: String, Serial Command 318 | :return: 319 | """ 320 | if self.connection_state: 321 | serial_id = self.__gen_serial_id() 322 | _msg = '#{} {}'.format(serial_id, msg) 323 | if PY3: 324 | self.__protocol.write_line(_msg) 325 | else: 326 | self.__serial.write(_msg) 327 | self.__serial.write('\n') 328 | printf("Send #{} {}".format(serial_id, msg), DEBUG) 329 | return serial_id 330 | else: 331 | raise UArmConnectException(4) 332 | 333 | # -------------------------------------------------------- Commands ---------------------------------------------------# 334 | 335 | def reset(self): 336 | """ 337 | Reset include below action: 338 | - Attach all servos 339 | - Move to default position (0, 150, 150) with speed 100mm/min 340 | - Turn off Pump/Gripper 341 | - Set Wrist Servo to Angle 90 342 | :return: 343 | """ 344 | self.set_servo_attach() 345 | time.sleep(0.1) 346 | self.set_position(0, 150, 150, speed=100, wait=True) 347 | self.set_pump(False) 348 | self.set_gripper(False) 349 | self.set_wrist(90) 350 | 351 | # -------------------------------------------------------- Get Commands -----------------------------------------------# 352 | @property 353 | @catch_exception 354 | def firmware_version(self): 355 | """ 356 | Get the firmware version. 357 | Protocol Cmd: ``protocol.GET_FIRMWARE_VERSION`` 358 | :return: firmware version, if failed return None 359 | """ 360 | if self.__firmware_version is not None: 361 | return self.__firmware_version 362 | else: 363 | try: 364 | cmd = protocol.GET_FIRMWARE_VERSION 365 | serial_id, response = self.send_and_receive(cmd) 366 | if response is not None: 367 | if response[0] == protocol.OK: 368 | self.__firmware_version = response[1].replace('V', '') 369 | return self.__firmware_version 370 | return None 371 | except Exception as e: 372 | printf("Error: {}".format(e), ERROR) 373 | 374 | @property 375 | @catch_exception 376 | def hardware_version(self): 377 | """ 378 | Get the Product version. 379 | Protocol Cmd: `protocol.GET_HARDWARE_VERSION`` 380 | :return: firmware version, if failed return None 381 | """ 382 | if self.__hardware_version is not None: 383 | return self.__hardware_version 384 | else: 385 | try: 386 | cmd = protocol.GET_HARDWARE_VERSION 387 | serial_id, response = self.send_and_receive(cmd) 388 | if response is not None: 389 | if response[0] == protocol.OK: 390 | self.__hardware_version = response[1].replace('V', '') 391 | return self.__hardware_version 392 | return None 393 | except Exception as e: 394 | printf("Error: {}".format(e), ERROR) 395 | 396 | @catch_exception 397 | def get_position(self): 398 | """ 399 | Get Current uArm position (x,y,z) 400 | :return: Float Array. Returns an array of the format [x, y, z] of the robots current location 401 | """ 402 | serial_id, response = self.send_and_receive(protocol.GET_COOR) 403 | if response is None: 404 | printf("No Message response {}".format(serial_id), ERROR) 405 | return None 406 | if response[0] == protocol.OK: 407 | x = float(response[1][1:]) 408 | y = float(response[2][1:]) 409 | z = float(response[3][1:]) 410 | coordinate = [x, y, z] 411 | return coordinate 412 | return None 413 | 414 | @catch_exception 415 | def get_is_moving(self): 416 | """ 417 | Get the uArm current moving status. 418 | :return: Boolean True or False 419 | """ 420 | serial_id, response = self.send_and_receive(protocol.GET_IS_MOVE) 421 | if response is None: 422 | printf("No Message response {}".format(serial_id), ERROR) 423 | return None 424 | if response[0] == protocol.OK: 425 | v = int(response[1][1:]) 426 | if v == 0: 427 | return False 428 | elif v == 1: 429 | return True 430 | 431 | @catch_exception 432 | def get_polar(self): 433 | """ 434 | get Polar coordinate 435 | :return: Float Array. Return an array of the format [rotation, stretch, height] 436 | """ 437 | serial_id, response = self.send_and_receive(protocol.GET_POLAR) 438 | if response is None: 439 | printf("No Message response {}".format(serial_id)) 440 | return 441 | if response[0] == protocol.OK: 442 | stretch = float(response[1][1:]) 443 | rotation = float(response[2][1:]) 444 | height = float(response[3][1:]) 445 | polar = [rotation, stretch, height] 446 | return polar 447 | else: 448 | return None 449 | 450 | @catch_exception 451 | def get_tip_sensor(self): 452 | """ 453 | Get Status from Tip Sensor 454 | :return: True On/ False Off 455 | """ 456 | serial_id, response = self.send_and_receive(protocol.GET_TIP_SENSOR) 457 | if response is None: 458 | printf("No Message response {}".format(serial_id)) 459 | return 460 | if response[0] == protocol.OK: 461 | if response[1] == 'V0': 462 | return True 463 | elif response[1] == 'V1': 464 | return False 465 | else: 466 | return None 467 | 468 | @catch_exception 469 | def get_servo_angle(self, servo_num=None): 470 | """ 471 | Get Servo Angle 472 | :param servo_num: if servo_num not provide, will return a array. for all servos, servo 0 473 | , servo 1, servo 2, servo 3 474 | :return: 475 | """ 476 | serial_id, response = self.send_and_receive(protocol.GET_SERVO_ANGLE) 477 | if response is None: 478 | printf("No Message response {}".format(serial_id)) 479 | return None 480 | if response[0] == protocol.OK: 481 | servo_0 = float(response[1][1:]) 482 | servo_1 = float(response[2][1:]) 483 | servo_2 = float(response[3][1:]) 484 | servo_3 = float(response[4][1:]) 485 | servo_array = [servo_0, servo_1, servo_2, servo_3] 486 | if servo_num is None: 487 | return servo_array 488 | elif servo_num == 0: 489 | return servo_0 490 | elif servo_num == 1: 491 | return servo_1 492 | elif servo_num == 2: 493 | return servo_2 494 | elif servo_num == 3: 495 | return servo_3 496 | else: 497 | return None 498 | 499 | @catch_exception 500 | def get_analog(self, pin): 501 | """ 502 | Get Analog Value from specific PIN 503 | :param pin: 504 | :return: 505 | """ 506 | try: 507 | cmd = protocol.GET_ANALOG.format(pin) 508 | serial_id, response = self.send_and_receive(cmd) 509 | if response is None: 510 | printf("No Message response {}".format(serial_id)) 511 | return 512 | if response[0] == protocol.OK: 513 | val = "".join(response[1:])[1:] 514 | return int(float(val)) 515 | else: 516 | return None 517 | except Exception as e: 518 | printf("Error {}".format(e)) 519 | return None 520 | 521 | @catch_exception 522 | def get_digital(self, pin): 523 | """ 524 | Get Digital Value from specific PIN. 525 | :param pin: 526 | :return: 527 | """ 528 | try: 529 | cmd = protocol.GET_DIGITAL.format(pin) 530 | serial_id, response = self.send_and_receive(cmd) 531 | if response is None: 532 | printf("No Message response {}".format(serial_id)) 533 | return 534 | if response[0] == protocol.OK: 535 | if response[1] == 'V0': 536 | return True 537 | elif response[1] == 'V1': 538 | return False 539 | else: 540 | return None 541 | except Exception as e: 542 | printf("Error {}".format(e)) 543 | return None 544 | 545 | @catch_exception 546 | def get_rom_data(self, address, data_type=protocol.EEPROM_DATA_TYPE_BYTE): 547 | """ 548 | Get DATA From EEPROM 549 | :param address: 0 - 2048 550 | :param data_type: EEPROM_DATA_TYPE_FLOAT, EEPROM_DATA_TYPE_INTEGER, EEPROM_DATA_TYPE_BYTE 551 | :return: 552 | """ 553 | try: 554 | cmd = protocol.GET_EEPROM.format(address, data_type) 555 | serial_id, response = self.send_and_receive(cmd) 556 | if response is None: 557 | printf("No Message response {}".format(serial_id)) 558 | return 559 | if response[0] == protocol.OK: 560 | if data_type == protocol.EEPROM_DATA_TYPE_FLOAT: 561 | return float(response[1][1:]) 562 | elif data_type == protocol.EEPROM_DATA_TYPE_INTEGER: 563 | return int(response[1][1:]) 564 | elif data_type == protocol.EEPROM_DATA_TYPE_BYTE: 565 | return int(response[1][1:]) 566 | except Exception as e: 567 | printf("Error {}".format(e)) 568 | return None 569 | 570 | # -------------------------------------------------------- Set Commands -----------------------------------------------# 571 | 572 | @catch_exception 573 | def set_position(self, x=None, y=None, z=None, speed=300, relative=False, wait=False): 574 | """ 575 | Move uArm to the position (x,y,z) unit is mm, speed unit is mm/sec 576 | :param x: 577 | :param y: 578 | :param z: 579 | :param speed: 580 | :param relative 581 | :param wait: if True, will block the thread, until get response or timeout 582 | :return: 583 | """ 584 | if relative: 585 | if x is None: 586 | x = 0.0 587 | if y is None: 588 | y = 0.0 589 | if z is None: 590 | z = 0.0 591 | x = str(round(x, 2)) 592 | y = str(round(y, 2)) 593 | z = str(round(z, 2)) 594 | s = str(round(speed, 2)) 595 | command = protocol.SET_POSITION_RELATIVE.format(x, y, z, s) 596 | else: 597 | if x is None or y is None or z is None: 598 | raise Exception('x, y, z can not be None in absolute mode') 599 | x = str(round(x, 2)) 600 | y = str(round(y, 2)) 601 | z = str(round(z, 2)) 602 | s = str(round(speed, 2)) 603 | command = protocol.SET_POSITION.format(x, y, z, s) 604 | if wait: 605 | serial_id, response = self.send_and_receive(command) 606 | while self.get_is_moving(): 607 | time.sleep(0.05) 608 | if response is not None: 609 | if response[0] == protocol.OK: 610 | return True 611 | else: 612 | return False 613 | else: 614 | self.send_msg(command) 615 | 616 | @catch_exception 617 | def set_pump(self, on, wait=False): 618 | """ 619 | Control uArm Pump On or OFF 620 | :param on: True On, False OFF 621 | :param wait: if True, will block the thread, until get response or timeout 622 | :return: succeed True or Failed False 623 | """ 624 | command = protocol.SET_PUMP.format(1 if on else 0) 625 | if wait: 626 | serial_id, response = self.send_and_receive(command) 627 | if response is None: 628 | printf("No Message response {}".format(serial_id)) 629 | return None 630 | if response[0] == protocol.OK: 631 | return True 632 | else: 633 | return False 634 | else: 635 | self.send_msg(command) 636 | 637 | @catch_exception 638 | def set_gripper(self, catch, wait=False): 639 | """ 640 | Turn On/Off Gripper 641 | :param catch: True On/ False Off 642 | :param wait: if True, will block the thread, until get response or timeout 643 | :return: 644 | """ 645 | command = protocol.SET_GRIPPER.format(1 if catch else 0) 646 | if wait: 647 | serial_id, response = self.send_and_receive(command) 648 | if response is None: 649 | printf("No Message response {}".format(serial_id)) 650 | return None 651 | if response[0] == protocol.OK: 652 | return True 653 | else: 654 | return False 655 | else: 656 | self.send_msg(command) 657 | 658 | @catch_exception 659 | def set_wrist(self, angle, wait=False): 660 | """ 661 | Set uArm Hand Wrist Angle. Include servo offset. 662 | :param angle: 663 | :param wait: if True, will block the thread, until get response or timeout 664 | :return: 665 | """ 666 | return self.set_servo_angle(protocol.SERVO_HAND, angle, wait=wait) 667 | 668 | @catch_exception 669 | def set_servo_angle(self, servo_number, angle, wait=False): 670 | """ 671 | Set uArm Servo Angle, 0 - 180 degrees, this Function will include the manual servo offset. 672 | :param servo_number: lease reference protocol.py SERVO_BOTTOM, SERVO_LEFT, SERVO_RIGHT, SERVO_HAND 673 | :param angle: 0 - 180 degrees 674 | :param wait: if True, will block the thread, until get response or timeout 675 | :return: succeed True or Failed False 676 | """ 677 | command = protocol.SET_SERVO_ANGLE.format(str(servo_number), str(angle)) 678 | if wait: 679 | serial_id, response = self.send_and_receive(command) 680 | if response is None: 681 | printf("No Message response {}".format(serial_id)) 682 | return None 683 | if response[0] == protocol.OK: 684 | return True 685 | else: 686 | return False 687 | else: 688 | self.send_msg(command) 689 | 690 | @catch_exception 691 | def set_buzzer(self, frequency, duration, wait=False): 692 | """ 693 | Turn on the uArm Buzzer 694 | :param frequency: The frequency, in Hz 695 | :param duration: The duration of the buzz, in seconds 696 | :param wait: if True, will block the thread, until get response or timeout 697 | :return: 698 | """ 699 | command = protocol.SET_BUZZER.format(frequency, duration) 700 | if wait: 701 | serial_id, response = self.send_and_receive(command) 702 | if response is None: 703 | printf("No Message response {}".format(serial_id)) 704 | return None 705 | if response[0] == protocol.OK: 706 | return True 707 | else: 708 | return False 709 | else: 710 | self.send_msg(command) 711 | 712 | @catch_exception 713 | def set_servo_attach(self, servo_number=None, move=True, wait=False): 714 | """ 715 | Set Servo status attach, Servo Attach will lock the servo, You can't move uArm with your hands. 716 | :param servo_number: If None, will attach all servos, please reference protocol.py SERVO_BOTTOM, SERVO_LEFT, 717 | SERVO_RIGHT, SERVO_HAND 718 | :param move: if True, will move to current position immediately 719 | :param wait: if True, will block the thread, until get response or timeout 720 | :return: succeed True or Failed False 721 | """ 722 | if servo_number is not None: 723 | if move: 724 | pos = self.get_position() 725 | self.set_position(pos[0], pos[1], pos[2], speed=100) 726 | command = protocol.ATTACH_SERVO.format(servo_number) 727 | if wait: 728 | serial_id, response = self.send_and_receive(command) 729 | if response is None: 730 | printf("No Message response {}".format(serial_id)) 731 | return None 732 | if response[0].startswith(protocol.OK): 733 | return True 734 | else: 735 | return False 736 | else: 737 | self.send_msg(command) 738 | else: 739 | if move: 740 | pos = self.get_position() 741 | self.set_position(pos[0], pos[1], pos[2], speed=0) 742 | if wait: 743 | if self.set_servo_attach(servo_number=0, move=False, wait=True) \ 744 | and self.set_servo_attach(servo_number=1, move=False, wait=True) \ 745 | and self.set_servo_attach(servo_number=2, move=False, wait=True) \ 746 | and self.set_servo_attach(servo_number=3, move=False, wait=True): 747 | return True 748 | else: 749 | return False 750 | else: 751 | self.set_servo_attach(servo_number=0, move=False) 752 | self.set_servo_attach(servo_number=1, move=False) 753 | self.set_servo_attach(servo_number=2, move=False) 754 | self.set_servo_attach(servo_number=3, move=False) 755 | 756 | @catch_exception 757 | def set_servo_detach(self, servo_number=None, wait=False): 758 | """ 759 | Set Servo status detach, Servo Detach will unlock the servo, You can move uArm with your hands. 760 | But move function won't be effect until you attach. 761 | :param servo_number: If None, will detach all servos, please reference protocol.py SERVO_BOTTOM, SERVO_LEFT, SERVO_RIGHT, SERVO_HAND 762 | :param wait: if True, will block the thread, until get response or timeout 763 | :return: succeed True or Failed False 764 | """ 765 | if servo_number is not None: 766 | command = protocol.DETACH_SERVO.format(servo_number) 767 | if wait: 768 | serial_id, response = self.send_and_receive(command) 769 | if response is None: 770 | printf("No Message response {}".format(serial_id)) 771 | return None 772 | if response[0].startswith(protocol.OK): 773 | return True 774 | else: 775 | return False 776 | else: 777 | self.send_msg(command) 778 | else: 779 | if wait: 780 | if self.set_servo_detach(servo_number=0, wait=True) \ 781 | and self.set_servo_detach(servo_number=1, wait=True) \ 782 | and self.set_servo_detach(servo_number=2, wait=True) \ 783 | and self.set_servo_detach(servo_number=3, wait=True): 784 | return True 785 | else: 786 | return False 787 | else: 788 | self.set_servo_detach(servo_number=0) 789 | self.set_servo_detach(servo_number=1) 790 | self.set_servo_detach(servo_number=2) 791 | self.set_servo_detach(servo_number=3) 792 | 793 | @catch_exception 794 | def set_polar_coordinate(self, rotation, stretch, height, speed=100, wait=False): 795 | """ 796 | Polar Coordinate, rotation, stretch, height. 797 | :param rotation: 798 | :param stretch: 799 | :param height: 800 | :param speed: 801 | :param wait: if True, will block the thread, until get response or timeout 802 | :return: 803 | """ 804 | rotation = str(round(rotation, 2)) 805 | stretch = str(round(stretch, 2)) 806 | height = str(round(height, 2)) 807 | speed = str(round(speed, 2)) 808 | command = protocol.SET_POLAR.format(stretch, rotation, height, speed) 809 | if wait: 810 | self.send_msg(command) 811 | while self.get_is_moving(): 812 | time.sleep(0.05) 813 | # if wait: 814 | # serial_id, response = self.send_and_receive(command) 815 | # if response is None: 816 | # printf("No Message response {}".format(serial_id)) 817 | # return None 818 | # if response[0].startswith(protocol.OK): 819 | # return True 820 | # else: 821 | # return False 822 | else: 823 | self.send_msg(command) 824 | 825 | # ---------------------------------------------------- Report Commands -----------------------------------------------# 826 | 827 | @catch_exception 828 | def set_report_position(self, interval, wait=False): 829 | """ 830 | Report Current Position in (interval) seconds. 831 | :param interval: Seconds if 0 disable report 832 | :param wait: if True, will block the thread, until get response or timeout 833 | :return 834 | """ 835 | interval = str(round(interval, 2)) 836 | command = protocol.SET_REPORT_POSITION.format(interval) 837 | if wait: 838 | serial_id, response = self.send_and_receive(command) 839 | if response is None: 840 | printf("No Message response {}".format(serial_id)) 841 | return None 842 | if response[0] == protocol.OK: 843 | return True 844 | else: 845 | return False 846 | else: 847 | self.send_msg(command) 848 | 849 | @catch_exception 850 | def close_report_position(self, wait=False): 851 | """ 852 | Stop Reporting the position 853 | :return: 854 | """ 855 | self.set_report_position(0, wait=wait) 856 | 857 | @catch_exception 858 | def get_report_position(self): 859 | """ 860 | If call `set_report_position`, uArm will report current position during the interval. 861 | Store the position in LIFO queue. 862 | :return: position array [x,y,z,r] 863 | """ 864 | item = self.__position_queue.get(self.timeout) 865 | self.__position_queue.task_done() 866 | return item 867 | 868 | def __del__(self): 869 | self.close() 870 | 871 | 872 | if __name__ == '__main__': 873 | uarm = UArm() 874 | uarm.connect() 875 | printf(uarm.firmware_version()) 876 | uarm.set_position(10, 150, 250, speed=100) 877 | uarm.set_position(10, 100, 250, speed=100) 878 | uarm.set_position(10, 200, 250, speed=100) 879 | uarm.set_position(10, 150, 200, speed=100) 880 | uarm.set_position(10, 150, 150, speed=100) 881 | uarm.set_position(10, 150, 100, speed=100) 882 | uarm.set_position(0, 150, 150, speed=100, wait=True) 883 | uarm.set_position(0, 150, 50, speed=100, wait=True) 884 | uarm.set_pump(True) 885 | uarm.set_position(0, 100, 0, speed=100, relative=True, wait=True) 886 | uarm.set_position(0, 0, 100, speed=100, relative=True, wait=True) 887 | uarm.set_buzzer(1000, 0.1) 888 | uarm.set_position(0, -100, 0, speed=100, relative=True, wait=True) 889 | uarm.set_position(0, 0, -100, speed=100, relative=True, wait=True) 890 | uarm.set_pump(False) 891 | uarm.set_position(-100, 0, 0, speed=100, relative=True, wait=True) 892 | uarm.set_position(100, 0, 0, speed=100, relative=True, wait=True) 893 | # uarm.set_polar_coordinate(133,26) 894 | # threading.Thread(target=lambda :uarm.set_servo_attach()).start() 895 | # threading.Thread(target=lambda :uarm.set_servo_attach()).start() 896 | uarm.set_position(0, 0, 100, relative=True, speed=100, wait=True) 897 | uarm.set_position(0, 100, 0, relative=True, speed=100, wait=True) 898 | uarm.set_position(0, 0, -100, relative=True, speed=100, wait=True) 899 | -------------------------------------------------------------------------------- /pyuarm/util.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from __future__ import print_function 3 | import math 4 | from .tools.list_uarms import uarm_ports 5 | from .log import printf, close_logger, ERROR 6 | from .uarm import UArm 7 | 8 | # Get UArm Instance 9 | 10 | 11 | def get_uarm(debug=False,logger=None): 12 | """ 13 | Get First uArm Port instance 14 | It will return the first uArm Port detected by **pyuarm.tools.list_uarms**, 15 | If no available uArm ports, will print *There is no uArm port available* and return None 16 | .. raw:python 17 | >>> import pyuarm 18 | >>> uarm = pyuarm.get_uarm() 19 | There is no uArm port available 20 | :returns: uArm() Instance 21 | 22 | """ 23 | ports = uarm_ports() 24 | if len(ports) > 0: 25 | return UArm(port_name=ports[0], logger=logger, debug=debug) 26 | else: 27 | printf("There is no uArm port available", ERROR) 28 | close_logger() 29 | return None 30 | 31 | # ################################### Other ################################ 32 | 33 | 34 | def progressbar(title, cur, total): 35 | percent = '{:.2%}'.format(cur / total) 36 | print(title + "[%-50s] %s" % ( 37 | '=' * int(math.floor(cur * 50 / total)), 38 | percent), end='\r') 39 | -------------------------------------------------------------------------------- /pyuarm/version.py: -------------------------------------------------------------------------------- 1 | import re 2 | from pkg_resources import parse_version 3 | __version__ = '2.4.0.12' 4 | support_versions = ['2.2'] 5 | 6 | 7 | def is_a_version(version): 8 | version_pattern = re.compile(r'\d+\.\d+\.\d+\w*') 9 | if version_pattern.match(version): 10 | return True 11 | else: 12 | return False 13 | 14 | 15 | def is_supported_version(version): 16 | pattern = re.compile(r'\d+\.\d+') 17 | major_version = pattern.match(version).group() 18 | for v in support_versions: 19 | if major_version == v: 20 | return True 21 | return False 22 | 23 | 24 | def check_version_update(version1, version2): 25 | if parse_version(version1) > parse_version(version2): 26 | return True 27 | else: 28 | return False 29 | 30 | 31 | if __name__ == '__main__': 32 | print(__version__) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyserial>=3.0 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.util import convert_path 2 | from setuptools import setup, find_packages 3 | # import platform 4 | 5 | # Get Version from version.py file 6 | main_ns = {} 7 | ver_path = convert_path('pyuarm/version.py') 8 | with open(ver_path) as ver_file: 9 | exec(ver_file.read(), main_ns) 10 | version = main_ns['__version__'] 11 | 12 | # Get README.rst for description 13 | long_description = open('README.rst').read() 14 | 15 | # Read requirements.txt as install_requires 16 | with open('requirements.txt') as f: 17 | requirements = f.read().splitlines() 18 | 19 | setup(name='pyuarm', 20 | version=version, 21 | author='Alex Tan', 22 | packages=find_packages(), 23 | entry_points={ 24 | 'scripts': [ 25 | 'uarmcli = pyuarm.tools.scripts:main', 26 | ] 27 | }, 28 | author_email='developer@ufactory.cc', 29 | url="https://github.com/uarm-developer/pyuarm", 30 | keywords="pyuarm uarm4py uarmForPython uarm ufactory", 31 | install_requires=requirements, 32 | long_description=long_description, 33 | description='A python library for uArm', 34 | license='MIT' 35 | ) 36 | --------------------------------------------------------------------------------