tino / pyFirmata

Python interface for the Firmata (http://firmata.org/) protocol. It is compliant with Firmata 2.1. Any help with updating to 2.2 is welcome. The Capability Query is implemented, but the Pin State Query feature not yet.
MIT License
575 stars 193 forks source link

Unable to create multiple instances Arduino board object #100

Open 01baftb opened 3 years ago

01baftb commented 3 years ago

I am using pyFirmata to control multiple DC motors connected to a single Arduino Nano.

Problem: I am unable to create multiple instances of the pyfirmata.Arduino object connecting to the same communication port. See example code below where I create a general class to control a DC motor. Each motor that needs to be controlled is connected to the same Arduino comm port but only thing that differs is the pin number at which the pwm and direction signals are outputted from. When I create two different instances of the MyMotor() class, I get the following error:

Error:

Traceback (most recent call last):
  File "c:/tmp1/myMotorExample.py", line 21, in <module>
    motor_2 = MyMotor(port='COM5', pin_pwm=5, pin_direction=7)
  File "c:/tmp1/myMotorExample.py", line 7, in __init__
    self.board = pyfirmata.Arduino(port)
  File "C:\tmp1\my_venv\lib\site-packages\pyfirmata\__init__.py", line 19, in __init__
    super(Arduino, self).__init__(*args, **kwargs)
  File "C:\tmp1\my_venv\lib\site-packages\pyfirmata\pyfirmata.py", line 89, in __init__
    self.sp = serial.Serial(port, baudrate, timeout=timeout)
  File "C:\tmp1\my_venv\lib\site-packages\serial\serialwin32.py", line 31, in __init__
    super(Serial, self).__init__(*args, **kwargs)
  File "C:\tmp1\my_venv\lib\site-packages\serial\serialutil.py", line 240, in __init__
    self.open()
  File "C:\tmp1\my_venv\lib\site-packages\serial\serialwin32.py", line 62, in open
    raise SerialException("could not open port {!r}: {!r}".format(self.portstr, ctypes.WinError()))
serial.serialutil.SerialException: could not open port 'COM5': PermissionError(13, 'Access is denied.', None, 5)

Example code

import pyfirmata

class MyMotor():

    def __init__(self, port, pin_pwm, pin_direction):
        # Setup the board 
        self.board = pyfirmata.Arduino(port)
        # Assign the pins 
        self.pin_pwm = self.board.get_pin('d:{}:p'.format(pin_pwm))
        self.pin_direction = self.board.get_pin('d:{}:o'.format(pin_direction))

    def run_motor(self, speed=0.5, direction=True):
        self.pin_pwm.write(speed)
        self.pin_direction.write(direction)

    def stop_motor(self):
        self.pin_pwm.write(0.0)

motor_1 = MyMotor(port='COM5', pin_pwm=3, pin_direction=4)
motor_2 = MyMotor(port='COM5', pin_pwm=5, pin_direction=7)
01baftb commented 3 years ago

I think I figured out a temporary solution to my issue. However maybe this solution could be incorporated into pyfirmata. In summary, class attribute can be used to keep track of all Arduino comm ports already in use by pyfirmata.

import serial
import pyfirmata

class MyMotor():

    open_boards = [] 

    def __init__(self, port, pin_pwm, pin_direction):

        # Setup the board 
        try:
            self.board = pyfirmata.Arduino(port)
            self.open_boards.append(self.board)
            print('Success connecting to board.')
        except serial.serialutil.SerialException:
            print('Problem connecting to Arduino. Comm port may already be in use. Checking and trying to fix...')
            problem_fixed = False 
            for i in self.open_boards:
                if i.sp.port == port:
                    self.board = i
                    problem_fixed = True 
                    break 

            if not problem_fixed:
                print('Unable to fix the problem.')
                return    
            else:
                print('Requested port was already in used. Success with connecting to board. ')

        # Assign the pins 
        self.pin_pwm = self.board.get_pin('d:{}:p'.format(pin_pwm))
        self.pin_direction = self.board.get_pin('d:{}:o'.format(pin_direction))

    def run_motor(self, speed=0.5, direction=True):
        self.pin_pwm.write(speed)
        self.pin_direction.write(direction)

    def stop_motor(self):
        self.pin_pwm.write(0.0)

motor_1 = MyMotor(port='COM5', pin_pwm=3, pin_direction=4)
motor_1.run_motor()
motor_2 = MyMotor(port='COM5', pin_pwm=5, pin_direction=7)
motor_2.run_motor()