Skip to content

Commit

Permalink
Merge branch 'master' of github.com:tino/pyFirmata
Browse files Browse the repository at this point in the history
  • Loading branch information
Tino de Bruijn committed Oct 19, 2013
2 parents 0926d52 + 4016b71 commit 301e645
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 44 deletions.
47 changes: 41 additions & 6 deletions README.rst
Expand Up @@ -2,16 +2,37 @@
pyFirmata
=========

pyFirmata is a Python interface for the `Firmata`_ protocol.

.. _Firmata: http://firmata.org

Master tests:

.. image:: https://travis-ci.org/tino/pyFirmata.png?branch=master
:target: https://travis-ci.org/tino/pyFirmata

Python 3 tests:

.. image:: https://travis-ci.org/tino/pyFirmata.png?branch=py3
:target: https://travis-ci.org/tino/pyFirmata

Installation
============

The preferred way to install is with pip_::

pip install pyfirmata

If you install from source with ``python setup.py install``, don't forget to install ``pyserial`` as well.
If you install from source with ``python setup.py install``, don't forget to
install `pyserial`_ as well.::

git clone https://github.com/tino/pyFirmata
cd pyFirmata
pip install pyserial
python setup.py install

.. _pip: http://www.pip-installer.org/en/latest/
.. _pyserial:http://pyserial.sourceforge.net/
Usage
=====
Expand All @@ -22,15 +43,21 @@ Basic usage::
>>> board = Arduino('/dev/tty.usbserial-A6008rIF')
>>> board.digital[13].write(1)

To use analog ports, it is probably handy to start an iterator thread. Otherwise the board will keep sending data to your serial, until it overflows::
To use analog ports, it is probably handy to start an iterator thread.
Otherwise the board will keep sending data to your serial, until it overflows::

>>> it = util.Iterator(board)
>>> it.start()
>>> board.analog[0].enable_reporting()
>>> board.analog[0].read()
0.661440304938

If you use a pin more often, it can be worth it to use the ``get_pin`` method of the board. It let's you specify what pin you need by a string, composed of 'a' or 'd' (depending on wether you need an analog or digital pin), the pin number, and the mode ('i' for input, 'o' for output, 'p' for pwm). All seperated by ``:``. Eg. ``a:0:i`` for analog 0 as input, or ``d:3:p`` for digital pin 3 as pwm.::
If you use a pin more often, it can be worth it to use the ``get_pin`` method
of the board. It let's you specify what pin you need by a string, composed of
'a' or 'd' (depending on wether you need an analog or digital pin), the pin
number, and the mode ('i' for input, 'o' for output, 'p' for pwm). All
seperated by ``:``. Eg. ``a:0:i`` for analog 0 as input or ``d:3:p`` for
digital pin 3 as pwm.::

>>> analog_0 = board.get_pin('a:0:i')
>>> analog_0.read()
Expand All @@ -41,7 +68,11 @@ If you use a pin more often, it can be worth it to use the ``get_pin`` method of
Board layout
============

If you want to use a board with a different layout than the standard Arduino, or the Arduino Mega (for wich there exist the shortcut classes ``pyfirmata.Arduino`` and ``pyfirmata.ArduinoMega``), instantiate the Board class with a dictionary as the ``layout`` argument. This is the layout dict for the Mega for example::
If you want to use a board with a different layout than the standard Arduino,
or the Arduino Mega (for wich there exist the shortcut classes
``pyfirmata.Arduino`` and ``pyfirmata.ArduinoMega``), instantiate the Board
class with a dictionary as the ``layout`` argument. This is the layout dict
for the Mega for example::

>>> mega = {
... 'digital' : tuple(x for x in range(54)),
Expand All @@ -56,5 +87,9 @@ Todo

The next things on my list are to implement the new protocol changes in firmata:

- Capability Query, which would eliminate the need to instantiate a board with the layout dict, as it will be able to determine the layout itself (http://firmata.org/wiki/Proposals#Capability_Query_.28added_in_version_2.2.29)
- Pin State Query, which allows it to populate on-screen controls with an accurate representation of the hardware's configuration (http://firmata.org/wiki/Proposals#Pin_State_Query_.28added_in_version_2.2.29)
- Capability Query, which would eliminate the need to instantiate a board with
the layout dict, as it will be able to determine the layout itself
(http://firmata.org/wiki/Proposals#Capability_Query_.28added_in_version_2.2.29)
- Pin State Query, which allows it to populate on-screen controls with an
accurate representation of the hardware's configuration
(http://firmata.org/wiki/Proposals#Pin_State_Query_.28added_in_version_2.2.29)
5 changes: 2 additions & 3 deletions pyfirmata/__init__.py
Expand Up @@ -5,10 +5,9 @@

# shortcut classes


class Arduino(Board):
"""
A board that wil set itself up as a normal Arduino.
A board that will set itself up as a normal Arduino.
"""
def __init__(self, *args, **kwargs):
args = list(args)
Expand All @@ -21,7 +20,7 @@ def __str__(self):

class ArduinoMega(Board):
"""
A board that wil set itself up as an Arduino Mega.
A board that will set itself up as an Arduino Mega.
"""
def __init__(self, *args, **kwargs):
args = list(args)
Expand Down
68 changes: 33 additions & 35 deletions pyfirmata/pyfirmata.py
Expand Up @@ -60,9 +60,7 @@ class NoInputWarning(RuntimeWarning):
pass

class Board(object):
"""
Base class for any board
"""
"""The Base class for any board."""
firmata_version = None
firmware = None
firmware_version = None
Expand All @@ -74,7 +72,7 @@ class Board(object):
def __init__(self, port, layout, baudrate=57600, name=None):
self.sp = serial.Serial(port, baudrate)
# Allow 5 secs for Arduino's auto-reset to happen
# Alas, Firmata blinks it's version before printing it to serial
# Alas, Firmata blinks its version before printing it to serial
# For 2.3, even 5 seconds might not be enough.
# TODO Find a more reliable way to wait until the board is ready
self.pass_time(BOARD_SETUP_WAIT_TIME)
Expand All @@ -92,19 +90,19 @@ def __str__(self):
return "Board %s on %s" % (self.name, self.sp.port)

def __del__(self):
'''
"""
The connection with the a board can get messed up when a script is
closed without calling board.exit() (which closes the serial
connection). Therefore also do it here and hope it helps.
'''
"""
self.exit()

def send_as_two_bytes(self, val):
self.sp.write(chr(val % 128) + chr(val >> 7))

def setup_layout(self, board_layout):
"""
Setup the Pin instances based on the given board-layout. Maybe it will
Setup the Pin instances based on the given board layout. Maybe it will
be possible to do this automatically in the future, by polling the
board for its type.
"""
Expand Down Expand Up @@ -143,9 +141,7 @@ def setup_layout(self, board_layout):
self.add_cmd_handler(REPORT_FIRMWARE, self._handle_report_firmware)

def add_cmd_handler(self, cmd, func):
"""
Adds a command handler for a command.
"""
"""Adds a command handler for a command."""
len_args = len(inspect.getargspec(func)[0])
def add_meta(f):
def decorator(*args, **kwargs):
Expand All @@ -161,9 +157,14 @@ def get_pin(self, pin_def):
Returns the activated pin given by the pin definition.
May raise an ``InvalidPinDefError`` or a ``PinAlreadyTakenError``.
:arg pin_def: Pin definition as described in TODO,
:arg pin_def: Pin definition as described below,
but without the arduino name. So for example ``a:1:i``.
'a' analog pin Pin number 'i' for input
'd' digital pin Pin number 'o' for output
'p' for pwm (Pulse-width modulation)
All seperated by ``:``.
"""
if type(pin_def) == list:
bits = pin_def
Expand Down Expand Up @@ -193,9 +194,7 @@ def get_pin(self, pin_def):
return pin

def pass_time(self, t):
"""
Non-blocking time-out for ``t`` seconds.
"""
"""Non-blocking time-out for ``t`` seconds."""
cont = time.time() + t
while time.time() < cont:
time.sleep(0)
Expand Down Expand Up @@ -227,8 +226,8 @@ def bytes_available(self):
def iterate(self):
"""
Reads and handles data from the microcontroller over the serial port.
This method should be called in a main loop, or in an
:class:`Iterator` instance to keep this boards pin values up to date
This method should be called in a main loop or in an :class:`Iterator`
instance to keep this boards pin values up to date.
"""
byte = self.sp.read()
if not byte:
Expand Down Expand Up @@ -269,7 +268,7 @@ def iterate(self):

def get_firmata_version(self):
"""
Returns a version tuple (major, mino) for the firmata firmware on the
Returns a version tuple (major, minor) for the firmata firmware on the
board.
"""
return self.firmata_version
Expand All @@ -291,7 +290,7 @@ def servo_config(self, pin, min_pulse=544, max_pulse=2400, angle=0):
self.digital[pin].write(angle)

def exit(self):
""" Call this to exit cleanly. """
"""Call this to exit cleanly."""
# First detach all servo's, otherwise it somehow doesn't want to close...
if hasattr(self, 'digital'):
for pin in self.digital:
Expand All @@ -313,7 +312,7 @@ def _handle_analog_message(self, pin_nr, lsb, msb):
def _handle_digital_message(self, port_nr, lsb, msb):
"""
Digital messages always go by the whole port. This means we have a
bitmask wich we update the port.
bitmask which we update the port.
"""
mask = (msb << 7) + lsb
try:
Expand All @@ -331,7 +330,7 @@ def _handle_report_firmware(self, *data):
self.firmware = two_byte_iter_to_str(data[2:])

class Port(object):
""" An 8-bit port on the board """
"""An 8-bit port on the board."""
def __init__(self, board, port_number, num_pins=8):
self.board = board
self.port_number = port_number
Expand All @@ -346,7 +345,7 @@ def __str__(self):
return "Digital Port %i on %s" % (self.port_number, self.board)

def enable_reporting(self):
""" Enable reporting of values for the whole port """
"""Enable reporting of values for the whole port."""
self.reporting = True
msg = chr(REPORT_DIGITAL + self.port_number)
msg += chr(1)
Expand All @@ -356,14 +355,14 @@ def enable_reporting(self):
pin.reporting = True # TODO Shouldn't this happen at the pin?

def disable_reporting(self):
""" Disable the reporting of the port """
"""Disable the reporting of the port."""
self.reporting = False
msg = chr(REPORT_DIGITAL + self.port_number)
msg += chr(0)
self.board.sp.write(msg)

def write(self):
"""Set the output pins of the port to the correct state"""
"""Set the output pins of the port to the correct state."""
mask = 0
for pin in self.pins:
if pin.mode == OUTPUT:
Expand All @@ -376,17 +375,15 @@ def write(self):
self.board.sp.write(msg)

def _update(self, mask):
"""
Update the values for the pins marked as input with the mask.
"""
"""Update the values for the pins marked as input with the mask."""
if self.reporting:
for pin in self.pins:
if pin.mode is INPUT:
pin_nr = pin.pin_number - self.port_number * 8
pin.value = (mask & (1 << pin_nr)) > 0

class Pin(object):
""" A Pin representation """
"""A Pin representation"""
def __init__(self, board, pin_number, type=ANALOG, port=None):
self.board = board
self.pin_number = pin_number
Expand All @@ -406,13 +403,13 @@ def _set_mode(self, mode):
self._mode = UNAVAILABLE
return
if self._mode is UNAVAILABLE:
raise IOError("%s can not be used through Firmata" % self)
raise IOError("%s can not be used through Firmata." % self)
if mode is PWM and not self.PWM_CAPABLE:
raise IOError("%s does not have PWM capabilities" % self)
raise IOError("%s does not have PWM capabilities." % self)
if mode == SERVO:
if self.type != DIGITAL:
raise IOError("Only digital pins can drive servos! %s is not"
"digital" % self)
"digital." % self)
self._mode = SERVO
self.board.servo_config(self.pin_number)
return
Expand All @@ -432,11 +429,11 @@ def _get_mode(self):
mode = property(_get_mode, _set_mode)
"""
Mode of operation for the pin. Can be one of the pin modes: INPUT, OUTPUT,
ANALOG, PWM or SERVO (or UNAVAILABLE)
ANALOG, PWM. or SERVO (or UNAVAILABLE).
"""

def enable_reporting(self):
""" Set an input pin to report values """
"""Set an input pin to report values."""
if self.mode is not INPUT:
raise IOError, "%s is not an input and can therefore not report" % self
if self.type == ANALOG:
Expand All @@ -448,7 +445,7 @@ def enable_reporting(self):
self.port.enable_reporting() # TODO This is not going to work for non-optimized boards like Mega

def disable_reporting(self):
""" Disable the reporting of an input pin """
"""Disable the reporting of an input pin."""
if self.type == ANALOG:
self.reporting = False
msg = chr(REPORT_ANALOG + self.pin_number)
Expand All @@ -460,7 +457,8 @@ def disable_reporting(self):
def read(self):
"""
Returns the output value of the pin. This value is updated by the
boards :meth:`Board.iterate` method. Value is alway in the range 0.0 - 1.0
boards :meth:`Board.iterate` method. Value is always in the range from
0.0 to 1.0.
"""
if self.mode == UNAVAILABLE:
raise IOError, "Cannot read pin %s"% self.__str__()
Expand All @@ -476,7 +474,7 @@ def write(self, value):
"""
if self.mode is UNAVAILABLE:
raise IOError, "%s can not be used through Firmata" % self
raise IOError, "%s can not be used through Firmata." % self
if self.mode is INPUT:
raise IOError, "%s is set up as an INPUT and can therefore not be written to" % self
if value is not self.value:
Expand Down

0 comments on commit 301e645

Please sign in to comment.