├── .gitattributes ├── .gitignore ├── Arduino ├── __init__.py └── arduino.py ├── MIT license.txt ├── README.md ├── examples.py ├── pypi_commands.txt ├── setup.py ├── sketches └── prototype │ └── prototype.ino └── tests ├── test_arduino.py └── test_main.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Directories # 3 | ############### 4 | build/ 5 | dist/ 6 | *.egg-info 7 | 8 | # Compiled source # 9 | ################### 10 | *.com 11 | *.class 12 | *.dll 13 | *.exe 14 | *.o 15 | *.so 16 | *.pyc 17 | 18 | # Packages # 19 | ############ 20 | # it's better to unpack these files and commit the raw source 21 | # git has its own built in compression methods 22 | *.7z 23 | *.dmg 24 | *.gz 25 | *.iso 26 | *.jar 27 | *.rar 28 | *.tar 29 | *.zip 30 | *.egg 31 | 32 | # Logs and databases # 33 | ###################### 34 | *.log 35 | *.sql 36 | *.sqlite 37 | 38 | # OS generated files # 39 | ###################### 40 | .DS_Store 41 | .DS_Store? 42 | ._* 43 | .Spotlight-V100 44 | .Trashes 45 | Icon? 46 | ehthumbs.db 47 | Thumbs.db 48 | -------------------------------------------------------------------------------- /Arduino/__init__.py: -------------------------------------------------------------------------------- 1 | name="arduino-python3" 2 | from .arduino import Arduino, Shrimp 3 | -------------------------------------------------------------------------------- /Arduino/arduino.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import logging 3 | import itertools 4 | import platform 5 | import serial 6 | import time 7 | from serial.tools import list_ports 8 | 9 | import sys 10 | if sys.platform.startswith('win'): 11 | import winreg 12 | else: 13 | import glob 14 | 15 | libraryVersion = 'V0.6' 16 | 17 | log = logging.getLogger(__name__) 18 | 19 | 20 | def enumerate_serial_ports(): 21 | """ 22 | Uses the Win32 registry to return a iterator of serial 23 | (COM) ports existing on this computer. 24 | """ 25 | path = 'HARDWARE\\DEVICEMAP\\SERIALCOMM' 26 | try: 27 | key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path) 28 | except OSError: 29 | raise Exception 30 | 31 | for i in itertools.count(): 32 | try: 33 | val = winreg.EnumValue(key, i) 34 | yield (str(val[1])) # , str(val[0])) 35 | except EnvironmentError: 36 | break 37 | 38 | 39 | def build_cmd_str(cmd, args=None): 40 | """ 41 | Build a command string that can be sent to the arduino. 42 | 43 | Input: 44 | cmd (str): the command to send to the arduino, must not 45 | contain a % character 46 | args (iterable): the arguments to send to the command 47 | 48 | @TODO: a strategy is needed to escape % characters in the args 49 | """ 50 | if args: 51 | args = '%'.join(map(str, args)) 52 | else: 53 | args = '' 54 | return "@{cmd}%{args}$!".format(cmd=cmd, args=args) 55 | 56 | 57 | def find_port(baud, timeout): 58 | """ 59 | Find the first port that is connected to an arduino with a compatible 60 | sketch installed. 61 | """ 62 | if platform.system() == 'Windows': 63 | ports = enumerate_serial_ports() 64 | elif platform.system() == 'Darwin': 65 | ports = [i[0] for i in list_ports.comports()] 66 | ports = ports[::-1] 67 | else: 68 | ports = glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") 69 | for p in ports: 70 | log.debug('Found {0}, testing...'.format(p)) 71 | try: 72 | sr = serial.Serial(p, baud, timeout=timeout) 73 | except (serial.serialutil.SerialException, OSError) as e: 74 | log.debug(str(e)) 75 | continue 76 | 77 | sr.readline() # wait for board to start up again 78 | 79 | version = get_version(sr) 80 | 81 | if version != libraryVersion: 82 | try: 83 | ver = version[0] 84 | except Exception: 85 | ver = '' 86 | 87 | if ver == 'V' or version == "version": 88 | print("You need to update the version of the Arduino-Python3", 89 | "library running on your Arduino.") 90 | print("The Arduino sketch is", version) 91 | print("The Python installation is", libraryVersion) 92 | print("Flash the prototype sketch again.") 93 | return sr 94 | 95 | # established to be the wrong board 96 | log.debug('Bad version {0}. This is not a Shrimp/Arduino!'.format( 97 | version)) 98 | sr.close() 99 | continue 100 | 101 | log.info('Using port {0}.'.format(p)) 102 | if sr: 103 | return sr 104 | return None 105 | 106 | def get_version(sr): 107 | cmd_str = build_cmd_str("version") 108 | try: 109 | sr.write(str.encode(cmd_str)) 110 | sr.flush() 111 | except Exception: 112 | return None 113 | return sr.readline().decode("utf-8").replace("\r\n", "") 114 | 115 | 116 | class Arduino(object): 117 | 118 | 119 | def __init__(self, baud=115200, port=None, timeout=2, sr=None): 120 | """ 121 | Initializes serial communication with Arduino if no connection is 122 | given. Attempts to self-select COM port, if not specified. 123 | """ 124 | if not sr: 125 | if not port: 126 | sr = find_port(baud, timeout) 127 | if not sr: 128 | raise ValueError("Could not find port.") 129 | else: 130 | sr = serial.Serial(port, baud, timeout=timeout) 131 | sr.readline() # wait til board has rebooted and is connected 132 | 133 | version = get_version(sr) 134 | 135 | if version != libraryVersion: 136 | # check version 137 | try: 138 | ver = version[0] 139 | except Exception: 140 | ver = '' 141 | 142 | if ver == 'V' or version == "version": 143 | print("You need to update the version of the Arduino-Python3", 144 | "library running on your Arduino.") 145 | print("The Arduino sketch is", version) 146 | print("The Python installation is", libraryVersion) 147 | print("Flash the prototype sketch again.") 148 | 149 | sr.flush() 150 | self.sr = sr 151 | self.SoftwareSerial = SoftwareSerial(self) 152 | self.Servos = Servos(self) 153 | self.EEPROM = EEPROM(self) 154 | 155 | def version(self): 156 | return get_version(self.sr) 157 | 158 | def digitalWrite(self, pin, val): 159 | """ 160 | Sends digitalWrite command 161 | to digital pin on Arduino 162 | ------------- 163 | inputs: 164 | pin : digital pin number 165 | val : either "HIGH" or "LOW" 166 | """ 167 | if val.upper() == "LOW": 168 | pin_ = -pin 169 | else: 170 | pin_ = pin 171 | cmd_str = build_cmd_str("dw", (pin_,)) 172 | try: 173 | self.sr.write(str.encode(cmd_str)) 174 | self.sr.flush() 175 | except: 176 | pass 177 | 178 | def analogWrite(self, pin, val): 179 | """ 180 | Sends analogWrite pwm command 181 | to pin on Arduino 182 | ------------- 183 | inputs: 184 | pin : pin number 185 | val : integer 0 (off) to 255 (always on) 186 | """ 187 | if val > 255: 188 | val = 255 189 | elif val < 0: 190 | val = 0 191 | cmd_str = build_cmd_str("aw", (pin, val)) 192 | try: 193 | self.sr.write(str.encode(cmd_str)) 194 | self.sr.flush() 195 | except: 196 | pass 197 | 198 | def analogRead(self, pin): 199 | """ 200 | Returns the value of a specified 201 | analog pin. 202 | inputs: 203 | pin : analog pin number for measurement 204 | returns: 205 | value: integer from 1 to 1023 206 | """ 207 | cmd_str = build_cmd_str("ar", (pin,)) 208 | try: 209 | self.sr.write(str.encode(cmd_str)) 210 | self.sr.flush() 211 | except: 212 | pass 213 | rd = self.sr.readline().decode("utf-8").replace("\r\n", "") 214 | try: 215 | return int(rd) 216 | except: 217 | return 0 218 | 219 | def pinMode(self, pin, val): 220 | """ 221 | Sets I/O mode of pin 222 | inputs: 223 | pin: pin number to toggle 224 | val: "INPUT" or "OUTPUT" 225 | """ 226 | if val == "INPUT": 227 | pin_ = -pin 228 | else: 229 | pin_ = pin 230 | cmd_str = build_cmd_str("pm", (pin_,)) 231 | try: 232 | self.sr.write(str.encode(cmd_str)) 233 | self.sr.flush() 234 | except: 235 | pass 236 | 237 | def pulseIn(self, pin, val): 238 | """ 239 | Reads a pulse from a pin 240 | 241 | inputs: 242 | pin: pin number for pulse measurement 243 | returns: 244 | duration : pulse length measurement 245 | """ 246 | if val.upper() == "LOW": 247 | pin_ = -pin 248 | else: 249 | pin_ = pin 250 | cmd_str = build_cmd_str("pi", (pin_,)) 251 | try: 252 | self.sr.write(str.encode(cmd_str)) 253 | self.sr.flush() 254 | except: 255 | pass 256 | rd = self.sr.readline().decode("utf-8").replace("\r\n", "") 257 | try: 258 | return float(rd) 259 | except: 260 | return -1 261 | 262 | def pulseIn_set(self, pin, val, numTrials=5): 263 | """ 264 | Sets a digital pin value, then reads the response 265 | as a pulse width. 266 | Useful for some ultrasonic rangefinders, etc. 267 | 268 | inputs: 269 | pin: pin number for pulse measurement 270 | val: "HIGH" or "LOW". Pulse is measured 271 | when this state is detected 272 | numTrials: number of trials (for an average) 273 | returns: 274 | duration : an average of pulse length measurements 275 | 276 | This method will automatically toggle 277 | I/O modes on the pin and precondition the 278 | measurment with a clean LOW/HIGH pulse. 279 | Arduino.pulseIn_set(pin,"HIGH") is 280 | equivalent to the Arduino sketch code: 281 | 282 | pinMode(pin, OUTPUT); 283 | digitalWrite(pin, LOW); 284 | delayMicroseconds(2); 285 | digitalWrite(pin, HIGH); 286 | delayMicroseconds(5); 287 | digitalWrite(pin, LOW); 288 | pinMode(pin, INPUT); 289 | long duration = pulseIn(pin, HIGH); 290 | """ 291 | if val.upper() == "LOW": 292 | pin_ = -pin 293 | else: 294 | pin_ = pin 295 | cmd_str = build_cmd_str("ps", (pin_,)) 296 | durations = [] 297 | for s in range(numTrials): 298 | try: 299 | self.sr.write(str.encode(cmd_str)) 300 | self.sr.flush() 301 | except: 302 | pass 303 | rd = self.sr.readline().decode("utf-8").replace("\r\n", "") 304 | if rd.isdigit(): 305 | if (int(rd) > 1): 306 | durations.append(int(rd)) 307 | if len(durations) > 0: 308 | duration = int(sum(durations)) / int(len(durations)) 309 | else: 310 | duration = None 311 | 312 | try: 313 | return float(duration) 314 | except: 315 | return -1 316 | 317 | def close(self): 318 | if self.sr.isOpen(): 319 | self.sr.flush() 320 | self.sr.close() 321 | 322 | def digitalRead(self, pin): 323 | """ 324 | Returns the value of a specified 325 | digital pin. 326 | inputs: 327 | pin : digital pin number for measurement 328 | returns: 329 | value: 0 for "LOW", 1 for "HIGH" 330 | """ 331 | cmd_str = build_cmd_str("dr", (pin,)) 332 | try: 333 | self.sr.write(str.encode(cmd_str)) 334 | self.sr.flush() 335 | except: 336 | pass 337 | rd = self.sr.readline().decode("utf-8").replace("\r\n", "") 338 | try: 339 | return int(rd) 340 | except: 341 | return 0 342 | 343 | def Melody(self, pin, melody, durations): 344 | """ 345 | Plays a melody. 346 | inputs: 347 | pin: digital pin number for playback 348 | melody: list of tones 349 | durations: list of duration (4=quarter note, 8=eighth note, etc.) 350 | length of melody should be of same 351 | length as length of duration 352 | 353 | Melodies of the following length, can cause trouble 354 | when playing it multiple times. 355 | board.Melody(9,["C4","G3","G3","A3","G3",0,"B3","C4"], 356 | [4,8,8,4,4,4,4,4]) 357 | Playing short melodies (1 or 2 tones) didn't cause 358 | trouble during testing 359 | """ 360 | NOTES = dict( 361 | B0=31, C1=33, CS1=35, D1=37, DS1=39, E1=41, F1=44, FS1=46, G1=49, 362 | GS1=52, A1=55, AS1=58, B1=62, C2=65, CS2=69, D2=73, DS2=78, E2=82, 363 | F2=87, FS2=93, G2=98, GS2=104, A2=110, AS2=117, B2=123, C3=131, 364 | CS3=139, D3=147, DS3=156, E3=165, F3=175, FS3=185, G3=196, GS3=208, 365 | A3=220, AS3=233, B3=247, C4=262, CS4=277, D4=294, DS4=311, E4=330, 366 | F4=349, FS4=370, G4=392, GS4=415, A4=440, 367 | AS4=466, B4=494, C5=523, CS5=554, D5=587, DS5=622, E5=659, F5=698, 368 | FS5=740, G5=784, GS5=831, A5=880, AS5=932, B5=988, C6=1047, 369 | CS6=1109, D6=1175, DS6=1245, E6=1319, F6=1397, FS6=1480, G6=1568, 370 | GS6=1661, A6=1760, AS6=1865, B6=1976, C7=2093, CS7=2217, D7=2349, 371 | DS7=2489, E7=2637, F7=2794, FS7=2960, G7=3136, GS7=3322, A7=3520, 372 | AS7=3729, B7=3951, C8=4186, CS8=4435, D8=4699, DS8=4978) 373 | if (isinstance(melody, list)) and (isinstance(durations, list)): 374 | length = len(melody) 375 | cmd_args = [length, pin] 376 | if length == len(durations): 377 | cmd_args.extend([NOTES.get(melody[note]) 378 | for note in range(length)]) 379 | cmd_args.extend([durations[duration] 380 | for duration in range(len(durations))]) 381 | cmd_str = build_cmd_str("to", cmd_args) 382 | try: 383 | self.sr.write(str.encode(cmd_str)) 384 | self.sr.flush() 385 | except: 386 | pass 387 | cmd_str = build_cmd_str("nto", [pin]) 388 | try: 389 | self.sr.write(str.encode(cmd_str)) 390 | self.sr.flush() 391 | except: 392 | pass 393 | else: 394 | return -1 395 | else: 396 | return -1 397 | 398 | def capacitivePin(self, pin): 399 | ''' 400 | Input: 401 | pin (int): pin to use as capacitive sensor 402 | 403 | Use it in a loop! 404 | DO NOT CONNECT ANY ACTIVE DRIVER TO THE USED PIN ! 405 | 406 | the pin is toggled to output mode to discharge the port, 407 | and if connected to a voltage source, 408 | will short circuit the pin, potentially damaging 409 | the Arduino/Shrimp and any hardware attached to the pin. 410 | ''' 411 | cmd_str = build_cmd_str("cap", (pin,)) 412 | self.sr.write(str.encode(cmd_str)) 413 | rd = self.sr.readline().decode("utf-8").replace("\r\n", "") 414 | if rd.isdigit(): 415 | return int(rd) 416 | 417 | def shiftOut(self, dataPin, clockPin, pinOrder, value): 418 | """ 419 | Shift a byte out on the datapin using Arduino's shiftOut() 420 | 421 | Input: 422 | dataPin (int): pin for data 423 | clockPin (int): pin for clock 424 | pinOrder (String): either 'MSBFIRST' or 'LSBFIRST' 425 | value (int): an integer from 0 and 255 426 | """ 427 | cmd_str = build_cmd_str("so", 428 | (dataPin, clockPin, pinOrder, value)) 429 | self.sr.write(str.encode(cmd_str)) 430 | self.sr.flush() 431 | 432 | def shiftIn(self, dataPin, clockPin, pinOrder): 433 | """ 434 | Shift a byte in from the datapin using Arduino's shiftIn(). 435 | 436 | Input: 437 | dataPin (int): pin for data 438 | clockPin (int): pin for clock 439 | pinOrder (String): either 'MSBFIRST' or 'LSBFIRST' 440 | Output: 441 | (int) an integer from 0 to 255 442 | """ 443 | cmd_str = build_cmd_str("si", (dataPin, clockPin, pinOrder)) 444 | self.sr.write(str.encode(cmd_str)) 445 | self.sr.flush() 446 | rd = self.sr.readline().decode("utf-8").replace("\r\n", "") 447 | if rd.isdigit(): 448 | return int(rd) 449 | 450 | 451 | class Shrimp(Arduino): 452 | 453 | def __init__(self): 454 | Arduino.__init__(self) 455 | 456 | 457 | class Wires(object): 458 | 459 | """ 460 | Class for Arduino wire (i2c) support 461 | """ 462 | 463 | def __init__(self, board): 464 | self.board = board 465 | self.sr = board.sr 466 | 467 | 468 | class Servos(object): 469 | 470 | """ 471 | Class for Arduino servo support 472 | 0.03 second delay noted 473 | """ 474 | 475 | def __init__(self, board): 476 | self.board = board 477 | self.sr = board.sr 478 | self.servo_pos = {} 479 | 480 | def attach(self, pin, min=544, max=2400): 481 | cmd_str = build_cmd_str("sva", (pin, min, max)) 482 | 483 | while True: 484 | self.sr.write(str.encode(cmd_str)) 485 | self.sr.flush() 486 | 487 | rd = self.sr.readline().decode("utf-8").replace("\r\n", "") 488 | if rd: 489 | break 490 | else: 491 | log.debug("trying to attach servo to pin {0}".format(pin)) 492 | position = int(rd) 493 | self.servo_pos[pin] = position 494 | return 1 495 | 496 | def detach(self, pin): 497 | position = self.servo_pos[pin] 498 | cmd_str = build_cmd_str("svd", (position,)) 499 | try: 500 | self.sr.write(str.encode(cmd_str)) 501 | self.sr.flush() 502 | except: 503 | pass 504 | del self.servo_pos[pin] 505 | 506 | def write(self, pin, angle): 507 | position = self.servo_pos[pin] 508 | cmd_str = build_cmd_str("svw", (position, angle)) 509 | 510 | self.sr.write(str.encode(cmd_str)) 511 | self.sr.flush() 512 | 513 | def writeMicroseconds(self, pin, uS): 514 | position = self.servo_pos[pin] 515 | cmd_str = build_cmd_str("svwm", (position, uS)) 516 | 517 | self.sr.write(str.encode(cmd_str)) 518 | self.sr.flush() 519 | 520 | def read(self, pin): 521 | if pin not in self.servo_pos.keys(): 522 | self.attach(pin) 523 | position = self.servo_pos[pin] 524 | cmd_str = build_cmd_str("svr", (position,)) 525 | try: 526 | self.sr.write(str.encode(cmd_str)) 527 | self.sr.flush() 528 | except: 529 | pass 530 | rd = self.sr.readline().decode("utf-8").replace("\r\n", "") 531 | try: 532 | angle = int(rd) 533 | return angle 534 | except: 535 | return None 536 | 537 | 538 | 539 | class SoftwareSerial(object): 540 | 541 | """ 542 | Class for Arduino software serial functionality 543 | """ 544 | 545 | def __init__(self, board): 546 | self.board = board 547 | self.sr = board.sr 548 | self.connected = False 549 | 550 | def begin(self, p1, p2, baud): 551 | """ 552 | Create software serial instance on 553 | specified tx,rx pins, at specified baud 554 | """ 555 | cmd_str = build_cmd_str("ss", (p1, p2, baud)) 556 | try: 557 | self.sr.write(str.encode(cmd_str)) 558 | self.sr.flush() 559 | except: 560 | pass 561 | response = self.sr.readline().decode("utf-8").replace("\r\n", "") 562 | if response == "ss OK": 563 | self.connected = True 564 | return True 565 | else: 566 | self.connected = False 567 | return False 568 | 569 | def write(self, data): 570 | """ 571 | sends data to existing software serial instance 572 | using Arduino's 'write' function 573 | """ 574 | if self.connected: 575 | cmd_str = build_cmd_str("sw", (data,)) 576 | try: 577 | self.sr.write(str.encode(cmd_str)) 578 | self.sr.flush() 579 | except: 580 | pass 581 | response = self.sr.readline().decode("utf-8").replace("\r\n", "") 582 | if response == "ss OK": 583 | return True 584 | else: 585 | return False 586 | 587 | def read(self): 588 | """ 589 | returns first character read from 590 | existing software serial instance 591 | """ 592 | if self.connected: 593 | cmd_str = build_cmd_str("sr") 594 | self.sr.write(str.encode(cmd_str)) 595 | self.sr.flush() 596 | response = self.sr.readline().decode("utf-8").replace("\r\n", "") 597 | if response: 598 | return response 599 | else: 600 | return False 601 | 602 | 603 | class EEPROM(object): 604 | """ 605 | Class for reading and writing to EEPROM. 606 | """ 607 | 608 | def __init__(self, board): 609 | self.board = board 610 | self.sr = board.sr 611 | 612 | def size(self): 613 | """ 614 | Returns size of EEPROM memory. 615 | """ 616 | cmd_str = build_cmd_str("sz") 617 | 618 | try: 619 | self.sr.write(str.encode(cmd_str)) 620 | self.sr.flush() 621 | response = self.sr.readline().decode("utf-8").replace("\r\n", "") 622 | return int(response) 623 | except: 624 | return 0 625 | 626 | def write(self, address, value=0): 627 | """ Write a byte to the EEPROM. 628 | 629 | :address: the location to write to, starting from 0 (int) 630 | :value: the value to write, from 0 to 255 (byte) 631 | """ 632 | 633 | if value > 255: 634 | value = 255 635 | elif value < 0: 636 | value = 0 637 | cmd_str = build_cmd_str("eewr", (address, value)) 638 | try: 639 | self.sr.write(str.encode(cmd_str)) 640 | self.sr.flush() 641 | except: 642 | pass 643 | 644 | def read(self, adrress): 645 | """ Reads a byte from the EEPROM. 646 | 647 | :address: the location to write to, starting from 0 (int) 648 | """ 649 | cmd_str = build_cmd_str("eer", (adrress,)) 650 | try: 651 | self.sr.write(str.encode(cmd_str)) 652 | self.sr.flush() 653 | response = self.sr.readline().decode("utf-8").replace("\r\n", "") 654 | if response: 655 | return int(response) 656 | except: 657 | return 0 658 | -------------------------------------------------------------------------------- /MIT license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013 Tristan A. Hearn 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino-Python3 Command API 2 | 3 | This API is forked from the original [Python Arduino Command API](https://github.com/thearn/Python-Arduino-Command-API) to add support for Python 3. 4 | 5 | The Arduino-Python3 Command API is a lightweight Python library for 6 | communicating with [Arduino microcontroller boards](http://www.arduino.cc/) from a connected computer using 7 | standard serial IO, either over a physical wire 8 | or wirelessly. It is written using a custom protocol, similar to [Firmata](http://firmata.org/wiki/Main_Page). 9 | 10 | This allows a user to quickly prototype programs for Arduino using Python code, or to 11 | simply read/control/troubleshoot/experiment 12 | with hardware connected to an Arduino board without ever having to recompile and reload sketches to the board itself. 13 | 14 | Method names within the Arduino-Python3 Command API are designed to be as close 15 | as possible to their Arduino programming language counterparts 16 | 17 | ## Simple usage example (LED blink) 18 | ```python 19 | #!/usr/bin/env python 20 | """ 21 | Blinks an LED on digital pin 13 22 | in 1 second intervals 23 | """ 24 | 25 | from Arduino import Arduino 26 | import time 27 | 28 | board = Arduino() # plugged in via USB, serial com at rate 115200 29 | board.pinMode(13, "OUTPUT") 30 | 31 | while True: 32 | board.digitalWrite(13, "LOW") 33 | time.sleep(1) 34 | board.digitalWrite(13, "HIGH") 35 | time.sleep(1) 36 | ``` 37 | 38 | ## Requirements: 39 | - [Python](http://python.org/) 3.7 tested on Windows and macOS. 40 | - [pyserial](http://pyserial.sourceforge.net/) 2.6 or higher 41 | - Any [Arduino compatible microcontroller](https://www.sparkfun.com/categories/242) with at least 14KB of flash memory 42 | 43 | ## Installation: 44 | Either run `pip install arduino-python3` from a command line, or run `python setup.py 45 | build install` from the source directory to install this library. 46 | 47 | ## Setup: 48 | 1. Verify that your Arduino board communicates at the baud rate specified in the 49 | `setup()` function (line 407) in `prototype.ino`. Change it there if necessary. 50 | 2. Load the `prototype.ino` sketch onto your Arduino board, using the Arduino IDE. 51 | 3. Set up some kind of serial I/O communication between the Arduino board and your computer (via physical USB cable, 52 | Bluetooth, xbee, etc. + associated drivers) 53 | 4. Add `from Arduino import Arduino` into your python script to communicate with your Arduino 54 | 55 | For a collection of examples, see `examples.py`. This file contains methods which replicate 56 | the functionality of many Arduino demo sketches. 57 | 58 | ## Testing: 59 | The `tests` directory contains some basic tests for the library. Extensive code coverage is a bit difficult to expect for every release, since a positive test involves actually 60 | connecting and issuing commands to a live Arduino, hosting any hardware 61 | required to test a particular function. But a core of basic communication tests 62 | should at least be maintained here and used before merging into the `master` branch. 63 | 64 | After installation, the interactive tests can be run from the source directory: 65 | ```bash 66 | $ python tests/test_main.py 67 | ``` 68 | 69 | Automated tests can be run from the source directory with: 70 | ```bash 71 | $ python tests/test_arduino.py 72 | ``` 73 | 74 | ## Classes 75 | - `Arduino(baud)` - Set up communication with currently connected and powered 76 | Arduino. 77 | 78 | ```python 79 | board = Arduino("115200") #Example 80 | ``` 81 | 82 | The device name / COM port of the connected Arduino will be auto-detected. 83 | If there are more than one Arduino boards connected, 84 | the desired COM port can be also be passed as an optional argument: 85 | 86 | ```python 87 | board = Arduino("115200", port="COM3") #Windows example 88 | ``` 89 | ```python 90 | board = Arduino("115200", port="/dev/tty.usbmodemfa141") #OSX example 91 | ``` 92 | 93 | A time-out for reading from the Arduino can also be specified as an optional 94 | argument: 95 | 96 | ```python 97 | board = Arduino("115200", timeout=2) #Serial reading functions will 98 | #wait for no more than 2 seconds 99 | ``` 100 | 101 | ## Methods 102 | 103 | **Digital I/O** 104 | 105 | - `Arduino.digitalWrite(pin_number, state)` turn digital pin on/off 106 | - `Arduino.digitalRead(pin_number)` read state of a digital pin 107 | 108 | ```python 109 | #Digital read / write example 110 | board.digitalWrite(13, "HIGH") #Set digital pin 13 voltage 111 | state_1 = board.digitalRead(13) #Will return integer 1 112 | board.digitalWrite(13, "LOW") #Set digital pin 13 voltage 113 | state_2 = board.digitalRead(13) #Will return integer 0 114 | ``` 115 | 116 | - `Arduino.pinMode(pin_number, io_mode)` set pin I/O mode 117 | - `Arduino.pulseIn(pin_number, state)` measures a pulse 118 | - `Arduino.pulseIn_set(pin_number, state)` measures a pulse, with preconditioning 119 | 120 | ```python 121 | #Digital mode / pulse example 122 | board.pinMode(7, "INPUT") #Set digital pin 7 mode to INPUT 123 | duration = board.pulseIn(7, "HIGH") #Return pulse width measurement on pin 7 124 | ``` 125 | 126 | **Analog I/O** 127 | 128 | - `Arduino.analogRead(pin_number)` returns the analog value 129 | - `Arduino.analogWrite(pin_number, value)` sets the analog value 130 | 131 | ```python 132 | #Analog I/O examples 133 | val=board.analogRead(5) #Read value on analog pin 5 (integer 0 to 1023) 134 | val = val / 4 # scale to 0 - 255 135 | board.analogWrite(11) #Set analog value (PWM) based on analog measurement 136 | ``` 137 | 138 | **Shift Register** 139 | 140 | - `Arduino.shiftIn(dataPin, clockPin, bitOrder)` shift a byte in and returns it 141 | - `Arduino.shiftOut(dataPin, clockPin, bitOrder, value)` shift the given byte out 142 | 143 | `bitOrder` should be either `"MSBFIRST"` or `"LSBFIRST"` 144 | 145 | **Servo Library Functionality** 146 | Support is included for up to 8 servos. 147 | 148 | - `Arduino.Servos.attach(pin, min=544, max=2400)` Create servo instance. Only 8 servos can be used at one time. 149 | - `Arduino.Servos.read(pin)` Returns the angle of the servo attached to the specified pin 150 | - `Arduino.Servos.write(pin, angle)` Move an attached servo on a pin to a specified angle 151 | - `Arduino.Servos.writeMicroseconds(pin, uS)` Write a value in microseconds to the servo on a specified pin 152 | - `Arduino.Servos.detach(pin)` Detaches the servo on the specified pin 153 | 154 | ```python 155 | #Servo example 156 | board.Servos.attach(9) #declare servo on pin 9 157 | board.Servos.write(9, 0) #move servo on pin 9 to 0 degrees 158 | print board.Servos.read(9) # should be 0 159 | board.Servos.detach(9) #free pin 9 160 | ``` 161 | 162 | **Software Serial Functionality** 163 | 164 | - `Arduino.SoftwareSerial.begin(ss_rxPin, ss_txPin, ss_device_baud)` initialize software serial device on 165 | specified pins. 166 | Only one software serial device can be used at a time. Existing software serial instance will 167 | be overwritten by calling this method, both in Python and on the Arduino board. 168 | - `Arduino.SoftwareSerial.write(data)` send data using the Arduino 'write' function to the existing software 169 | serial connection. 170 | - `Arduino.SoftwareSerial.read()` returns one byte from the existing software serial connection 171 | 172 | ```python 173 | #Software serial example 174 | board.SoftwareSerial.begin(0, 7, "19200") # Start software serial for transmit only (tx on pin 7) 175 | board.SoftwareSerial.write(" test ") #Send some data 176 | response_char = board.SoftwareSerial.read() #read response character 177 | ``` 178 | 179 | **EEPROM** 180 | 181 | - `Arduino.EEPROM.read(address)` reads a byte from the EEPROM 182 | - `Arduino.EEPROM.write(address, value)` writes a byte to the EEPROM 183 | - `Arduino.EEPROM.size()` returns size of the EEPROM 184 | 185 | ```python 186 | #EEPROM read and write examples 187 | location = 42 188 | value = 10 # 0-255(byte) 189 | 190 | board.EEPROM.write(location, 10) 191 | print(board.EEPROM.read(location)) 192 | print('EEPROM size {size}'.format(size=board.EEPROM.size())) 193 | ``` 194 | 195 | 196 | **Misc** 197 | 198 | - `Arduino.close()` closes serial connection to the Arduino. 199 | 200 | ## To-do list: 201 | - Expand software serial functionality (`print()` and `println()`) 202 | - Add simple reset functionality that zeros out all pin values 203 | - Add I2C / TWI function support (Arduino `Wire.h` commands) 204 | - Include a wizard which generates 'prototype.ino' with selected serial baud rate and Arduino function support 205 | (to help reduce memory requirements). 206 | - Multi-serial support for Arduino mega (`Serial1.read()`, etc) 207 | -------------------------------------------------------------------------------- /examples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from Arduino import Arduino 3 | import time 4 | 5 | 6 | def Blink(led_pin, baud, port=""): 7 | """ 8 | Blinks an LED in 1 sec intervals 9 | """ 10 | board = Arduino(baud, port=port) 11 | board.pinMode(led_pin, "OUTPUT") 12 | while True: 13 | board.digitalWrite(led_pin, "LOW") 14 | print board.digitalRead(led_pin) # confirm LOW (0) 15 | time.sleep(1) 16 | board.digitalWrite(led_pin, "HIGH") 17 | print board.digitalRead(led_pin) # confirm HIGH (1) 18 | time.sleep(1) 19 | 20 | 21 | def softBlink(led_pin, baud, port=""): 22 | """ 23 | Fades an LED off and on, using 24 | Arduino's analogWrite (PWM) function 25 | """ 26 | board = Arduino(baud, port=port) 27 | i = 0 28 | while True: 29 | i += 1 30 | k = i % 510 31 | if k % 5 == 0: 32 | if k > 255: 33 | k = 510 - k 34 | board.analogWrite(led_pin, k) 35 | 36 | 37 | def adjustBrightness(pot_pin, led_pin, baud, port=""): 38 | """ 39 | Adjusts brightness of an LED using a 40 | potentiometer. 41 | """ 42 | board = Arduino(baud, port=port) 43 | while True: 44 | time.sleep(0.01) 45 | val = board.analogRead(pot_pin) / 4 46 | print val 47 | board.analogWrite(led_pin, val) 48 | 49 | 50 | def PingSonar(pw_pin, baud, port=""): 51 | """ 52 | Gets distance measurement from Ping))) 53 | ultrasonic rangefinder connected to pw_pin 54 | """ 55 | board = Arduino(baud, port=port) 56 | pingPin = pw_pin 57 | while True: 58 | duration = board.pulseIn(pingPin, "HIGH") 59 | inches = duration / 72. / 2. 60 | # cent = duration / 29. / 2. 61 | print inches, "inches" 62 | time.sleep(0.1) 63 | 64 | 65 | def LCD(tx, baud, ssbaud, message, port=""): 66 | """ 67 | Prints to two-line LCD connected to 68 | pin tx 69 | """ 70 | board = Arduino(baud, port=port) 71 | board.SoftwareSerial.begin(0, tx, ssbaud) 72 | while True: 73 | board.SoftwareSerial.write(" test ") 74 | 75 | if __name__ == "__main__": 76 | Blink(13, '115200') 77 | -------------------------------------------------------------------------------- /pypi_commands.txt: -------------------------------------------------------------------------------- 1 | python3 setup.py sdist bdist_wheel 2 | python3 -m twine upload dist/* 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="arduino-python3", 8 | version="0.6", 9 | install_requires=['pyserial'], 10 | author="Morten Kals", 11 | author_email="morten@kals.no", 12 | description="A light-weight Python library that provides a serial \ 13 | bridge for communicating with Arduino microcontroller boards. Extended to work with Python 3", 14 | long_description=long_description, 15 | long_description_content_type="text/markdown", 16 | url='https://github.com/mkals/Arduino-Python3-Command-API', 17 | packages=['Arduino'], 18 | license='MIT', 19 | ) 20 | -------------------------------------------------------------------------------- /sketches/prototype/prototype.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void Version(){ 7 | Serial.println(F("V0.6")); 8 | } 9 | 10 | 11 | SoftwareSerial *sserial = NULL; 12 | Servo servos[8]; 13 | int servo_pins[] = {0, 0, 0, 0, 0, 0, 0, 0}; 14 | boolean connected = false; 15 | 16 | int Str2int (String Str_value) 17 | { 18 | char buffer[10]; //max length is three units 19 | Str_value.toCharArray(buffer, 10); 20 | int int_value = atoi(buffer); 21 | return int_value; 22 | } 23 | 24 | void split(String results[], int len, String input, char spChar) { 25 | String temp = input; 26 | for (int i=0; ibegin(baud_); 197 | Serial.println("ss OK"); 198 | } 199 | 200 | void SS_write(String data) { 201 | int len = data.length()+1; 202 | char buffer[len]; 203 | data.toCharArray(buffer,len); 204 | Serial.println("ss OK"); 205 | sserial->write(buffer); 206 | } 207 | void SS_read(String data) { 208 | char c = sserial->read(); 209 | Serial.println(c); 210 | } 211 | 212 | void pulseInHandler(String data){ 213 | int pin = Str2int(data); 214 | long duration; 215 | if(pin <=0){ 216 | pinMode(-pin, INPUT); 217 | duration = pulseIn(-pin, LOW); 218 | }else{ 219 | pinMode(pin, INPUT); 220 | duration = pulseIn(pin, HIGH); 221 | } 222 | Serial.println(duration); 223 | } 224 | 225 | void pulseInSHandler(String data){ 226 | int pin = Str2int(data); 227 | long duration; 228 | if(pin <=0){ 229 | pinMode(-pin, OUTPUT); 230 | digitalWrite(-pin, HIGH); 231 | delayMicroseconds(2); 232 | digitalWrite(-pin, LOW); 233 | delayMicroseconds(5); 234 | digitalWrite(-pin, HIGH); 235 | pinMode(-pin, INPUT); 236 | duration = pulseIn(-pin, LOW); 237 | }else{ 238 | pinMode(pin, OUTPUT); 239 | digitalWrite(pin, LOW); 240 | delayMicroseconds(2); 241 | digitalWrite(pin, HIGH); 242 | delayMicroseconds(5); 243 | digitalWrite(pin, LOW); 244 | pinMode(pin, INPUT); 245 | duration = pulseIn(pin, HIGH); 246 | } 247 | Serial.println(duration); 248 | } 249 | 250 | void SV_add(String data) { 251 | String sdata[3]; 252 | split(sdata,3,data,'%'); 253 | int pin = Str2int(sdata[0]); 254 | int min = Str2int(sdata[1]); 255 | int max = Str2int(sdata[2]); 256 | int pos = -1; 257 | for (int i = 0; i<8;i++) { 258 | if (servo_pins[i] == pin) { //reset in place 259 | servos[pos].detach(); 260 | servos[pos].attach(pin, min, max); 261 | servo_pins[pos] = pin; 262 | Serial.println(pos); 263 | return; 264 | } 265 | } 266 | for (int i = 0; i<8;i++) { 267 | if (servo_pins[i] == 0) {pos = i;break;} // find spot in servo array 268 | } 269 | if (pos == -1) {;} //no array position available! 270 | else { 271 | servos[pos].attach(pin, min, max); 272 | servo_pins[pos] = pin; 273 | Serial.println(pos); 274 | } 275 | } 276 | 277 | void SV_remove(String data) { 278 | int pos = Str2int(data); 279 | servos[pos].detach(); 280 | servo_pins[pos] = 0; 281 | } 282 | 283 | void SV_read(String data) { 284 | int pos = Str2int(data); 285 | int angle; 286 | angle = servos[pos].read(); 287 | Serial.println(angle); 288 | } 289 | 290 | void SV_write(String data) { 291 | String sdata[2]; 292 | split(sdata,2,data,'%'); 293 | int pos = Str2int(sdata[0]); 294 | int angle = Str2int(sdata[1]); 295 | servos[pos].write(angle); 296 | } 297 | 298 | void SV_write_ms(String data) { 299 | String sdata[2]; 300 | split(sdata,2,data,'%'); 301 | int pos = Str2int(sdata[0]); 302 | int uS = Str2int(sdata[1]); 303 | servos[pos].writeMicroseconds(uS); 304 | } 305 | 306 | void sizeEEPROM() { 307 | Serial.println(E2END + 1); 308 | } 309 | 310 | void EEPROMHandler(int mode, String data) { 311 | String sdata[2]; 312 | split(sdata, 2, data, '%'); 313 | if (mode == 0) { 314 | EEPROM.write(Str2int(sdata[0]), Str2int(sdata[1])); 315 | } else { 316 | Serial.println(EEPROM.read(Str2int(sdata[0]))); 317 | } 318 | } 319 | 320 | void SerialParser(void) { 321 | char readChar[64]; 322 | Serial.readBytesUntil(33,readChar,64); 323 | String read_ = String(readChar); 324 | //Serial.println(readChar); 325 | int idx1 = read_.indexOf('%'); 326 | int idx2 = read_.indexOf('$'); 327 | // separate command from associated data 328 | String cmd = read_.substring(1,idx1); 329 | String data = read_.substring(idx1+1,idx2); 330 | 331 | // determine command sent 332 | if (cmd == "dw") { 333 | DigitalHandler(1, data); 334 | } 335 | else if (cmd == "dr") { 336 | DigitalHandler(0, data); 337 | } 338 | else if (cmd == "aw") { 339 | AnalogHandler(1, data); 340 | } 341 | else if (cmd == "ar") { 342 | AnalogHandler(0, data); 343 | } 344 | else if (cmd == "pm") { 345 | ConfigurePinHandler(data); 346 | } 347 | else if (cmd == "ps") { 348 | pulseInSHandler(data); 349 | } 350 | else if (cmd == "pi") { 351 | pulseInHandler(data); 352 | } 353 | else if (cmd == "ss") { 354 | SS_set(data); 355 | } 356 | else if (cmd == "sw") { 357 | SS_write(data); 358 | } 359 | else if (cmd == "sr") { 360 | SS_read(data); 361 | } 362 | else if (cmd == "sva") { 363 | SV_add(data); 364 | } 365 | else if (cmd == "svr") { 366 | SV_read(data); 367 | } 368 | else if (cmd == "svw") { 369 | SV_write(data); 370 | } 371 | else if (cmd == "svwm") { 372 | SV_write_ms(data); 373 | } 374 | else if (cmd == "svd") { 375 | SV_remove(data); 376 | } 377 | else if (cmd == "version") { 378 | Version(); 379 | } 380 | else if (cmd == "to") { 381 | Tone(data); 382 | } 383 | else if (cmd == "nto") { 384 | ToneNo(data); 385 | } 386 | else if (cmd == "cap") { 387 | readCapacitivePin(data); 388 | } 389 | else if (cmd == "so") { 390 | shiftOutHandler(data); 391 | } 392 | else if (cmd == "si") { 393 | shiftInHandler(data); 394 | } 395 | else if (cmd == "eewr") { 396 | EEPROMHandler(0, data); 397 | } 398 | else if (cmd == "eer") { 399 | EEPROMHandler(1, data); 400 | } 401 | else if (cmd == "sz") { 402 | sizeEEPROM(); 403 | } 404 | } 405 | 406 | void setup() { 407 | Serial.begin(115200); 408 | while (!Serial) { 409 | ; // wait for serial port to connect. Needed for Leonardo only 410 | } 411 | Serial.println("connected"); 412 | } 413 | 414 | void loop() { 415 | SerialParser(); 416 | } 417 | -------------------------------------------------------------------------------- /tests/test_arduino.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import unittest 3 | 4 | 5 | logging.basicConfig(level=logging.DEBUG) 6 | 7 | 8 | class MockSerial(object): 9 | 10 | def __init__(self, baud, port, timeout=None): 11 | self.port = port 12 | self.baud = baud 13 | self.timeout = timeout 14 | self.output = [] 15 | self.input = [] 16 | self.is_open = True 17 | 18 | def flush(self): 19 | pass 20 | 21 | def write(self, line): 22 | self.output.append(line) 23 | 24 | def isOpen(self): 25 | return self.is_open 26 | 27 | def close(self): 28 | if self.is_open: 29 | self.is_open = False 30 | else: 31 | raise ValueError('Mock serial port is already closed.') 32 | 33 | def readline(self): 34 | """ 35 | @TODO: This does not take timeout into account at all. 36 | """ 37 | return self.input.pop(0) 38 | 39 | def reset_mock(self): 40 | self.output = [] 41 | self.input = [] 42 | 43 | def push_line(self, line, term='\r\n'): 44 | self.input.append(str(line) + term) 45 | 46 | 47 | INPUT = "INPUT" 48 | OUTPUT = "OUTPUT" 49 | LOW = "LOW" 50 | HIGH = "HIGH" 51 | READ_LOW = 0 52 | READ_HIGH = 1 53 | MSBFIRST = "MSBFIRST" 54 | LSBFIRST = "LSBFIRST" 55 | 56 | 57 | class ArduinoTestCase(unittest.TestCase): 58 | 59 | def setUp(self): 60 | from Arduino.arduino import Arduino 61 | self.mock_serial = MockSerial(115200, '/dev/ttyACM0') 62 | self.board = Arduino(sr=self.mock_serial) 63 | 64 | 65 | class TestArduino(ArduinoTestCase): 66 | 67 | def parse_cmd_sr(self, cmd_str): 68 | assert cmd_str[0] == '@' 69 | first_index = cmd_str.find('%') 70 | assert first_index != -1 71 | assert cmd_str[-2:] == '$!' 72 | # Skip over the @ and read up to but not including the %. 73 | cmd = cmd_str[1:first_index] 74 | # Skip over the first % and ignore the trailing $!. 75 | args_str = cmd_str[first_index+1:-2] 76 | args = args_str.split('%') 77 | return cmd, args 78 | 79 | def test_close(self): 80 | self.board.close() 81 | # Call again, should skip calling close. 82 | self.board.close() 83 | 84 | def test_version(self): 85 | from Arduino.arduino import build_cmd_str 86 | expected_version = "version" 87 | self.mock_serial.push_line(expected_version) 88 | self.assertEqual(self.board.version(), expected_version) 89 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), build_cmd_str('version')) 90 | 91 | def test_pinMode_input(self): 92 | from Arduino.arduino import build_cmd_str 93 | pin = 9 94 | self.board.pinMode(pin, INPUT) 95 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 96 | build_cmd_str('pm', (-pin,))) 97 | 98 | def test_pinMode_output(self): 99 | from Arduino.arduino import build_cmd_str 100 | pin = 9 101 | self.board.pinMode(pin, OUTPUT) 102 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 103 | build_cmd_str('pm', (pin,))) 104 | 105 | def test_pulseIn_low(self): 106 | from Arduino.arduino import build_cmd_str 107 | expected_duration = 230 108 | self.mock_serial.push_line(expected_duration) 109 | pin = 9 110 | self.assertEqual(self.board.pulseIn(pin, LOW), expected_duration) 111 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 112 | build_cmd_str('pi', (-pin,))) 113 | 114 | def test_pulseIn_high(self): 115 | from Arduino.arduino import build_cmd_str 116 | expected_duration = 230 117 | pin = 9 118 | self.mock_serial.push_line(expected_duration) 119 | self.assertEqual(self.board.pulseIn(pin, HIGH), expected_duration) 120 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), build_cmd_str('pi', (pin,))) 121 | 122 | def test_digitalRead(self): 123 | from Arduino.arduino import build_cmd_str 124 | pin = 9 125 | self.mock_serial.push_line(READ_LOW) 126 | self.assertEqual(self.board.digitalRead(pin), READ_LOW) 127 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), build_cmd_str('dr', (pin,))) 128 | 129 | def test_digitalWrite_low(self): 130 | from Arduino.arduino import build_cmd_str 131 | pin = 9 132 | self.board.digitalWrite(pin, LOW) 133 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), build_cmd_str('dw', (-pin,))) 134 | 135 | def test_digitalWrite_high(self): 136 | from Arduino.arduino import build_cmd_str 137 | pin = 9 138 | self.board.digitalWrite(pin, HIGH) 139 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), build_cmd_str('dw', (pin,))) 140 | 141 | def test_melody(self): 142 | from Arduino.arduino import build_cmd_str 143 | pin = 9 144 | notes = ["C4"] 145 | duration = 4 146 | C4_NOTE = 262 147 | self.board.Melody(pin, notes, [duration]) 148 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 149 | build_cmd_str('to', (len(notes), pin, C4_NOTE, duration))) 150 | self.assertEqual(self.mock_serial.output[1].decode('UTF-8'), 151 | build_cmd_str('nto', (pin,))) 152 | 153 | def test_shiftIn(self): 154 | from Arduino.arduino import build_cmd_str 155 | dataPin = 2 156 | clockPin = 3 157 | pinOrder = MSBFIRST 158 | expected = 0xff 159 | self.mock_serial.push_line(expected) 160 | self.assertEqual(self.board.shiftIn(dataPin, clockPin, pinOrder), 161 | expected) 162 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 163 | build_cmd_str('si', (dataPin, clockPin, pinOrder,))) 164 | 165 | def test_shiftOut(self): 166 | from Arduino.arduino import build_cmd_str 167 | dataPin = 2 168 | clockPin = 3 169 | pinOrder = MSBFIRST 170 | value = 0xff 171 | self.board.shiftOut(dataPin, clockPin, pinOrder, value) 172 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 173 | build_cmd_str('so', (dataPin, clockPin, pinOrder, value))) 174 | 175 | def test_analogRead(self): 176 | from Arduino.arduino import build_cmd_str 177 | pin = 9 178 | expected = 1023 179 | self.mock_serial.push_line(expected) 180 | self.assertEqual(self.board.analogRead(pin), expected) 181 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 182 | build_cmd_str('ar', (pin,))) 183 | 184 | def test_analogWrite(self): 185 | from Arduino.arduino import build_cmd_str 186 | pin = 9 187 | value = 255 188 | self.board.analogWrite(pin, value) 189 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 190 | build_cmd_str('aw', (pin, value))) 191 | 192 | 193 | class TestServos(ArduinoTestCase): 194 | 195 | def test_attach(self): 196 | from Arduino.arduino import build_cmd_str 197 | pin = 10 198 | position = 0 199 | self.mock_serial.push_line(position) 200 | servo_min = 544 201 | servo_max = 2400 202 | self.board.Servos.attach(pin, min=servo_min, max=servo_max) 203 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 204 | build_cmd_str('sva', (pin, servo_min, servo_max))) 205 | 206 | def test_detach(self): 207 | from Arduino.arduino import build_cmd_str 208 | pin = 10 209 | position = 0 210 | # Attach first. 211 | self.mock_serial.push_line(position) 212 | self.board.Servos.attach(pin) 213 | self.mock_serial.reset_mock() 214 | self.board.Servos.detach(pin) 215 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 216 | build_cmd_str('svd', (position,))) 217 | 218 | def test_write(self): 219 | from Arduino.arduino import build_cmd_str 220 | pin = 10 221 | position = 0 222 | angle = 90 223 | # Attach first. 224 | self.mock_serial.push_line(position) 225 | self.board.Servos.attach(pin) 226 | self.mock_serial.reset_mock() 227 | self.board.Servos.write(pin, angle) 228 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 229 | build_cmd_str("svw", (position, angle))) 230 | 231 | def test_writeMicroseconds(self): 232 | from Arduino.arduino import build_cmd_str 233 | pin = 10 234 | position = 0 235 | microseconds = 1500 236 | # Attach first. 237 | self.mock_serial.push_line(position) 238 | self.board.Servos.attach(pin) 239 | self.mock_serial.reset_mock() 240 | self.board.Servos.writeMicroseconds(pin, microseconds) 241 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 242 | build_cmd_str("svwm", (position, microseconds))) 243 | 244 | def test_read(self): 245 | from Arduino.arduino import build_cmd_str 246 | pin = 10 247 | position = 0 248 | angle = 90 249 | # Attach first. 250 | self.mock_serial.push_line(position) 251 | self.board.Servos.attach(pin) 252 | self.mock_serial.reset_mock() 253 | self.mock_serial.push_line(angle) 254 | self.assertEqual(self.board.Servos.read(pin), angle) 255 | self.assertEqual(self.mock_serial.output[0].decode('UTF-8'), 256 | build_cmd_str("svr", (position,))) 257 | 258 | 259 | if __name__ == '__main__': 260 | unittest.main() 261 | -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import unittest 3 | import time 4 | 5 | """ 6 | A collection of some basic tests for the Arduino library. 7 | 8 | Extensive coverage is a bit difficult, since a positive test involves actually 9 | connecting and issuing commands to a live Arduino, hosting any hardware 10 | required to test a particular function. But a core of basic communication tests 11 | should at least be maintained here. 12 | """ 13 | 14 | 15 | logging.basicConfig(level=logging.DEBUG) 16 | 17 | # Bind raw_input to input in python 2.7 18 | try: 19 | input = raw_input 20 | except NameError: 21 | pass 22 | 23 | 24 | class TestBasics(unittest.TestCase): 25 | 26 | def test_find(self): 27 | """ Tests auto-connection/board detection. """ 28 | input( 29 | 'Plug in Arduino board w/LED at pin 13, reset, then press enter') 30 | from Arduino import Arduino 31 | board = None 32 | try: 33 | # This will trigger automatic port resolution. 34 | board = Arduino(115200) 35 | finally: 36 | if board: 37 | board.close() 38 | 39 | def test_open(self): 40 | """ Tests connecting to an explicit port. """ 41 | port = None 42 | while not port: 43 | port = input( 44 | 'Plug in Arduino board w/LED at pin 13, reset.\n'\ 45 | 'Enter the port where the Arduino is connected, then press enter:') 46 | if not port: 47 | print('You must enter a port.') 48 | from Arduino import Arduino 49 | board = None 50 | try: 51 | board = Arduino(115200, port=port) 52 | finally: 53 | if board: 54 | board.close() 55 | 56 | if __name__ == '__main__': 57 | unittest.main() 58 | --------------------------------------------------------------------------------