diff --git a/.hgignore b/.hgignore deleted file mode 100644 index 875a341e..00000000 --- a/.hgignore +++ /dev/null @@ -1,12 +0,0 @@ -syntax:glob - -*.pyc -.DS_Store -cov -.coverage -applet -Firmata-* -Firmata -hardwareAbstraction -docs/_build -build/ diff --git a/.hgtags b/.hgtags deleted file mode 100644 index 7b3e57b2..00000000 --- a/.hgtags +++ /dev/null @@ -1,8 +0,0 @@ -93c8842c16634ec43b7fd0ec2ef00fa27807356c v0.9 -6fbd81f7e58cb80934e3fc871383082da4f94e6d v0.9.1 -ce210059ca315635e6e921f5fe56e48a622a324a s -ce210059ca315635e6e921f5fe56e48a622a324a s -0000000000000000000000000000000000000000 s -7d003b2d8a46a485f6b7db21f69631bd13aeaf86 v0.9.2 -a78d3dce81ea000cc469b8d8db055195861466cd 0.9.3 -599e57b5f400a2e309394f07d1222a31d7f82f96 0.9.4 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4e661e41..00000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: python -python: - - "2.7" - - "3.3" - - "3.4" - - "3.5" - - -# install deps -install: - - pip install flake8 isort coverage coveralls - - pip install -r requirements.txt - -before_script: - - flake8 --show-source . - - isort -rc . - -# run tests -script: coverage run --source=pyfirmata tests.py - -after_success: - - coveralls diff --git a/CHANGES.rst b/CHANGES.rst deleted file mode 100644 index b6f65e32..00000000 --- a/CHANGES.rst +++ /dev/null @@ -1,30 +0,0 @@ -======= -Changes -======= - -Version 1.0.x -============= - -1.0.3 - 17/05/2015 ------------------- - -- Pass ``timeout`` parameter on board trough to ``pyserial.Serial``. -- Added this change list. - -1.0.2 - 17/01/2015 ------------------- - -- Configure ``bumpversion``. -- Update to-do list. - -1.0.1 - 17/01/2015 ------------------- - -- Added Firmata's "Capability Query", that allows to auto setup a board. (This probably deserved a minor version bump...) -- Start distributing as wheels - -1.0.0 - 04/01/2015 ------------------- - -- Added Python 3 support -- Testing on Python 2.6 is dropped, but it might still work. Not actively supported though. diff --git a/LICENSE b/LICENSE index 8f263a01..aa8465f4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ Copyright (c) 2010, Tino de Bruijn +Copyright (c) 2018-2020, Bernd Porr Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.md b/README.md new file mode 100644 index 00000000..6d6b212f --- /dev/null +++ b/README.md @@ -0,0 +1,250 @@ +# pyFirmata2 + +![alt tag](screenshot.png) + +PyFirmata2 turns your Arduino into an analogue to digital data acquistion +card controlled by Python. +Just upload the default firmata sketch into your Arduino and you are all set. + +## Inputs + - Analogue, measuring volt. Up to 1kHz precise sampling. + - Digital, reacting to digital state changes. + +## Outputs + - Digital out + - PWM + - Servo control + +No loops and "sleep" commands: pyFirmata2 is an updated version of +pyFirmata which *replaces loops with callbacks*. Instead of unreliable +and disruptive "sleep" commands in a loop the Python application +registers callbacks with pyfirmata2 which are then called every time +new data has arrived. This means for the analogue channels the +callbacks are called at the specified sampling rate while the digital +ports call the callback functions after a state change at the port +(from 0 to 1 or 1 to 0). + +This API has been used in the Digital Signal Processing (DSP) class to +practise realtime filtering of analogue sensor +data. Examples can be viewed on the YouTube channel of the +class: https://www.youtube.com/user/DSPcourse + + +## Installation + + + +### Upload firmata + + +Install the Arduino IDE on your computer: https://www.arduino.cc/en/Main/Software + + - Start the Arduino IDE + - Select the serial port under "Tools" + - Select your Arduino board under "Tools" + - Upload the standard firmata sketch to your Arduino with: +``` + File -> Examples -> Firmata -> Standard Firmata +``` + + +### Install pyfirmata2 + + +The preferred way to install is with `pip` / `pip3`. Under Linux: +``` + pip3 install pyfirmata2 [--user] [--upgrade] +``` + +and under Windows/Mac type: +``` + pip install pyfirmata2 [--user] [--upgrade] +``` + +You can also install from source with: +``` + git clone https://github.com/berndporr/pyFirmata2 + cd pyFirmata2 +``` + +Under Linux type: +``` + python3 setup.py install +``` + +Under Windows / Mac: +``` + python setup.py install +``` + +## Usage + + +### Initialisation + +Create an instance of the `Arduino` class: +``` +PORT = pyfirmata2.Arduino.AUTODETECT +board = pyfirmata2.Arduino(PORT) +``` +which automatically detects the serial port of the Arduino. + +If this fails you can also specify the serial port manually, for example: +``` +board = pyfirmata2.Arduino('COM4') +``` +Under Linux this is usually `/dev/ttyACM0`. Under Windows this is a +COM port, for example `COM4`. On a MAC it's `/dev/ttys000`, `/dev/cu.usbmodem14101` or +check for the latest addition: `ls -l -t /dev/*`. + + +### Starting sampling at a given sampling interval + +In order to sample analogue data you need to specify a sampling +interval in ms which then applies to all channels. The smallest +interval is 1ms: +``` +board.samplingOn(samplinginterval in ms) +``` +Note that the sampling interval is an *integer* number. +Calling `samplingOn()` without its argument sets the sampling interval +to 19ms. + + +### Enabling and reading from analogue or digital input pins + +To receive data register a callback +handler and then enable it: +``` + board.analog[0].register_callback(myCallback) + board.analog[0].enable_reporting() +``` +where `myCallback(data)` is then called every time after data has been received +and is timed by the arduino itself. For analogue inputs that's at +the given sampling rate and for digital ones at state changes from 0 to 1 or +1 to 0. + +### Writing to a digital port + +Digital ports can be written to at any time: +``` + board.digital[13].write(True) +``` +For any other functionality (PWM or servo) use the pin class below. + + +### The pin class + +The command `get_pin` requests the class of a pin +by specifying a string, composed of +'a' or 'd' (depending on if you need an analog or digital pin), the pin +number, and the mode: + - 'i' for input (digital or analogue) + - 'u' for input with pullup (digital) + - 'o' for output (digital) + - 'p' for pwm (digital) + - 's' for servo (digital) +All seperated by `:`, for example: +``` +analog_in_0 = board.get_pin('a:0:i') +analog_in_0.register_callback(myCallback) +analog_in_0.enable_reporting() + +digital_out_3 = board.get_pin('d:3:o') +digital_out_3.write(True) +``` +Values for analogue ports and PWM are 0..1, +for servo between 0 and 180 (degrees) and for digital ports +`True` & `False`. + +``` +class Pin(builtins.object) + | Pin(board, pin_number, type=2, port=None) + | + | A Pin representation + | + | Methods defined here: + | + | __init__(self, board, pin_number, type=2, port=None) + | Initialize self. See help(type(self)) for accurate signature. + | + | __str__(self) + | Return str(self). + | + | disable_reporting(self) + | Disable the reporting of an input pin. + | + | enable_reporting(self) + | Set an input pin to report values. + | + | read(self) + | Returns the value of an output pin. + | + | register_callback(self, _callback) + | Register a callback to read from an analogue or digital port + | + | :arg value: callback with one argument which receives the data: + | boolean if the pin is digital, or + | float from 0 to 1 if the pin is an analgoue input + | + | unregiser_callback(self) + | Unregisters the callback which receives data from a pin + | + | write(self, value) + | Output a voltage from the pin + | + | :arg value: Uses value as a boolean if the pin is in output mode, or + | expects a float from 0 to 1 if the pin is in PWM mode. If the pin + | is in SERVO the value should be in degrees. + | + +``` + +### Closing the board + +To close the serial port to the Arduino use the exit command: +``` +board.exit() +``` + +## Example code + +The directory https://github.com/berndporr/pyFirmata2/tree/master/examples +contains two realtime Oscilloscopes with precise sampling rate, +a digital port reader, the ubiquitous flashing LED program, pwm, servo control +and +a program which prints data using the callback handler. + + +## Troubleshooting + +### Spyder / pyCharm / IDEs + +Start your program from the console / terminal and never within an IDE. Here is +an example for Windows: +``` + (base) D:\> + (base) D:\>cd pyFirmata2\examples + (base) D:\pyFirmata2\examples>python realtime_two_channel_scope.py +``` +The problem with IDEs is that they won't let your Python program terminate properly +which leaves the serial port in an undefined state. If you then re-run your program +it won't be able to talk to your Arduino. In the worst case you need to reboot your +computer. Bottomline: use your IDE for editing, run the program from the console / terminal. + + +### After an update still the old version is being used + +If you use the `--user` option to install / update packages Python might keep older versions. + +Solution: Do a `pip uninstall pyfirmata2` multiple times until no version is left +on your computer. Then install it again as described above. + + + + +### Credits + +The [original pyFirmata](https://github.com/tino/pyFirmata) +was written by Tino de Bruijn and is recommended if you'd rather +prefer loops and sleep()-commands. diff --git a/README.rst b/README.rst deleted file mode 100644 index 3cc7a8e5..00000000 --- a/README.rst +++ /dev/null @@ -1,93 +0,0 @@ -========= -pyFirmata -========= - -pyFirmata is a Python interface for the `Firmata`_ protocol. It is fully -compatible with Firmata 2.1, and has some functionality of version 2.2. It runs -on Python 2.7, 3.3 and 3.4. - -.. _Firmata: http://firmata.org - -Test & coverage status: - -.. image:: https://travis-ci.org/tino/pyFirmata.png?branch=master - :target: https://travis-ci.org/tino/pyFirmata - -.. image:: https://coveralls.io/repos/github/tino/pyFirmata/badge.svg?branch=master - :target: https://coveralls.io/github/tino/pyFirmata?branch=master - -Installation -============ - -The preferred way to install is with pip_:: - - pip install pyfirmata - -You can also install from source with ``python setup.py install``. You will -need to have `setuptools`_ installed:: - - git clone https://github.com/tino/pyFirmata - cd pyFirmata - python setup.py install - -.. _pip: http://www.pip-installer.org/en/latest/ -.. _setuptools: https://pypi.python.org/pypi/setuptools - - -Usage -===== - -Basic usage:: - - >>> from pyfirmata import Arduino, util - >>> 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:: - - >>> 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.:: - - >>> analog_0 = board.get_pin('a:0:i') - >>> analog_0.read() - 0.661440304938 - >>> pin3 = board.get_pin('d:3:p') - >>> pin3.write(0.6) - -Board layout -============ - -If you want to use a board with a different layout than the standard Arduino -or the Arduino Mega (for which 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)), - ... 'analog' : tuple(x for x in range(16)), - ... 'pwm' : tuple(x for x in range(2,14)), - ... 'use_ports' : True, - ... 'disabled' : (0, 1, 14, 15) # Rx, Tx, Crystal - ... } - -Todo -==== - -The next things on my list are to implement the new protocol changes in -firmata: - -- 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) diff --git a/README_py.rst b/README_py.rst new file mode 100644 index 00000000..91b0776f --- /dev/null +++ b/README_py.rst @@ -0,0 +1,86 @@ +========== +pyFirmata2 +========== + +PyFirmata2 turns your Arduino into a data acquisition card controlled by Python. + +Up to 1kHz precise sampling at the analogue ports for digital filtering. + +Just upload the default firmata sketch into your Arduino and you are all set. + +pyFirmata2 is an updated version of pyFirmata which *replaces loops +with callbacks*. Instead of unreliable "sleep" commands in a loop the +Python application registers callbacks which are then called every +time after new data has arrived. This means for the analogue +channels the callbacks are called at the specified sampling rate +while the digital ports call the callback functions after +a state change at the port (from 0 to 1 or 1 to 0). + +This API has been used in my Digital Signal Processing (DSP) class to +practise realtime filtering of analogue sensor +data. Examples can be viewed on the YouTube channel of the +class: https://www.youtube.com/user/DSPcourse + + +Installation +============ + + +Upload firmata +-------------- + +Install the Arduino IDE on your computer: https://www.arduino.cc/en/Main/Software + +Start the IDE and upload the standard firmata sketch into your Arduino with:: + + File -> Examples -> Firmata -> Standard Firmata + + + +Install pyfirmata2 +------------------ + +The preferred way to install is with `pip` / `pip3`. Under Linux:: + + pip3 install pyfirmata2 [--user] [--upgrade] + + +and under Windows/Mac type:: + + pip install pyfirmata2 [--user] [--upgrade] + + +You can also install from source with:: + + git clone https://github.com/berndporr/pyFirmata2 + cd pyFirmata2 + +Under Linux type:: + + python3 setup.py install + +Under Windows / Mac:: + + python setup.py install + + +Usage +===== + +Please go to https://github.com/berndporr/pyFirmata2 for the +documentation and in particular the example code. + + +Example code +============ + +It's strongly recommended to check out the example code at +https://github.com/berndporr/pyFirmata2/tree/master/examples +to see how the callbacks work. + + +Credits +======= + +The original pyFirmata was written by Tino de Bruijn. +The realtime sampling / callback has been added by Bernd Porr. diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 2bb17bbb..00000000 --- a/docs/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyFirmata.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyFirmata.qhc" - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 4cb5bba7..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pyFirmata documentation build configuration file, created by -# sphinx-quickstart on Wed Feb 17 18:59:00 2010. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'pyFirmata' -copyright = u'2010, Tino de Bruijn' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '1.0.0' -# The full version, including alpha/beta/rc tags. -release = '1.0.0' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -# html_theme = 'nature' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'pyFirmatadoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'pyFirmata.tex', u'pyFirmata Documentation', - u'Tino de Bruijn', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True - -import sys, os -sys.path.append(os.path.abspath('../')) diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index e8cb7a26..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,71 +0,0 @@ -.. pyFirmata documentation master file, created by - sphinx-quickstart on Wed Feb 17 18:59:00 2010. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to pyFirmata's documentation! -===================================== - -Module reference: - -.. toctree:: - :maxdepth: 2 - - 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. - -.. _pip: http://www.pip-installer.org/en/latest/ - -Usage -===== - -Basic usage:: - - >>> from pyfirmata import Arduino, util - >>> 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:: - - >>> 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.:: - - >>> analog_0 = board.get_pin('a:0:i') - >>> analog_0.read() - 0.661440304938 - >>> pin3 = board.get_pin('d:3:p') - >>> pin3.write(0.6) - -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:: - - >>> mega = { - ... 'digital' : tuple(x for x in range(54)), - ... 'analog' : tuple(x for x in range(16)), - ... 'pwm' : tuple(x for x in range(2,14)), - ... 'use_ports' : True, - ... 'disabled' : (0, 1, 14, 15) # Rx, Tx, Crystal - ... } - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/docs/pyfirmata.rst b/docs/pyfirmata.rst deleted file mode 100644 index d708c504..00000000 --- a/docs/pyfirmata.rst +++ /dev/null @@ -1,5 +0,0 @@ -pyFirmata -========= - -.. automodule:: pyfirmata.pyfirmata - :members: diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..8f149895 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,12 @@ +# Examples + +![alt tag](screenshot_realtime_scope.png) + + - Realtime oscilloscope. This script shows how to process data at a given sampling rate. + - Printing data on the screen using an event handler + - Digital in reads from a digital pin using an event handler + - Flashing LED using a timer + - PWM + - Servo + +There is also a scanner for COM ports. diff --git a/examples/blink.py b/examples/blink.py new file mode 100755 index 00000000..0839ef2f --- /dev/null +++ b/examples/blink.py @@ -0,0 +1,99 @@ +#!/usr/bin/python3 + +# Copyright (c) 2012, Fabian Affolter +# Copyright (c) 2019-2021, Bernd Porr +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the pyfirmata team nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# +# This program toggles the digital port 13 on/off every second. +# Port 13 has an LED connected so you'll see def a flashing light! +# Coding is done with a timer callback to avoid evil loops / delays. + +import pyfirmata2 +from threading import Timer + +class Blink(): + def __init__(self, board, seconds): + # pin 13 which is connected to the internal LED + self.digital_0 = board.get_pin('d:13:o') + + # flag that we want the timer to restart itself in the callback + self.timer = None + + # delay + self.DELAY = seconds + + # callback function which toggles the digital port and + # restarts the timer + def blinkCallback(self): + # call itself again so that it runs periodically + self.timer = Timer(self.DELAY,self.blinkCallback) + + # start the timer + self.timer.start() + + # now let's toggle the LED + v = self.digital_0.read() + v = not v + if v: + print("On") + else: + print("Off") + self.digital_0.write(v) + + # starts the blinking + def start(self): + # Kickstarting the perpetual timer by calling the + # callback function once + self.blinkCallback() + + # stops the blinking + def stop(self): + # Cancel the timer + self.timer.cancel() + +# main program + +# Adjust that the port match your system, see samples below: +# On Linux: /dev/ttyACM0, +# On Windows: COM1, COM2, ... +PORT = pyfirmata2.Arduino.AUTODETECT + +# Creates a new board +board = pyfirmata2.Arduino(PORT) + +t = Blink(board,1) +t.start() + +print("To stop the program press return.") +# Just blocking here to do nothing. +input() + +t.stop() + +# close the serial connection +board.exit() diff --git a/examples/digital-in.py b/examples/digital-in.py new file mode 100755 index 00000000..bd870292 --- /dev/null +++ b/examples/digital-in.py @@ -0,0 +1,78 @@ +#!/usr/bin/python3 + +# Copyright (c) 2012, Fabian Affolter +# Copyright (c) 2018-2021, Bernd Porr +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the pyfirmata team nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import pyfirmata2 + +# The program monitors the digital pin 6. +# Connect a switch between pin 6 and GND. +# Whenever there is a change of the state +# at pin 6 the callback function "pinCallback" +# is called. + +# Adjust that the port match your system, see samples below: +# On Linux: /dev/tty.usbserial-A6008rIF, /dev/ttyACM0, +# On Windows: \\.\COM1, \\.\COM2 +# PORT = '/dev/ttyACM0' +PORT = pyfirmata2.Arduino.AUTODETECT + + +# Callback function which is called whenever there is a +# change at the digital port 6. +def pinCallback(value): + if value: + print("Button released") + else: + print("Button pressed") + + +# Creates a new board +board = pyfirmata2.Arduino(PORT) +print("Setting up the connection to the board ...") + +# default sampling interval of 19ms +board.samplingOn() + +# Setup the digital pin with pullup resistor: "u" +digital_0 = board.get_pin('d:6:u') + +# points to the callback +digital_0.register_callback(pinCallback) + +# Switches the callback on +digital_0.enable_reporting() + +print("To stop the program press return.") + +# Do nothing here. Just preventing the program from reaching the +# exit function. +input() + +# Close the serial connection to the Arduino +board.exit() diff --git a/examples/print_analog_data.py b/examples/print_analog_data.py new file mode 100755 index 00000000..c36e366d --- /dev/null +++ b/examples/print_analog_data.py @@ -0,0 +1,52 @@ +#!/usr/bin/python3 + +from pyfirmata2 import Arduino +import time + +PORT = Arduino.AUTODETECT +# PORT = '/dev/ttyACM0' + +# prints data on the screen at the sampling rate of 50Hz +# can easily be changed to saving data to a file + +# It uses a callback operation so that timing is precise and +# the main program can just go to sleep. +# Copyright (c) 2018-2020, Bernd Porr +# see LICENSE file. + + +class AnalogPrinter: + + def __init__(self): + # sampling rate: 10Hz + self.samplingRate = 10 + self.timestamp = 0 + self.board = Arduino(PORT) + + def start(self): + self.board.analog[0].register_callback(self.myPrintCallback) + self.board.samplingOn(1000 / self.samplingRate) + self.board.analog[0].enable_reporting() + + def myPrintCallback(self, data): + print("%f,%f" % (self.timestamp, data)) + self.timestamp += (1 / self.samplingRate) + + def stop(self): + self.board.exit() + +print("Let's print data from Arduino's analogue pins for 10secs.") + +# Let's create an instance +analogPrinter = AnalogPrinter() + +# and start DAQ +analogPrinter.start() + +# let's acquire data for 10secs. We could do something else but we just sleep! +time.sleep(10) + +# let's stop it +analogPrinter.stop() + +print("finished") diff --git a/examples/pwm.py b/examples/pwm.py new file mode 100755 index 00000000..57dd87ae --- /dev/null +++ b/examples/pwm.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 + +# Copyright (c) 2018-2021, Bernd Porr +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the pyfirmata team nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import pyfirmata2 + +# PWM demo on port 5. The default PWM frequency is 1kHz. + +# Adjust that the port match your system, see samples below: +# On Linux: /dev/tty.usbserial-A6008rIF, /dev/ttyACM0, +# On Windows: \\.\COM1, \\.\COM2 +# PORT = '/dev/ttyACM0' +PORT = pyfirmata2.Arduino.AUTODETECT + +# Creates a new board +board = pyfirmata2.Arduino(PORT) +print("Setting up the connection to the board ...") + +# Setup the digital pin for PWM +pwm_5 = board.get_pin('d:5:p') + +v = float(input("PWM duty cycle from 0 to 100: ")) / 100.0 + +# Set the duty cycle (0..1) +pwm_5.write(v) + +# just idle here +input("Press enter to exit") + +# pwm off +pwm_5.write(0) + +# Close the serial connection to the Arduino +board.exit() diff --git a/examples/realtime_1kHz_scope.py b/examples/realtime_1kHz_scope.py new file mode 100755 index 00000000..17cfe7d5 --- /dev/null +++ b/examples/realtime_1kHz_scope.py @@ -0,0 +1,92 @@ +#!/usr/bin/python3 +""" +Plots channel zero at 1kHz. Requires pyqtgraph. + +Copyright (c) 2018-2021, Bernd Porr +see LICENSE file. + +""" + +import sys + +import pyqtgraph as pg +from pyqtgraph.Qt import QtCore, QtWidgets + +import numpy as np + +from pyfirmata2 import Arduino + +PORT = Arduino.AUTODETECT +# sampling rate: 1kHz +samplingRate = 1000 + +class QtPanningPlot: + + def __init__(self,layout,title): + self.pw = pg.PlotWidget() + layout.addWidget(self.pw) + self.pw.setYRange(-1,1) + self.pw.setXRange(0,500/samplingRate) + self.plt = self.pw.plot() + self.data = [] + # any additional initalisation code goes here (filters etc) + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.update) + self.timer.start(100) + + def update(self): + self.data=self.data[-500:] + if self.data: + self.plt.setData(x=np.linspace(0,len(self.data)/samplingRate,len(self.data)),y=self.data) + + def addData(self,d): + self.data.append(d) + +app = pg.mkQApp() +mw = QtWidgets.QMainWindow() +mw.setWindowTitle('1kHz PlotWidget') +mw.resize(800,800) +cw = QtWidgets.QWidget() +mw.setCentralWidget(cw) + +# Vertical arrangement +l = QtWidgets.QVBoxLayout() +cw.setLayout(l) + +# Let's create a plot window +qtPanningPlot1 = QtPanningPlot(l,"Arduino 1st channel") +label = QtWidgets.QLabel("This label show how to add another Widget to the layout.") +l.addWidget(label) + +# called for every new sample at channel 0 which has arrived from the Arduino +# "data" contains the new sample +def callBack(data): + # filter your channel 0 samples here: + # data = self.filter_of_channel0.dofilter(data) + # send the sample to the plotwindow + qtPanningPlot1.addData(data) + +# Get the Ardunio board. +board = Arduino(PORT,debug=True) + +# Set the sampling rate in the Arduino +board.samplingOn(1000 / samplingRate) + +# Register the callback which adds the data to the animated plot +# The function "callback" (see above) is called when data has +# arrived on channel 0. +board.analog[0].register_callback(callBack) + +# Enable the callback +board.analog[0].enable_reporting() + +# Show the window +mw.show() + +# showing all the windows +pg.exec() + +# needs to be called to close the serial port +board.exit() + +print("Finished") diff --git a/examples/realtime_scope.py b/examples/realtime_scope.py new file mode 100755 index 00000000..6fbc0815 --- /dev/null +++ b/examples/realtime_scope.py @@ -0,0 +1,87 @@ +#!/usr/bin/python3 + +from pyfirmata2 import Arduino +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.animation as animation + +# Realtime oscilloscope at a sampling rate of 100Hz +# It displays analog channel 0. +# You can plot multiple chnannels just by instantiating +# more RealtimePlotWindow instances and registering +# callbacks from the other channels. +# Copyright (c) 2018-2020, Bernd Porr +# see LICENSE file. + +PORT = Arduino.AUTODETECT +# PORT = '/dev/ttyUSB0' + +# Creates a scrolling data display +class RealtimePlotWindow: + + def __init__(self): + # create a plot window + self.fig, self.ax = plt.subplots() + # that's our plotbuffer + self.plotbuffer = np.zeros(500) + # create an empty line + self.line, = self.ax.plot(self.plotbuffer) + # axis + self.ax.set_ylim(0, 1.5) + # That's our ringbuffer which accumluates the samples + # It's emptied every time when the plot window below + # does a repaint + self.ringbuffer = [] + # add any initialisation code here (filters etc) + # start the animation + self.ani = animation.FuncAnimation(self.fig, self.update, interval=100) + + # updates the plot + def update(self, data): + # add new data to the buffer + self.plotbuffer = np.append(self.plotbuffer, self.ringbuffer) + # only keep the 500 newest ones and discard the old ones + self.plotbuffer = self.plotbuffer[-500:] + self.ringbuffer = [] + # set the new 500 points of channel 9 + self.line.set_ydata(self.plotbuffer) + return self.line, + + # appends data to the ringbuffer + def addData(self, v): + self.ringbuffer.append(v) + + +# Create an instance of an animated scrolling window +# To plot more channels just create more instances and add callback handlers below +realtimePlotWindow = RealtimePlotWindow() + +# sampling rate: 100Hz +samplingRate = 100 + +# called for every new sample which has arrived from the Arduino +def callBack(data): + # send the sample to the plotwindow + # add any filtering here: + # data = self.myfilter.dofilter(data) + realtimePlotWindow.addData(data) + +# Get the Ardunio board. +board = Arduino(PORT) + +# Set the sampling rate in the Arduino +board.samplingOn(1000 / samplingRate) + +# Register the callback which adds the data to the animated plot +board.analog[0].register_callback(callBack) + +# Enable the callback +board.analog[0].enable_reporting() + +# show the plot and start the animation +plt.show() + +# needs to be called to close the serial port +board.exit() + +print("finished") diff --git a/examples/realtime_two_channel_scope.py b/examples/realtime_two_channel_scope.py new file mode 100755 index 00000000..46778f5b --- /dev/null +++ b/examples/realtime_two_channel_scope.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +""" +Plots channels zero and one at 100Hz. Requires pyqtgraph. + +Copyright (c) 2018-2022, Bernd Porr +see LICENSE file. + +""" + +import sys + +import pyqtgraph as pg +from pyqtgraph.Qt import QtCore, QtWidgets + +import numpy as np + +from pyfirmata2 import Arduino + +PORT = Arduino.AUTODETECT +# sampling rate: 100Hz +samplingRate = 100 + +class QtPanningPlot: + + def __init__(self,title): + self.pw = pg.PlotWidget() + self.pw.setYRange(-1,1) + self.pw.setXRange(0,500/samplingRate) + self.plt = self.pw.plot() + self.data = [] + # any additional initalisation code goes here (filters etc) + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.update) + self.timer.start(100) + + def getWidget(self): + return self.pw + + def update(self): + self.data=self.data[-500:] + if self.data: + self.plt.setData(x=np.linspace(0,len(self.data)/samplingRate,len(self.data)),y=self.data) + + def addData(self,d): + self.data.append(d) + +app = pg.mkQApp() +mw = QtWidgets.QMainWindow() +mw.setWindowTitle('100Hz dual PlotWidget') +mw.resize(800,800) +cw = QtWidgets.QWidget() +mw.setCentralWidget(cw) + +# Let's arrange the two plots horizontally +layout = QtWidgets.QHBoxLayout() +cw.setLayout(layout) + +# Let's create two instances of plot windows +qtPanningPlot1 = QtPanningPlot("Arduino 1st channel") +layout.addWidget(qtPanningPlot1.getWidget()) + +qtPanningPlot2 = QtPanningPlot("Arduino 2nd channel") +layout.addWidget(qtPanningPlot2.getWidget()) + +# called for every new sample at channel 0 which has arrived from the Arduino +# "data" contains the new sample +def callBack1(data): + # filter your channel 0 samples here: + # data = self.filter_of_channel0.dofilter(data) + # send the sample to the plotwindow + qtPanningPlot1.addData(data) + +# called for every new sample at channel 1 which has arrived from the Arduino +# "data" contains the new sample +def callBack2(data): + # filter your channel 1 samples here: + # data = self.filter_of_channel1.dofilter(data) + # send the sample to the plotwindow + qtPanningPlot2.addData(data) + +# Get the Ardunio board. +board = Arduino(PORT) + +# Set the sampling rate in the Arduino +board.samplingOn(1000 / samplingRate) + +# Register the callback which adds the data to the animated plot +board.analog[0].register_callback(callBack1) +board.analog[1].register_callback(callBack2) + +# Enable the callback +board.analog[0].enable_reporting() +board.analog[1].enable_reporting() + +# showing the plots +mw.show() + +# Starting the QT GUI +# This is a blocking call and only returns when the user closes the window. +pg.exec() + +# needs to be called to close the serial port +board.exit() + +print("Finished") diff --git a/examples/scan.py b/examples/scan.py new file mode 100755 index 00000000..49be6476 --- /dev/null +++ b/examples/scan.py @@ -0,0 +1,9 @@ +#!/usr/bin/python + +import serial +import serial.tools.list_ports + +print("Serial devices available:") +l = serial.tools.list_ports.comports() +for ll in l: + print(ll.device, ll.description) diff --git a/examples/screenshot_realtime_scope.png b/examples/screenshot_realtime_scope.png new file mode 100644 index 00000000..e732adce Binary files /dev/null and b/examples/screenshot_realtime_scope.png differ diff --git a/examples/servo.py b/examples/servo.py new file mode 100755 index 00000000..68b8402e --- /dev/null +++ b/examples/servo.py @@ -0,0 +1,57 @@ +#!/usr/bin/python3 + +# Copyright (c) 2018-2021, Bernd Porr +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the pyfirmata team nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import pyfirmata2 + +# Servo demo on port 5. The default frequency is 50Hz approx. +# Lowest dutycycle (1ms) at 0 degrees and highest (2ms) at 180 degrees. + +# Adjust that the port match your system, see samples below: +# On Linux: /dev/tty.usbserial-A6008rIF, /dev/ttyACM0, +# On Windows: \\.\COM1, \\.\COM2 +# PORT = '/dev/ttyACM0' +PORT = pyfirmata2.Arduino.AUTODETECT + +# Creates a new board +board = pyfirmata2.Arduino(PORT) +print("Setting up the connection to the board ...") + +# Setup the digital pin as servo +servo_5 = board.get_pin('d:5:s') + +v = float(input("Servo angle from 0 to 180 degrees: ")) + +# Set the duty cycle +servo_5.write(v) + +# just idle here +input("Press enter to exit") + +# Close the serial connection to the Arduino +board.exit() diff --git a/pyfirmata/__init__.py b/pyfirmata2/__init__.py similarity index 84% rename from pyfirmata/__init__.py rename to pyfirmata2/__init__.py index f6b5cb2f..a5d53d49 100644 --- a/pyfirmata/__init__.py +++ b/pyfirmata2/__init__.py @@ -1,11 +1,5 @@ from .boards import BOARDS -from .pyfirmata import * # NOQA - -# TODO: should change above import to an explicit list, but people might rely on -# it, so do it in a backwards breaking release - -__version__ = '1.0.3' # Use bumpversion! - +from .pyfirmata2 import * # NOQA # shortcut classes @@ -50,7 +44,7 @@ def __str__(self): class ArduinoNano(Board): """ - A board that will set itself up as an Arduino Due. + A board that will set itself up as an Arduino Nano. """ def __init__(self, *args, **kwargs): args = list(args) diff --git a/pyfirmata/boards.py b/pyfirmata2/boards.py similarity index 100% rename from pyfirmata/boards.py rename to pyfirmata2/boards.py diff --git a/pyfirmata/mockup.py b/pyfirmata2/mockup.py similarity index 92% rename from pyfirmata/mockup.py rename to pyfirmata2/mockup.py index 13fdd7f9..64fbb68d 100644 --- a/pyfirmata/mockup.py +++ b/pyfirmata2/mockup.py @@ -1,6 +1,5 @@ from collections import deque - -import pyfirmata +import pyfirmata2 class MockupSerial(deque): @@ -47,13 +46,14 @@ def inWaiting(self): return len(self) -class MockupBoard(pyfirmata.Board): +class MockupBoard(pyfirmata2.Board): def __init__(self, port, layout, values_dict={}): self.sp = MockupSerial(port, 57600) self.setup_layout(layout) self.values_dict = values_dict self.id = 1 + self.samplerThread = Iterator(self) def reset_taken(self): for key in self.taken['analog']: @@ -69,7 +69,7 @@ def update_values_dict(self): pin.values_dict = self.values_dict -class MockupPort(pyfirmata.Port): +class MockupPort(pyfirmata2.Port): def __init__(self, board, port_number): self.board = board self.port_number = port_number @@ -78,14 +78,14 @@ def __init__(self, board, port_number): self.pins = [] for i in range(8): pin_nr = i + self.port_number * 8 - self.pins.append(MockupPin(self.board, pin_nr, type=pyfirmata.DIGITAL, port=self)) + self.pins.append(MockupPin(self.board, pin_nr, type=pyfirmata2.DIGITAL, port=self)) def update_values_dict(self): for pin in self.pins: pin.values_dict = self.values_dict -class MockupPin(pyfirmata.Pin): +class MockupPin(pyfirmata2.Pin): def __init__(self, *args, **kwargs): self.values_dict = kwargs.get('values_dict', {}) try: @@ -117,9 +117,9 @@ def get_active(self): return self.is_active def write(self, value): - if self.mode == pyfirmata.UNAVAILABLE: + if self.mode == pyfirmata2.UNAVAILABLE: raise IOError("Cannot read from pin {0}".format(self.pin_number)) - if self.mode == pyfirmata.INPUT: + if self.mode == pyfirmata2.INPUT: raise IOError("{0} pin {1} is not an output" .format(self.port and "Digital" or "Analog", self.get_pin_number())) if not self.port: @@ -130,7 +130,7 @@ def write(self, value): class Iterator(object): def __init__(self, *args, **kwargs): - pass + self.running = False def start(self): pass @@ -138,6 +138,7 @@ def start(self): def stop(self): pass + if __name__ == '__main__': import doctest doctest.testmod() diff --git a/pyfirmata/pyfirmata.py b/pyfirmata2/pyfirmata2.py similarity index 81% rename from pyfirmata/pyfirmata.py rename to pyfirmata2/pyfirmata2.py index 0963cea7..71676206 100755 --- a/pyfirmata/pyfirmata.py +++ b/pyfirmata2/pyfirmata2.py @@ -2,10 +2,14 @@ import inspect import time +import warnings import serial +import serial.tools.list_ports +from sys import platform + +from .util import pin_list_to_board_dict, to_two_bytes, two_byte_iter_to_str, Iterator -from .util import pin_list_to_board_dict, to_two_bytes, two_byte_iter_to_str # Message command bytes (0x80(128) to 0xFF(255)) - straight from Firmata.h DIGITAL_MESSAGE = 0x90 # send data for a digital pin @@ -54,6 +58,7 @@ ANALOG = 2 # analog pin in analogInput mode PWM = 3 # digital pin in PWM output mode SERVO = 4 # digital pin in SERVO mode +INPUT_PULLUP = 11 # Same as INPUT, but with the pin's internal pull-up resistor enabled # Pin types DIGITAL = OUTPUT # same as OUTPUT below @@ -84,14 +89,43 @@ class Board(object): _command = None _stored_data = [] _parsing_sysex = False - - def __init__(self, port, layout=None, baudrate=57600, name=None, timeout=None): + AUTODETECT = None + + def __init__(self, port, layout=None, baudrate=57600, name=None, timeout=None, debug=False): + if port == self.AUTODETECT: + l = serial.tools.list_ports.comports() + if l: + if platform == "linux" or platform == "linux2": + for d in l: + if 'ACM' in d.device or 'usbserial' in d.device or 'ttyUSB' in d.device: + port = str(d.device) + elif platform == "win32": + comports = [] + for d in l: + if d.device: + if ("USB" in d.description) or (not d.description) or ("Arduino" in d.description): + devname = str(d.device) + comports.append(devname) + comports.sort() + if len(comports) > 0: + port = comports[0] + else: + for d in l: + if d.vid: + port = str(d.device) + if port == self.AUTODETECT: + self.samplerThread = None + self.sp = None + raise Exception('Could not find a serial port.') + if debug: + print("Port=",port) + self.samplerThread = Iterator(self) self.sp = serial.Serial(port, baudrate, timeout=timeout) # Allow 5 secs for Arduino's auto-reset to happen # 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) + self.__pass_time(BOARD_SETUP_WAIT_TIME) self.name = name self._layout = layout if not self.name: @@ -163,13 +197,29 @@ def _set_default_handlers(self): self.add_cmd_handler(REPORT_VERSION, self._handle_report_version) self.add_cmd_handler(REPORT_FIRMWARE, self._handle_report_firmware) + def samplingOn(self, sample_interval=19): + # enables sampling + if not self.samplerThread.running: + if sample_interval < 1: + raise ValueError("Sampling interval less than 1ms") + self.setSamplingInterval(sample_interval) + self.samplerThread.start() + + def samplingOff(self): + # disables sampling + if not self.samplerThread: + return + if self.samplerThread.running: + self.samplerThread.stop() + self.samplerThread.join() + def auto_setup(self): """ Automatic setup based on Firmata's "Capability Query" """ self.add_cmd_handler(CAPABILITY_RESPONSE, self._handle_report_capability_response) self.send_sysex(CAPABILITY_QUERY, []) - self.pass_time(0.1) # Serial SYNC + self.__pass_time(0.1) # Serial SYNC while self.bytes_available(): self.iterate() @@ -182,7 +232,7 @@ def auto_setup(self): def add_cmd_handler(self, cmd, func): """Adds a command handler for a command.""" - len_args = len(inspect.getargspec(func)[0]) + len_args = len(inspect.getfullargspec(func)[0]) def add_meta(f): def decorator(*args, **kwargs): @@ -204,6 +254,8 @@ def get_pin(self, pin_def): 'a' analog pin Pin number 'i' for input 'd' digital pin Pin number 'o' for output 'p' for pwm (Pulse-width modulation) + 's' for servo + 'u' for input with pull-up resistor enabled All seperated by ``:``. """ @@ -232,13 +284,19 @@ def get_pin(self, pin_def): pin.mode = PWM elif bits[2] == 's': pin.mode = SERVO - elif bits[2] != 'o': + elif bits[2] == 'u': + pin.mode = INPUT_PULLUP + elif bits[2] == 'i': + pin.mode = INPUT + elif bits[2] == 'o': + pin.mode = OUTPUT + else: pin.mode = INPUT else: pin.enable_reporting() return pin - def pass_time(self, t): + def __pass_time(self, t): """Non-blocking time-out for ``t`` seconds.""" cont = time.time() + t while time.time() < cont: @@ -327,15 +385,25 @@ def servo_config(self, pin, min_pulse=544, max_pulse=2400, angle=0): self.digital[pin]._mode = SERVO self.digital[pin].write(angle) + def setSamplingInterval(self, intervalInMs): + data = to_two_bytes(int(intervalInMs)) + self.send_sysex(SAMPLING_INTERVAL, data) + def exit(self): """Call this to exit cleanly.""" + for a in self.analog: + a.disable_reporting() + for d in self.digital: + d.disable_reporting() + self.samplingOff() # First detach all servo's, otherwise it somehow doesn't want to close... if hasattr(self, 'digital'): for pin in self.digital: if pin.mode == SERVO: pin.mode = OUTPUT if hasattr(self, 'sp'): - self.sp.close() + if self.sp: + self.sp.close() # Command handlers def _handle_analog_message(self, pin_nr, lsb, msb): @@ -344,6 +412,8 @@ def _handle_analog_message(self, pin_nr, lsb, msb): try: if self.analog[pin_nr].reporting: self.analog[pin_nr].value = value + if not self.analog[pin_nr].callback is None: + self.analog[pin_nr].callback(value) except IndexError: raise ValueError @@ -406,11 +476,13 @@ def enable_reporting(self): self.board.sp.write(msg) for pin in self.pins: - if pin.mode == INPUT: + if pin.mode == INPUT or pin.mode == INPUT_PULLUP: pin.reporting = True # TODO Shouldn't this happen at the pin? def disable_reporting(self): """Disable the reporting of the port.""" + if not self.reporting: + return self.reporting = False msg = bytearray([REPORT_DIGITAL + self.port_number, 0]) self.board.sp.write(msg) @@ -433,9 +505,11 @@ def _update(self, 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: + if pin.mode is INPUT or pin.mode is INPUT_PULLUP: pin_nr = pin.pin_number - self.port_number * 8 pin.value = (mask & (1 << pin_nr)) > 0 + if not pin.callback is None: + pin.callback(pin.value) class Pin(object): @@ -449,6 +523,7 @@ def __init__(self, board, pin_number, type=ANALOG, port=None): self._mode = (type == DIGITAL and OUTPUT or INPUT) self.reporting = False self.value = None + self.callback = None def __str__(self): type = {ANALOG: 'Analog', DIGITAL: 'Digital'}[self.type] @@ -473,7 +548,7 @@ def _set_mode(self, mode): # Set mode with SET_PIN_MODE message self._mode = mode self.board.sp.write(bytearray([SET_PIN_MODE, self.pin_number, mode])) - if mode == INPUT: + if mode == INPUT or mode == INPUT_PULLUP: self.enable_reporting() def _get_mode(self): @@ -487,7 +562,7 @@ def _get_mode(self): def enable_reporting(self): """Set an input pin to report values.""" - if self.mode is not INPUT: + if self.mode is not INPUT and self.mode is not INPUT_PULLUP: raise IOError("{0} is not an input and can therefore not report".format(self)) if self.type == ANALOG: self.reporting = True @@ -500,6 +575,8 @@ def enable_reporting(self): def disable_reporting(self): """Disable the reporting of an input pin.""" if self.type == ANALOG: + if not self.reporting: + return self.reporting = False msg = bytearray([REPORT_ANALOG + self.pin_number, 0]) self.board.sp.write(msg) @@ -508,15 +585,29 @@ def disable_reporting(self): # TODO This is not going to work for non-optimized boards like Mega def read(self): - """ - Returns the output value of the pin. This value is updated by the - boards :meth:`Board.iterate` method. Value is always in the range from - 0.0 to 1.0. - """ + """Returns the value of an output pin.""" if self.mode == UNAVAILABLE: raise IOError("Cannot read pin {0}".format(self.__str__())) + if (self.mode is INPUT) or (self.mode is INPUT_PULLUP) or (self.type == ANALOG): + raise IOError("Reading via polling is not supported by this library. Please use the original pyfirmata.") return self.value + def register_callback(self, _callback): + """ + Register a callback to read from an analogue or digital port + + :arg value: callback with one argument which receives the data: + boolean if the pin is digital, or + float from 0 to 1 if the pin is an analgoue input + """ + self.callback = _callback + + def unregiser_callback(self): + """ + Unregisters the callback which receives data from a pin + """ + self.callback = None + def write(self, value): """ Output a voltage from the pin @@ -528,7 +619,7 @@ def write(self, value): """ if self.mode is UNAVAILABLE: raise IOError("{0} can not be used through Firmata".format(self)) - if self.mode is INPUT: + if self.mode is INPUT or self.mode is INPUT_PULLUP: raise IOError("{0} is set up as an INPUT and can therefore not be written to" .format(self)) if value is not self.value: diff --git a/pyfirmata/util.py b/pyfirmata2/util.py similarity index 85% rename from pyfirmata/util.py rename to pyfirmata2/util.py index 80c1a564..47f055eb 100644 --- a/pyfirmata/util.py +++ b/pyfirmata2/util.py @@ -10,7 +10,9 @@ from .boards import BOARDS -def get_the_board(layout=BOARDS['arduino'], base_dir='/dev/', identifier='tty.usbserial',): +def get_the_board( + layout=BOARDS["arduino"], base_dir="/dev/", identifier="tty.usbserial" +): """ Helper function to get the one and only board connected to the computer running this. It assumes a normal arduino layout, but this can be @@ -19,7 +21,7 @@ def get_the_board(layout=BOARDS['arduino'], base_dir='/dev/', identifier='tty.us IOError if it can't find a board, on a serial, or if it finds more than one. """ - from .pyfirmata import Board # prevent a circular import + from .pyfirmata2 import Board # prevent a circular import boards = [] for device in os.listdir(base_dir): if device.startswith(identifier): @@ -41,14 +43,16 @@ def __init__(self, board): super(Iterator, self).__init__() self.board = board self.daemon = True + self.running = False def run(self): - while 1: + self.running = True + while self.running: try: while self.board.bytes_available(): self.board.iterate() time.sleep(0.001) - except (AttributeError, serial.SerialException, OSError) as e: + except (AttributeError, serial.SerialException, OSError): # this way we can kill the thread by setting the board object # to None, or when the serial port is closed by board.exit() break @@ -64,9 +68,12 @@ def run(self): except (TypeError, IndexError): pass raise - except (KeyboardInterrupt) as e: + except (KeyboardInterrupt): sys.exit() + def stop(self): + self.running = False + def to_two_bytes(integer): """ @@ -164,18 +171,17 @@ def pin_list_to_board_dict(pinlist): """ board_dict = { - 'digital': [], - 'analog': [], - 'pwm': [], - 'servo': [], # 2.2 specs - # 'i2c': [], # 2.3 specs - 'disabled': [], + "digital": [], + "analog": [], + "pwm": [], + "servo": [], + "disabled": [], } for i, pin in enumerate(pinlist): pin.pop() # removes the 0x79 on end if not pin: - board_dict['disabled'] += [i] - board_dict['digital'] += [i] + board_dict["disabled"] += [i] + board_dict["digital"] += [i] continue for j, _ in enumerate(pin): @@ -183,16 +189,16 @@ def pin_list_to_board_dict(pinlist): if j % 2 == 0: # This is safe. try: range(10)[5:50] if pin[j:j + 4] == [0, 1, 1, 1]: - board_dict['digital'] += [i] + board_dict["digital"] += [i] if pin[j:j + 2] == [2, 10]: - board_dict['analog'] += [i] + board_dict["analog"] += [i] if pin[j:j + 2] == [3, 8]: - board_dict['pwm'] += [i] + board_dict["pwm"] += [i] if pin[j:j + 2] == [4, 14]: - board_dict['servo'] += [i] + board_dict["servo"] += [i] # Desable I2C if pin[j:j + 2] == [6, 1]: @@ -201,16 +207,16 @@ def pin_list_to_board_dict(pinlist): # We have to deal with analog pins: # - (14, 15, 16, 17, 18, 19) # + (0, 1, 2, 3, 4, 5) - diff = set(board_dict['digital']) - set(board_dict['analog']) - board_dict['analog'] = [n for n, _ in enumerate(board_dict['analog'])] + diff = set(board_dict["digital"]) - set(board_dict["analog"]) + board_dict["analog"] = [n for n, _ in enumerate(board_dict["analog"])] # Digital pin problems: # - (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) # + (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) - board_dict['digital'] = [n for n, _ in enumerate(diff)] + board_dict["digital"] = [n for n, _ in enumerate(diff)] # Based on lib Arduino 0017 - board_dict['servo'] = board_dict['digital'] + board_dict["servo"] = board_dict["digital"] # Turn lists into tuples # Using dict for Python 2.6 compatibility diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 00000000..278802a2 Binary files /dev/null and b/screenshot.png differ diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 24eb9347..00000000 --- a/setup.cfg +++ /dev/null @@ -1,26 +0,0 @@ -[bumpversion] -current_version = 1.0.3 -commit = True -tag = True -tag_name = {new_version} - -[bumpversion:file:setup.py] -parse = "version='(?P\d+)\.(?P\d+)\.(?P\d+)'" - -[bumpversion:file:pyfirmata/__init__.py] -parse = "__version__ = '(?P\d+)\.(?P\d+)\.(?P\d+)'" - -[wheel] -universal = true - -[flake8] -max_line_length = 99 -exclude = docs -ignore = W503,F405 - -[isort] -line_length = 99 -multi_line_output = 5 -balanced_wrapping = 1 -skip = build,dist,docs -not_skip = __init__.py diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 8475e46c..59448cc8 --- a/setup.py +++ b/setup.py @@ -1,35 +1,27 @@ -#!/usr/bin/env python +#!/usr/bin/python3 from setuptools import setup -with open('README.rst') as f: +with open('README_py.rst') as f: long_description = f.read() setup( - name='pyFirmata', - version='1.0.3', # Use bumpversion! - description="A Python interface for the Firmata procotol", + name='pyFirmata2', + version='2.5.0', + description="Use your Arduino as a data acquisition card under Python", long_description=long_description, - author='Tino de Bruijn', - author_email='tinodb@gmail.com', - packages=['pyfirmata'], + author='Bernd Porr', + author_email='mail@berndporr.me.uk', + packages=['pyfirmata2'], include_package_data=True, install_requires=['pyserial'], zip_safe=False, - url='https://github.com/tino/pyFirmata', + url='https://github.com/berndporr/pyFirmata2', classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: Other Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', 'Topic :: Utilities', - 'Topic :: Home Automation', ], ) diff --git a/tests.py b/tests.py index 9d3b51ad..a812b332 100644 --- a/tests.py +++ b/tests.py @@ -5,10 +5,10 @@ import serial -import pyfirmata -from pyfirmata import mockup -from pyfirmata.boards import BOARDS -from pyfirmata.util import ( +import pyfirmata2 +from pyfirmata2 import mockup +from pyfirmata2.boards import BOARDS +from pyfirmata2.util import ( break_to_bytes, from_two_bytes, str_to_two_byte_iter, to_two_bytes, two_byte_iter_to_str ) @@ -25,11 +25,11 @@ class BoardBaseTest(unittest.TestCase): def setUp(self): # Test with the MockupSerial so no real connection is needed - pyfirmata.pyfirmata.serial.Serial = mockup.MockupSerial + pyfirmata2.pyfirmata2.serial.Serial = mockup.MockupSerial # Set the wait time to a zero so we won't have to wait a couple of secs # each test - pyfirmata.pyfirmata.BOARD_SETUP_WAIT_TIME = 0 - self.board = pyfirmata.Board('', BOARDS['arduino']) + pyfirmata2.pyfirmata2.BOARD_SETUP_WAIT_TIME = 0 + self.board = pyfirmata2.Board('', BOARDS['arduino']) self.board._stored_data = [] # FIXME How can it be that a fresh instance sometimes still contains data? @@ -85,13 +85,13 @@ def test_incoming_analog_message(self): self.assertEqual(self.board.analog[4].read(), None) self.assertEqual(self.board.analog[4].reporting, False) # Should do nothing as the pin isn't set to report - self.board.sp.write([pyfirmata.ANALOG_MESSAGE + 4, 127, 7]) + self.board.sp.write([pyfirmata2.ANALOG_MESSAGE + 4, 127, 7]) self.board.iterate() self.assertEqual(self.board.analog[4].read(), None) self.board.analog[4].enable_reporting() self.board.sp.clear() # This should set analog port 4 to 1 - self.board.sp.write([pyfirmata.ANALOG_MESSAGE + 4, 127, 7]) + self.board.sp.write([pyfirmata2.ANALOG_MESSAGE + 4, 127, 7]) self.board.iterate() self.assertEqual(self.board.analog[4].read(), 1.0) self.board._stored_data = [] @@ -186,13 +186,13 @@ def test_handle_capability_response(self): def test_incoming_digital_message(self): # A digital message sets the value for a whole port. We will set pin # 9 (on port 1) to 1 to test if this is working. - self.board.digital[9].mode = pyfirmata.INPUT + self.board.digital[9].mode = pyfirmata2.INPUT self.board.sp.clear() # clear mode sent over the wire. # Create the mask mask = 0 mask |= 1 << (9 - 8) # set the bit for pin 9 to to 1 self.assertEqual(self.board.digital[9].read(), None) - self.board.sp.write([pyfirmata.DIGITAL_MESSAGE + 1, mask % 128, mask >> 7]) + self.board.sp.write([pyfirmata2.DIGITAL_MESSAGE + 1, mask % 128, mask >> 7]) self.board.iterate() self.assertEqual(self.board.digital[9].read(), True) @@ -203,7 +203,7 @@ def test_incoming_digital_message(self): # 2 minor version (0-127) def test_incoming_report_version(self): self.assertEqual(self.board.firmata_version, None) - self.board.sp.write([pyfirmata.REPORT_VERSION, 2, 1]) + self.board.sp.write([pyfirmata2.REPORT_VERSION, 2, 1]) self.board.iterate() self.assertEqual(self.board.firmata_version, (2, 1)) @@ -219,11 +219,11 @@ def test_incoming_report_version(self): def test_incoming_report_firmware(self): self.assertEqual(self.board.firmware, None) self.assertEqual(self.board.firmware_version, None) - msg = [pyfirmata.START_SYSEX, - pyfirmata.REPORT_FIRMWARE, + msg = [pyfirmata2.START_SYSEX, + pyfirmata2.REPORT_FIRMWARE, 2, 1] + list(str_to_two_byte_iter('Firmware_name')) + \ - [pyfirmata.END_SYSEX] + [pyfirmata2.END_SYSEX] self.board.sp.write(msg) self.board.iterate() self.assertEqual(self.board.firmware, 'Firmware_name') @@ -245,7 +245,7 @@ def test_report_analog(self): # report digital port 0xD0 port disable/enable(0/1) - n/a - def test_report_digital(self): # This should enable reporting of whole port 1 - self.board.digital[8]._mode = pyfirmata.INPUT # Outputs can't report + self.board.digital[8]._mode = pyfirmata2.INPUT # Outputs can't report self.board.digital[8].enable_reporting() self.assert_serial(0xD0 + 1, 1) self.assertTrue(self.board.digital_ports[1].reporting) @@ -291,7 +291,7 @@ def test_too_much_data(self): # Crap self.board.sp.write([i for i in range(10)]) # This should set analog port 4 to 1 - self.board.sp.write([pyfirmata.ANALOG_MESSAGE + 4, 127, 7]) + self.board.sp.write([pyfirmata2.ANALOG_MESSAGE + 4, 127, 7]) # Crap self.board.sp.write([10 - i for i in range(10)]) while len(self.board.sp): @@ -341,7 +341,7 @@ def test_servo_config_invalid_pin(self): def test_set_mode_servo(self): p = self.board.digital[2] - p.mode = pyfirmata.SERVO + p.mode = pyfirmata2.SERVO data = chain([0xF0, 0x70, 2], to_two_bytes(544), to_two_bytes(2400), @@ -356,8 +356,8 @@ def test_layout_arduino(self): self.assertEqual(len(BOARDS['arduino']['analog']), len(self.board.analog)) def test_layout_arduino_mega(self): - pyfirmata.pyfirmata.serial.Serial = mockup.MockupSerial - mega = pyfirmata.Board('', BOARDS['arduino_mega']) + pyfirmata2.pyfirmata2.serial.Serial = mockup.MockupSerial + mega = pyfirmata2.Board('', BOARDS['arduino_mega']) self.assertEqual(len(BOARDS['arduino_mega']['digital']), len(mega.digital)) self.assertEqual(len(BOARDS['arduino_mega']['analog']), len(mega.analog)) @@ -367,14 +367,14 @@ def test_pwm_layout(self): if pin.PWM_CAPABLE: pins.append(self.board.get_pin('d:%d:p' % pin.pin_number)) for pin in pins: - self.assertEqual(pin.mode, pyfirmata.PWM) + self.assertEqual(pin.mode, pyfirmata2.PWM) self.assertTrue(pin.pin_number in BOARDS['arduino']['pwm']) self.assertTrue(len(pins) == len(BOARDS['arduino']['pwm'])) def test_get_pin_digital(self): pin = self.board.get_pin('d:13:o') self.assertEqual(pin.pin_number, 13) - self.assertEqual(pin.mode, pyfirmata.OUTPUT) + self.assertEqual(pin.mode, pyfirmata2.OUTPUT) self.assertEqual(pin.port.port_number, 1) self.assertEqual(pin.port.reporting, False) @@ -386,7 +386,7 @@ def test_get_pin_analog(self): def tearDown(self): self.board.exit() - pyfirmata.serial.Serial = serial.Serial + pyfirmata2.serial.Serial = serial.Serial class TestMockupSerial(unittest.TestCase): @@ -444,7 +444,7 @@ def test_handle_digital_inputs(self): """ for i in range(8, 16): # pins of port 1 if not bool(i % 2) and i != 14: # all even pins - self.board.digital[i].mode = pyfirmata.INPUT + self.board.digital[i].mode = pyfirmata2.INPUT self.assertEqual(self.board.digital[i].value, None) mask = 0 # Set the mask high for the first 4 pins